Create MPImage type for Web
PiperOrigin-RevId: 525873209
This commit is contained in:
		
							parent
							
								
									e9bb849503
								
							
						
					
					
						commit
						9be748db00
					
				| 
						 | 
					@ -20,6 +20,7 @@ mediapipe_files(srcs = [
 | 
				
			||||||
VISION_LIBS = [
 | 
					VISION_LIBS = [
 | 
				
			||||||
    "//mediapipe/tasks/web/core:fileset_resolver",
 | 
					    "//mediapipe/tasks/web/core:fileset_resolver",
 | 
				
			||||||
    "//mediapipe/tasks/web/vision/core:drawing_utils",
 | 
					    "//mediapipe/tasks/web/vision/core:drawing_utils",
 | 
				
			||||||
 | 
					    "//mediapipe/tasks/web/vision/core:image",
 | 
				
			||||||
    "//mediapipe/tasks/web/vision/face_detector",
 | 
					    "//mediapipe/tasks/web/vision/face_detector",
 | 
				
			||||||
    "//mediapipe/tasks/web/vision/face_landmarker",
 | 
					    "//mediapipe/tasks/web/vision/face_landmarker",
 | 
				
			||||||
    "//mediapipe/tasks/web/vision/face_stylizer",
 | 
					    "//mediapipe/tasks/web/vision/face_stylizer",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,6 +39,23 @@ mediapipe_ts_library(
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mediapipe_ts_library(
 | 
				
			||||||
 | 
					    name = "image",
 | 
				
			||||||
 | 
					    srcs = ["image.ts"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mediapipe_ts_library(
 | 
				
			||||||
 | 
					    name = "image_test_lib",
 | 
				
			||||||
 | 
					    testonly = True,
 | 
				
			||||||
 | 
					    srcs = ["image.test.ts"],
 | 
				
			||||||
 | 
					    deps = [":image"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jasmine_node_test(
 | 
				
			||||||
 | 
					    name = "image_test",
 | 
				
			||||||
 | 
					    deps = [":image_test_lib"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mediapipe_ts_library(
 | 
					mediapipe_ts_library(
 | 
				
			||||||
    name = "vision_task_runner",
 | 
					    name = "vision_task_runner",
 | 
				
			||||||
    srcs = ["vision_task_runner.ts"],
 | 
					    srcs = ["vision_task_runner.ts"],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										287
									
								
								mediapipe/tasks/web/vision/core/image.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								mediapipe/tasks/web/vision/core/image.test.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,287 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright 2022 The MediaPipe Authors. All Rights Reserved.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'jasmine';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {MPImage, MPImageShaderContext, MPImageStorageType} from './image';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const WIDTH = 2;
 | 
				
			||||||
 | 
					const HEIGHT = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const skip = typeof document === 'undefined';
 | 
				
			||||||
 | 
					if (skip) {
 | 
				
			||||||
 | 
					  console.log('These tests must be run in a browser.');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** The image types supported by MPImage. */
 | 
				
			||||||
 | 
					type ImageType = ImageData|ImageBitmap|WebGLTexture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function createTestData(
 | 
				
			||||||
 | 
					    gl: WebGL2RenderingContext, data: number[], width: number,
 | 
				
			||||||
 | 
					    height: number): Promise<[ImageData, ImageBitmap, WebGLTexture]> {
 | 
				
			||||||
 | 
					  const imageData = new ImageData(new Uint8ClampedArray(data), width, height);
 | 
				
			||||||
 | 
					  const imageBitmap = await createImageBitmap(imageData);
 | 
				
			||||||
 | 
					  const webGlTexture = gl.createTexture()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gl.bindTexture(gl.TEXTURE_2D, webGlTexture);
 | 
				
			||||||
 | 
					  gl.texImage2D(
 | 
				
			||||||
 | 
					      gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageBitmap);
 | 
				
			||||||
 | 
					  gl.bindTexture(gl.TEXTURE_2D, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [imageData, imageBitmap, webGlTexture];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(skip ? xdescribe : describe)('MPImage', () => {
 | 
				
			||||||
 | 
					  let canvas: OffscreenCanvas;
 | 
				
			||||||
 | 
					  let gl: WebGL2RenderingContext;
 | 
				
			||||||
 | 
					  let imageData: ImageData;
 | 
				
			||||||
 | 
					  let imageBitmap: ImageBitmap;
 | 
				
			||||||
 | 
					  let webGlTexture: WebGLTexture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(async () => {
 | 
				
			||||||
 | 
					    canvas = new OffscreenCanvas(WIDTH, HEIGHT);
 | 
				
			||||||
 | 
					    gl = canvas.getContext('webgl2') as WebGL2RenderingContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const images = await createTestData(
 | 
				
			||||||
 | 
					        gl, [1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, 4, 0, 0, 255], WIDTH,
 | 
				
			||||||
 | 
					        HEIGHT);
 | 
				
			||||||
 | 
					    imageData = images[0];
 | 
				
			||||||
 | 
					    imageBitmap = images[1];
 | 
				
			||||||
 | 
					    webGlTexture = images[2];
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(() => {
 | 
				
			||||||
 | 
					    gl.deleteTexture(webGlTexture);
 | 
				
			||||||
 | 
					    imageBitmap.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function readPixelsFromImageBitmap(imageBitmap: ImageBitmap): ImageData {
 | 
				
			||||||
 | 
					    const canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
 | 
				
			||||||
 | 
					    const ctx = canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;
 | 
				
			||||||
 | 
					    ctx.drawImage(imageBitmap, 0, 0);
 | 
				
			||||||
 | 
					    return ctx.getImageData(0, 0, imageBitmap.width, imageBitmap.height);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function readPixelsFromWebGLTexture(texture: WebGLTexture): Uint8Array {
 | 
				
			||||||
 | 
					    const pixels = new Uint8Array(WIDTH * WIDTH * 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.bindTexture(gl.TEXTURE_2D, texture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const framebuffer = gl.createFramebuffer()!;
 | 
				
			||||||
 | 
					    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
 | 
				
			||||||
 | 
					    gl.framebufferTexture2D(
 | 
				
			||||||
 | 
					        gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
 | 
				
			||||||
 | 
					    gl.readPixels(0, 0, WIDTH, HEIGHT, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
 | 
				
			||||||
 | 
					    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
 | 
				
			||||||
 | 
					    gl.deleteFramebuffer(framebuffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.bindTexture(gl.TEXTURE_2D, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return pixels;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function assertEquality(image: MPImage, expected: ImageType): void {
 | 
				
			||||||
 | 
					    if (expected instanceof ImageData) {
 | 
				
			||||||
 | 
					      const result = image.getImage(MPImageStorageType.IMAGE_DATA);
 | 
				
			||||||
 | 
					      expect(result).toEqual(expected);
 | 
				
			||||||
 | 
					    } else if (expected instanceof ImageBitmap) {
 | 
				
			||||||
 | 
					      const result = image.getImage(MPImageStorageType.IMAGE_BITMAP);
 | 
				
			||||||
 | 
					      expect(readPixelsFromImageBitmap(result))
 | 
				
			||||||
 | 
					          .toEqual(readPixelsFromImageBitmap(expected));
 | 
				
			||||||
 | 
					    } else {  // WebGLTexture
 | 
				
			||||||
 | 
					      const result = image.getImage(MPImageStorageType.WEBGL_TEXTURE);
 | 
				
			||||||
 | 
					      expect(readPixelsFromWebGLTexture(result))
 | 
				
			||||||
 | 
					          .toEqual(readPixelsFromWebGLTexture(expected));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function createImage(
 | 
				
			||||||
 | 
					      shaderContext: MPImageShaderContext, input: ImageType, width: number,
 | 
				
			||||||
 | 
					      height: number): MPImage {
 | 
				
			||||||
 | 
					    return new MPImage(
 | 
				
			||||||
 | 
					        input instanceof ImageData ? input : null,
 | 
				
			||||||
 | 
					        input instanceof ImageBitmap ? input : null,
 | 
				
			||||||
 | 
					        input instanceof WebGLTexture ? input : null,
 | 
				
			||||||
 | 
					        /* ownsImageBitmap= */ false, /* ownsWebGLTexture= */ false, canvas,
 | 
				
			||||||
 | 
					        shaderContext, width, height);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function runConversionTest(
 | 
				
			||||||
 | 
					      input: ImageType, output: ImageType, width = WIDTH,
 | 
				
			||||||
 | 
					      height = HEIGHT): void {
 | 
				
			||||||
 | 
					    const shaderContext = new MPImageShaderContext();
 | 
				
			||||||
 | 
					    const image = createImage(shaderContext, input, width, height);
 | 
				
			||||||
 | 
					    assertEquality(image, output);
 | 
				
			||||||
 | 
					    image.close();
 | 
				
			||||||
 | 
					    shaderContext.close();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function runCloneTest(input: ImageType): void {
 | 
				
			||||||
 | 
					    const shaderContext = new MPImageShaderContext();
 | 
				
			||||||
 | 
					    const image = createImage(shaderContext, input, WIDTH, HEIGHT);
 | 
				
			||||||
 | 
					    const clone = image.clone();
 | 
				
			||||||
 | 
					    assertEquality(clone, input);
 | 
				
			||||||
 | 
					    clone.close();
 | 
				
			||||||
 | 
					    shaderContext.close();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from ImageData to ImageData`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(imageData, imageData);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from ImageData to ImageBitmap`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(imageData, imageBitmap);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from ImageData to WebGLTexture`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(imageData, webGlTexture);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from ImageBitmap to ImageData`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(imageBitmap, imageData);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from ImageBitmap to ImageBitmap`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(imageBitmap, imageBitmap);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from ImageBitmap to WebGLTexture`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(imageBitmap, webGlTexture);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from WebGLTexture to ImageData`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(webGlTexture, imageData);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from WebGLTexture to ImageBitmap`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(webGlTexture, imageBitmap);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`converts from WebGLTexture to WebGLTexture`, () => {
 | 
				
			||||||
 | 
					    runConversionTest(webGlTexture, webGlTexture);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`clones ImageData`, () => {
 | 
				
			||||||
 | 
					    runCloneTest(imageData);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`clones ImageBitmap`, () => {
 | 
				
			||||||
 | 
					    runCloneTest(imageBitmap);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`clones WebGLTextures`, () => {
 | 
				
			||||||
 | 
					    runCloneTest(webGlTexture);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`does not flip textures twice`, async () => {
 | 
				
			||||||
 | 
					    const [imageData, , webGlTexture] = await createTestData(
 | 
				
			||||||
 | 
					        gl, [1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, 4, 0, 0, 255], WIDTH,
 | 
				
			||||||
 | 
					        HEIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const shaderContext = new MPImageShaderContext();
 | 
				
			||||||
 | 
					    const image = new MPImage(
 | 
				
			||||||
 | 
					        /* imageData= */ null, /* imageBitmap= */ null, webGlTexture,
 | 
				
			||||||
 | 
					        /* ownsImageBitmap= */ false, /* ownsWebGLTexture= */ false, canvas,
 | 
				
			||||||
 | 
					        shaderContext, WIDTH, HEIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = image.clone().getImage(MPImageStorageType.IMAGE_DATA);
 | 
				
			||||||
 | 
					    expect(result).toEqual(imageData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.deleteTexture(webGlTexture);
 | 
				
			||||||
 | 
					    shaderContext.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it(`can clone and get image`, async () => {
 | 
				
			||||||
 | 
					    const [imageData, , webGlTexture] = await createTestData(
 | 
				
			||||||
 | 
					        gl, [1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, 4, 0, 0, 255], WIDTH,
 | 
				
			||||||
 | 
					        HEIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const shaderContext = new MPImageShaderContext();
 | 
				
			||||||
 | 
					    const image = new MPImage(
 | 
				
			||||||
 | 
					        /* imageData= */ null, /* imageBitmap= */ null, webGlTexture,
 | 
				
			||||||
 | 
					        /* ownsImageBitmap= */ false, /* ownsWebGLTexture= */ false, canvas,
 | 
				
			||||||
 | 
					        shaderContext, WIDTH, HEIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Verify that we can mix the different shader modes by running them out of
 | 
				
			||||||
 | 
					    // order.
 | 
				
			||||||
 | 
					    let result = image.getImage(MPImageStorageType.IMAGE_DATA);
 | 
				
			||||||
 | 
					    expect(result).toEqual(imageData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result = image.clone().getImage(MPImageStorageType.IMAGE_DATA);
 | 
				
			||||||
 | 
					    expect(result).toEqual(imageData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result = image.getImage(MPImageStorageType.IMAGE_DATA);
 | 
				
			||||||
 | 
					    expect(result).toEqual(imageData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.deleteTexture(webGlTexture);
 | 
				
			||||||
 | 
					    shaderContext.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('supports hasType()', async () => {
 | 
				
			||||||
 | 
					    const shaderContext = new MPImageShaderContext();
 | 
				
			||||||
 | 
					    const image = createImage(shaderContext, imageData, WIDTH, HEIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.IMAGE_DATA)).toBe(true);
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.WEBGL_TEXTURE)).toBe(false);
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.IMAGE_BITMAP)).toBe(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    image.getImage(MPImageStorageType.WEBGL_TEXTURE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.IMAGE_DATA)).toBe(true);
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.WEBGL_TEXTURE)).toBe(true);
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.IMAGE_BITMAP)).toBe(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await image.getImage(MPImageStorageType.IMAGE_BITMAP);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.IMAGE_DATA)).toBe(true);
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.WEBGL_TEXTURE)).toBe(true);
 | 
				
			||||||
 | 
					    expect(image.hasType(MPImageStorageType.IMAGE_BITMAP)).toBe(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    image.close();
 | 
				
			||||||
 | 
					    shaderContext.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('supports image that is smaller than the canvas', async () => {
 | 
				
			||||||
 | 
					    const [imageData, imageBitmap, webGlTexture] = await createTestData(
 | 
				
			||||||
 | 
					        gl, [1, 0, 0, 255, 2, 0, 0, 255], /* width= */ 2, /* height= */ 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runConversionTest(imageData, webGlTexture, /* width= */ 2, /* height= */ 1);
 | 
				
			||||||
 | 
					    runConversionTest(
 | 
				
			||||||
 | 
					        webGlTexture, imageBitmap, /* width= */ 2, /* height= */ 1);
 | 
				
			||||||
 | 
					    runConversionTest(imageBitmap, imageData, /* width= */ 2, /* height= */ 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.deleteTexture(webGlTexture);
 | 
				
			||||||
 | 
					    imageBitmap.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('supports image that is larger than the canvas', async () => {
 | 
				
			||||||
 | 
					    const [imageData, imageBitmap, webGlTexture] = await createTestData(
 | 
				
			||||||
 | 
					        gl,
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					          1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255,
 | 
				
			||||||
 | 
					          4, 0, 0, 255, 5, 0, 0, 255, 6, 0, 0, 255
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        /* width= */ 2, /* height= */ 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runConversionTest(imageData, webGlTexture, /* width= */ 2, /* height= */ 3);
 | 
				
			||||||
 | 
					    runConversionTest(
 | 
				
			||||||
 | 
					        webGlTexture, imageBitmap, /* width= */ 2, /* height= */ 3);
 | 
				
			||||||
 | 
					    runConversionTest(imageBitmap, imageData, /* width= */ 2, /* height= */ 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.deleteTexture(webGlTexture);
 | 
				
			||||||
 | 
					    imageBitmap.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										595
									
								
								mediapipe/tasks/web/vision/core/image.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										595
									
								
								mediapipe/tasks/web/vision/core/image.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,595 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright 2023 The MediaPipe Authors. All Rights Reserved.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** The underlying type of the image. */
 | 
				
			||||||
 | 
					export enum MPImageStorageType {
 | 
				
			||||||
 | 
					  /** Represents the native `ImageData` type. */
 | 
				
			||||||
 | 
					  IMAGE_DATA,
 | 
				
			||||||
 | 
					  /** Represents the native `ImageBitmap` type. */
 | 
				
			||||||
 | 
					  IMAGE_BITMAP,
 | 
				
			||||||
 | 
					  /** Represents the native `WebGLTexture` type. */
 | 
				
			||||||
 | 
					  WEBGL_TEXTURE
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MPImageNativeContainer = ImageData|ImageBitmap|WebGLTexture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const VERTEX_SHADER = `
 | 
				
			||||||
 | 
					  attribute vec2 aVertex;
 | 
				
			||||||
 | 
					  attribute vec2 aTex;
 | 
				
			||||||
 | 
					  varying vec2 vTex;
 | 
				
			||||||
 | 
					  void main(void) {
 | 
				
			||||||
 | 
					    gl_Position = vec4(aVertex, 0.0, 1.0);
 | 
				
			||||||
 | 
					    vTex = aTex;
 | 
				
			||||||
 | 
					  }`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FRAGMENT_SHADER = `
 | 
				
			||||||
 | 
					  precision mediump float;
 | 
				
			||||||
 | 
					  varying vec2 vTex;
 | 
				
			||||||
 | 
					  uniform sampler2D inputTexture;
 | 
				
			||||||
 | 
					   void main() {
 | 
				
			||||||
 | 
					     gl_FragColor = texture2D(inputTexture, vTex);
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					 `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function assertNotNull<T>(value: T|null, msg: string): T {
 | 
				
			||||||
 | 
					  if (value === null) {
 | 
				
			||||||
 | 
					    throw new Error(`Unable to obtain required WebGL resource: ${msg}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Utility class that encapsulates the buffers used by `MPImageShaderContext`.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class MPImageShaderBuffers {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					      private readonly gl: WebGL2RenderingContext,
 | 
				
			||||||
 | 
					      private readonly vertexArrayObject: WebGLVertexArrayObject,
 | 
				
			||||||
 | 
					      private readonly vertexBuffer: WebGLBuffer,
 | 
				
			||||||
 | 
					      private readonly textureBuffer: WebGLBuffer) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bind() {
 | 
				
			||||||
 | 
					    this.gl.bindVertexArray(this.vertexArrayObject);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unbind() {
 | 
				
			||||||
 | 
					    this.gl.bindVertexArray(null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  close() {
 | 
				
			||||||
 | 
					    this.gl.deleteVertexArray(this.vertexArrayObject);
 | 
				
			||||||
 | 
					    this.gl.deleteBuffer(this.vertexBuffer);
 | 
				
			||||||
 | 
					    this.gl.deleteBuffer(this.textureBuffer);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A class that encapsulates the shaders used by an MPImage. Can be re-used
 | 
				
			||||||
 | 
					 * across MPImages that use the same WebGL2Rendering context.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class MPImageShaderContext {
 | 
				
			||||||
 | 
					  private gl?: WebGL2RenderingContext;
 | 
				
			||||||
 | 
					  private framebuffer?: WebGLFramebuffer;
 | 
				
			||||||
 | 
					  private program?: WebGLProgram;
 | 
				
			||||||
 | 
					  private vertexShader?: WebGLShader;
 | 
				
			||||||
 | 
					  private fragmentShader?: WebGLShader;
 | 
				
			||||||
 | 
					  private aVertex?: GLint;
 | 
				
			||||||
 | 
					  private aTex?: GLint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * The shader buffers used for passthrough renders that don't modify the
 | 
				
			||||||
 | 
					   * input texture.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private shaderBuffersPassthrough?: MPImageShaderBuffers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * The shader buffers used for passthrough renders that flip the input texture
 | 
				
			||||||
 | 
					   * vertically before conversion to a different type. This is used to flip the
 | 
				
			||||||
 | 
					   * texture to the expected orientation for drawing in the browser.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private shaderBuffersFlipVertically?: MPImageShaderBuffers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private compileShader(source: string, type: number): WebGLShader {
 | 
				
			||||||
 | 
					    const gl = this.gl!;
 | 
				
			||||||
 | 
					    const shader =
 | 
				
			||||||
 | 
					        assertNotNull(gl.createShader(type), 'Failed to create WebGL shader');
 | 
				
			||||||
 | 
					    gl.shaderSource(shader, source);
 | 
				
			||||||
 | 
					    gl.compileShader(shader);
 | 
				
			||||||
 | 
					    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 | 
				
			||||||
 | 
					      const info = gl.getShaderInfoLog(shader);
 | 
				
			||||||
 | 
					      throw new Error(`Could not compile WebGL shader: ${info}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    gl.attachShader(this.program!, shader);
 | 
				
			||||||
 | 
					    return shader;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private setupShaders(): void {
 | 
				
			||||||
 | 
					    const gl = this.gl!;
 | 
				
			||||||
 | 
					    this.program =
 | 
				
			||||||
 | 
					        assertNotNull(gl.createProgram()!, 'Failed to create WebGL program');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.vertexShader = this.compileShader(VERTEX_SHADER, gl.VERTEX_SHADER);
 | 
				
			||||||
 | 
					    this.fragmentShader =
 | 
				
			||||||
 | 
					        this.compileShader(FRAGMENT_SHADER, gl.FRAGMENT_SHADER);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.linkProgram(this.program);
 | 
				
			||||||
 | 
					    const linked = gl.getProgramParameter(this.program, gl.LINK_STATUS);
 | 
				
			||||||
 | 
					    if (!linked) {
 | 
				
			||||||
 | 
					      const info = gl.getProgramInfoLog(this.program);
 | 
				
			||||||
 | 
					      throw new Error(`Error during program linking: ${info}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.aVertex = gl.getAttribLocation(this.program, 'aVertex');
 | 
				
			||||||
 | 
					    this.aTex = gl.getAttribLocation(this.program, 'aTex');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private createBuffers(flipVertically: boolean): MPImageShaderBuffers {
 | 
				
			||||||
 | 
					    const gl = this.gl!;
 | 
				
			||||||
 | 
					    const vertexArrayObject =
 | 
				
			||||||
 | 
					        assertNotNull(gl.createVertexArray(), 'Failed to create vertex array');
 | 
				
			||||||
 | 
					    gl.bindVertexArray(vertexArrayObject);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const vertexBuffer =
 | 
				
			||||||
 | 
					        assertNotNull(gl.createBuffer(), 'Failed to create buffer');
 | 
				
			||||||
 | 
					    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
 | 
				
			||||||
 | 
					    gl.enableVertexAttribArray(this.aVertex!);
 | 
				
			||||||
 | 
					    gl.vertexAttribPointer(this.aVertex!, 2, gl.FLOAT, false, 0, 0);
 | 
				
			||||||
 | 
					    gl.bufferData(
 | 
				
			||||||
 | 
					        gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]),
 | 
				
			||||||
 | 
					        gl.STATIC_DRAW);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const textureBuffer =
 | 
				
			||||||
 | 
					        assertNotNull(gl.createBuffer(), 'Failed to create buffer');
 | 
				
			||||||
 | 
					    gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
 | 
				
			||||||
 | 
					    gl.enableVertexAttribArray(this.aTex!);
 | 
				
			||||||
 | 
					    gl.vertexAttribPointer(this.aTex!, 2, gl.FLOAT, false, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bufferData =
 | 
				
			||||||
 | 
					        flipVertically ? [0, 1, 0, 0, 1, 0, 1, 1] : [0, 0, 0, 1, 1, 1, 1, 0];
 | 
				
			||||||
 | 
					    gl.bufferData(
 | 
				
			||||||
 | 
					        gl.ARRAY_BUFFER, new Float32Array(bufferData), gl.STATIC_DRAW);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.bindBuffer(gl.ARRAY_BUFFER, null);
 | 
				
			||||||
 | 
					    gl.bindVertexArray(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new MPImageShaderBuffers(
 | 
				
			||||||
 | 
					        gl, vertexArrayObject, vertexBuffer, textureBuffer);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getShaderBuffers(flipVertically: boolean): MPImageShaderBuffers {
 | 
				
			||||||
 | 
					    if (flipVertically) {
 | 
				
			||||||
 | 
					      if (!this.shaderBuffersFlipVertically) {
 | 
				
			||||||
 | 
					        this.shaderBuffersFlipVertically =
 | 
				
			||||||
 | 
					            this.createBuffers(/* flipVertically= */ true);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this.shaderBuffersFlipVertically;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (!this.shaderBuffersPassthrough) {
 | 
				
			||||||
 | 
					        this.shaderBuffersPassthrough =
 | 
				
			||||||
 | 
					            this.createBuffers(/* flipVertically= */ false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this.shaderBuffersPassthrough;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private maybeInitGL(gl: WebGL2RenderingContext): void {
 | 
				
			||||||
 | 
					    if (!this.gl) {
 | 
				
			||||||
 | 
					      this.gl = gl;
 | 
				
			||||||
 | 
					    } else if (gl !== this.gl) {
 | 
				
			||||||
 | 
					      throw new Error('Cannot change GL context once initialized');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Runs the callback using the shader. */
 | 
				
			||||||
 | 
					  run<T>(
 | 
				
			||||||
 | 
					      gl: WebGL2RenderingContext, flipVertically: boolean,
 | 
				
			||||||
 | 
					      callback: () => T): T {
 | 
				
			||||||
 | 
					    this.maybeInitGL(gl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!this.program) {
 | 
				
			||||||
 | 
					      this.setupShaders();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const shaderBuffers = this.getShaderBuffers(flipVertically);
 | 
				
			||||||
 | 
					    gl.useProgram(this.program!);
 | 
				
			||||||
 | 
					    shaderBuffers.bind();
 | 
				
			||||||
 | 
					    const result = callback();
 | 
				
			||||||
 | 
					    shaderBuffers.unbind();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Binds a framebuffer to the canvas. If the framebuffer does not yet exist,
 | 
				
			||||||
 | 
					   * creates it first. Binds the provided texture to the framebuffer.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  bindFramebuffer(gl: WebGL2RenderingContext, texture: WebGLTexture): void {
 | 
				
			||||||
 | 
					    this.maybeInitGL(gl);
 | 
				
			||||||
 | 
					    if (!this.framebuffer) {
 | 
				
			||||||
 | 
					      this.framebuffer =
 | 
				
			||||||
 | 
					          assertNotNull(gl.createFramebuffer(), 'Failed to create framebuffe.');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
 | 
				
			||||||
 | 
					    gl.framebufferTexture2D(
 | 
				
			||||||
 | 
					        gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unbindFramebuffer(): void {
 | 
				
			||||||
 | 
					    this.gl?.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  close() {
 | 
				
			||||||
 | 
					    if (this.program) {
 | 
				
			||||||
 | 
					      const gl = this.gl!;
 | 
				
			||||||
 | 
					      gl.deleteProgram(this.program);
 | 
				
			||||||
 | 
					      gl.deleteShader(this.vertexShader!);
 | 
				
			||||||
 | 
					      gl.deleteShader(this.fragmentShader!);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.framebuffer) {
 | 
				
			||||||
 | 
					      this.gl!.deleteFramebuffer(this.framebuffer);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.shaderBuffersPassthrough) {
 | 
				
			||||||
 | 
					      this.shaderBuffersPassthrough.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.shaderBuffersFlipVertically) {
 | 
				
			||||||
 | 
					      this.shaderBuffersFlipVertically.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The wrapper class for MediaPipe Image objects.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Images are stored as `ImageData`, `ImageBitmap` or `WebGLTexture` objects.
 | 
				
			||||||
 | 
					 * You can convert the underlying type to any other type by passing the
 | 
				
			||||||
 | 
					 * desired type to `getImage()`. As type conversions can be expensive, it is
 | 
				
			||||||
 | 
					 * recommended to limit these conversions. You can verify what underlying
 | 
				
			||||||
 | 
					 * types are already available by invoking `hasType()`.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Images that are returned from a MediaPipe Tasks are owned by by the
 | 
				
			||||||
 | 
					 * underlying C++ Task. If you need to extend the lifetime of these objects,
 | 
				
			||||||
 | 
					 * you can invoke the `clone()` method. To free up the resources obtained
 | 
				
			||||||
 | 
					 * during any clone or type conversion operation, it is important to invoke
 | 
				
			||||||
 | 
					 * `close()` on the `MPImage` instance.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Converting to and from ImageBitmap requires that the MediaPipe task is
 | 
				
			||||||
 | 
					 * initialized with an `OffscreenCanvas`. As we require WebGL2 support, this
 | 
				
			||||||
 | 
					 * places some limitations on Browser support as outlined here:
 | 
				
			||||||
 | 
					 * https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/getContext
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class MPImage {
 | 
				
			||||||
 | 
					  private gl?: WebGL2RenderingContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** @hideconstructor */
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					      private imageData: ImageData|null,
 | 
				
			||||||
 | 
					      private imageBitmap: ImageBitmap|null,
 | 
				
			||||||
 | 
					      private webGLTexture: WebGLTexture|null,
 | 
				
			||||||
 | 
					      private ownsImageBitmap: boolean,
 | 
				
			||||||
 | 
					      private ownsWebGLTexture: boolean,
 | 
				
			||||||
 | 
					      /** Returns the canvas element that the image is bound to. */
 | 
				
			||||||
 | 
					      readonly canvas: HTMLCanvasElement|OffscreenCanvas|undefined,
 | 
				
			||||||
 | 
					      private shaderContext: MPImageShaderContext|undefined,
 | 
				
			||||||
 | 
					      /** Returns the width of the image. */
 | 
				
			||||||
 | 
					      readonly width: number,
 | 
				
			||||||
 | 
					      /** Returns the height of the image. */
 | 
				
			||||||
 | 
					      readonly height: number,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Returns whether this `MPImage` stores the image in the desired format.
 | 
				
			||||||
 | 
					   * This method can be called to reduce expensive conversion before invoking
 | 
				
			||||||
 | 
					   * `getType()`.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  hasType(type: MPImageStorageType): boolean {
 | 
				
			||||||
 | 
					    if (type === MPImageStorageType.IMAGE_DATA) {
 | 
				
			||||||
 | 
					      return !!this.imageData;
 | 
				
			||||||
 | 
					    } else if (type === MPImageStorageType.IMAGE_BITMAP) {
 | 
				
			||||||
 | 
					      return !!this.imageBitmap;
 | 
				
			||||||
 | 
					    } else if (type === MPImageStorageType.WEBGL_TEXTURE) {
 | 
				
			||||||
 | 
					      return !!this.webGLTexture;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      throw new Error(`Type is not supported: ${type}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Returns the underlying image as an `ImageData` object. Note that this
 | 
				
			||||||
 | 
					   * involves an expensive GPU to CPU transfer if the current image is only
 | 
				
			||||||
 | 
					   * available as an `ImageBitmap` or `WebGLTexture`.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @return The current image as an ImageData object.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getImage(type: MPImageStorageType.IMAGE_DATA): ImageData;
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Returns the underlying image as an `ImageBitmap`. Note that
 | 
				
			||||||
 | 
					   * conversions to `ImageBitmap` are expensive, especially if the data
 | 
				
			||||||
 | 
					   * currently resides on CPU.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * Processing with `ImageBitmap`s requires that the MediaPipe Task was
 | 
				
			||||||
 | 
					   * initialized with an `OffscreenCanvas` with WebGL2 support. See
 | 
				
			||||||
 | 
					   * https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/getContext
 | 
				
			||||||
 | 
					   * for a list of supported platforms.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @return The current image as an ImageBitmap object.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getImage(type: MPImageStorageType.IMAGE_BITMAP): ImageBitmap;
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Returns the underlying image as a `WebGLTexture` object. Note that this
 | 
				
			||||||
 | 
					   * involves a CPU to GPU transfer if the current image is only available as
 | 
				
			||||||
 | 
					   * an `ImageData` object. The returned texture is bound to the current
 | 
				
			||||||
 | 
					   * canvas (see `.canvas`).
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @return The current image as a WebGLTexture.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getImage(type: MPImageStorageType.WEBGL_TEXTURE): WebGLTexture;
 | 
				
			||||||
 | 
					  getImage(type?: MPImageStorageType): MPImageNativeContainer {
 | 
				
			||||||
 | 
					    if (type === MPImageStorageType.IMAGE_DATA) {
 | 
				
			||||||
 | 
					      return this.convertToImageData();
 | 
				
			||||||
 | 
					    } else if (type === MPImageStorageType.IMAGE_BITMAP) {
 | 
				
			||||||
 | 
					      return this.convertToImageBitmap();
 | 
				
			||||||
 | 
					    } else if (type === MPImageStorageType.WEBGL_TEXTURE) {
 | 
				
			||||||
 | 
					      return this.convertToWebGLTexture();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      throw new Error(`Type is not supported: ${type}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Creates a copy of the resources stored in this `MPImage`. You can invoke
 | 
				
			||||||
 | 
					   * this method to extend the lifetime of an image returned by a MediaPipe
 | 
				
			||||||
 | 
					   * Task. Note that performance critical applications should aim to only use
 | 
				
			||||||
 | 
					   * the `MPImage` within the MediaPipe Task callback so that copies can be
 | 
				
			||||||
 | 
					   * avoided.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  clone(): MPImage {
 | 
				
			||||||
 | 
					    // TODO: We might only want to clone one backing datastructure
 | 
				
			||||||
 | 
					    // even if multiple are defined.
 | 
				
			||||||
 | 
					    let destinationImageData: ImageData|null = null;
 | 
				
			||||||
 | 
					    let destinationImageBitmap: ImageBitmap|null = null;
 | 
				
			||||||
 | 
					    let destinationWebGLTexture: WebGLTexture|null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.imageData) {
 | 
				
			||||||
 | 
					      destinationImageData =
 | 
				
			||||||
 | 
					          new ImageData(this.imageData.data, this.width, this.height);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.webGLTexture) {
 | 
				
			||||||
 | 
					      const gl = this.getGL();
 | 
				
			||||||
 | 
					      const shaderContext = this.getShaderContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Create a new texture and use it to back a framebuffer
 | 
				
			||||||
 | 
					      gl.activeTexture(gl.TEXTURE1);
 | 
				
			||||||
 | 
					      destinationWebGLTexture =
 | 
				
			||||||
 | 
					          assertNotNull(gl.createTexture(), 'Failed to create texture');
 | 
				
			||||||
 | 
					      gl.bindTexture(gl.TEXTURE_2D, destinationWebGLTexture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      gl.texImage2D(
 | 
				
			||||||
 | 
					          gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA,
 | 
				
			||||||
 | 
					          gl.UNSIGNED_BYTE, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      shaderContext.bindFramebuffer(gl, destinationWebGLTexture);
 | 
				
			||||||
 | 
					      shaderContext.run(gl, /* flipVertically= */ false, () => {
 | 
				
			||||||
 | 
					        this.bindTexture();  // This activates gl.TEXTURE0
 | 
				
			||||||
 | 
					        gl.clearColor(0, 0, 0, 0);
 | 
				
			||||||
 | 
					        gl.clear(gl.COLOR_BUFFER_BIT);
 | 
				
			||||||
 | 
					        gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
 | 
				
			||||||
 | 
					        this.unbindTexture();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      shaderContext.unbindFramebuffer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.unbindTexture();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.imageBitmap) {
 | 
				
			||||||
 | 
					      this.convertToWebGLTexture();
 | 
				
			||||||
 | 
					      this.bindTexture();
 | 
				
			||||||
 | 
					      destinationImageBitmap = this.copyTextureToBitmap();
 | 
				
			||||||
 | 
					      this.unbindTexture();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new MPImage(
 | 
				
			||||||
 | 
					        destinationImageData, destinationImageBitmap, destinationWebGLTexture,
 | 
				
			||||||
 | 
					        !!destinationImageBitmap, !!destinationWebGLTexture, this.canvas,
 | 
				
			||||||
 | 
					        this.shaderContext, this.width, this.height);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getOffscreenCanvas(): OffscreenCanvas {
 | 
				
			||||||
 | 
					    if (!(this.canvas instanceof OffscreenCanvas)) {
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					          'Conversion to ImageBitmap requires that the MediaPipe Tasks is ' +
 | 
				
			||||||
 | 
					          'initialized with an OffscreenCanvas');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return this.canvas;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getGL(): WebGL2RenderingContext {
 | 
				
			||||||
 | 
					    if (!this.canvas) {
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					          'Conversion to different image formats require that a canvas ' +
 | 
				
			||||||
 | 
					          'is passed when iniitializing the image.');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!this.gl) {
 | 
				
			||||||
 | 
					      this.gl = assertNotNull(
 | 
				
			||||||
 | 
					          this.canvas.getContext('webgl2') as WebGL2RenderingContext | null,
 | 
				
			||||||
 | 
					          'You cannot use a canvas that is already bound to a different ' +
 | 
				
			||||||
 | 
					              'type of rendering context.');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return this.gl;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getShaderContext(): MPImageShaderContext {
 | 
				
			||||||
 | 
					    if (!this.shaderContext) {
 | 
				
			||||||
 | 
					      this.shaderContext = new MPImageShaderContext();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return this.shaderContext;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private convertToImageBitmap(): ImageBitmap {
 | 
				
			||||||
 | 
					    if (!this.imageBitmap) {
 | 
				
			||||||
 | 
					      if (!this.webGLTexture) {
 | 
				
			||||||
 | 
					        this.webGLTexture = this.convertToWebGLTexture();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.imageBitmap = this.convertWebGLTextureToImageBitmap();
 | 
				
			||||||
 | 
					      this.ownsImageBitmap = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.imageBitmap;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private convertToImageData(): ImageData {
 | 
				
			||||||
 | 
					    if (!this.imageData) {
 | 
				
			||||||
 | 
					      const gl = this.getGL();
 | 
				
			||||||
 | 
					      const shaderContext = this.getShaderContext();
 | 
				
			||||||
 | 
					      const pixels = new Uint8Array(this.width * this.height * 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Create texture if needed
 | 
				
			||||||
 | 
					      this.convertToWebGLTexture();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Create a framebuffer from the texture and read back pixels
 | 
				
			||||||
 | 
					      shaderContext.bindFramebuffer(gl, this.webGLTexture!);
 | 
				
			||||||
 | 
					      gl.readPixels(
 | 
				
			||||||
 | 
					          0, 0, this.width, this.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
 | 
				
			||||||
 | 
					      shaderContext.unbindFramebuffer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.imageData = new ImageData(
 | 
				
			||||||
 | 
					          new Uint8ClampedArray(pixels.buffer), this.width, this.height);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.imageData;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private convertToWebGLTexture(): WebGLTexture {
 | 
				
			||||||
 | 
					    if (!this.webGLTexture) {
 | 
				
			||||||
 | 
					      const gl = this.getGL();
 | 
				
			||||||
 | 
					      this.bindTexture();
 | 
				
			||||||
 | 
					      const source = (this.imageBitmap || this.imageData)!;
 | 
				
			||||||
 | 
					      gl.texImage2D(
 | 
				
			||||||
 | 
					          gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
 | 
				
			||||||
 | 
					      this.unbindTexture();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.webGLTexture!;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Binds the backing texture to the canvas. If the texture does not yet
 | 
				
			||||||
 | 
					   * exist, creates it first.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private bindTexture() {
 | 
				
			||||||
 | 
					    const gl = this.getGL();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.viewport(0, 0, this.width, this.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.activeTexture(gl.TEXTURE0);
 | 
				
			||||||
 | 
					    if (!this.webGLTexture) {
 | 
				
			||||||
 | 
					      this.webGLTexture =
 | 
				
			||||||
 | 
					          assertNotNull(gl.createTexture(), 'Failed to create texture');
 | 
				
			||||||
 | 
					      this.ownsWebGLTexture = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gl.bindTexture(gl.TEXTURE_2D, this.webGLTexture);
 | 
				
			||||||
 | 
					    // TODO: Ideally, we would only set these once per texture and
 | 
				
			||||||
 | 
					    // not once every frame.
 | 
				
			||||||
 | 
					    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
 | 
				
			||||||
 | 
					    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 | 
				
			||||||
 | 
					    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
 | 
				
			||||||
 | 
					    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private unbindTexture(): void {
 | 
				
			||||||
 | 
					    this.gl!.bindTexture(this.gl!.TEXTURE_2D, null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Invokes a shader to render the current texture and return it as an
 | 
				
			||||||
 | 
					   * ImageBitmap
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private copyTextureToBitmap(): ImageBitmap {
 | 
				
			||||||
 | 
					    const gl = this.getGL();
 | 
				
			||||||
 | 
					    const shaderContext = this.getShaderContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return shaderContext.run(gl, /* flipVertically= */ true, () => {
 | 
				
			||||||
 | 
					      return this.runWithResizedCanvas(() => {
 | 
				
			||||||
 | 
					        // Unbind any framebuffer that may be bound since
 | 
				
			||||||
 | 
					        // `transferToImageBitmap()` requires rendering into the display (null)
 | 
				
			||||||
 | 
					        // framebuffer.
 | 
				
			||||||
 | 
					        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.clearColor(0, 0, 0, 0);
 | 
				
			||||||
 | 
					        gl.clear(gl.COLOR_BUFFER_BIT);
 | 
				
			||||||
 | 
					        gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
 | 
				
			||||||
 | 
					        return this.getOffscreenCanvas().transferToImageBitmap();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private convertWebGLTextureToImageBitmap(): ImageBitmap {
 | 
				
			||||||
 | 
					    this.bindTexture();
 | 
				
			||||||
 | 
					    const result = this.copyTextureToBitmap();
 | 
				
			||||||
 | 
					    this.unbindTexture();
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Temporarily resizes the underlying canvas to match the dimensions of the
 | 
				
			||||||
 | 
					   * image. Runs the provided callback on the resized canvas.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * Note that while resizing is an expensive operation, it allows us to use
 | 
				
			||||||
 | 
					   * the synchronous `transferToImageBitmap()` API.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private runWithResizedCanvas<T>(callback: () => T): T {
 | 
				
			||||||
 | 
					    const canvas = this.canvas!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (canvas.width === this.width && canvas.height === this.height) {
 | 
				
			||||||
 | 
					      return callback();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const originalWidth = canvas.width;
 | 
				
			||||||
 | 
					    const originalHeight = canvas.height;
 | 
				
			||||||
 | 
					    canvas.width = this.width;
 | 
				
			||||||
 | 
					    canvas.height = this.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = callback();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas.width = originalWidth;
 | 
				
			||||||
 | 
					    canvas.height = originalHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Frees up any resources owned by this `MPImage` instance.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * Note that this method does not free images that are owned by the C++
 | 
				
			||||||
 | 
					   * Task, as these are freed automatically once you leave the MediaPipe
 | 
				
			||||||
 | 
					   * callback. Additionally, some shared state is freed only once you invoke the
 | 
				
			||||||
 | 
					   * Task's `close()` method.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  close(): void {
 | 
				
			||||||
 | 
					    if (this.ownsImageBitmap) {
 | 
				
			||||||
 | 
					      this.imageBitmap!.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!this.gl) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.ownsWebGLTexture) {
 | 
				
			||||||
 | 
					      this.gl.deleteTexture(this.webGLTexture!);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {FilesetResolver as FilesetResolverImpl} from '../../../tasks/web/core/fileset_resolver';
 | 
					import {FilesetResolver as FilesetResolverImpl} from '../../../tasks/web/core/fileset_resolver';
 | 
				
			||||||
import {DrawingUtils as DrawingUtilsImpl} from '../../../tasks/web/vision/core/drawing_utils';
 | 
					import {DrawingUtils as DrawingUtilsImpl} from '../../../tasks/web/vision/core/drawing_utils';
 | 
				
			||||||
 | 
					import {MPImage as MPImageImpl} from '../../../tasks/web/vision/core/image';
 | 
				
			||||||
import {FaceDetector as FaceDetectorImpl} from '../../../tasks/web/vision/face_detector/face_detector';
 | 
					import {FaceDetector as FaceDetectorImpl} from '../../../tasks/web/vision/face_detector/face_detector';
 | 
				
			||||||
import {FaceLandmarker as FaceLandmarkerImpl, FaceLandmarksConnections as FaceLandmarksConnectionsImpl} from '../../../tasks/web/vision/face_landmarker/face_landmarker';
 | 
					import {FaceLandmarker as FaceLandmarkerImpl, FaceLandmarksConnections as FaceLandmarksConnectionsImpl} from '../../../tasks/web/vision/face_landmarker/face_landmarker';
 | 
				
			||||||
import {FaceStylizer as FaceStylizerImpl} from '../../../tasks/web/vision/face_stylizer/face_stylizer';
 | 
					import {FaceStylizer as FaceStylizerImpl} from '../../../tasks/web/vision/face_stylizer/face_stylizer';
 | 
				
			||||||
| 
						 | 
					@ -31,6 +32,7 @@ import {ObjectDetector as ObjectDetectorImpl} from '../../../tasks/web/vision/ob
 | 
				
			||||||
// as exports.
 | 
					// as exports.
 | 
				
			||||||
const DrawingUtils = DrawingUtilsImpl;
 | 
					const DrawingUtils = DrawingUtilsImpl;
 | 
				
			||||||
const FilesetResolver = FilesetResolverImpl;
 | 
					const FilesetResolver = FilesetResolverImpl;
 | 
				
			||||||
 | 
					const MPImage = MPImageImpl;
 | 
				
			||||||
const FaceDetector = FaceDetectorImpl;
 | 
					const FaceDetector = FaceDetectorImpl;
 | 
				
			||||||
const FaceLandmarker = FaceLandmarkerImpl;
 | 
					const FaceLandmarker = FaceLandmarkerImpl;
 | 
				
			||||||
const FaceLandmarksConnections = FaceLandmarksConnectionsImpl;
 | 
					const FaceLandmarksConnections = FaceLandmarksConnectionsImpl;
 | 
				
			||||||
| 
						 | 
					@ -46,6 +48,7 @@ const ObjectDetector = ObjectDetectorImpl;
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
  DrawingUtils,
 | 
					  DrawingUtils,
 | 
				
			||||||
  FilesetResolver,
 | 
					  FilesetResolver,
 | 
				
			||||||
 | 
					  MPImage,
 | 
				
			||||||
  FaceDetector,
 | 
					  FaceDetector,
 | 
				
			||||||
  FaceLandmarker,
 | 
					  FaceLandmarker,
 | 
				
			||||||
  FaceLandmarksConnections,
 | 
					  FaceLandmarksConnections,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export * from '../../../tasks/web/core/fileset_resolver';
 | 
					export * from '../../../tasks/web/core/fileset_resolver';
 | 
				
			||||||
export * from '../../../tasks/web/vision/core/drawing_utils';
 | 
					export * from '../../../tasks/web/vision/core/drawing_utils';
 | 
				
			||||||
 | 
					export * from '../../../tasks/web/vision/core/image';
 | 
				
			||||||
export * from '../../../tasks/web/vision/face_detector/face_detector';
 | 
					export * from '../../../tasks/web/vision/face_detector/face_detector';
 | 
				
			||||||
export * from '../../../tasks/web/vision/face_landmarker/face_landmarker';
 | 
					export * from '../../../tasks/web/vision/face_landmarker/face_landmarker';
 | 
				
			||||||
export * from '../../../tasks/web/vision/face_stylizer/face_stylizer';
 | 
					export * from '../../../tasks/web/vision/face_stylizer/face_stylizer';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user