ppu-ocv 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,7 @@ Based on [TechStark/opencv-js](https://github.com/TechStark/opencv-js).
29
29
  - [Usage (Node.js / Bun)](#usage-nodejs--bun)
30
30
  - [Canvas-only Usage (no OpenCV)](#canvas-only-usage-no-opencv)
31
31
  - [Web / Browser Support](#web--browser-support)
32
+ - [React Native / Mobile Support](#react-native--mobile-support)
32
33
  - [Built-in Pipeline Operations](#built-in-pipeline-operations)
33
34
  - [Extending Operations](#extending-operations)
34
35
  - [Class Documentation](#class-documentation)
@@ -211,12 +212,13 @@ See the [interactive demo](./index.html) for a full working example.
211
212
 
212
213
  ### Entry point reference
213
214
 
214
- | Import path | OpenCV | Canvas backend | `CanvasToolkit` | Use case |
215
- | -------------------- | ------ | ------------------------------------- | -------------------- | ------------------------------------------ |
216
- | `ppu-ocv` | ✅ | `@napi-rs/canvas` | Full (with file I/O) | Full pipeline, Node.js / Bun |
217
- | `ppu-ocv/web` | ✅ | `HTMLCanvasElement`/`OffscreenCanvas` | Base only | Full pipeline, browser |
218
- | `ppu-ocv/canvas` | ❌ | `@napi-rs/canvas` | Full (with file I/O) | Canvas-only, Node (extensions, edge, etc.) |
219
- | `ppu-ocv/canvas-web` | ❌ | `HTMLCanvasElement`/`OffscreenCanvas` | Base only | Canvas-only, browser extensions / SW |
215
+ | Import path | OpenCV | Canvas backend | `CanvasToolkit` | Use case |
216
+ | ----------------------- | ------ | ------------------------------------- | -------------------- | ------------------------------------------ |
217
+ | `ppu-ocv` | ✅ | `@napi-rs/canvas` | Full (with file I/O) | Full pipeline, Node.js / Bun |
218
+ | `ppu-ocv/web` | ✅ | `HTMLCanvasElement`/`OffscreenCanvas` | Base only | Full pipeline, browser |
219
+ | `ppu-ocv/canvas` | ❌ | `@napi-rs/canvas` | Full (with file I/O) | Canvas-only, Node (extensions, edge, etc.) |
220
+ | `ppu-ocv/canvas-web` | ❌ | `HTMLCanvasElement`/`OffscreenCanvas` | Base only | Canvas-only, browser extensions / SW |
221
+ | `ppu-ocv/canvas-mobile` | ❌ | `@shopify/react-native-skia` | Base only | Canvas-only, React Native / Expo |
220
222
 
221
223
  ### Platform abstraction
222
224
 
@@ -240,6 +242,57 @@ const myPlatform: CanvasPlatform = {
240
242
  setPlatform(myPlatform);
241
243
  ```
242
244
 
245
+ ## React Native / Mobile Support
246
+
247
+ Import from `ppu-ocv/canvas-mobile` for React Native apps (iOS / Android). This
248
+ entry point is backed by [`@shopify/react-native-skia`](https://shopify.github.io/react-native-skia/)
249
+ and is functionally equivalent to `ppu-ocv/canvas-web` — it exposes `CanvasProcessor`,
250
+ `CanvasToolkitBase`, and canvas factory types **without any OpenCV or WASM dependency**.
251
+
252
+ ### Installation
253
+
254
+ ```bash
255
+ # In your React Native / Expo project
256
+ npm install ppu-ocv @shopify/react-native-skia
257
+ # or
258
+ bun add ppu-ocv @shopify/react-native-skia
259
+ ```
260
+
261
+ Follow the [react-native-skia setup guide](https://shopify.github.io/react-native-skia/docs/getting-started/installation)
262
+ to complete native installation (pod install / Gradle sync). Skia **must** be
263
+ initialised by the time you call any `ppu-ocv` API.
264
+
265
+ ### Usage
266
+
267
+ ```ts
268
+ import { CanvasProcessor } from "ppu-ocv/canvas-mobile";
269
+
270
+ // Load from an ArrayBuffer (e.g. from expo-file-system or fetch)
271
+ const response = await fetch("https://example.com/receipt.jpg");
272
+ const canvas = await CanvasProcessor.prepareCanvas(await response.arrayBuffer());
273
+
274
+ // Or load directly from a URI (file:// or https://)
275
+ const canvas = await CanvasProcessor.prepareCanvas("file:///path/to/photo.jpg");
276
+
277
+ // Chainable canvas-only pipeline (no OpenCV)
278
+ const regions = new CanvasProcessor(canvas)
279
+ .grayscale()
280
+ .threshold({ thresh: 127 })
281
+ .findRegions({ foreground: "light", minArea: 20 });
282
+
283
+ // Export back to bytes
284
+ const buffer = await CanvasProcessor.prepareBuffer(canvas);
285
+ ```
286
+
287
+ ### Requirements
288
+
289
+ - `@shopify/react-native-skia` ≥ 1.0.0
290
+ - React Native ≥ 0.74 / Expo SDK ≥ 51 (Hermes engine)
291
+
292
+ > **Note:** OpenCV (`@techstark/opencv-js`) is never loaded by this entry point.
293
+ > If you need full OpenCV operations in a React Native context, consider running
294
+ > the processing server-side and streaming results to the device.
295
+
243
296
  ## Built-in pipeline operations
244
297
 
245
298
  To avoid bloat, we only ship essential operations for chaining. Currently shipped operations are:
@@ -12,7 +12,6 @@ export type CanvasLike = {
12
12
  getContext(contextId: "2d"): any;
13
13
  /** Serialize the canvas to a binary buffer (Node-side `@napi-rs/canvas`). Absent on browser canvases. */
14
14
  toBuffer?: (...args: any[]) => Buffer;
15
- /** Serialize the canvas to a data-URL string (browser canvases). Absent on Node-side `@napi-rs/canvas`. */
16
15
  toDataURL?: (...args: any[]) => string;
17
16
  /** Asynchronously serialize to a `Blob` via callback (browser `HTMLCanvasElement`). */
18
17
  toBlob?: (callback: (blob: Blob | null) => void, type?: string, quality?: number) => void;
package/canvas-factory.js CHANGED
@@ -1 +1 @@
1
- let _platform=null;export function setPlatform(platform){_platform=platform}export function getPlatform(){if(!_platform){throw new Error("No canvas platform registered. "+'Import "ppu-ocv" (Node), "ppu-ocv/web" (browser), '+'"ppu-ocv/canvas" (Node canvas-only), or "ppu-ocv/canvas-web" (browser canvas-only) to auto-register.')}return _platform}export function isCanvasLike(value){return typeof value==="object"&&value!==null&&typeof value.getContext==="function"&&typeof value.width==="number"&&typeof value.height==="number"}
1
+ let _platform=null;export function setPlatform(platform){_platform=platform}export function getPlatform(){if(!_platform){throw new Error("No canvas platform registered. "+'Import "ppu-ocv" (Node), "ppu-ocv/web" (browser), '+'"ppu-ocv/canvas" (Node canvas-only), "ppu-ocv/canvas-web" (browser canvas-only), '+'or "ppu-ocv/canvas-mobile" (React Native / Skia) to auto-register.')}return _platform}export function isCanvasLike(value){return typeof value==="object"&&value!==null&&typeof value.getContext==="function"&&typeof value.width==="number"&&typeof value.height==="number"}
package/canvas-io.d.ts CHANGED
@@ -5,10 +5,10 @@
5
5
  */
6
6
  import type { CanvasLike } from "./canvas-factory.js";
7
7
  /**
8
- * Convert an ArrayBuffer (image file bytes) to a CanvasLike. If the value is
8
+ * Convert an ArrayBuffer (image file bytes) or string URI to a CanvasLike. If the value is
9
9
  * already a CanvasLike it is returned as-is.
10
10
  */
11
- export declare function bufferToCanvas(file: ArrayBuffer): Promise<CanvasLike>;
11
+ export declare function bufferToCanvas(file: ArrayBuffer | string | CanvasLike): Promise<CanvasLike>;
12
12
  /**
13
13
  * Convert a CanvasLike to an ArrayBuffer (PNG bytes). If the value is already
14
14
  * an ArrayBuffer it is returned as-is.
@@ -173,10 +173,10 @@ export declare class CanvasProcessor {
173
173
  */
174
174
  toCanvas(): CanvasLike;
175
175
  /**
176
- * Convert an ArrayBuffer (image file bytes) to a CanvasLike.
176
+ * Convert an ArrayBuffer (image file bytes) or string URI to a CanvasLike.
177
177
  * If the value is already a CanvasLike it is returned as-is.
178
178
  */
179
- static prepareCanvas(file: ArrayBuffer): Promise<CanvasLike>;
179
+ static prepareCanvas(file: ArrayBuffer | string | CanvasLike): Promise<CanvasLike>;
180
180
  /**
181
181
  * Convert a CanvasLike to an ArrayBuffer (PNG bytes).
182
182
  * If the value is already an ArrayBuffer it is returned as-is.
package/deskew-angles.js CHANGED
@@ -1 +1 @@
1
- import{cv}from"./cv-provider.js";export function calculateLineAngle(points){if(points.length<2)return 0;let n=points.length;let sumX=points.reduce((sum,p)=>sum+p.x,0);let sumY=points.reduce((sum,p)=>sum+p.y,0);let sumXY=points.reduce((sum,p)=>sum+p.x*p.y,0);let sumXX=points.reduce((sum,p)=>sum+p.x*p.x,0);let denominator=n*sumXX-sumX*sumX;if(Math.abs(denominator)<0.0000000001)return 0;let slope=(n*sumXY-sumX*sumY)/denominator;let angle=Math.atan(slope)*180/Math.PI;if(angle>45)angle-=90;if(angle<-45)angle+=90;return angle}export function calculateMinRectAngles(textRegions){let angles=[];for(let region of textRegions){try{let minRect=cv.minAreaRect(region.contour);if(!minRect)continue;let angle=minRect.angle;if(angle>45){angle-=90}else if(angle<-45){angle+=90}let areaWeight=Math.log(region.area+1);let aspectWeight=Math.min(region.aspectRatio,1/region.aspectRatio)*2;let weight=areaWeight*aspectWeight;angles.push({angle,weight})}catch{continue}}return angles}export function calculateBaselineAngles(textRegions){let angles=[];for(let region of textRegions){try{let points=region.contour.data32S;if(!points||points.length<8)continue;let bottomPoints=[];for(let i=0;i<points.length;i+=2){let x=points[i];let y=points[i+1];if(x!==undefined&&y!==undefined){bottomPoints.push({x,y})}}if(bottomPoints.length<3)continue;bottomPoints.sort((a,b)=>a.x-b.x);let segments=3;let segmentSize=Math.floor(bottomPoints.length/segments);let baselinePoints=[];for(let seg=0;seg<segments;seg++){let start=seg*segmentSize;let end=seg===segments-1?bottomPoints.length:(seg+1)*segmentSize;let segmentPoints=bottomPoints.slice(start,end);if(segmentPoints.length>0){let maxYPoint=segmentPoints.reduce((max,point)=>point.y>max.y?point:max);baselinePoints.push(maxYPoint)}}if(baselinePoints.length>=2){let angle=calculateLineAngle(baselinePoints);let weight=region.area*Math.min(region.aspectRatio,1/region.aspectRatio);angles.push({angle,weight})}}catch{continue}}return angles}export function calculateHoughAngles(mat,minAngle,maxAngle,log){let angles=[];try{let kernel=cv.getStructuringElement(cv.MORPH_RECT,new cv.Size(3,1));let morphed=new cv.Mat;cv.morphologyEx(mat,morphed,cv.MORPH_CLOSE,kernel);let lines=new cv.Mat;cv.HoughLinesP(morphed,lines,1,Math.PI/180,30,50,10);for(let i=0;i<lines.rows;i++){let line=lines.data32S.subarray(i*4,(i+1)*4);const[x1,y1,x2,y2]=line;if(x1!==undefined&&y1!==undefined&&x2!==undefined&&y2!==undefined){let dx=x2-x1;let dy=y2-y1;if(Math.abs(dx)>1){let angle=Math.atan2(dy,dx)*180/Math.PI;if(angle>45)angle-=90;if(angle<-45)angle+=90;if(angle>=minAngle&&angle<=maxAngle){let lineLength=Math.sqrt(dx*dx+dy*dy);angles.push({angle,weight:lineLength})}}}}morphed.delete();lines.delete();kernel.delete()}catch{log("Hough transform failed, skipping this method.")}return angles}export function calculateConsensusAngle(angles,minAngle,maxAngle,log){if(angles.length===0)return 0;let sortedAngles=[...angles].sort((a,b)=>a.angle-b.angle);let q1Index=Math.floor(sortedAngles.length*0.25);let q3Index=Math.floor(sortedAngles.length*0.75);let q1=sortedAngles[q1Index]?.angle||0;let q3=sortedAngles[q3Index]?.angle||0;let iqr=q3-q1;let lowerBound=q1-1.5*iqr;let upperBound=q3+1.5*iqr;let filteredAngles=angles.filter((a)=>a.angle>=lowerBound&&a.angle<=upperBound&&a.angle>=minAngle&&a.angle<=maxAngle);if(filteredAngles.length===0){log("All angles filtered out as outliers, using median of original set.");let medianIndex=Math.floor(sortedAngles.length/2);return sortedAngles[medianIndex]?.angle||0}let totalWeight=filteredAngles.reduce((sum,a)=>sum+a.weight,0);if(totalWeight===0){let average=filteredAngles.reduce((sum,a)=>sum+a.angle,0)/filteredAngles.length;return average}let weightedSum=filteredAngles.reduce((sum,a)=>sum+a.angle*a.weight,0);let weightedAverage=weightedSum/totalWeight;let methodCounts=filteredAngles.reduce((counts,a)=>{counts[a.method]=(counts[a.method]||0)+1;return counts},{});log(`Angle methods used: ${Object.entries(methodCounts).map(([method,count])=>`${method}:${count}`).join(", ")}`);return Math.max(minAngle,Math.min(maxAngle,weightedAverage))}
1
+ import{cv}from"./cv-provider.js";export function calculateLineAngle(points){if(points.length<2)return 0;let n=points.length;let sumX=0,sumY=0,sumXY=0,sumXX=0;for(let p of points){sumX+=p.x;sumY+=p.y;sumXY+=p.x*p.y;sumXX+=p.x*p.x}let denominator=n*sumXX-sumX*sumX;if(Math.abs(denominator)<0.0000000001)return 0;let slope=(n*sumXY-sumX*sumY)/denominator;let angle=Math.atan(slope)*180/Math.PI;if(angle>45)angle-=90;if(angle<-45)angle+=90;return angle}export function calculateMinRectAngles(textRegions){let angles=[];for(let region of textRegions){try{let minRect=cv.minAreaRect(region.contour);if(!minRect)continue;let angle=minRect.angle;if(angle>45){angle-=90}else if(angle<-45){angle+=90}let areaWeight=Math.log(region.area+1);let aspectWeight=Math.min(region.aspectRatio,1/region.aspectRatio)*2;let weight=areaWeight*aspectWeight;angles.push({angle,weight})}catch{continue}}return angles}export function calculateBaselineAngles(textRegions){let angles=[];for(let region of textRegions){try{let points=region.contour.data32S;if(!points||points.length<8)continue;let minX=1/0;let maxX=-1/0;for(let i=0;i<points.length;i+=2){let x=points[i];if(x!==undefined){if(x<minX)minX=x;if(x>maxX)maxX=x}}if(minX===1/0)continue;let bucketMaxY=[null,null,null];let xRange=maxX-minX||1;for(let i=0;i<points.length;i+=2){let x=points[i];let y=points[i+1];if(x===undefined||y===undefined)continue;let bucket=Math.min(2,Math.floor((x-minX)/xRange*3));let current=bucketMaxY[bucket];if(current===null||y>current.y){bucketMaxY[bucket]={x,y}}}let baselinePoints=bucketMaxY.filter((p)=>p!==null);if(baselinePoints.length>=2){let angle=calculateLineAngle(baselinePoints);let weight=region.area*Math.min(region.aspectRatio,1/region.aspectRatio);angles.push({angle,weight})}}catch{continue}}return angles}export function calculateHoughAngles(mat,minAngle,maxAngle,log){let angles=[];try{let kernel=cv.getStructuringElement(cv.MORPH_RECT,new cv.Size(3,1));let morphed=new cv.Mat;cv.morphologyEx(mat,morphed,cv.MORPH_CLOSE,kernel);let lines=new cv.Mat;cv.HoughLinesP(morphed,lines,1,Math.PI/180,30,50,10);for(let i=0;i<lines.rows;i++){let line=lines.data32S.subarray(i*4,(i+1)*4);const[x1,y1,x2,y2]=line;if(x1!==undefined&&y1!==undefined&&x2!==undefined&&y2!==undefined){let dx=x2-x1;let dy=y2-y1;if(Math.abs(dx)>1){let angle=Math.atan2(dy,dx)*180/Math.PI;if(angle>45)angle-=90;if(angle<-45)angle+=90;if(angle>=minAngle&&angle<=maxAngle){let lineLength=Math.sqrt(dx*dx+dy*dy);angles.push({angle,weight:lineLength})}}}}morphed.delete();lines.delete();kernel.delete()}catch{log("Hough transform failed, skipping this method.")}return angles}export function calculateConsensusAngle(angles,minAngle,maxAngle,log){if(angles.length===0)return 0;let sortedAngles=[...angles].sort((a,b)=>a.angle-b.angle);let q1Index=Math.floor(sortedAngles.length*0.25);let q3Index=Math.floor(sortedAngles.length*0.75);let q1=sortedAngles[q1Index]?.angle||0;let q3=sortedAngles[q3Index]?.angle||0;let iqr=q3-q1;let lowerBound=q1-1.5*iqr;let upperBound=q3+1.5*iqr;let filteredAngles=angles.filter((a)=>a.angle>=lowerBound&&a.angle<=upperBound&&a.angle>=minAngle&&a.angle<=maxAngle);if(filteredAngles.length===0){log("All angles filtered out as outliers, using median of original set.");let medianIndex=Math.floor(sortedAngles.length/2);return sortedAngles[medianIndex]?.angle||0}let totalWeight=0;let weightedSum=0;let unweightedSum=0;let methodCounts={};for(let a of filteredAngles){totalWeight+=a.weight;weightedSum+=a.angle*a.weight;unweightedSum+=a.angle;methodCounts[a.method]=(methodCounts[a.method]||0)+1}if(totalWeight===0){return unweightedSum/filteredAngles.length}log(`Angle methods used: ${Object.entries(methodCounts).map(([method,count])=>`${method}:${count}`).join(", ")}`);let weightedAverage=weightedSum/totalWeight;return Math.max(minAngle,Math.min(maxAngle,weightedAverage))}
@@ -0,0 +1,6 @@
1
+ export type { BoundingBox, Coordinate, Points } from "./index.interface.js";
2
+ export { getPlatform, setPlatform } from "./canvas-factory.js";
3
+ export type { CanvasLike, CanvasPlatform, Context2DLike } from "./canvas-factory.js";
4
+ export { mobilePlatform } from "./platform/mobile.js";
5
+ export { CanvasToolkitBase as CanvasToolkit, CanvasToolkitBase, type ContourLike, } from "./canvas-toolkit.base.js";
6
+ export { CanvasProcessor, type DetectedRegion } from "./canvas-processor.js";
@@ -0,0 +1 @@
1
+ import{setPlatform}from"./canvas-factory.js";import{mobilePlatform}from"./platform/mobile.js";setPlatform(mobilePlatform);export{getPlatform,setPlatform}from"./canvas-factory.js";export{mobilePlatform}from"./platform/mobile.js";export{CanvasToolkitBase as CanvasToolkit,CanvasToolkitBase}from"./canvas-toolkit.base.js";export{CanvasProcessor}from"./canvas-processor.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ppu-ocv",
3
- "version": "3.2.2",
3
+ "version": "3.3.0",
4
4
  "description": "A type-safe, modular, chainable image processing library built on top of OpenCV.js with a fluent API leveraging pipeline processing.",
5
5
  "keywords": [
6
6
  "open-cv",
@@ -41,6 +41,10 @@
41
41
  "./canvas-web": {
42
42
  "types": "./index.canvas-web.d.ts",
43
43
  "default": "./index.canvas-web.js"
44
+ },
45
+ "./canvas-mobile": {
46
+ "types": "./index.canvas-mobile.d.ts",
47
+ "default": "./index.canvas-mobile.js"
44
48
  }
45
49
  },
46
50
  "repository": {
@@ -50,5 +54,16 @@
50
54
  "dependencies": {
51
55
  "@napi-rs/canvas": "^1.0.0",
52
56
  "@techstark/opencv-js": "^4.10.0-release.1"
53
- }
57
+ },
58
+ "peerDependencies": {
59
+ "@shopify/react-native-skia": ">=1.0.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@shopify/react-native-skia": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "trustedDependencies": [
67
+ "@shopify/react-native-skia"
68
+ ]
54
69
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * React Native platform adapter for ppu-ocv, backed by @shopify/react-native-skia.
3
+ *
4
+ * The Skia JS Canvas2D API surface is close enough to the browser's that a
5
+ * thin shim layer is all we need:
6
+ *
7
+ * - SkiaCanvasLike wraps an SkSurface and exposes width / height / getContext("2d")
8
+ * - SkiaContext2DLike bridges Skia's SkCanvas drawing commands to the Context2DLike
9
+ * interface consumed by CanvasProcessor and CanvasToolkitBase
10
+ *
11
+ * Import requirements:
12
+ * - @shopify/react-native-skia ≥ 1.0.0 must be installed by the consuming app
13
+ * - React Native ≥ 0.74 / Expo SDK ≥ 51 (Hermes engine)
14
+ *
15
+ * This file uses a **lazy import** pattern — the Skia module is only resolved at
16
+ * runtime inside mobilePlatform, so bundlers for other entries (Node / browser)
17
+ * never pull Skia code in.
18
+ */
19
+ import type { CanvasPlatform } from "../canvas-factory.js";
20
+ /** React Native canvas platform backed by @shopify/react-native-skia */
21
+ export declare const mobilePlatform: CanvasPlatform;
@@ -0,0 +1 @@
1
+ class SkiaContext2DLike{_surface;_skCanvas;_path=null;_parentCanvas;strokeStyle="#000000";fillStyle="#000000";lineWidth=1;constructor(surface,parentCanvas){this._surface=surface;this._skCanvas=surface.getCanvas();this._parentCanvas=parentCanvas}get canvas(){return this._parentCanvas}getImageData(sx,sy,sw,sh){const{Skia,AlphaType,ColorType}=getSkia();let snapshot=this._surface.makeImageSnapshot();let info={width:sw,height:sh,colorType:ColorType.RGBA_8888,alphaType:AlphaType.Unpremul};let pixels=snapshot.readPixels(sx,sy,info);if(!pixels){throw new Error(`SkiaContext2DLike.getImageData: readPixels returned null `+`(surface may have been released, region: ${sx},${sy} ${sw}×${sh})`)}return{data:new Uint8ClampedArray(pixels.buffer,pixels.byteOffset,pixels.byteLength),width:sw,height:sh}}putImageData(imageData,dx,dy){const{Skia,AlphaType,ColorType}=getSkia();const{data,width,height}=imageData;let bytes=new Uint8Array(data.buffer,data.byteOffset,data.byteLength);let skData=Skia.Data.fromBytes(bytes);let img=Skia.Image.MakeImage({width,height,colorType:ColorType.RGBA_8888,alphaType:AlphaType.Unpremul},skData,width*4);if(!img){throw new Error("SkiaContext2DLike.putImageData: MakeImage returned null")}this._skCanvas.drawImage(img,dx,dy);this._surface.flush()}createImageData(width,height){return{data:new Uint8ClampedArray(width*height*4),width,height}}drawImage(...args){let src=args[0];let img;if(src instanceof SkiaCanvasLike){img=src._surface.makeImageSnapshot()}else if(src&&typeof src._skiaImage!=="undefined"){img=src._skiaImage}else{throw new Error("SkiaContext2DLike.drawImage: source must be a SkiaCanvasLike (use mobilePlatform.loadImage)")}if(args.length===3){this._skCanvas.drawImage(img,args[1],args[2])}else if(args.length===5){const{Skia}=getSkia();let dstRect=Skia.XYWHRect(args[1],args[2],args[3],args[4]);let srcRect=Skia.XYWHRect(0,0,img.width(),img.height());this._skCanvas.drawImageRect(img,srcRect,dstRect,Skia.Paint())}else if(args.length===9){const{Skia}=getSkia();let srcRect=Skia.XYWHRect(args[1],args[2],args[3],args[4]);let dstRect=Skia.XYWHRect(args[5],args[6],args[7],args[8]);this._skCanvas.drawImageRect(img,srcRect,dstRect,Skia.Paint())}else{throw new Error(`SkiaContext2DLike.drawImage: unsupported argument count (${args.length})`)}this._surface.flush()}fillRect(x,y,w,h){const{Skia}=getSkia();let paint=Skia.Paint();paint.setColor(Skia.Color(this.fillStyle));this._skCanvas.drawRect(Skia.XYWHRect(x,y,w,h),paint);this._surface.flush()}strokeRect(x,y,w,h){const{Skia,PaintStyle}=getSkia();let paint=Skia.Paint();paint.setColor(Skia.Color(this.strokeStyle));paint.setStyle(PaintStyle.Stroke);paint.setStrokeWidth(this.lineWidth);this._skCanvas.drawRect(Skia.XYWHRect(x,y,w,h),paint);this._surface.flush()}beginPath(){const{Skia}=getSkia();this._path=Skia.Path.Make()}closePath(){this._path?.close()}moveTo(x,y){this._path?.moveTo(x,y)}lineTo(x,y){this._path?.lineTo(x,y)}stroke(){if(!this._path)return;const{Skia,PaintStyle}=getSkia();let paint=Skia.Paint();paint.setColor(Skia.Color(this.strokeStyle));paint.setStyle(PaintStyle.Stroke);paint.setStrokeWidth(this.lineWidth);this._skCanvas.drawPath(this._path,paint);this._surface.flush()}save(){this._skCanvas.save()}restore(){this._skCanvas.restore()}translate(x,y){this._skCanvas.translate(x,y)}rotate(angle){this._skCanvas.rotate(angle*180/Math.PI,0,0)}}class SkiaCanvasLike{_surface;_ctx=null;constructor(surface){this._surface=surface}get width(){return this._surface.width()}get height(){return this._surface.height()}getContext(_contextId){if(!this._ctx){this._ctx=new SkiaContext2DLike(this._surface,this)}return this._ctx}}let _skia=null;function getSkia(){if(_skia)return _skia;_skia=require("@shopify/react-native-skia");return _skia}export let mobilePlatform={createCanvas(width,height){const{Skia}=getSkia();let surface=Skia.Surface.Make(width,height);if(!surface){throw new Error(`mobilePlatform.createCanvas: Skia.Surface.Make(${width}, ${height}) returned null. `+"Ensure @shopify/react-native-skia is correctly installed and Skia is initialised.")}return new SkiaCanvasLike(surface)},async loadImage(source){const{Skia,AlphaType,ColorType}=getSkia();let skData;if(source instanceof ArrayBuffer){skData=Skia.Data.fromBytes(new Uint8Array(source))}else if(typeof source==="string"){skData=await Skia.Data.fromURI(source)}else{throw new Error("mobilePlatform.loadImage: source must be an ArrayBuffer or string URI")}let image=Skia.Image.MakeImageFromEncoded(skData);if(!image){throw new Error("mobilePlatform.loadImage: MakeImageFromEncoded returned null — "+"the image data may be corrupt or in an unsupported format.")}let width=image.width();let height=image.height();let surface=Skia.Surface.Make(width,height);if(!surface){throw new Error("mobilePlatform.loadImage: Skia.Surface.Make returned null")}let canvas=surface.getCanvas();canvas.drawImage(image,0,0);surface.flush();return new SkiaCanvasLike(surface)},isCanvas(value){return value instanceof SkiaCanvasLike}};