/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ export function convertVideoFrameToImageData( videoFrame: VideoFrame, ): ImageData | undefined { const canvas = new OffscreenCanvas( videoFrame.displayWidth, videoFrame.displayHeight, ); const ctx = canvas.getContext('2d'); ctx?.drawImage(videoFrame, 0, 0); return ctx?.getImageData(0, 0, canvas.width, canvas.height); } /** * This utility provides two functions: * `process`: to find the bounding box of non-empty pixels from an ImageData, when looping through all its pixels * `crop` to cut out the subsection found in `process` * @returns */ export function findBoundingBox() { let xMin = Number.MAX_VALUE; let yMin = Number.MAX_VALUE; let xMax = Number.MIN_VALUE; let yMax = Number.MIN_VALUE; return { process: function (x: number, y: number, hasData: boolean) { if (hasData) { xMin = Math.min(x, xMin); xMax = Math.max(x, xMax); yMin = Math.min(y, yMin); yMax = Math.max(y, yMax); } return [xMin, xMax, yMin, yMax]; }, crop(imageData: ImageData): ImageData | null { const canvas = new OffscreenCanvas(imageData.width, imageData.height); const ctx = canvas.getContext('2d'); const boundingBoxWidth = xMax - xMin; const boundingBoxHeight = yMax - yMin; if (ctx && boundingBoxWidth > 0 && boundingBoxHeight > 0) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.putImageData(imageData, 0, 0); return ctx.getImageData( xMin, yMin, boundingBoxWidth, boundingBoxHeight, ); } else { return null; } }, getBox(): [[number, number], [number, number]] { return [ [xMin, yMin], [xMax, yMax], ]; }, }; } export function magnifyImageRegion( canvas: HTMLCanvasElement | null, x: number, y: number, radius: number = 25, scale: number = 2, ): string { if (canvas == null) { return ''; } const ctx = canvas.getContext('2d'); if (ctx) { const minX = x - radius < 0 ? radius - x : 0; const minY = y - radius < 0 ? radius - y : 0; const region = ctx.getImageData( Math.max(x - radius, 0), Math.max(y - radius, 0), radius * 2, radius * 2, ); // ImageData doesn't scale-transform correctly on canvas // So we first draw the original size on an offscreen canvas, and then scale it const regionCanvas = new OffscreenCanvas(region.width, region.height); const regionCtx = regionCanvas.getContext('2d'); regionCtx?.putImageData(region, minX > 0 ? minX : 0, minY > 0 ? minY : 0); const scaleCanvas = document.createElement('canvas'); scaleCanvas.width = Math.round(region.width * scale); scaleCanvas.height = Math.round(region.height * scale); const scaleCtx = scaleCanvas.getContext('2d'); scaleCtx?.scale(scale, scale); scaleCtx?.drawImage(regionCanvas, 0, 0); return scaleCanvas.toDataURL(); } return ''; }