@gyeonghokim/fisheye.js 1.0.2 → 1.1.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
@@ -6,6 +6,10 @@ DEMO: https://gyeonghokim.github.io/fisheye.js/
6
6
 
7
7
  fisheye.js processes [VideoFrame](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame)s with **WebGPU compute shaders**—no canvas 2D—and corrects fisheye lens distortion using the **OpenCV fisheye model** (Kannala–Brandt–style polynomial in angle θ with coefficients k1–k4). This is the same model as in [OpenCV’s fisheye module](https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html), not UCM (Unified Camera Model) or a simple radial model.
8
8
 
9
+ ## Learning Docs
10
+
11
+ See the full educational curriculum in `doc/index.md`.
12
+
9
13
  ## Features
10
14
 
11
15
  - **WebGPU GPGPU**: Compute-shader pipeline via [TypeGPU](https://www.npmjs.com/package/typegpu); input/output as textures and readback to VideoFrame—no canvas element for dewarping
package/dist/index.d.ts CHANGED
@@ -126,12 +126,13 @@ export declare interface CreateVideoFrameOptions {
126
126
  * @example
127
127
  * ```ts
128
128
  * const dewarper = new Fisheye({
129
- * distortion: 0.5,
130
129
  * width: 1920,
131
130
  * height: 1080,
131
+ * fov: 180,
132
+ * projection: "rectilinear",
132
133
  * });
133
134
  *
134
- * const dewarpedFrame = await dewarper.dewarp(videoFrame);
135
+ * const dewarpedFrame = await dewarper.dewarp(inputVideoFrame);
135
136
  * ```
136
137
  */
137
138
  export declare class Fisheye {
@@ -209,58 +210,113 @@ export declare class Fisheye {
209
210
  export declare interface FisheyeConfig extends Required<FisheyeOptions> {
210
211
  }
211
212
 
213
+ /**
214
+ * Camera mount position. Affects which view range is meaningful (e.g. ceiling → 360° azimuth).
215
+ * Used for defaults or validation when combined with projection.
216
+ */
217
+ export declare type FisheyeMount = "ceiling" | "wall" | "desk";
218
+
219
+ /**
220
+ * **Presets for UI:** Combine `mount` + `projection` (+ `fov`) and expose them to end users
221
+ * as preset names instead of raw options. Example mapping:
222
+ *
223
+ * | User-facing name | mount | projection | fov |
224
+ * |-------------------------|---------|----------------|----------------------|
225
+ * | Panoramic 180 | any | equirectangular| 180 |
226
+ * | 360° Panorama | ceiling | equirectangular| 360 |
227
+ * | Normal / PTZ view | any | rectilinear | e.g. 90 |
228
+ * | Quad (4 panes) | ceiling | rectilinear ×4 | 90 per pane (layout) |
229
+ *
230
+ * The library does not define preset names; the app chooses labels and maps them to
231
+ * `FisheyeOptions` (and, for Quad, to multiple Fisheye instances or a layout pipeline).
232
+ */
212
233
  /**
213
234
  * Options for configuring the Fisheye dewarper
214
235
  */
215
236
  export declare interface FisheyeOptions {
216
237
  /**
217
- * Fisheye distortion coefficient k1.
238
+ * Fisheye distortion coefficient k1 (OpenCV fisheye / Kannala–Brandt).
239
+ * From calibration; omit or 0 for ideal equidistant.
240
+ * @default 0
218
241
  */
219
242
  k1?: number;
220
243
  /**
221
244
  * Fisheye distortion coefficient k2.
245
+ * @default 0
222
246
  */
223
247
  k2?: number;
224
248
  /**
225
249
  * Fisheye distortion coefficient k3.
250
+ * @default 0
226
251
  */
227
252
  k3?: number;
228
253
  /**
229
254
  * Fisheye distortion coefficient k4.
255
+ * @default 0
230
256
  */
231
257
  k4?: number;
232
258
  /**
233
- * Canvas width for output
259
+ * Output image width in pixels.
234
260
  * @default 300
235
261
  */
236
262
  width?: number;
237
263
  /**
238
- * Canvas height for output
264
+ * Output image height in pixels.
239
265
  * @default 150
240
266
  */
241
267
  height?: number;
242
268
  /**
243
- * Field of view in degrees
269
+ * Field of view in degrees.
270
+ * For rectilinear: angular diameter of the output. For equirectangular: horizontal span.
244
271
  * @default 180
245
272
  */
246
273
  fov?: number;
247
274
  /**
248
- * X offset of the lens center (normalized, -1.0 to 1.0)
275
+ * Projection used to map corrected angles to output pixels.
276
+ * @default "rectilinear"
277
+ */
278
+ projection?: FisheyeProjection;
279
+ /**
280
+ * Camera mount position. Optional; can affect default FOV or valid range when projection is equirectangular.
281
+ * @default "ceiling"
282
+ */
283
+ mount?: FisheyeMount;
284
+ /**
285
+ * X offset of the lens center (normalized, -1.0 to 1.0).
249
286
  * @default 0
250
287
  */
251
288
  centerX?: number;
252
289
  /**
253
- * Y offset of the lens center (normalized, -1.0 to 1.0)
290
+ * Y offset of the lens center (normalized, -1.0 to 1.0).
254
291
  * @default 0
255
292
  */
256
293
  centerY?: number;
257
294
  /**
258
- * Zoom factor
295
+ * Zoom factor applied after distortion correction.
259
296
  * @default 1.0
260
297
  */
261
298
  zoom?: number;
262
299
  }
263
300
 
301
+ /**
302
+ * How the corrected ray directions are mapped to the output image.
303
+ *
304
+ * - **rectilinear**: Standard perspective (like a normal camera). Straight lines in the
305
+ * scene stay straight; the center is natural, edges are compressed. With wide FOV (e.g.
306
+ * 180°) the edges look very squished. One "window" view.
307
+ *
308
+ * - **equirectangular**: Horizontal axis = azimuth (longitude), vertical = elevation
309
+ * (latitude). The full horizontal span (e.g. 180°) maps linearly to the width, so you
310
+ * get a wide panoramic strip: left = one side, right = the other. Classic panorama look.
311
+ *
312
+ * Commercial equivalents:
313
+ * - "Panoramic 180" / "180° Panorama" → equirectangular with 180° horizontal.
314
+ * - "Panoramic" / "360° Panorama" → equirectangular with 360° horizontal (ceiling mount).
315
+ * - "Normal" / "PTZ view" / single window → rectilinear (often with ~90° FOV per pane).
316
+ * - "Panoramic with 4 panes" / "Quad" → layout of four rectilinear ~90° views, not one projection.
317
+ */
318
+ export declare type FisheyeProjection = "rectilinear" | "equirectangular";
319
+
264
320
  /**
265
321
  * Supported YUV pixel formats for VideoFrame creation
266
322
  */
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import M from "typegpu";
1
+ import Y from "typegpu";
2
2
  import * as i from "typegpu/data";
3
- import * as b from "typegpu/std";
4
- const D = (globalThis.__TYPEGPU_AUTONAME__ ?? ((s) => s))(i.struct({
3
+ import * as l from "typegpu/std";
4
+ const G = (globalThis.__TYPEGPU_AUTONAME__ ?? ((s) => s))(i.struct({
5
5
  k1: i.f32,
6
6
  k2: i.f32,
7
7
  k3: i.f32,
@@ -14,13 +14,15 @@ const D = (globalThis.__TYPEGPU_AUTONAME__ ?? ((s) => s))(i.struct({
14
14
  height: i.f32,
15
15
  inputWidth: i.f32,
16
16
  inputHeight: i.f32,
17
+ projection: i.f32,
18
+ // 0 = rectilinear, 1 = equirectangular
17
19
  padding: i.f32
18
- }), "FisheyeUniforms"), P = (globalThis.__TYPEGPU_AUTONAME__ ?? ((s) => s))(M.bindGroupLayout({
20
+ }), "FisheyeUniforms"), z = (globalThis.__TYPEGPU_AUTONAME__ ?? ((s) => s))(Y.bindGroupLayout({
19
21
  inputTexture: { texture: i.texture2d() },
20
22
  outputTexture: { storageTexture: i.textureStorage2d("rgba8unorm") },
21
- uniforms: { uniform: D }
23
+ uniforms: { uniform: G }
22
24
  }), "fisheyeLayout");
23
- class X {
25
+ class $ {
24
26
  config;
25
27
  root = null;
26
28
  uniformBuffer = null;
@@ -53,6 +55,8 @@ class X {
53
55
  width: t.width ?? 300,
54
56
  height: t.height ?? 150,
55
57
  fov: t.fov ?? 180,
58
+ projection: t.projection ?? "rectilinear",
59
+ mount: t.mount ?? "ceiling",
56
60
  centerX: t.centerX ?? 0,
57
61
  centerY: t.centerY ?? 0,
58
62
  zoom: t.zoom ?? 1
@@ -62,29 +66,35 @@ class X {
62
66
  * Initialize TypeGPU root and resources
63
67
  */
64
68
  async initialize() {
65
- this.root || (this.root = await M.init(), this.uniformBuffer = (globalThis.__TYPEGPU_AUTONAME__ ?? ((t) => t))(this.root.createBuffer(D, this.getUniformData()).$usage("uniform"), "uniformBuffer"), this.dewarpPipeline = (globalThis.__TYPEGPU_AUTONAME__ ?? ((t) => t))(this.root["~unstable"].createGuardedComputePipeline(
69
+ this.root || (this.root = await Y.init(), this.uniformBuffer = (globalThis.__TYPEGPU_AUTONAME__ ?? ((t) => t))(this.root.createBuffer(G, this.getUniformData()).$usage("uniform"), "uniformBuffer"), this.dewarpPipeline = (globalThis.__TYPEGPU_AUTONAME__ ?? ((t) => t))(this.root["~unstable"].createGuardedComputePipeline(
66
70
  ((t) => (globalThis.__TYPEGPU_META__ ??= /* @__PURE__ */ new WeakMap()).set(t.f = ((o, r) => {
67
71
  "use gpu";
68
- const e = P.$.uniforms, n = e.width, c = e.height, a = e.inputWidth, p = e.inputHeight, l = i.vec2i(o, r);
69
- if (i.f32(o) >= n || i.f32(r) >= c)
72
+ const e = z.$.uniforms, n = e.width, h = e.height, u = e.inputWidth, x = e.inputHeight, p = i.vec2i(o, r);
73
+ if (i.f32(o) >= n || i.f32(r) >= h)
70
74
  return;
71
- const x = i.vec2f(
72
- (i.f32(l.x) / n - 0.5) * 2,
73
- (i.f32(l.y) / c - 0.5) * 2
74
- ), m = e.centerX, g = e.centerY, u = x.sub(i.vec2f(m, g)), f = b.length(u), k = Math.SQRT2, U = b.min(e.fov * 0.017453293, 3.1), A = b.tan(U * 0.5) / k, H = b.min(A, 1), G = f * H, d = b.atan(G), h = d * d, y = h * h, T = y * h, R = y * y, B = d * (1 + e.k1 * h + e.k2 * y + e.k3 * T + e.k4 * R) / e.zoom;
75
- let v = i.vec2f(u.x, u.y);
76
- f > 1e-4 && (v = i.vec2f(u.x * (B / f), u.y * (B / f)));
77
- const w = v.add(i.vec2f(m, g)).mul(0.5).add(0.5);
78
- if (w.x >= 0 && w.x <= 1 && w.y >= 0 && w.y <= 1) {
79
- const I = i.vec2i(i.i32(w.x * a), i.i32(w.y * p)), z = b.textureLoad(P.$.inputTexture, I, 0);
80
- b.textureStore(P.$.outputTexture, l, z);
75
+ const b = e.centerX, w = e.centerY;
76
+ let c = i.vec2f(0, 0);
77
+ if (e.projection < 0.5) {
78
+ const a = i.vec2f(
79
+ (i.f32(p.x) / n - 0.5) * 2,
80
+ (i.f32(p.y) / h - 0.5) * 2
81
+ ).sub(i.vec2f(b, w)), k = l.length(a), B = Math.SQRT2, P = l.min(e.fov * (Math.PI / 180), 3.1), v = l.tan(P * 0.5) / B, T = l.min(v, 1), g = k * T, d = l.atan(g), y = d * d, m = y * y, U = m * y, R = m * m, S = d * (1 + e.k1 * y + e.k2 * m + e.k3 * U + e.k4 * R) / e.zoom;
82
+ let A = i.vec2f(a.x, a.y);
83
+ k > 1e-4 && (A = i.vec2f(a.x * (S / k), a.y * (S / k))), c = A.add(i.vec2f(b, w)).mul(0.5).add(0.5);
84
+ } else {
85
+ const f = e.fov * (Math.PI / 180), a = (i.f32(p.x) / n - 0.5) * f, k = (i.f32(p.y) / h - 0.5) * Math.PI, B = l.sin(k), P = l.acos(l.min(l.max(B, -1), 1)), v = P * P, T = v * v, g = T * v, d = T * T, m = P * (1 + e.k1 * v + e.k2 * T + e.k3 * g + e.k4 * d) / e.zoom, U = b + m * l.cos(a), R = w + m * l.sin(a);
86
+ c = i.vec2f(U * 0.5 + 0.5, R * 0.5 + 0.5);
87
+ }
88
+ if (c.x >= 0 && c.x <= 1 && c.y >= 0 && c.y <= 1) {
89
+ const f = i.vec2i(i.i32(c.x * u), i.i32(c.y * x)), a = l.textureLoad(z.$.inputTexture, f, 0);
90
+ l.textureStore(z.$.outputTexture, p, a);
81
91
  } else
82
- b.textureStore(P.$.outputTexture, l, i.vec4f(0, 0, 0, 1));
92
+ l.textureStore(z.$.outputTexture, p, i.vec4f(0, 0, 0, 1));
83
93
  }), {
84
94
  v: 1,
85
95
  name: void 0,
86
- ast: { params: [{ type: "i", name: "x" }, { type: "i", name: "y" }], body: [0, [[13, "p", [7, [7, "fisheyeLayout", "$"], "uniforms"]], [13, "outputW", [7, "p", "width"]], [13, "outputH", [7, "p", "height"]], [13, "inputW", [7, "p", "inputWidth"]], [13, "inputH", [7, "p", "inputHeight"]], [13, "coord", [6, [7, "d", "vec2i"], ["x", "y"]]], [11, [3, [1, [6, [7, "d", "f32"], ["x"]], ">=", "outputW"], "||", [1, [6, [7, "d", "f32"], ["y"]], ">=", "outputH"]], [0, [[10]]]], [13, "uv", [6, [7, "d", "vec2f"], [[1, [1, [1, [6, [7, "d", "f32"], [[7, "coord", "x"]]], "/", "outputW"], "-", [5, "0.5"]], "*", [5, "2"]], [1, [1, [1, [6, [7, "d", "f32"], [[7, "coord", "y"]]], "/", "outputH"], "-", [5, "0.5"]], "*", [5, "2"]]]]], [13, "centerX", [7, "p", "centerX"]], [13, "centerY", [7, "p", "centerY"]], [13, "centered", [6, [7, "uv", "sub"], [[6, [7, "d", "vec2f"], ["centerX", "centerY"]]]]], [13, "r", [6, [7, "std", "length"], ["centered"]]], [13, "cornerNorm", [7, "Math", "SQRT2"]], [13, "fovRad", [6, [7, "std", "min"], [[1, [7, "p", "fov"], "*", [5, "0.017453293"]], [5, "3.1"]]]], [13, "scaleRaw", [1, [6, [7, "std", "tan"], [[1, "fovRad", "*", [5, "0.5"]]]], "/", "cornerNorm"]], [13, "scale", [6, [7, "std", "min"], ["scaleRaw", [5, "1"]]]], [13, "rScaledForFov", [1, "r", "*", "scale"]], [13, "theta", [6, [7, "std", "atan"], ["rScaledForFov"]]], [13, "theta2", [1, "theta", "*", "theta"]], [13, "theta4", [1, "theta2", "*", "theta2"]], [13, "theta6", [1, "theta4", "*", "theta2"]], [13, "theta8", [1, "theta4", "*", "theta4"]], [13, "thetaDistorted", [1, "theta", "*", [1, [1, [1, [1, [5, "1"], "+", [1, [7, "p", "k1"], "*", "theta2"]], "+", [1, [7, "p", "k2"], "*", "theta4"]], "+", [1, [7, "p", "k3"], "*", "theta6"]], "+", [1, [7, "p", "k4"], "*", "theta8"]]]], [13, "rDistorted", "thetaDistorted"], [13, "rScaled", [1, "rDistorted", "/", [7, "p", "zoom"]]], [12, "distortedUv", [6, [7, "d", "vec2f"], [[7, "centered", "x"], [7, "centered", "y"]]]], [11, [1, "r", ">", [5, "0.0001"]], [0, [[2, "distortedUv", "=", [6, [7, "d", "vec2f"], [[1, [7, "centered", "x"], "*", [1, "rScaled", "/", "r"]], [1, [7, "centered", "y"], "*", [1, "rScaled", "/", "r"]]]]]]]], [13, "finalUv", [6, [7, [6, [7, [6, [7, "distortedUv", "add"], [[6, [7, "d", "vec2f"], ["centerX", "centerY"]]]], "mul"], [[5, "0.5"]]], "add"], [[5, "0.5"]]]], [11, [3, [3, [3, [1, [7, "finalUv", "x"], ">=", [5, "0"]], "&&", [1, [7, "finalUv", "x"], "<=", [5, "1"]]], "&&", [1, [7, "finalUv", "y"], ">=", [5, "0"]]], "&&", [1, [7, "finalUv", "y"], "<=", [5, "1"]]], [0, [[13, "sampleCoord", [6, [7, "d", "vec2i"], [[6, [7, "d", "i32"], [[1, [7, "finalUv", "x"], "*", "inputW"]]], [6, [7, "d", "i32"], [[1, [7, "finalUv", "y"], "*", "inputH"]]]]]], [13, "color", [6, [7, "std", "textureLoad"], [[7, [7, "fisheyeLayout", "$"], "inputTexture"], "sampleCoord", [5, "0"]]]], [6, [7, "std", "textureStore"], [[7, [7, "fisheyeLayout", "$"], "outputTexture"], "coord", "color"]]]], [0, [[6, [7, "std", "textureStore"], [[7, [7, "fisheyeLayout", "$"], "outputTexture"], "coord", [6, [7, "d", "vec4f"], [[5, "0"], [5, "0"], [5, "0"], [5, "1"]]]]]]]]]], externalNames: ["fisheyeLayout", "d", "std", "Math"] },
87
- externals: () => ({ fisheyeLayout: P, d: i, std: b, Math })
96
+ ast: { params: [{ type: "i", name: "x" }, { type: "i", name: "y" }], body: [0, [[13, "p", [7, [7, "fisheyeLayout", "$"], "uniforms"]], [13, "outputW", [7, "p", "width"]], [13, "outputH", [7, "p", "height"]], [13, "inputW", [7, "p", "inputWidth"]], [13, "inputH", [7, "p", "inputHeight"]], [13, "coord", [6, [7, "d", "vec2i"], ["x", "y"]]], [11, [3, [1, [6, [7, "d", "f32"], ["x"]], ">=", "outputW"], "||", [1, [6, [7, "d", "f32"], ["y"]], ">=", "outputH"]], [0, [[10]]]], [13, "centerX", [7, "p", "centerX"]], [13, "centerY", [7, "p", "centerY"]], [12, "finalUv", [6, [7, "d", "vec2f"], [[5, "0"], [5, "0"]]]], [11, [1, [7, "p", "projection"], "<", [5, "0.5"]], [0, [[13, "uv", [6, [7, "d", "vec2f"], [[1, [1, [1, [6, [7, "d", "f32"], [[7, "coord", "x"]]], "/", "outputW"], "-", [5, "0.5"]], "*", [5, "2"]], [1, [1, [1, [6, [7, "d", "f32"], [[7, "coord", "y"]]], "/", "outputH"], "-", [5, "0.5"]], "*", [5, "2"]]]]], [13, "centered", [6, [7, "uv", "sub"], [[6, [7, "d", "vec2f"], ["centerX", "centerY"]]]]], [13, "r", [6, [7, "std", "length"], ["centered"]]], [13, "cornerNorm", [7, "Math", "SQRT2"]], [13, "fovRad", [6, [7, "std", "min"], [[1, [7, "p", "fov"], "*", [1, [7, "Math", "PI"], "/", [5, "180"]]], [5, "3.1"]]]], [13, "scaleRaw", [1, [6, [7, "std", "tan"], [[1, "fovRad", "*", [5, "0.5"]]]], "/", "cornerNorm"]], [13, "scale", [6, [7, "std", "min"], ["scaleRaw", [5, "1"]]]], [13, "rScaledForFov", [1, "r", "*", "scale"]], [13, "theta", [6, [7, "std", "atan"], ["rScaledForFov"]]], [13, "theta2", [1, "theta", "*", "theta"]], [13, "theta4", [1, "theta2", "*", "theta2"]], [13, "theta6", [1, "theta4", "*", "theta2"]], [13, "theta8", [1, "theta4", "*", "theta4"]], [13, "thetaDistorted", [1, "theta", "*", [1, [1, [1, [1, [5, "1"], "+", [1, [7, "p", "k1"], "*", "theta2"]], "+", [1, [7, "p", "k2"], "*", "theta4"]], "+", [1, [7, "p", "k3"], "*", "theta6"]], "+", [1, [7, "p", "k4"], "*", "theta8"]]]], [13, "rDistorted", "thetaDistorted"], [13, "rScaled", [1, "rDistorted", "/", [7, "p", "zoom"]]], [12, "distortedUv", [6, [7, "d", "vec2f"], [[7, "centered", "x"], [7, "centered", "y"]]]], [11, [1, "r", ">", [5, "0.0001"]], [0, [[2, "distortedUv", "=", [6, [7, "d", "vec2f"], [[1, [7, "centered", "x"], "*", [1, "rScaled", "/", "r"]], [1, [7, "centered", "y"], "*", [1, "rScaled", "/", "r"]]]]]]]], [2, "finalUv", "=", [6, [7, [6, [7, [6, [7, "distortedUv", "add"], [[6, [7, "d", "vec2f"], ["centerX", "centerY"]]]], "mul"], [[5, "0.5"]]], "add"], [[5, "0.5"]]]]]], [0, [[13, "fovRad", [1, [7, "p", "fov"], "*", [1, [7, "Math", "PI"], "/", [5, "180"]]]], [13, "lon", [1, [1, [1, [6, [7, "d", "f32"], [[7, "coord", "x"]]], "/", "outputW"], "-", [5, "0.5"]], "*", "fovRad"]], [13, "lat", [1, [1, [1, [6, [7, "d", "f32"], [[7, "coord", "y"]]], "/", "outputH"], "-", [5, "0.5"]], "*", [7, "Math", "PI"]]], [13, "sinLat", [6, [7, "std", "sin"], ["lat"]]], [13, "theta", [6, [7, "std", "acos"], [[6, [7, "std", "min"], [[6, [7, "std", "max"], ["sinLat", [4, "-", [5, "1"]]]], [5, "1"]]]]]], [13, "theta2", [1, "theta", "*", "theta"]], [13, "theta4", [1, "theta2", "*", "theta2"]], [13, "theta6", [1, "theta4", "*", "theta2"]], [13, "theta8", [1, "theta4", "*", "theta4"]], [13, "rDistorted", [1, "theta", "*", [1, [1, [1, [1, [5, "1"], "+", [1, [7, "p", "k1"], "*", "theta2"]], "+", [1, [7, "p", "k2"], "*", "theta4"]], "+", [1, [7, "p", "k3"], "*", "theta6"]], "+", [1, [7, "p", "k4"], "*", "theta8"]]]], [13, "rScaled", [1, "rDistorted", "/", [7, "p", "zoom"]]], [13, "uNorm", [1, "centerX", "+", [1, "rScaled", "*", [6, [7, "std", "cos"], ["lon"]]]]], [13, "vNorm", [1, "centerY", "+", [1, "rScaled", "*", [6, [7, "std", "sin"], ["lon"]]]]], [2, "finalUv", "=", [6, [7, "d", "vec2f"], [[1, [1, "uNorm", "*", [5, "0.5"]], "+", [5, "0.5"]], [1, [1, "vNorm", "*", [5, "0.5"]], "+", [5, "0.5"]]]]]]]], [11, [3, [3, [3, [1, [7, "finalUv", "x"], ">=", [5, "0"]], "&&", [1, [7, "finalUv", "x"], "<=", [5, "1"]]], "&&", [1, [7, "finalUv", "y"], ">=", [5, "0"]]], "&&", [1, [7, "finalUv", "y"], "<=", [5, "1"]]], [0, [[13, "sampleCoord", [6, [7, "d", "vec2i"], [[6, [7, "d", "i32"], [[1, [7, "finalUv", "x"], "*", "inputW"]]], [6, [7, "d", "i32"], [[1, [7, "finalUv", "y"], "*", "inputH"]]]]]], [13, "color", [6, [7, "std", "textureLoad"], [[7, [7, "fisheyeLayout", "$"], "inputTexture"], "sampleCoord", [5, "0"]]]], [6, [7, "std", "textureStore"], [[7, [7, "fisheyeLayout", "$"], "outputTexture"], "coord", "color"]]]], [0, [[6, [7, "std", "textureStore"], [[7, [7, "fisheyeLayout", "$"], "outputTexture"], "coord", [6, [7, "d", "vec4f"], [[5, "0"], [5, "0"], [5, "0"], [5, "1"]]]]]]]]]], externalNames: ["fisheyeLayout", "d", "std", "Math"] },
97
+ externals: () => ({ fisheyeLayout: z, d: i, std: l, Math })
88
98
  }) && t.f)({})
89
99
  ), "dewarpPipeline"));
90
100
  }
@@ -105,6 +115,7 @@ class X {
105
115
  height: this.config.height,
106
116
  inputWidth: this.uniformInputWidth,
107
117
  inputHeight: this.uniformInputHeight,
118
+ projection: this.config.projection === "equirectangular" ? 1 : 0,
108
119
  padding: 0
109
120
  };
110
121
  }
@@ -118,26 +129,26 @@ class X {
118
129
  const e = this.readbackBuffers;
119
130
  if (!e)
120
131
  throw new Error("Readback buffer not initialized");
121
- const n = this.readbackIndex, c = 1 - n, a = e[n], p = e[c];
122
- if (!a || !p)
132
+ const n = this.readbackIndex, h = 1 - n, u = e[n], x = e[h];
133
+ if (!u || !x)
123
134
  throw new Error("Readback buffer not initialized");
124
- const l = t.createCommandEncoder();
125
- l.copyTextureToBuffer(
135
+ const p = t.createCommandEncoder();
136
+ p.copyTextureToBuffer(
126
137
  { texture: o },
127
- { buffer: a, bytesPerRow: this.readbackBytesPerRow },
138
+ { buffer: u, bytesPerRow: this.readbackBytesPerRow },
128
139
  [this.config.width, this.config.height]
129
- ), t.queue.submit([l.finish()]), await t.queue.onSubmittedWorkDone(), this.readbackHasData[n] = !0, this.readbackIndex = c;
130
- const x = this.readbackHasData[c] ? p : a;
131
- await x.mapAsync(GPUMapMode.READ);
132
- const m = x.getMappedRange(), g = this.pixelBuffer ?? new Uint8Array(this.config.width * this.config.height * 4), u = new Uint8Array(m);
133
- for (let f = 0; f < this.config.height; f++) {
134
- const k = f * this.readbackBytesPerRow, U = f * this.readbackActualBytesPerRow;
135
- g.set(
136
- u.subarray(k, k + this.readbackActualBytesPerRow),
137
- U
140
+ ), t.queue.submit([p.finish()]), await t.queue.onSubmittedWorkDone(), this.readbackHasData[n] = !0, this.readbackIndex = h;
141
+ const b = this.readbackHasData[h] ? x : u;
142
+ await b.mapAsync(GPUMapMode.READ);
143
+ const w = b.getMappedRange(), c = this.pixelBuffer ?? new Uint8Array(this.config.width * this.config.height * 4), f = new Uint8Array(w);
144
+ for (let a = 0; a < this.config.height; a++) {
145
+ const k = a * this.readbackBytesPerRow, B = a * this.readbackActualBytesPerRow;
146
+ c.set(
147
+ f.subarray(k, k + this.readbackActualBytesPerRow),
148
+ B
138
149
  );
139
150
  }
140
- return x.unmap(), new VideoFrame(g, {
151
+ return b.unmap(), new VideoFrame(c, {
141
152
  format: "RGBA",
142
153
  codedWidth: this.config.width,
143
154
  codedHeight: this.config.height,
@@ -191,23 +202,24 @@ class X {
191
202
  ], this.readbackIndex = 0, this.readbackHasData = [!1, !1], this.outputTextureSize = [this.config.width, this.config.height], this.bindGroup = null);
192
203
  const e = this.inputTexture, n = this.outputTexture;
193
204
  if (!e || !n) throw new Error("Textures not initialized");
194
- e.write(t), this.uniformInputWidth = t.displayWidth, this.uniformInputHeight = t.displayHeight, this.updateUniforms(), this.bindGroup || (this.bindGroup = o.createBindGroup(P, {
205
+ e.write(t), this.uniformInputWidth = t.displayWidth, this.uniformInputHeight = t.displayHeight, this.updateUniforms(), this.bindGroup || (this.bindGroup = o.createBindGroup(z, {
195
206
  inputTexture: e,
196
207
  outputTexture: n,
197
208
  uniforms: this.uniformBuffer
198
209
  }));
199
- const c = this.bindGroup, a = this.dewarpPipeline;
200
- if (!a || !n)
210
+ const h = this.bindGroup, u = this.dewarpPipeline;
211
+ if (!u || !n)
201
212
  throw new Error("Compute pipeline or output texture not initialized");
202
- a.with(c).dispatchThreads(this.config.width, this.config.height);
203
- const p = o.unwrap(n);
204
- return this.readbackToVideoFrame(r, p, t.timestamp);
213
+ u.with(h).dispatchThreads(this.config.width, this.config.height);
214
+ const x = o.unwrap(n);
215
+ return this.readbackToVideoFrame(r, x, t.timestamp);
205
216
  }
206
217
  /**
207
218
  * Update configuration
208
219
  */
209
220
  updateConfig(t) {
210
- this.config = this.applyDefaults({ ...this.config, ...t }), this.updateUniforms(), (t.width || t.height) && (this.outputTexture?.destroy(), this.readbackBuffers?.[0]?.destroy(), this.readbackBuffers?.[1]?.destroy(), this.outputTexture = null, this.readbackBuffers = null, this.readbackIndex = 0, this.readbackHasData = [!1, !1], this.outputTextureSize = [0, 0], this.readbackBytesPerRow = 0, this.readbackActualBytesPerRow = 0, this.pixelBuffer = null);
221
+ const o = this.config.width, r = this.config.height;
222
+ this.config = this.applyDefaults({ ...this.config, ...t }), this.updateUniforms(), (this.config.width !== o || this.config.height !== r) && (this.outputTexture?.destroy(), this.readbackBuffers?.[0]?.destroy(), this.readbackBuffers?.[1]?.destroy(), this.outputTexture = null, this.readbackBuffers = null, this.readbackIndex = 0, this.readbackHasData = [!1, !1], this.outputTextureSize = [0, 0], this.readbackBytesPerRow = 0, this.readbackActualBytesPerRow = 0, this.pixelBuffer = null);
211
223
  }
212
224
  /**
213
225
  * Clean up GPU resources
@@ -216,60 +228,60 @@ class X {
216
228
  this.inputTexture?.destroy(), this.outputTexture?.destroy(), this.readbackBuffers?.[0]?.destroy(), this.readbackBuffers?.[1]?.destroy(), this.root?.destroy(), this.inputTexture = null, this.outputTexture = null, this.readbackBuffers = null, this.readbackIndex = 0, this.readbackHasData = [!1, !1], this.uniformBuffer = null, this.root = null, this.bindGroup = null, this.dewarpPipeline = null, this.readbackBytesPerRow = 0, this.readbackActualBytesPerRow = 0, this.pixelBuffer = null, this.inputTextureSize = [0, 0], this.outputTextureSize = [0, 0];
217
229
  }
218
230
  }
219
- function O(s, t) {
231
+ function X(s, t) {
220
232
  const {
221
233
  format: o,
222
234
  width: r,
223
235
  height: e,
224
236
  timestamp: n,
225
- duration: c,
226
- displayWidth: a,
227
- displayHeight: p,
228
- colorSpace: l,
229
- transfer: x
237
+ duration: h,
238
+ displayWidth: u,
239
+ displayHeight: x,
240
+ colorSpace: p,
241
+ transfer: b
230
242
  } = t;
231
243
  if (r <= 0 || e <= 0)
232
244
  throw new Error("Width and height must be positive integers");
233
- const m = $(o, r, e), g = (s instanceof ArrayBuffer, s.byteLength);
234
- if (g < m)
245
+ const w = F(o, r, e), c = (s instanceof ArrayBuffer, s.byteLength);
246
+ if (c < w)
235
247
  throw new Error(
236
- `Buffer too small for ${o} format. Expected at least ${m} bytes, got ${g} bytes`
248
+ `Buffer too small for ${o} format. Expected at least ${w} bytes, got ${c} bytes`
237
249
  );
238
- const u = {
250
+ const f = {
239
251
  format: o,
240
252
  codedWidth: r,
241
253
  codedHeight: e,
242
254
  timestamp: n
243
255
  };
244
- if (c !== void 0 && (u.duration = c), a !== void 0 && (u.displayWidth = a), p !== void 0 && (u.displayHeight = p), l !== void 0 && (u.colorSpace = l), x) {
245
- const f = s instanceof ArrayBuffer ? s : s.buffer;
246
- u.transfer = [f];
256
+ if (h !== void 0 && (f.duration = h), u !== void 0 && (f.displayWidth = u), x !== void 0 && (f.displayHeight = x), p !== void 0 && (f.colorSpace = p), b) {
257
+ const a = s instanceof ArrayBuffer ? s : s.buffer;
258
+ f.transfer = [a];
247
259
  }
248
- return new VideoFrame(s, u);
260
+ return new VideoFrame(s, f);
249
261
  }
250
- function N(s, t, o, r = "I420") {
262
+ function O(s, t, o, r = "I420") {
251
263
  if (r !== "I420")
252
264
  throw new Error(`Unsupported format: ${r}. Only I420 is currently supported.`);
253
- const e = t * o, n = t / 2 * (o / 2), c = e + n * 2, a = new Uint8Array(c), p = 0.299, l = 0.587, x = 0.114, m = -0.169, g = -0.331, u = 0.5, f = 0.5, k = -0.419, U = -0.081, A = a.subarray(0, e), H = a.subarray(e, e + n), G = a.subarray(e + n, c);
254
- for (let d = 0; d < o; d++)
255
- for (let h = 0; h < t; h++) {
256
- const y = (d * t + h) * 4, T = s[y], R = s[y + 1], _ = s[y + 2], S = p * T + l * R + x * _;
257
- A[d * t + h] = Math.round(Math.max(0, Math.min(255, S)));
265
+ const e = t * o, n = t / 2 * (o / 2), h = e + n * 2, u = new Uint8Array(h), x = 0.299, p = 0.587, b = 0.114, w = -0.169, c = -0.331, f = 0.5, a = 0.5, k = -0.419, B = -0.081, P = u.subarray(0, e), v = u.subarray(e, e + n), T = u.subarray(e + n, h);
266
+ for (let g = 0; g < o; g++)
267
+ for (let d = 0; d < t; d++) {
268
+ const y = (g * t + d) * 4, m = s[y], U = s[y + 1], R = s[y + 2], _ = x * m + p * U + b * R;
269
+ P[g * t + d] = Math.round(Math.max(0, Math.min(255, _)));
258
270
  }
259
- for (let d = 0; d < o / 2; d++)
260
- for (let h = 0; h < t / 2; h++) {
261
- let y = 0, T = 0;
262
- for (let B = 0; B < 2; B++)
263
- for (let v = 0; v < 2; v++) {
264
- const w = h * 2 + v, z = ((d * 2 + B) * t + w) * 4, Y = s[z], W = s[z + 1], E = s[z + 2], V = m * Y + g * W + u * E + 128, F = f * Y + k * W + U * E + 128;
265
- y += V, T += F;
271
+ for (let g = 0; g < o / 2; g++)
272
+ for (let d = 0; d < t / 2; d++) {
273
+ let y = 0, m = 0;
274
+ for (let I = 0; I < 2; I++)
275
+ for (let S = 0; S < 2; S++) {
276
+ const A = d * 2 + S, H = ((g * 2 + I) * t + A) * 4, M = s[H], D = s[H + 1], W = s[H + 2], E = w * M + c * D + f * W + 128, V = a * M + k * D + B * W + 128;
277
+ y += E, m += V;
266
278
  }
267
- const R = y / 4, _ = T / 4, S = d * (t / 2) + h;
268
- H[S] = Math.round(Math.max(0, Math.min(255, R))), G[S] = Math.round(Math.max(0, Math.min(255, _)));
279
+ const U = y / 4, R = m / 4, _ = g * (t / 2) + d;
280
+ v[_] = Math.round(Math.max(0, Math.min(255, U))), T[_] = Math.round(Math.max(0, Math.min(255, R)));
269
281
  }
270
- return a;
282
+ return u;
271
283
  }
272
- function $(s, t, o) {
284
+ function F(s, t, o) {
273
285
  const r = t * o;
274
286
  switch (s) {
275
287
  case "I420":
@@ -286,9 +298,9 @@ function $(s, t, o) {
286
298
  }
287
299
  }
288
300
  export {
289
- X as Fisheye,
290
- $ as calculateYUVDataSize,
291
- N as convertRGBAtoYUV,
292
- O as createVideoFrameFromYUV
301
+ $ as Fisheye,
302
+ F as calculateYUVDataSize,
303
+ O as convertRGBAtoYUV,
304
+ X as createVideoFrameFromYUV
293
305
  };
294
306
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/fisheye.ts","../src/utils.ts"],"sourcesContent":["import tgpu, { type TgpuBuffer, type TgpuTexture } from \"typegpu\";\nimport * as d from \"typegpu/data\";\nimport * as std from \"typegpu/std\";\nimport type { FisheyeConfig, FisheyeOptions } from \"./types\";\n\n/**\n * Uniform struct for fisheye dewarping parameters (type-safe with layout)\n */\nconst FisheyeUniforms = d.struct({\n k1: d.f32,\n k2: d.f32,\n k3: d.f32,\n k4: d.f32,\n fov: d.f32,\n centerX: d.f32,\n centerY: d.f32,\n zoom: d.f32,\n width: d.f32,\n height: d.f32,\n inputWidth: d.f32,\n inputHeight: d.f32,\n padding: d.f32,\n});\n\ntype TgpuRootType = Awaited<ReturnType<typeof tgpu.init>>;\n\n/**\n * Bind group layout for fisheye dewarping compute shader (type-safe access via layout.$)\n */\nconst fisheyeLayout = tgpu.bindGroupLayout({\n inputTexture: { texture: d.texture2d() },\n outputTexture: { storageTexture: d.textureStorage2d(\"rgba8unorm\") },\n uniforms: { uniform: FisheyeUniforms },\n});\n\n/** Input texture props (sampled + render for write(); per TypeGPU docs) */\ntype InputTextureProps = {\n size: readonly [number, number];\n format: \"rgba8unorm\";\n};\n\n/** Output texture props (storage) */\ntype OutputTextureProps = {\n size: readonly [number, number];\n format: \"rgba8unorm\";\n};\n\ntype InputTextureType = TgpuTexture<InputTextureProps> & {\n usableAsSampled: true;\n usableAsRender: true;\n};\n\ntype OutputTextureType = TgpuTexture<OutputTextureProps> & {\n usableAsStorage: true;\n};\n\ntype UniformBufferType = TgpuBuffer<typeof FisheyeUniforms> & {\n usableAsUniform: true;\n};\n\n/**\n * Fisheye dewarper using WebGPU via TypeGPU (Pure GPGPU)\n *\n * @example\n * ```ts\n * const dewarper = new Fisheye({\n * distortion: 0.5,\n * width: 1920,\n * height: 1080,\n * });\n *\n * const dewarpedFrame = await dewarper.dewarp(videoFrame);\n * ```\n */\nexport class Fisheye {\n private config: FisheyeConfig;\n private root: TgpuRootType | null = null;\n private uniformBuffer: UniformBufferType | null = null;\n private inputTexture: InputTextureType | null = null;\n private outputTexture: OutputTextureType | null = null;\n private bindGroup: ReturnType<TgpuRootType[\"createBindGroup\"]> | null = null;\n private dewarpPipeline: ReturnType<\n TgpuRootType[\"~unstable\"][\"createGuardedComputePipeline\"]\n > | null = null;\n private readbackBuffers: [GPUBuffer | null, GPUBuffer | null] | null = null;\n private readbackIndex = 0;\n private readbackHasData: [boolean, boolean] = [false, false];\n private readbackBytesPerRow = 0;\n private readbackActualBytesPerRow = 0;\n private pixelBuffer: Uint8Array | null = null;\n private inputTextureSize: [number, number] = [0, 0];\n private outputTextureSize: [number, number] = [0, 0];\n private uniformInputWidth = 0;\n private uniformInputHeight = 0;\n\n constructor(options: FisheyeOptions = {}) {\n this.config = this.applyDefaults(options);\n }\n\n /**\n * Apply default values to options\n */\n private applyDefaults(options: FisheyeOptions): FisheyeConfig {\n const k1 = options.k1 ?? 0;\n return {\n k1,\n k2: options.k2 ?? 0,\n k3: options.k3 ?? 0,\n k4: options.k4 ?? 0,\n width: options.width ?? 300,\n height: options.height ?? 150,\n fov: options.fov ?? 180,\n centerX: options.centerX ?? 0,\n centerY: options.centerY ?? 0,\n zoom: options.zoom ?? 1.0,\n };\n }\n\n /**\n * Initialize TypeGPU root and resources\n */\n private async initialize(): Promise<void> {\n if (this.root) {\n return;\n }\n\n this.root = await tgpu.init();\n\n // Create uniform buffer with TypeGPU for type-safe data handling\n this.uniformBuffer = this.root\n .createBuffer(FisheyeUniforms, this.getUniformData())\n .$usage(\"uniform\");\n\n this.dewarpPipeline = this.root[\"~unstable\"].createGuardedComputePipeline(\n (x: number, y: number) => {\n \"use gpu\";\n\n const p = fisheyeLayout.$.uniforms;\n const outputW = p.width;\n const outputH = p.height;\n const inputW = p.inputWidth;\n const inputH = p.inputHeight;\n const coord = d.vec2i(x, y);\n\n if (d.f32(x) >= outputW || d.f32(y) >= outputH) {\n return;\n }\n\n const uv = d.vec2f(\n (d.f32(coord.x) / outputW - 0.5) * 2.0,\n (d.f32(coord.y) / outputH - 0.5) * 2.0,\n );\n\n const centerX = p.centerX;\n const centerY = p.centerY;\n const centered = uv.sub(d.vec2f(centerX, centerY));\n const r = std.length(centered);\n // Scale r so output corner maps to fov/2, but cap so sampling stays inside input (theta_d <= sqrt(2)).\n const cornerNorm = Math.SQRT2;\n const fovRad = std.min(p.fov * 0.017453293, 3.1);\n const scaleRaw = std.tan(fovRad * 0.5) / cornerNorm;\n const scale = std.min(scaleRaw, 1.0);\n const rScaledForFov = r * scale;\n\n const theta = std.atan(rScaledForFov);\n const theta2 = theta * theta;\n const theta4 = theta2 * theta2;\n const theta6 = theta4 * theta2;\n const theta8 = theta4 * theta4;\n // OpenCV fisheye: distorted normalized radius = θ_d. x' = (θ_d/r)*a => |(x',y')| = θ_d.\n const thetaDistorted =\n theta * (1.0 + p.k1 * theta2 + p.k2 * theta4 + p.k3 * theta6 + p.k4 * theta8);\n const rDistorted = thetaDistorted;\n const rScaled = rDistorted / p.zoom;\n\n let distortedUv = d.vec2f(centered.x, centered.y);\n if (r > 0.0001) {\n distortedUv = d.vec2f(centered.x * (rScaled / r), centered.y * (rScaled / r));\n }\n\n const finalUv = distortedUv.add(d.vec2f(centerX, centerY)).mul(0.5).add(0.5);\n\n if (finalUv.x >= 0.0 && finalUv.x <= 1.0 && finalUv.y >= 0.0 && finalUv.y <= 1.0) {\n const sampleCoord = d.vec2i(d.i32(finalUv.x * inputW), d.i32(finalUv.y * inputH));\n const color = std.textureLoad(fisheyeLayout.$.inputTexture, sampleCoord, 0);\n std.textureStore(fisheyeLayout.$.outputTexture, coord, color);\n } else {\n std.textureStore(fisheyeLayout.$.outputTexture, coord, d.vec4f(0.0, 0.0, 0.0, 1.0));\n }\n },\n );\n }\n\n /**\n * Get uniform data from current configuration\n */\n private getUniformData(): d.Infer<typeof FisheyeUniforms> {\n return {\n k1: this.config.k1,\n k2: this.config.k2,\n k3: this.config.k3,\n k4: this.config.k4,\n fov: this.config.fov,\n centerX: this.config.centerX,\n centerY: this.config.centerY,\n zoom: this.config.zoom,\n width: this.config.width,\n height: this.config.height,\n inputWidth: this.uniformInputWidth,\n inputHeight: this.uniformInputHeight,\n padding: 0,\n };\n }\n\n /**\n * Update uniform buffer with current configuration\n */\n private updateUniforms(): void {\n if (!this.uniformBuffer) {\n return;\n }\n this.uniformBuffer.write(this.getUniformData());\n }\n\n private async readbackToVideoFrame(\n device: GPUDevice,\n outputTexture: GPUTexture,\n timestamp: number,\n ): Promise<VideoFrame> {\n const readbackBuffers = this.readbackBuffers;\n\n if (!readbackBuffers) {\n throw new Error(\"Readback buffer not initialized\");\n }\n\n const writeIndex = this.readbackIndex;\n const readIndex = 1 - writeIndex;\n const writeBuffer = readbackBuffers[writeIndex];\n const readBuffer = readbackBuffers[readIndex];\n\n if (!writeBuffer || !readBuffer) {\n throw new Error(\"Readback buffer not initialized\");\n }\n\n const commandEncoder = device.createCommandEncoder();\n commandEncoder.copyTextureToBuffer(\n { texture: outputTexture },\n { buffer: writeBuffer, bytesPerRow: this.readbackBytesPerRow },\n [this.config.width, this.config.height],\n );\n device.queue.submit([commandEncoder.finish()]);\n await device.queue.onSubmittedWorkDone();\n\n this.readbackHasData[writeIndex] = true;\n this.readbackIndex = readIndex;\n\n const bufferToRead = this.readbackHasData[readIndex] ? readBuffer : writeBuffer;\n\n await bufferToRead.mapAsync(GPUMapMode.READ);\n const mappedData = bufferToRead.getMappedRange();\n\n const pixelData =\n this.pixelBuffer ?? new Uint8Array(this.config.width * this.config.height * 4);\n const srcView = new Uint8Array(mappedData);\n\n for (let row = 0; row < this.config.height; row++) {\n const srcOffset = row * this.readbackBytesPerRow;\n const dstOffset = row * this.readbackActualBytesPerRow;\n pixelData.set(\n srcView.subarray(srcOffset, srcOffset + this.readbackActualBytesPerRow),\n dstOffset,\n );\n }\n\n bufferToRead.unmap();\n\n return new VideoFrame(pixelData, {\n format: \"RGBA\",\n codedWidth: this.config.width,\n codedHeight: this.config.height,\n timestamp,\n });\n }\n\n /**\n * Create input texture (TypeGPU; per official docs: sampled + render for .write(image/VideoFrame)).\n */\n private createInputTexture(root: TgpuRootType, width: number, height: number): InputTextureType {\n const size: readonly [number, number] = [width, height];\n const format: \"rgba8unorm\" = \"rgba8unorm\";\n return root[\"~unstable\"].createTexture({ size, format }).$usage(\"sampled\", \"render\");\n }\n\n /**\n * Create output storage texture (TypeGPU; type-safe with layout.$)\n */\n private createOutputTexture(\n root: TgpuRootType,\n width: number,\n height: number,\n ): OutputTextureType {\n const size: readonly [number, number] = [width, height];\n const format: \"rgba8unorm\" = \"rgba8unorm\";\n return root[\"~unstable\"].createTexture({ size, format }).$usage(\"storage\");\n }\n\n /**\n * Calculate bytes per row with proper alignment (256-byte alignment for WebGPU)\n */\n private calculateBytesPerRow(width: number): number {\n const bytesPerPixel = 4; // RGBA8\n const unalignedBytesPerRow = width * bytesPerPixel;\n // WebGPU requires 256-byte alignment for buffer copies\n return Math.ceil(unalignedBytesPerRow / 256) * 256;\n }\n\n /**\n * Create or recreate readback buffer for GPU to CPU data transfer\n */\n private createReadbackBuffer(device: GPUDevice, width: number, height: number): GPUBuffer {\n const bytesPerRow = this.calculateBytesPerRow(width);\n const bufferSize = bytesPerRow * height;\n\n return device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n }\n\n /**\n * Dewarp a VideoFrame\n *\n * @param frame - Input VideoFrame with fisheye distortion\n * @returns Dewarped VideoFrame\n */\n async dewarp(frame: VideoFrame): Promise<VideoFrame> {\n await this.initialize();\n\n if (!this.root || !this.uniformBuffer) {\n throw new Error(\"GPU resources not initialized\");\n }\n\n // Capture root for type narrowing\n const root = this.root;\n const device = root.device;\n\n if (\n !this.inputTexture ||\n this.inputTextureSize[0] !== frame.displayWidth ||\n this.inputTextureSize[1] !== frame.displayHeight\n ) {\n this.inputTexture?.destroy();\n this.inputTexture = this.createInputTexture(root, frame.displayWidth, frame.displayHeight);\n this.inputTextureSize = [frame.displayWidth, frame.displayHeight];\n this.bindGroup = null;\n }\n\n if (\n !this.outputTexture ||\n this.outputTextureSize[0] !== this.config.width ||\n this.outputTextureSize[1] !== this.config.height\n ) {\n this.outputTexture?.destroy();\n this.readbackBuffers?.[0]?.destroy();\n this.readbackBuffers?.[1]?.destroy();\n\n this.outputTexture = this.createOutputTexture(root, this.config.width, this.config.height);\n this.readbackBytesPerRow = this.calculateBytesPerRow(this.config.width);\n this.readbackActualBytesPerRow = this.config.width * 4;\n this.pixelBuffer = new Uint8Array(this.config.width * this.config.height * 4);\n this.readbackBuffers = [\n this.createReadbackBuffer(device, this.config.width, this.config.height),\n this.createReadbackBuffer(device, this.config.width, this.config.height),\n ];\n this.readbackIndex = 0;\n this.readbackHasData = [false, false];\n this.outputTextureSize = [this.config.width, this.config.height];\n this.bindGroup = null;\n }\n\n const inputTexture = this.inputTexture;\n const outputTexture = this.outputTexture;\n\n if (!inputTexture || !outputTexture) throw new Error(\"Textures not initialized\");\n\n inputTexture.write(frame);\n\n this.uniformInputWidth = frame.displayWidth;\n this.uniformInputHeight = frame.displayHeight;\n this.updateUniforms();\n\n if (!this.bindGroup) {\n this.bindGroup = root.createBindGroup(fisheyeLayout, {\n inputTexture,\n outputTexture,\n uniforms: this.uniformBuffer,\n });\n }\n\n const bindGroup = this.bindGroup;\n const dewarpPipeline = this.dewarpPipeline;\n\n if (!dewarpPipeline || !outputTexture) {\n throw new Error(\"Compute pipeline or output texture not initialized\");\n }\n\n dewarpPipeline.with(bindGroup).dispatchThreads(this.config.width, this.config.height);\n\n const outputGpuTexture = root.unwrap(outputTexture);\n return this.readbackToVideoFrame(device, outputGpuTexture, frame.timestamp);\n }\n\n /**\n * Update configuration\n */\n updateConfig(options: Partial<FisheyeOptions>): void {\n this.config = this.applyDefaults({ ...this.config, ...options });\n this.updateUniforms();\n\n // Recreate output texture and readback buffer if size changed\n if (options.width || options.height) {\n this.outputTexture?.destroy();\n this.readbackBuffers?.[0]?.destroy();\n this.readbackBuffers?.[1]?.destroy();\n this.outputTexture = null;\n this.readbackBuffers = null;\n this.readbackIndex = 0;\n this.readbackHasData = [false, false];\n this.outputTextureSize = [0, 0];\n this.readbackBytesPerRow = 0;\n this.readbackActualBytesPerRow = 0;\n this.pixelBuffer = null;\n }\n }\n\n /**\n * Clean up GPU resources\n */\n destroy(): void {\n this.inputTexture?.destroy();\n this.outputTexture?.destroy();\n this.readbackBuffers?.[0]?.destroy();\n this.readbackBuffers?.[1]?.destroy();\n this.root?.destroy();\n\n this.inputTexture = null;\n this.outputTexture = null;\n this.readbackBuffers = null;\n this.readbackIndex = 0;\n this.readbackHasData = [false, false];\n this.uniformBuffer = null;\n this.root = null;\n this.bindGroup = null;\n this.dewarpPipeline = null;\n this.readbackBytesPerRow = 0;\n this.readbackActualBytesPerRow = 0;\n this.pixelBuffer = null;\n this.inputTextureSize = [0, 0];\n this.outputTextureSize = [0, 0];\n }\n}\n","/**\n * Supported YUV pixel formats for VideoFrame creation\n */\nexport type YUVFormat = \"I420\" | \"I420A\" | \"I422\" | \"I444\" | \"NV12\";\n\n/**\n * Extended VideoFrameBufferInit with transfer support\n * (transfer is part of the spec but may not be in all TypeScript definitions)\n */\ninterface VideoFrameBufferInitExtended extends VideoFrameBufferInit {\n transfer?: ArrayBuffer[];\n}\n\n/**\n * Options for creating a VideoFrame from YUV data\n */\nexport interface CreateVideoFrameOptions {\n /**\n * YUV pixel format\n * - I420: YUV 4:2:0 planar (Y plane, U plane, V plane)\n * - I420A: YUV 4:2:0 planar with alpha\n * - I422: YUV 4:2:2 planar\n * - I444: YUV 4:4:4 planar\n * - NV12: YUV 4:2:0 semi-planar (Y plane, interleaved UV plane)\n */\n format: YUVFormat;\n\n /**\n * Width of the video frame in pixels\n */\n width: number;\n\n /**\n * Height of the video frame in pixels\n */\n height: number;\n\n /**\n * Timestamp in microseconds\n */\n timestamp: number;\n\n /**\n * Duration in microseconds (optional)\n */\n duration?: number;\n\n /**\n * Display width (optional, defaults to width)\n */\n displayWidth?: number;\n\n /**\n * Display height (optional, defaults to height)\n */\n displayHeight?: number;\n\n /**\n * Color space configuration (optional)\n */\n colorSpace?: VideoColorSpaceInit;\n\n /**\n * Transfer ownership of the buffer for zero-copy (optional)\n * If true, the input buffer will be detached after VideoFrame creation\n */\n transfer?: boolean;\n}\n\n/**\n * Create a VideoFrame from YUV binary data\n *\n * @param data - YUV binary data (ArrayBuffer, TypedArray, or DataView)\n * @param options - Configuration options including format, dimensions, and timestamp\n * @returns A new VideoFrame object\n *\n * @example\n * ```ts\n * // Create VideoFrame from I420 (YUV 4:2:0) data\n * const yuvData = new Uint8Array(width * height * 1.5); // I420 size\n * const frame = createVideoFrameFromYUV(yuvData, {\n * format: \"I420\",\n * width: 1920,\n * height: 1080,\n * timestamp: 0,\n * });\n * ```\n *\n * @example\n * ```ts\n * // Create VideoFrame from NV12 data with zero-copy transfer\n * const nv12Data = new Uint8Array(width * height * 1.5);\n * const frame = createVideoFrameFromYUV(nv12Data, {\n * format: \"NV12\",\n * width: 1920,\n * height: 1080,\n * timestamp: 0,\n * transfer: true, // Transfer buffer ownership for better performance\n * });\n * ```\n */\nexport function createVideoFrameFromYUV(\n data: BufferSource,\n options: CreateVideoFrameOptions,\n): VideoFrame {\n const {\n format,\n width,\n height,\n timestamp,\n duration,\n displayWidth,\n displayHeight,\n colorSpace,\n transfer,\n } = options;\n\n // Validate dimensions\n if (width <= 0 || height <= 0) {\n throw new Error(\"Width and height must be positive integers\");\n }\n\n // Calculate expected data size based on format\n const expectedSize = calculateYUVDataSize(format, width, height);\n const actualSize = data instanceof ArrayBuffer ? data.byteLength : data.byteLength;\n\n if (actualSize < expectedSize) {\n throw new Error(\n `Buffer too small for ${format} format. Expected at least ${expectedSize} bytes, got ${actualSize} bytes`,\n );\n }\n\n // Build VideoFrame init options\n const init: VideoFrameBufferInitExtended = {\n format,\n codedWidth: width,\n codedHeight: height,\n timestamp,\n };\n\n if (duration !== undefined) {\n init.duration = duration;\n }\n\n if (displayWidth !== undefined) {\n init.displayWidth = displayWidth;\n }\n\n if (displayHeight !== undefined) {\n init.displayHeight = displayHeight;\n }\n\n if (colorSpace !== undefined) {\n init.colorSpace = colorSpace;\n }\n\n // Handle buffer transfer for zero-copy\n if (transfer) {\n const buffer = data instanceof ArrayBuffer ? data : data.buffer;\n init.transfer = [buffer];\n }\n\n return new VideoFrame(data, init);\n}\n\n/**\n * Convert RGBA image data to YUV format (I420 by default)\n *\n * Uses ITU-R BT.601 color space conversion:\n * - Y = 0.299*R + 0.587*G + 0.114*B\n * - U = -0.169*R - 0.331*G + 0.5*B + 128\n * - V = 0.5*R - 0.419*G - 0.081*B + 128\n *\n * For I420 format:\n * - Y plane: full resolution (width * height)\n * - U plane: quarter resolution ((width/2) * (height/2))\n * - V plane: quarter resolution ((width/2) * (height/2))\n *\n * @param rgbaData - RGBA pixel data (Uint8ClampedArray from ImageData)\n * @param width - Image width in pixels\n * @param height - Image height in pixels\n * @param format - YUV format to convert to (default: \"I420\")\n * @returns YUV data as Uint8Array\n *\n * @example\n * ```ts\n * const canvas = document.createElement('canvas');\n * const ctx = canvas.getContext('2d');\n * ctx.drawImage(image, 0, 0);\n * const imageData = ctx.getImageData(0, 0, width, height);\n * const yuvData = convertRGBAtoYUV(imageData.data, width, height);\n * ```\n */\nexport function convertRGBAtoYUV(\n rgbaData: Uint8ClampedArray,\n width: number,\n height: number,\n format: YUVFormat = \"I420\",\n): Uint8Array {\n if (format !== \"I420\") {\n throw new Error(`Unsupported format: ${format}. Only I420 is currently supported.`);\n }\n\n const lumaSize = width * height;\n const chromaSize = (width / 2) * (height / 2);\n const yuvSize = lumaSize + chromaSize * 2; // Y + U + V\n const yuvData = new Uint8Array(yuvSize);\n\n // BT.601 coefficients\n const Y_R = 0.299;\n const Y_G = 0.587;\n const Y_B = 0.114;\n const U_R = -0.169;\n const U_G = -0.331;\n const U_B = 0.5;\n const V_R = 0.5;\n const V_G = -0.419;\n const V_B = -0.081;\n\n // Convert RGB to YUV and downsample chroma for I420\n const yPlane = yuvData.subarray(0, lumaSize);\n const uPlane = yuvData.subarray(lumaSize, lumaSize + chromaSize);\n const vPlane = yuvData.subarray(lumaSize + chromaSize, yuvSize);\n\n // First pass: convert to YUV and store Y plane\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const rgbaIdx = (y * width + x) * 4;\n const r = rgbaData[rgbaIdx];\n const g = rgbaData[rgbaIdx + 1];\n const b = rgbaData[rgbaIdx + 2];\n\n // Calculate Y (luma)\n const yVal = Y_R * r + Y_G * g + Y_B * b;\n yPlane[y * width + x] = Math.round(Math.max(0, Math.min(255, yVal)));\n\n // Calculate U and V for chroma downsampling\n // We'll accumulate these in the second pass\n }\n }\n\n // Second pass: downsample U and V planes (average 2x2 blocks)\n for (let y = 0; y < height / 2; y++) {\n for (let x = 0; x < width / 2; x++) {\n // Sample 2x2 block from original image\n let uSum = 0;\n let vSum = 0;\n\n for (let dy = 0; dy < 2; dy++) {\n for (let dx = 0; dx < 2; dx++) {\n const srcX = x * 2 + dx;\n const srcY = y * 2 + dy;\n const rgbaIdx = (srcY * width + srcX) * 4;\n const r = rgbaData[rgbaIdx];\n const g = rgbaData[rgbaIdx + 1];\n const b = rgbaData[rgbaIdx + 2];\n\n // Calculate U and V\n const uVal = U_R * r + U_G * g + U_B * b + 128;\n const vVal = V_R * r + V_G * g + V_B * b + 128;\n\n uSum += uVal;\n vSum += vVal;\n }\n }\n\n // Average the 2x2 block\n const uAvg = uSum / 4;\n const vAvg = vSum / 4;\n\n const chromaIdx = y * (width / 2) + x;\n uPlane[chromaIdx] = Math.round(Math.max(0, Math.min(255, uAvg)));\n vPlane[chromaIdx] = Math.round(Math.max(0, Math.min(255, vAvg)));\n }\n }\n\n return yuvData;\n}\n\n/**\n * Calculate the expected byte size for YUV data based on format and dimensions\n *\n * @param format - YUV pixel format\n * @param width - Frame width in pixels\n * @param height - Frame height in pixels\n * @returns Expected byte size\n */\nexport function calculateYUVDataSize(format: YUVFormat, width: number, height: number): number {\n const lumaSize = width * height;\n\n switch (format) {\n case \"I420\":\n case \"NV12\":\n // 4:2:0 - chroma is half resolution in both dimensions\n // Y: width * height, U: (width/2) * (height/2), V: (width/2) * (height/2)\n return lumaSize + lumaSize / 2;\n\n case \"I420A\":\n // 4:2:0 with alpha\n // Y: width * height, U: (width/2) * (height/2), V: (width/2) * (height/2), A: width * height\n return lumaSize * 2 + lumaSize / 2;\n\n case \"I422\":\n // 4:2:2 - chroma is half resolution horizontally only\n // Y: width * height, U: (width/2) * height, V: (width/2) * height\n return lumaSize * 2;\n\n case \"I444\":\n // 4:4:4 - full resolution for all planes\n // Y: width * height, U: width * height, V: width * height\n return lumaSize * 3;\n\n default:\n throw new Error(`Unsupported YUV format: ${format}`);\n }\n}\n"],"names":["FisheyeUniforms","d","fisheyeLayout","tgpu","Fisheye","options","a","x","y","p","outputW","outputH","inputW","inputH","coord","uv","centerX","centerY","centered","r","std","cornerNorm","fovRad","scaleRaw","scale","rScaledForFov","theta","theta2","theta4","theta6","theta8","rScaled","distortedUv","finalUv","sampleCoord","color","device","outputTexture","timestamp","readbackBuffers","writeIndex","readIndex","writeBuffer","readBuffer","commandEncoder","bufferToRead","mappedData","pixelData","srcView","row","srcOffset","dstOffset","root","width","height","size","unalignedBytesPerRow","bufferSize","frame","inputTexture","bindGroup","dewarpPipeline","outputGpuTexture","createVideoFrameFromYUV","data","format","duration","displayWidth","displayHeight","colorSpace","transfer","expectedSize","calculateYUVDataSize","actualSize","init","buffer","convertRGBAtoYUV","rgbaData","lumaSize","chromaSize","yuvSize","yuvData","Y_R","Y_G","Y_B","U_R","U_G","U_B","V_R","V_G","V_B","yPlane","uPlane","vPlane","rgbaIdx","g","b","yVal","uSum","vSum","dy","dx","srcX","uVal","vVal","uAvg","vAvg","chromaIdx"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/fisheye.ts","../src/utils.ts"],"sourcesContent":["import tgpu, { type TgpuBuffer, type TgpuTexture } from \"typegpu\";\nimport * as d from \"typegpu/data\";\nimport * as std from \"typegpu/std\";\nimport type { FisheyeConfig, FisheyeOptions } from \"./types\";\n\n/**\n * Uniform struct for fisheye dewarping parameters (type-safe with layout)\n */\nconst FisheyeUniforms = d.struct({\n k1: d.f32,\n k2: d.f32,\n k3: d.f32,\n k4: d.f32,\n fov: d.f32,\n centerX: d.f32,\n centerY: d.f32,\n zoom: d.f32,\n width: d.f32,\n height: d.f32,\n inputWidth: d.f32,\n inputHeight: d.f32,\n projection: d.f32, // 0 = rectilinear, 1 = equirectangular\n padding: d.f32,\n});\n\ntype TgpuRootType = Awaited<ReturnType<typeof tgpu.init>>;\n\n/**\n * Bind group layout for fisheye dewarping compute shader (type-safe access via layout.$)\n */\nconst fisheyeLayout = tgpu.bindGroupLayout({\n inputTexture: { texture: d.texture2d() },\n outputTexture: { storageTexture: d.textureStorage2d(\"rgba8unorm\") },\n uniforms: { uniform: FisheyeUniforms },\n});\n\n/** Input texture props (sampled + render for write(); per TypeGPU docs) */\ntype InputTextureProps = {\n size: readonly [number, number];\n format: \"rgba8unorm\";\n};\n\n/** Output texture props (storage) */\ntype OutputTextureProps = {\n size: readonly [number, number];\n format: \"rgba8unorm\";\n};\n\ntype InputTextureType = TgpuTexture<InputTextureProps> & {\n usableAsSampled: true;\n usableAsRender: true;\n};\n\ntype OutputTextureType = TgpuTexture<OutputTextureProps> & {\n usableAsStorage: true;\n};\n\ntype UniformBufferType = TgpuBuffer<typeof FisheyeUniforms> & {\n usableAsUniform: true;\n};\n\n/**\n * Fisheye dewarper using WebGPU via TypeGPU (Pure GPGPU)\n *\n * @example\n * ```ts\n * const dewarper = new Fisheye({\n * width: 1920,\n * height: 1080,\n * fov: 180,\n * projection: \"rectilinear\",\n * });\n *\n * const dewarpedFrame = await dewarper.dewarp(inputVideoFrame);\n * ```\n */\nexport class Fisheye {\n private config: FisheyeConfig;\n private root: TgpuRootType | null = null;\n private uniformBuffer: UniformBufferType | null = null;\n private inputTexture: InputTextureType | null = null;\n private outputTexture: OutputTextureType | null = null;\n private bindGroup: ReturnType<TgpuRootType[\"createBindGroup\"]> | null = null;\n private dewarpPipeline: ReturnType<\n TgpuRootType[\"~unstable\"][\"createGuardedComputePipeline\"]\n > | null = null;\n private readbackBuffers: [GPUBuffer | null, GPUBuffer | null] | null = null;\n private readbackIndex = 0;\n private readbackHasData: [boolean, boolean] = [false, false];\n private readbackBytesPerRow = 0;\n private readbackActualBytesPerRow = 0;\n private pixelBuffer: Uint8Array | null = null;\n private inputTextureSize: [number, number] = [0, 0];\n private outputTextureSize: [number, number] = [0, 0];\n private uniformInputWidth = 0;\n private uniformInputHeight = 0;\n\n constructor(options: FisheyeOptions = {}) {\n this.config = this.applyDefaults(options);\n }\n\n /**\n * Apply default values to options\n */\n private applyDefaults(options: FisheyeOptions): FisheyeConfig {\n const k1 = options.k1 ?? 0;\n return {\n k1,\n k2: options.k2 ?? 0,\n k3: options.k3 ?? 0,\n k4: options.k4 ?? 0,\n width: options.width ?? 300,\n height: options.height ?? 150,\n fov: options.fov ?? 180,\n projection: options.projection ?? \"rectilinear\",\n mount: options.mount ?? \"ceiling\",\n centerX: options.centerX ?? 0,\n centerY: options.centerY ?? 0,\n zoom: options.zoom ?? 1.0,\n };\n }\n\n /**\n * Initialize TypeGPU root and resources\n */\n private async initialize(): Promise<void> {\n if (this.root) {\n return;\n }\n\n this.root = await tgpu.init();\n\n // Create uniform buffer with TypeGPU for type-safe data handling\n this.uniformBuffer = this.root\n .createBuffer(FisheyeUniforms, this.getUniformData())\n .$usage(\"uniform\");\n\n this.dewarpPipeline = this.root[\"~unstable\"].createGuardedComputePipeline(\n (x: number, y: number) => {\n \"use gpu\";\n\n const p = fisheyeLayout.$.uniforms;\n const outputW = p.width;\n const outputH = p.height;\n const inputW = p.inputWidth;\n const inputH = p.inputHeight;\n const coord = d.vec2i(x, y);\n\n if (d.f32(x) >= outputW || d.f32(y) >= outputH) {\n return;\n }\n\n const centerX = p.centerX;\n const centerY = p.centerY;\n let finalUv = d.vec2f(0.0, 0.0);\n\n if (p.projection < 0.5) {\n // Rectilinear: output pixel → normalized direction → θ → r_d → input UV\n const uv = d.vec2f(\n (d.f32(coord.x) / outputW - 0.5) * 2.0,\n (d.f32(coord.y) / outputH - 0.5) * 2.0,\n );\n const centered = uv.sub(d.vec2f(centerX, centerY));\n const r = std.length(centered);\n const cornerNorm = Math.SQRT2;\n const fovRad = std.min(p.fov * (Math.PI / 180), 3.1);\n const scaleRaw = std.tan(fovRad * 0.5) / cornerNorm;\n const scale = std.min(scaleRaw, 1.0);\n const rScaledForFov = r * scale;\n\n const theta = std.atan(rScaledForFov);\n const theta2 = theta * theta;\n const theta4 = theta2 * theta2;\n const theta6 = theta4 * theta2;\n const theta8 = theta4 * theta4;\n const thetaDistorted =\n theta * (1.0 + p.k1 * theta2 + p.k2 * theta4 + p.k3 * theta6 + p.k4 * theta8);\n const rDistorted = thetaDistorted;\n const rScaled = rDistorted / p.zoom;\n\n let distortedUv = d.vec2f(centered.x, centered.y);\n if (r > 0.0001) {\n distortedUv = d.vec2f(centered.x * (rScaled / r), centered.y * (rScaled / r));\n }\n finalUv = distortedUv.add(d.vec2f(centerX, centerY)).mul(0.5).add(0.5);\n } else {\n // Equirectangular: output (x,y) → lon, lat → θ (angle from optical axis) → r_d → input UV\n const fovRad = p.fov * (Math.PI / 180);\n const lon = (d.f32(coord.x) / outputW - 0.5) * fovRad;\n const lat = (d.f32(coord.y) / outputH - 0.5) * Math.PI;\n const sinLat = std.sin(lat);\n const theta = std.acos(std.min(std.max(sinLat, -1.0), 1.0));\n const theta2 = theta * theta;\n const theta4 = theta2 * theta2;\n const theta6 = theta4 * theta2;\n const theta8 = theta4 * theta4;\n const rDistorted =\n theta * (1.0 + p.k1 * theta2 + p.k2 * theta4 + p.k3 * theta6 + p.k4 * theta8);\n const rScaled = rDistorted / p.zoom;\n const uNorm = centerX + rScaled * std.cos(lon);\n const vNorm = centerY + rScaled * std.sin(lon);\n finalUv = d.vec2f(uNorm * 0.5 + 0.5, vNorm * 0.5 + 0.5);\n }\n\n if (finalUv.x >= 0.0 && finalUv.x <= 1.0 && finalUv.y >= 0.0 && finalUv.y <= 1.0) {\n const sampleCoord = d.vec2i(d.i32(finalUv.x * inputW), d.i32(finalUv.y * inputH));\n const color = std.textureLoad(fisheyeLayout.$.inputTexture, sampleCoord, 0);\n std.textureStore(fisheyeLayout.$.outputTexture, coord, color);\n } else {\n std.textureStore(fisheyeLayout.$.outputTexture, coord, d.vec4f(0.0, 0.0, 0.0, 1.0));\n }\n },\n );\n }\n\n /**\n * Get uniform data from current configuration\n */\n private getUniformData(): d.Infer<typeof FisheyeUniforms> {\n return {\n k1: this.config.k1,\n k2: this.config.k2,\n k3: this.config.k3,\n k4: this.config.k4,\n fov: this.config.fov,\n centerX: this.config.centerX,\n centerY: this.config.centerY,\n zoom: this.config.zoom,\n width: this.config.width,\n height: this.config.height,\n inputWidth: this.uniformInputWidth,\n inputHeight: this.uniformInputHeight,\n projection: this.config.projection === \"equirectangular\" ? 1 : 0,\n padding: 0,\n };\n }\n\n /**\n * Update uniform buffer with current configuration\n */\n private updateUniforms(): void {\n if (!this.uniformBuffer) {\n return;\n }\n this.uniformBuffer.write(this.getUniformData());\n }\n\n private async readbackToVideoFrame(\n device: GPUDevice,\n outputTexture: GPUTexture,\n timestamp: number,\n ): Promise<VideoFrame> {\n const readbackBuffers = this.readbackBuffers;\n\n if (!readbackBuffers) {\n throw new Error(\"Readback buffer not initialized\");\n }\n\n const writeIndex = this.readbackIndex;\n const readIndex = 1 - writeIndex;\n const writeBuffer = readbackBuffers[writeIndex];\n const readBuffer = readbackBuffers[readIndex];\n\n if (!writeBuffer || !readBuffer) {\n throw new Error(\"Readback buffer not initialized\");\n }\n\n const commandEncoder = device.createCommandEncoder();\n commandEncoder.copyTextureToBuffer(\n { texture: outputTexture },\n { buffer: writeBuffer, bytesPerRow: this.readbackBytesPerRow },\n [this.config.width, this.config.height],\n );\n device.queue.submit([commandEncoder.finish()]);\n await device.queue.onSubmittedWorkDone();\n\n this.readbackHasData[writeIndex] = true;\n this.readbackIndex = readIndex;\n\n const bufferToRead = this.readbackHasData[readIndex] ? readBuffer : writeBuffer;\n\n await bufferToRead.mapAsync(GPUMapMode.READ);\n const mappedData = bufferToRead.getMappedRange();\n\n const pixelData =\n this.pixelBuffer ?? new Uint8Array(this.config.width * this.config.height * 4);\n const srcView = new Uint8Array(mappedData);\n\n for (let row = 0; row < this.config.height; row++) {\n const srcOffset = row * this.readbackBytesPerRow;\n const dstOffset = row * this.readbackActualBytesPerRow;\n pixelData.set(\n srcView.subarray(srcOffset, srcOffset + this.readbackActualBytesPerRow),\n dstOffset,\n );\n }\n\n bufferToRead.unmap();\n\n return new VideoFrame(pixelData, {\n format: \"RGBA\",\n codedWidth: this.config.width,\n codedHeight: this.config.height,\n timestamp,\n });\n }\n\n /**\n * Create input texture (TypeGPU; per official docs: sampled + render for .write(image/VideoFrame)).\n */\n private createInputTexture(root: TgpuRootType, width: number, height: number): InputTextureType {\n const size: readonly [number, number] = [width, height];\n const format: \"rgba8unorm\" = \"rgba8unorm\";\n return root[\"~unstable\"].createTexture({ size, format }).$usage(\"sampled\", \"render\");\n }\n\n /**\n * Create output storage texture (TypeGPU; type-safe with layout.$)\n */\n private createOutputTexture(\n root: TgpuRootType,\n width: number,\n height: number,\n ): OutputTextureType {\n const size: readonly [number, number] = [width, height];\n const format: \"rgba8unorm\" = \"rgba8unorm\";\n return root[\"~unstable\"].createTexture({ size, format }).$usage(\"storage\");\n }\n\n /**\n * Calculate bytes per row with proper alignment (256-byte alignment for WebGPU)\n */\n private calculateBytesPerRow(width: number): number {\n const bytesPerPixel = 4; // RGBA8\n const unalignedBytesPerRow = width * bytesPerPixel;\n // WebGPU requires 256-byte alignment for buffer copies\n return Math.ceil(unalignedBytesPerRow / 256) * 256;\n }\n\n /**\n * Create or recreate readback buffer for GPU to CPU data transfer\n */\n private createReadbackBuffer(device: GPUDevice, width: number, height: number): GPUBuffer {\n const bytesPerRow = this.calculateBytesPerRow(width);\n const bufferSize = bytesPerRow * height;\n\n return device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n }\n\n /**\n * Dewarp a VideoFrame\n *\n * @param frame - Input VideoFrame with fisheye distortion\n * @returns Dewarped VideoFrame\n */\n async dewarp(frame: VideoFrame): Promise<VideoFrame> {\n await this.initialize();\n\n if (!this.root || !this.uniformBuffer) {\n throw new Error(\"GPU resources not initialized\");\n }\n\n // Capture root for type narrowing\n const root = this.root;\n const device = root.device;\n\n if (\n !this.inputTexture ||\n this.inputTextureSize[0] !== frame.displayWidth ||\n this.inputTextureSize[1] !== frame.displayHeight\n ) {\n this.inputTexture?.destroy();\n this.inputTexture = this.createInputTexture(root, frame.displayWidth, frame.displayHeight);\n this.inputTextureSize = [frame.displayWidth, frame.displayHeight];\n this.bindGroup = null;\n }\n\n if (\n !this.outputTexture ||\n this.outputTextureSize[0] !== this.config.width ||\n this.outputTextureSize[1] !== this.config.height\n ) {\n this.outputTexture?.destroy();\n this.readbackBuffers?.[0]?.destroy();\n this.readbackBuffers?.[1]?.destroy();\n\n this.outputTexture = this.createOutputTexture(root, this.config.width, this.config.height);\n this.readbackBytesPerRow = this.calculateBytesPerRow(this.config.width);\n this.readbackActualBytesPerRow = this.config.width * 4;\n this.pixelBuffer = new Uint8Array(this.config.width * this.config.height * 4);\n this.readbackBuffers = [\n this.createReadbackBuffer(device, this.config.width, this.config.height),\n this.createReadbackBuffer(device, this.config.width, this.config.height),\n ];\n this.readbackIndex = 0;\n this.readbackHasData = [false, false];\n this.outputTextureSize = [this.config.width, this.config.height];\n this.bindGroup = null;\n }\n\n const inputTexture = this.inputTexture;\n const outputTexture = this.outputTexture;\n\n if (!inputTexture || !outputTexture) throw new Error(\"Textures not initialized\");\n\n inputTexture.write(frame);\n\n this.uniformInputWidth = frame.displayWidth;\n this.uniformInputHeight = frame.displayHeight;\n this.updateUniforms();\n\n if (!this.bindGroup) {\n this.bindGroup = root.createBindGroup(fisheyeLayout, {\n inputTexture,\n outputTexture,\n uniforms: this.uniformBuffer,\n });\n }\n\n const bindGroup = this.bindGroup;\n const dewarpPipeline = this.dewarpPipeline;\n\n if (!dewarpPipeline || !outputTexture) {\n throw new Error(\"Compute pipeline or output texture not initialized\");\n }\n\n dewarpPipeline.with(bindGroup).dispatchThreads(this.config.width, this.config.height);\n\n const outputGpuTexture = root.unwrap(outputTexture);\n return this.readbackToVideoFrame(device, outputGpuTexture, frame.timestamp);\n }\n\n /**\n * Update configuration\n */\n updateConfig(options: Partial<FisheyeOptions>): void {\n const prevWidth = this.config.width;\n const prevHeight = this.config.height;\n this.config = this.applyDefaults({ ...this.config, ...options });\n this.updateUniforms();\n\n // Recreate output texture and readback buffer only when size actually changed\n if (this.config.width !== prevWidth || this.config.height !== prevHeight) {\n this.outputTexture?.destroy();\n this.readbackBuffers?.[0]?.destroy();\n this.readbackBuffers?.[1]?.destroy();\n this.outputTexture = null;\n this.readbackBuffers = null;\n this.readbackIndex = 0;\n this.readbackHasData = [false, false];\n this.outputTextureSize = [0, 0];\n this.readbackBytesPerRow = 0;\n this.readbackActualBytesPerRow = 0;\n this.pixelBuffer = null;\n }\n }\n\n /**\n * Clean up GPU resources\n */\n destroy(): void {\n this.inputTexture?.destroy();\n this.outputTexture?.destroy();\n this.readbackBuffers?.[0]?.destroy();\n this.readbackBuffers?.[1]?.destroy();\n this.root?.destroy();\n\n this.inputTexture = null;\n this.outputTexture = null;\n this.readbackBuffers = null;\n this.readbackIndex = 0;\n this.readbackHasData = [false, false];\n this.uniformBuffer = null;\n this.root = null;\n this.bindGroup = null;\n this.dewarpPipeline = null;\n this.readbackBytesPerRow = 0;\n this.readbackActualBytesPerRow = 0;\n this.pixelBuffer = null;\n this.inputTextureSize = [0, 0];\n this.outputTextureSize = [0, 0];\n }\n}\n","/**\n * Supported YUV pixel formats for VideoFrame creation\n */\nexport type YUVFormat = \"I420\" | \"I420A\" | \"I422\" | \"I444\" | \"NV12\";\n\n/**\n * Extended VideoFrameBufferInit with transfer support\n * (transfer is part of the spec but may not be in all TypeScript definitions)\n */\ninterface VideoFrameBufferInitExtended extends VideoFrameBufferInit {\n transfer?: ArrayBuffer[];\n}\n\n/**\n * Options for creating a VideoFrame from YUV data\n */\nexport interface CreateVideoFrameOptions {\n /**\n * YUV pixel format\n * - I420: YUV 4:2:0 planar (Y plane, U plane, V plane)\n * - I420A: YUV 4:2:0 planar with alpha\n * - I422: YUV 4:2:2 planar\n * - I444: YUV 4:4:4 planar\n * - NV12: YUV 4:2:0 semi-planar (Y plane, interleaved UV plane)\n */\n format: YUVFormat;\n\n /**\n * Width of the video frame in pixels\n */\n width: number;\n\n /**\n * Height of the video frame in pixels\n */\n height: number;\n\n /**\n * Timestamp in microseconds\n */\n timestamp: number;\n\n /**\n * Duration in microseconds (optional)\n */\n duration?: number;\n\n /**\n * Display width (optional, defaults to width)\n */\n displayWidth?: number;\n\n /**\n * Display height (optional, defaults to height)\n */\n displayHeight?: number;\n\n /**\n * Color space configuration (optional)\n */\n colorSpace?: VideoColorSpaceInit;\n\n /**\n * Transfer ownership of the buffer for zero-copy (optional)\n * If true, the input buffer will be detached after VideoFrame creation\n */\n transfer?: boolean;\n}\n\n/**\n * Create a VideoFrame from YUV binary data\n *\n * @param data - YUV binary data (ArrayBuffer, TypedArray, or DataView)\n * @param options - Configuration options including format, dimensions, and timestamp\n * @returns A new VideoFrame object\n *\n * @example\n * ```ts\n * // Create VideoFrame from I420 (YUV 4:2:0) data\n * const yuvData = new Uint8Array(width * height * 1.5); // I420 size\n * const frame = createVideoFrameFromYUV(yuvData, {\n * format: \"I420\",\n * width: 1920,\n * height: 1080,\n * timestamp: 0,\n * });\n * ```\n *\n * @example\n * ```ts\n * // Create VideoFrame from NV12 data with zero-copy transfer\n * const nv12Data = new Uint8Array(width * height * 1.5);\n * const frame = createVideoFrameFromYUV(nv12Data, {\n * format: \"NV12\",\n * width: 1920,\n * height: 1080,\n * timestamp: 0,\n * transfer: true, // Transfer buffer ownership for better performance\n * });\n * ```\n */\nexport function createVideoFrameFromYUV(\n data: BufferSource,\n options: CreateVideoFrameOptions,\n): VideoFrame {\n const {\n format,\n width,\n height,\n timestamp,\n duration,\n displayWidth,\n displayHeight,\n colorSpace,\n transfer,\n } = options;\n\n // Validate dimensions\n if (width <= 0 || height <= 0) {\n throw new Error(\"Width and height must be positive integers\");\n }\n\n // Calculate expected data size based on format\n const expectedSize = calculateYUVDataSize(format, width, height);\n const actualSize = data instanceof ArrayBuffer ? data.byteLength : data.byteLength;\n\n if (actualSize < expectedSize) {\n throw new Error(\n `Buffer too small for ${format} format. Expected at least ${expectedSize} bytes, got ${actualSize} bytes`,\n );\n }\n\n // Build VideoFrame init options\n const init: VideoFrameBufferInitExtended = {\n format,\n codedWidth: width,\n codedHeight: height,\n timestamp,\n };\n\n if (duration !== undefined) {\n init.duration = duration;\n }\n\n if (displayWidth !== undefined) {\n init.displayWidth = displayWidth;\n }\n\n if (displayHeight !== undefined) {\n init.displayHeight = displayHeight;\n }\n\n if (colorSpace !== undefined) {\n init.colorSpace = colorSpace;\n }\n\n // Handle buffer transfer for zero-copy\n if (transfer) {\n const buffer = data instanceof ArrayBuffer ? data : data.buffer;\n init.transfer = [buffer];\n }\n\n return new VideoFrame(data, init);\n}\n\n/**\n * Convert RGBA image data to YUV format (I420 by default)\n *\n * Uses ITU-R BT.601 color space conversion:\n * - Y = 0.299*R + 0.587*G + 0.114*B\n * - U = -0.169*R - 0.331*G + 0.5*B + 128\n * - V = 0.5*R - 0.419*G - 0.081*B + 128\n *\n * For I420 format:\n * - Y plane: full resolution (width * height)\n * - U plane: quarter resolution ((width/2) * (height/2))\n * - V plane: quarter resolution ((width/2) * (height/2))\n *\n * @param rgbaData - RGBA pixel data (Uint8ClampedArray from ImageData)\n * @param width - Image width in pixels\n * @param height - Image height in pixels\n * @param format - YUV format to convert to (default: \"I420\")\n * @returns YUV data as Uint8Array\n *\n * @example\n * ```ts\n * const canvas = document.createElement('canvas');\n * const ctx = canvas.getContext('2d');\n * ctx.drawImage(image, 0, 0);\n * const imageData = ctx.getImageData(0, 0, width, height);\n * const yuvData = convertRGBAtoYUV(imageData.data, width, height);\n * ```\n */\nexport function convertRGBAtoYUV(\n rgbaData: Uint8ClampedArray,\n width: number,\n height: number,\n format: YUVFormat = \"I420\",\n): Uint8Array {\n if (format !== \"I420\") {\n throw new Error(`Unsupported format: ${format}. Only I420 is currently supported.`);\n }\n\n const lumaSize = width * height;\n const chromaSize = (width / 2) * (height / 2);\n const yuvSize = lumaSize + chromaSize * 2; // Y + U + V\n const yuvData = new Uint8Array(yuvSize);\n\n // BT.601 coefficients\n const Y_R = 0.299;\n const Y_G = 0.587;\n const Y_B = 0.114;\n const U_R = -0.169;\n const U_G = -0.331;\n const U_B = 0.5;\n const V_R = 0.5;\n const V_G = -0.419;\n const V_B = -0.081;\n\n // Convert RGB to YUV and downsample chroma for I420\n const yPlane = yuvData.subarray(0, lumaSize);\n const uPlane = yuvData.subarray(lumaSize, lumaSize + chromaSize);\n const vPlane = yuvData.subarray(lumaSize + chromaSize, yuvSize);\n\n // First pass: convert to YUV and store Y plane\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const rgbaIdx = (y * width + x) * 4;\n const r = rgbaData[rgbaIdx];\n const g = rgbaData[rgbaIdx + 1];\n const b = rgbaData[rgbaIdx + 2];\n\n // Calculate Y (luma)\n const yVal = Y_R * r + Y_G * g + Y_B * b;\n yPlane[y * width + x] = Math.round(Math.max(0, Math.min(255, yVal)));\n\n // Calculate U and V for chroma downsampling\n // We'll accumulate these in the second pass\n }\n }\n\n // Second pass: downsample U and V planes (average 2x2 blocks)\n for (let y = 0; y < height / 2; y++) {\n for (let x = 0; x < width / 2; x++) {\n // Sample 2x2 block from original image\n let uSum = 0;\n let vSum = 0;\n\n for (let dy = 0; dy < 2; dy++) {\n for (let dx = 0; dx < 2; dx++) {\n const srcX = x * 2 + dx;\n const srcY = y * 2 + dy;\n const rgbaIdx = (srcY * width + srcX) * 4;\n const r = rgbaData[rgbaIdx];\n const g = rgbaData[rgbaIdx + 1];\n const b = rgbaData[rgbaIdx + 2];\n\n // Calculate U and V\n const uVal = U_R * r + U_G * g + U_B * b + 128;\n const vVal = V_R * r + V_G * g + V_B * b + 128;\n\n uSum += uVal;\n vSum += vVal;\n }\n }\n\n // Average the 2x2 block\n const uAvg = uSum / 4;\n const vAvg = vSum / 4;\n\n const chromaIdx = y * (width / 2) + x;\n uPlane[chromaIdx] = Math.round(Math.max(0, Math.min(255, uAvg)));\n vPlane[chromaIdx] = Math.round(Math.max(0, Math.min(255, vAvg)));\n }\n }\n\n return yuvData;\n}\n\n/**\n * Calculate the expected byte size for YUV data based on format and dimensions\n *\n * @param format - YUV pixel format\n * @param width - Frame width in pixels\n * @param height - Frame height in pixels\n * @returns Expected byte size\n */\nexport function calculateYUVDataSize(format: YUVFormat, width: number, height: number): number {\n const lumaSize = width * height;\n\n switch (format) {\n case \"I420\":\n case \"NV12\":\n // 4:2:0 - chroma is half resolution in both dimensions\n // Y: width * height, U: (width/2) * (height/2), V: (width/2) * (height/2)\n return lumaSize + lumaSize / 2;\n\n case \"I420A\":\n // 4:2:0 with alpha\n // Y: width * height, U: (width/2) * (height/2), V: (width/2) * (height/2), A: width * height\n return lumaSize * 2 + lumaSize / 2;\n\n case \"I422\":\n // 4:2:2 - chroma is half resolution horizontally only\n // Y: width * height, U: (width/2) * height, V: (width/2) * height\n return lumaSize * 2;\n\n case \"I444\":\n // 4:4:4 - full resolution for all planes\n // Y: width * height, U: width * height, V: width * height\n return lumaSize * 3;\n\n default:\n throw new Error(`Unsupported YUV format: ${format}`);\n }\n}\n"],"names":["FisheyeUniforms","d","fisheyeLayout","tgpu","Fisheye","options","a","x","y","p","outputW","outputH","inputW","inputH","coord","centerX","centerY","finalUv","centered","r","std","cornerNorm","fovRad","scaleRaw","scale","rScaledForFov","theta","theta2","theta4","theta6","theta8","rScaled","distortedUv","lon","lat","sinLat","uNorm","vNorm","sampleCoord","color","device","outputTexture","timestamp","readbackBuffers","writeIndex","readIndex","writeBuffer","readBuffer","commandEncoder","bufferToRead","mappedData","pixelData","srcView","row","srcOffset","dstOffset","root","width","height","size","unalignedBytesPerRow","bufferSize","frame","inputTexture","bindGroup","dewarpPipeline","outputGpuTexture","prevWidth","prevHeight","createVideoFrameFromYUV","data","format","duration","displayWidth","displayHeight","colorSpace","transfer","expectedSize","calculateYUVDataSize","actualSize","init","buffer","convertRGBAtoYUV","rgbaData","lumaSize","chromaSize","yuvSize","yuvData","Y_R","Y_G","Y_B","U_R","U_G","U_B","V_R","V_G","V_B","yPlane","uPlane","vPlane","rgbaIdx","g","b","yVal","uSum","vSum","dy","dx","srcX","uVal","vVal","uAvg","vAvg","chromaIdx"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gyeonghokim/fisheye.js",
3
3
  "private": false,
4
- "version": "1.0.2",
4
+ "version": "1.1.0",
5
5
  "description": "Modern fisheye dewarping library for the web using WebGPU",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -28,9 +28,6 @@
28
28
  "test:unit:watch": "vitest",
29
29
  "test:unit:ui": "vitest --ui",
30
30
  "test:e2e": "playwright test",
31
- "test:e2e:headed": "playwright test --headed",
32
- "test:e2e:repro": "playwright test test/dewarp-typegpu-invariant.spec.ts --config=playwright.invariant.config.ts",
33
- "test:e2e:repro:headed": "playwright test test/dewarp-typegpu-invariant.spec.ts --config=playwright.invariant.config.ts --headed",
34
31
  "test:e2e:update": "playwright test --update-snapshots",
35
32
  "test:e2e:ui": "playwright test --ui",
36
33
  "prepare": "husky",