@flighthq/surface 0.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.
Files changed (210) hide show
  1. package/dist/index.d.ts +43 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +42 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/surface.d.ts +16 -0
  6. package/dist/surface.d.ts.map +1 -0
  7. package/dist/surface.js +84 -0
  8. package/dist/surface.js.map +1 -0
  9. package/dist/surfaceAffine.d.ts +27 -0
  10. package/dist/surfaceAffine.d.ts.map +1 -0
  11. package/dist/surfaceAffine.js +159 -0
  12. package/dist/surfaceAffine.js.map +1 -0
  13. package/dist/surfaceAlpha.d.ts +27 -0
  14. package/dist/surfaceAlpha.d.ts.map +1 -0
  15. package/dist/surfaceAlpha.js +83 -0
  16. package/dist/surfaceAlpha.js.map +1 -0
  17. package/dist/surfaceBevel.d.ts +42 -0
  18. package/dist/surfaceBevel.d.ts.map +1 -0
  19. package/dist/surfaceBevel.js +102 -0
  20. package/dist/surfaceBevel.js.map +1 -0
  21. package/dist/surfaceBlur.d.ts +62 -0
  22. package/dist/surfaceBlur.d.ts.map +1 -0
  23. package/dist/surfaceBlur.js +263 -0
  24. package/dist/surfaceBlur.js.map +1 -0
  25. package/dist/surfaceChannel.d.ts +24 -0
  26. package/dist/surfaceChannel.d.ts.map +1 -0
  27. package/dist/surfaceChannel.js +126 -0
  28. package/dist/surfaceChannel.js.map +1 -0
  29. package/dist/surfaceColorMatrix.d.ts +57 -0
  30. package/dist/surfaceColorMatrix.d.ts.map +1 -0
  31. package/dist/surfaceColorMatrix.js +127 -0
  32. package/dist/surfaceColorMatrix.js.map +1 -0
  33. package/dist/surfaceCompare.d.ts +18 -0
  34. package/dist/surfaceCompare.d.ts.map +1 -0
  35. package/dist/surfaceCompare.js +64 -0
  36. package/dist/surfaceCompare.js.map +1 -0
  37. package/dist/surfaceComposite.d.ts +53 -0
  38. package/dist/surfaceComposite.d.ts.map +1 -0
  39. package/dist/surfaceComposite.js +230 -0
  40. package/dist/surfaceComposite.js.map +1 -0
  41. package/dist/surfaceConvolution.d.ts +27 -0
  42. package/dist/surfaceConvolution.d.ts.map +1 -0
  43. package/dist/surfaceConvolution.js +103 -0
  44. package/dist/surfaceConvolution.js.map +1 -0
  45. package/dist/surfaceCopy.d.ts +23 -0
  46. package/dist/surfaceCopy.d.ts.map +1 -0
  47. package/dist/surfaceCopy.js +78 -0
  48. package/dist/surfaceCopy.js.map +1 -0
  49. package/dist/surfaceCoverage.d.ts +11 -0
  50. package/dist/surfaceCoverage.d.ts.map +1 -0
  51. package/dist/surfaceCoverage.js +29 -0
  52. package/dist/surfaceCoverage.js.map +1 -0
  53. package/dist/surfaceCrop.d.ts +33 -0
  54. package/dist/surfaceCrop.d.ts.map +1 -0
  55. package/dist/surfaceCrop.js +174 -0
  56. package/dist/surfaceCrop.js.map +1 -0
  57. package/dist/surfaceDisplacement.d.ts +48 -0
  58. package/dist/surfaceDisplacement.d.ts.map +1 -0
  59. package/dist/surfaceDisplacement.js +131 -0
  60. package/dist/surfaceDisplacement.js.map +1 -0
  61. package/dist/surfaceDissolve.d.ts +21 -0
  62. package/dist/surfaceDissolve.d.ts.map +1 -0
  63. package/dist/surfaceDissolve.js +97 -0
  64. package/dist/surfaceDissolve.js.map +1 -0
  65. package/dist/surfaceDraw.d.ts +8 -0
  66. package/dist/surfaceDraw.d.ts.map +1 -0
  67. package/dist/surfaceDraw.js +14 -0
  68. package/dist/surfaceDraw.js.map +1 -0
  69. package/dist/surfaceEncode.d.ts +3 -0
  70. package/dist/surfaceEncode.d.ts.map +1 -0
  71. package/dist/surfaceEncode.js +18 -0
  72. package/dist/surfaceEncode.js.map +1 -0
  73. package/dist/surfaceFill.d.ts +12 -0
  74. package/dist/surfaceFill.d.ts.map +1 -0
  75. package/dist/surfaceFill.js +85 -0
  76. package/dist/surfaceFill.js.map +1 -0
  77. package/dist/surfaceFingerprint.d.ts +26 -0
  78. package/dist/surfaceFingerprint.d.ts.map +1 -0
  79. package/dist/surfaceFingerprint.js +99 -0
  80. package/dist/surfaceFingerprint.js.map +1 -0
  81. package/dist/surfaceFlip.d.ts +16 -0
  82. package/dist/surfaceFlip.d.ts.map +1 -0
  83. package/dist/surfaceFlip.js +98 -0
  84. package/dist/surfaceFlip.js.map +1 -0
  85. package/dist/surfaceFormat.d.ts +26 -0
  86. package/dist/surfaceFormat.d.ts.map +1 -0
  87. package/dist/surfaceFormat.js +80 -0
  88. package/dist/surfaceFormat.js.map +1 -0
  89. package/dist/surfaceFrom.d.ts +14 -0
  90. package/dist/surfaceFrom.d.ts.map +1 -0
  91. package/dist/surfaceFrom.js +87 -0
  92. package/dist/surfaceFrom.js.map +1 -0
  93. package/dist/surfaceGradient.d.ts +57 -0
  94. package/dist/surfaceGradient.d.ts.map +1 -0
  95. package/dist/surfaceGradient.js +176 -0
  96. package/dist/surfaceGradient.js.map +1 -0
  97. package/dist/surfaceGradientFill.d.ts +32 -0
  98. package/dist/surfaceGradientFill.d.ts.map +1 -0
  99. package/dist/surfaceGradientFill.js +120 -0
  100. package/dist/surfaceGradientFill.js.map +1 -0
  101. package/dist/surfaceHistogram.d.ts +20 -0
  102. package/dist/surfaceHistogram.d.ts.map +1 -0
  103. package/dist/surfaceHistogram.js +61 -0
  104. package/dist/surfaceHistogram.js.map +1 -0
  105. package/dist/surfaceImageChannel.d.ts +2 -0
  106. package/dist/surfaceImageChannel.d.ts.map +1 -0
  107. package/dist/surfaceImageChannel.js +2 -0
  108. package/dist/surfaceImageChannel.js.map +1 -0
  109. package/dist/surfaceMedian.d.ts +14 -0
  110. package/dist/surfaceMedian.d.ts.map +1 -0
  111. package/dist/surfaceMedian.js +77 -0
  112. package/dist/surfaceMedian.js.map +1 -0
  113. package/dist/surfaceMorphological.d.ts +29 -0
  114. package/dist/surfaceMorphological.d.ts.map +1 -0
  115. package/dist/surfaceMorphological.js +81 -0
  116. package/dist/surfaceMorphological.js.map +1 -0
  117. package/dist/surfaceNoise.d.ts +48 -0
  118. package/dist/surfaceNoise.d.ts.map +1 -0
  119. package/dist/surfaceNoise.js +245 -0
  120. package/dist/surfaceNoise.js.map +1 -0
  121. package/dist/surfacePaletteMap.d.ts +17 -0
  122. package/dist/surfacePaletteMap.d.ts.map +1 -0
  123. package/dist/surfacePaletteMap.js +45 -0
  124. package/dist/surfacePaletteMap.js.map +1 -0
  125. package/dist/surfacePixel.d.ts +9 -0
  126. package/dist/surfacePixel.d.ts.map +1 -0
  127. package/dist/surfacePixel.js +36 -0
  128. package/dist/surfacePixel.js.map +1 -0
  129. package/dist/surfacePixelate.d.ts +13 -0
  130. package/dist/surfacePixelate.d.ts.map +1 -0
  131. package/dist/surfacePixelate.js +61 -0
  132. package/dist/surfacePixelate.js.map +1 -0
  133. package/dist/surfaceQuery.d.ts +13 -0
  134. package/dist/surfaceQuery.d.ts.map +1 -0
  135. package/dist/surfaceQuery.js +46 -0
  136. package/dist/surfaceQuery.js.map +1 -0
  137. package/dist/surfaceRegion.d.ts +20 -0
  138. package/dist/surfaceRegion.d.ts.map +1 -0
  139. package/dist/surfaceRegion.js +28 -0
  140. package/dist/surfaceRegion.js.map +1 -0
  141. package/dist/surfaceResize.d.ts +27 -0
  142. package/dist/surfaceResize.d.ts.map +1 -0
  143. package/dist/surfaceResize.js +191 -0
  144. package/dist/surfaceResize.js.map +1 -0
  145. package/dist/surfaceRotate.d.ts +32 -0
  146. package/dist/surfaceRotate.d.ts.map +1 -0
  147. package/dist/surfaceRotate.js +143 -0
  148. package/dist/surfaceRotate.js.map +1 -0
  149. package/dist/surfaceShadow.d.ts +107 -0
  150. package/dist/surfaceShadow.d.ts.map +1 -0
  151. package/dist/surfaceShadow.js +173 -0
  152. package/dist/surfaceShadow.js.map +1 -0
  153. package/dist/surfaceSharpen.d.ts +25 -0
  154. package/dist/surfaceSharpen.d.ts.map +1 -0
  155. package/dist/surfaceSharpen.js +47 -0
  156. package/dist/surfaceSharpen.js.map +1 -0
  157. package/dist/surfaceTone.d.ts +28 -0
  158. package/dist/surfaceTone.d.ts.map +1 -0
  159. package/dist/surfaceTone.js +69 -0
  160. package/dist/surfaceTone.js.map +1 -0
  161. package/dist/surfaceTransform.d.ts +40 -0
  162. package/dist/surfaceTransform.d.ts.map +1 -0
  163. package/dist/surfaceTransform.js +163 -0
  164. package/dist/surfaceTransform.js.map +1 -0
  165. package/dist/surfaceWarp.d.ts +48 -0
  166. package/dist/surfaceWarp.d.ts.map +1 -0
  167. package/dist/surfaceWarp.js +284 -0
  168. package/dist/surfaceWarp.js.map +1 -0
  169. package/package.json +39 -0
  170. package/src/surface.test.ts +78 -0
  171. package/src/surfaceAffine.test.ts +98 -0
  172. package/src/surfaceAlpha.test.ts +113 -0
  173. package/src/surfaceBevel.test.ts +67 -0
  174. package/src/surfaceBlur.test.ts +240 -0
  175. package/src/surfaceChannel.test.ts +97 -0
  176. package/src/surfaceColorMatrix.test.ts +220 -0
  177. package/src/surfaceCompare.test.ts +92 -0
  178. package/src/surfaceComposite.test.ts +256 -0
  179. package/src/surfaceConvolution.test.ts +136 -0
  180. package/src/surfaceCopy.test.ts +54 -0
  181. package/src/surfaceCoverage.test.ts +39 -0
  182. package/src/surfaceCrop.test.ts +116 -0
  183. package/src/surfaceDisplacement.test.ts +142 -0
  184. package/src/surfaceDissolve.test.ts +99 -0
  185. package/src/surfaceDraw.test.ts +38 -0
  186. package/src/surfaceEncode.test.ts +18 -0
  187. package/src/surfaceFill.test.ts +89 -0
  188. package/src/surfaceFingerprint.test.ts +103 -0
  189. package/src/surfaceFlip.test.ts +59 -0
  190. package/src/surfaceFormat.test.ts +124 -0
  191. package/src/surfaceFrom.test.ts +62 -0
  192. package/src/surfaceGradient.test.ts +91 -0
  193. package/src/surfaceGradientFill.test.ts +107 -0
  194. package/src/surfaceHistogram.test.ts +70 -0
  195. package/src/surfaceImageChannel.test.ts +19 -0
  196. package/src/surfaceMedian.test.ts +45 -0
  197. package/src/surfaceMorphological.test.ts +75 -0
  198. package/src/surfaceNoise.test.ts +169 -0
  199. package/src/surfacePaletteMap.test.ts +55 -0
  200. package/src/surfacePixel.test.ts +99 -0
  201. package/src/surfacePixelate.test.ts +46 -0
  202. package/src/surfaceQuery.test.ts +48 -0
  203. package/src/surfaceRegion.test.ts +34 -0
  204. package/src/surfaceResize.test.ts +112 -0
  205. package/src/surfaceRotate.test.ts +119 -0
  206. package/src/surfaceShadow.test.ts +165 -0
  207. package/src/surfaceSharpen.test.ts +50 -0
  208. package/src/surfaceTone.test.ts +104 -0
  209. package/src/surfaceTransform.test.ts +134 -0
  210. package/src/surfaceWarp.test.ts +124 -0
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Warps `source` by sampling each output pixel from a displaced source position,
3
+ * writing into `out`. The displacement for pixel (px, py) is read from `map` at
4
+ * the aligned position and scaled:
5
+ *
6
+ * dx = (mapValueX / 255 - 0.5) * scaleX
7
+ * dy = (mapValueY / 255 - 0.5) * scaleY
8
+ *
9
+ * A map value of 128 is approximately neutral (< 0.2px shift). Sampling uses
10
+ * bilinear interpolation.
11
+ *
12
+ * When `edgeMode` is set, it controls out-of-bounds behaviour using the standard
13
+ * `SurfaceEdgeMode` values (`'clamp'`, `'wrap'`, `'mirror'`, `'transparent'`).
14
+ * Otherwise `mode` is used: `'wrap'` tiles, `'clamp'` holds the edge, `'ignore'`
15
+ * keeps the undisplaced source pixel, and `'color'` fills with `fillColor`.
16
+ *
17
+ * `out` must be at least `source.width * source.height * 4` bytes and must NOT
18
+ * alias `source.surface.data` — output pixels read arbitrary source positions.
19
+ */
20
+ export function displaceSurface(out, source, options) {
21
+ const w = source.width;
22
+ const h = source.height;
23
+ const map = options.map;
24
+ const componentX = options.componentX ?? 0;
25
+ const componentY = options.componentY ?? 1;
26
+ const scaleX = options.scaleX ?? 0;
27
+ const scaleY = options.scaleY ?? 0;
28
+ const edgeMode = options.edgeMode;
29
+ const mode = options.mode ?? 'wrap';
30
+ const fillColor = options.fillColor ?? 0;
31
+ const fillR = (fillColor >>> 24) & 0xff;
32
+ const fillG = (fillColor >> 16) & 0xff;
33
+ const fillB = (fillColor >> 8) & 0xff;
34
+ const fillA = fillColor & 0xff;
35
+ for (let py = 0; py < h; py++) {
36
+ for (let px = 0; px < w; px++) {
37
+ const di = (py * w + px) * 4;
38
+ const mapVx = sampleMapChannel(map, px, py, componentX);
39
+ const mapVy = sampleMapChannel(map, px, py, componentY);
40
+ const rawSampleX = px + (mapVx / 255 - 0.5) * scaleX;
41
+ const rawSampleY = py + (mapVy / 255 - 0.5) * scaleY;
42
+ let sampleX = rawSampleX;
43
+ let sampleY = rawSampleY;
44
+ if (rawSampleX < 0 || rawSampleX >= w || rawSampleY < 0 || rawSampleY >= h) {
45
+ if (edgeMode !== undefined) {
46
+ const rx = resolveDisplacementEdge(rawSampleX, w, edgeMode);
47
+ const ry = resolveDisplacementEdge(rawSampleY, h, edgeMode);
48
+ if (rx === null || ry === null) {
49
+ out[di] = 0;
50
+ out[di + 1] = 0;
51
+ out[di + 2] = 0;
52
+ out[di + 3] = 0;
53
+ continue;
54
+ }
55
+ sampleX = rx;
56
+ sampleY = ry;
57
+ }
58
+ else if (mode === 'wrap') {
59
+ sampleX = ((rawSampleX % w) + w) % w;
60
+ sampleY = ((rawSampleY % h) + h) % h;
61
+ }
62
+ else if (mode === 'clamp') {
63
+ sampleX = Math.max(0, Math.min(w - 1, rawSampleX));
64
+ sampleY = Math.max(0, Math.min(h - 1, rawSampleY));
65
+ }
66
+ else if (mode === 'ignore') {
67
+ sampleX = px;
68
+ sampleY = py;
69
+ }
70
+ else {
71
+ out[di] = fillR;
72
+ out[di + 1] = fillG;
73
+ out[di + 2] = fillB;
74
+ out[di + 3] = fillA;
75
+ continue;
76
+ }
77
+ }
78
+ const x0 = Math.floor(sampleX);
79
+ const y0 = Math.floor(sampleY);
80
+ const tx = sampleX - x0;
81
+ const ty = sampleY - y0;
82
+ const sStride = source.surface.width;
83
+ const sData = source.surface.data;
84
+ const x0c = source.x + Math.max(0, Math.min(w - 1, x0));
85
+ const x1c = source.x + Math.max(0, Math.min(w - 1, x0 + 1));
86
+ const y0c = source.y + Math.max(0, Math.min(h - 1, y0));
87
+ const y1c = source.y + Math.max(0, Math.min(h - 1, y0 + 1));
88
+ if (x0c < 0 || x0c >= sData.length / 4 || y0c < 0) {
89
+ out[di] = 0;
90
+ out[di + 1] = 0;
91
+ out[di + 2] = 0;
92
+ out[di + 3] = 0;
93
+ continue;
94
+ }
95
+ const i00 = (y0c * sStride + x0c) * 4;
96
+ const i10 = (y0c * sStride + x1c) * 4;
97
+ const i01 = (y1c * sStride + x0c) * 4;
98
+ const i11 = (y1c * sStride + x1c) * 4;
99
+ for (let c = 0; c < 4; c++) {
100
+ const top = sData[i00 + c] * (1 - tx) + sData[i10 + c] * tx;
101
+ const bottom = sData[i01 + c] * (1 - tx) + sData[i11 + c] * tx;
102
+ out[di + c] = Math.round(top * (1 - ty) + bottom * ty);
103
+ }
104
+ }
105
+ }
106
+ }
107
+ function resolveDisplacementEdge(v, size, mode) {
108
+ if (v >= 0 && v < size)
109
+ return v;
110
+ switch (mode) {
111
+ case 'clamp':
112
+ return Math.max(0, Math.min(size - 1, v));
113
+ case 'wrap':
114
+ return ((v % size) + size) % size;
115
+ case 'mirror': {
116
+ const period = 2 * size;
117
+ const wrapped = ((v % period) + period) % period;
118
+ return wrapped < size ? wrapped : period - 1 - wrapped;
119
+ }
120
+ default:
121
+ return null;
122
+ }
123
+ }
124
+ function sampleMapChannel(map, px, py, component) {
125
+ const mx = map.x + px;
126
+ const my = map.y + py;
127
+ if (mx < 0 || mx >= map.surface.width || my < 0 || my >= map.surface.height)
128
+ return 128;
129
+ return map.surface.data[(my * map.surface.width + mx) * 4 + component];
130
+ }
131
+ //# sourceMappingURL=surfaceDisplacement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceDisplacement.js","sourceRoot":"","sources":["../src/surfaceDisplacement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { SurfaceRegion } from '@flighthq/types';
2
+ /**
3
+ * Transitions `dest` toward `source` one batch of pixels at a time, in a
4
+ * deterministic pseudo-random order — the classic dissolve wipe. Each call
5
+ * dissolves up to `pixelCount` not-yet-dissolved pixels and returns the next
6
+ * seed; pass that value back on the following call to continue the wipe without
7
+ * revisiting any pixel. Start a fresh dissolve with `seed = 0`. Once every pixel
8
+ * has dissolved the returned seed is terminal: further calls with it are no-ops.
9
+ *
10
+ * `seed` is an opaque progress cursor over a fixed permutation of the region's
11
+ * pixels, not a color or coordinate. The dissolve order is fully deterministic
12
+ * in the region's dimensions.
13
+ *
14
+ * When `source` is the same region as `dest` (same surface and identical
15
+ * bounds), dissolved pixels are set to `fillColor` (packed `0xRRGGBBAA`);
16
+ * otherwise each is copied from the matching pixel in
17
+ * `source` and `fillColor` is ignored. Pixels that fall outside the surface are
18
+ * clipped but still consume a step of the sequence.
19
+ */
20
+ export declare function dissolveSurfacePixels(dest: Readonly<SurfaceRegion>, source: Readonly<SurfaceRegion>, seed: number, pixelCount: number, fillColor?: number): number;
21
+ //# sourceMappingURL=surfaceDissolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceDissolve.d.ts","sourceRoot":"","sources":["../src/surfaceDissolve.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,CAAC,aAAa,CAAC,EAC7B,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,MAAU,GACpB,MAAM,CAkER"}
@@ -0,0 +1,97 @@
1
+ import { invalidateImageResource } from '@flighthq/image';
2
+ /**
3
+ * Transitions `dest` toward `source` one batch of pixels at a time, in a
4
+ * deterministic pseudo-random order — the classic dissolve wipe. Each call
5
+ * dissolves up to `pixelCount` not-yet-dissolved pixels and returns the next
6
+ * seed; pass that value back on the following call to continue the wipe without
7
+ * revisiting any pixel. Start a fresh dissolve with `seed = 0`. Once every pixel
8
+ * has dissolved the returned seed is terminal: further calls with it are no-ops.
9
+ *
10
+ * `seed` is an opaque progress cursor over a fixed permutation of the region's
11
+ * pixels, not a color or coordinate. The dissolve order is fully deterministic
12
+ * in the region's dimensions.
13
+ *
14
+ * When `source` is the same region as `dest` (same surface and identical
15
+ * bounds), dissolved pixels are set to `fillColor` (packed `0xRRGGBBAA`);
16
+ * otherwise each is copied from the matching pixel in
17
+ * `source` and `fillColor` is ignored. Pixels that fall outside the surface are
18
+ * clipped but still consume a step of the sequence.
19
+ */
20
+ export function dissolveSurfacePixels(dest, source, seed, pixelCount, fillColor = 0) {
21
+ const width = dest.width;
22
+ const height = dest.height;
23
+ const total = width * height;
24
+ if (total <= 0)
25
+ return seed;
26
+ let bits = 0;
27
+ while (1 << bits < total)
28
+ bits++;
29
+ const period = 1 << bits;
30
+ const mask = period - 1;
31
+ let cursor = seed < 0 ? 0 : seed > period ? period : seed | 0;
32
+ if (pixelCount <= 0)
33
+ return cursor;
34
+ const toFill = source.surface === dest.surface &&
35
+ source.x === dest.x &&
36
+ source.y === dest.y &&
37
+ source.width === dest.width &&
38
+ source.height === dest.height;
39
+ const fillR = (fillColor >>> 24) & 0xff;
40
+ const fillG = (fillColor >> 16) & 0xff;
41
+ const fillB = (fillColor >> 8) & 0xff;
42
+ const fillA = fillColor & 0xff;
43
+ const destData = dest.surface.data;
44
+ const destStride = dest.surface.width;
45
+ const destSurfaceHeight = dest.surface.height;
46
+ const sourceData = source.surface.data;
47
+ const sourceStride = source.surface.width;
48
+ const sourceSurfaceHeight = source.surface.height;
49
+ let dissolved = 0;
50
+ while (dissolved < pixelCount && cursor < period) {
51
+ const pixelIndex = permutePixelIndex(cursor, bits, mask);
52
+ cursor++;
53
+ if (pixelIndex >= total)
54
+ continue;
55
+ dissolved++;
56
+ const px = pixelIndex % width;
57
+ const py = (pixelIndex / width) | 0;
58
+ const dx = dest.x + px;
59
+ const dy = dest.y + py;
60
+ if (dx < 0 || dx >= destStride || dy < 0 || dy >= destSurfaceHeight)
61
+ continue;
62
+ const di = (dy * destStride + dx) * 4;
63
+ if (toFill) {
64
+ destData[di] = fillR;
65
+ destData[di + 1] = fillG;
66
+ destData[di + 2] = fillB;
67
+ destData[di + 3] = fillA;
68
+ continue;
69
+ }
70
+ const sx = source.x + px;
71
+ const sy = source.y + py;
72
+ if (sx < 0 || sx >= sourceStride || sy < 0 || sy >= sourceSurfaceHeight)
73
+ continue;
74
+ const si = (sy * sourceStride + sx) * 4;
75
+ destData[di] = sourceData[si];
76
+ destData[di + 1] = sourceData[si + 1];
77
+ destData[di + 2] = sourceData[si + 2];
78
+ destData[di + 3] = sourceData[si + 3];
79
+ }
80
+ invalidateImageResource(dest.surface);
81
+ return cursor;
82
+ }
83
+ // Bijection on [0, 2^bits): a composition of multiply-by-odd (invertible mod
84
+ // 2^bits), xor-shift, and xor-constant, each individually reversible. Walking
85
+ // `cursor` across the full period therefore visits every index in [0, total)
86
+ // exactly once (cycle-walking skips the indices >= total), which is what
87
+ // guarantees a dissolve eventually covers the whole region without repeats.
88
+ function permutePixelIndex(cursor, bits, mask) {
89
+ let v = cursor & mask;
90
+ const shift = bits > 1 ? bits >> 1 : 1;
91
+ v = Math.imul(v, 0x9e3779b1) & mask;
92
+ v ^= v >>> shift;
93
+ v = Math.imul(v, 0x85ebca77) & mask;
94
+ v ^= 0x27d4eb2f & mask;
95
+ return v & mask;
96
+ }
97
+ //# sourceMappingURL=surfaceDissolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceDissolve.js","sourceRoot":"","sources":["../src/surfaceDissolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { SurfaceRegion } from '@flighthq/types';
2
+ /**
3
+ * Draws the `source` region onto a 2D canvas at `(x, y)` via `putImageData`.
4
+ * A region with a zero dimension is a no-op (`ImageData` requires positive
5
+ * dimensions).
6
+ */
7
+ export declare function drawSurface(dest: HTMLCanvasElement, source: Readonly<SurfaceRegion>, x: number, y: number): void;
8
+ //# sourceMappingURL=surfaceDraw.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceDraw.d.ts","sourceRoot":"","sources":["../src/surfaceDraw.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIrD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAKhH"}
@@ -0,0 +1,14 @@
1
+ import { extractSurfacePixels } from './surfaceComposite';
2
+ /**
3
+ * Draws the `source` region onto a 2D canvas at `(x, y)` via `putImageData`.
4
+ * A region with a zero dimension is a no-op (`ImageData` requires positive
5
+ * dimensions).
6
+ */
7
+ export function drawSurface(dest, source, x, y) {
8
+ if (source.width <= 0 || source.height <= 0)
9
+ return;
10
+ const domImageData = new globalThis.ImageData(source.width, source.height);
11
+ extractSurfacePixels(domImageData.data, source);
12
+ dest.getContext('2d').putImageData(domImageData, x, y);
13
+ }
14
+ //# sourceMappingURL=surfaceDraw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceDraw.js","sourceRoot":"","sources":["../src/surfaceDraw.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAuB,EAAE,MAA+B,EAAE,CAAS,EAAE,CAAS;IACxG,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO;IACpD,MAAM,YAAY,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3E,oBAAoB,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAE,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ImageFormat, Surface } from '@flighthq/types';
2
+ export declare function encodeSurface(source: Readonly<Surface>, format?: ImageFormat, quality?: number): Uint8Array;
3
+ //# sourceMappingURL=surfaceEncode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceEncode.d.ts","sourceRoot":"","sources":["../src/surfaceEncode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE5D,wBAAgB,aAAa,CAC3B,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,EACzB,MAAM,GAAE,WAAmB,EAC3B,OAAO,GAAE,MAAY,GACpB,UAAU,CAgBZ"}
@@ -0,0 +1,18 @@
1
+ export function encodeSurface(source, format = 'png', quality = 0.9) {
2
+ const canvas = document.createElement('canvas');
3
+ canvas.width = source.width;
4
+ canvas.height = source.height;
5
+ const domImageData = new globalThis.ImageData(source.width, source.height);
6
+ domImageData.data.set(source.data);
7
+ canvas.getContext('2d').putImageData(domImageData, 0, 0);
8
+ const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png';
9
+ const dataUrl = canvas.toDataURL(mimeType, quality);
10
+ const base64 = dataUrl.slice(dataUrl.indexOf(',') + 1);
11
+ const binary = atob(base64);
12
+ const bytes = new Uint8Array(binary.length);
13
+ for (let i = 0; i < binary.length; i++) {
14
+ bytes[i] = binary.charCodeAt(i);
15
+ }
16
+ return bytes;
17
+ }
18
+ //# sourceMappingURL=surfaceEncode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceEncode.js","sourceRoot":"","sources":["../src/surfaceEncode.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAC3B,MAAyB,EACzB,SAAsB,KAAK,EAC3B,UAAkB,GAAG;IAErB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9B,MAAM,YAAY,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3E,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAE,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;IAChE,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Surface, SurfaceRegion } from '@flighthq/types';
2
+ /**
3
+ * Fills the rectangular `dest` region with a packed RGBA `color`. Pixels of the
4
+ * region that fall outside the surface are skipped.
5
+ */
6
+ export declare function fillSurfaceRectangle(dest: Readonly<SurfaceRegion>, color: number): void;
7
+ /**
8
+ * Flood-fills a connected region of `out` starting at `(x, y)`. Uses a
9
+ * module-level visited buffer that grows as needed and is reused across calls.
10
+ */
11
+ export declare function floodFillSurface(out: Surface, x: number, y: number, color: number): void;
12
+ //# sourceMappingURL=surfaceFill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceFill.d.ts","sourceRoot":"","sources":["../src/surfaceFill.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAI9D;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAmBvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAuDxF"}
@@ -0,0 +1,85 @@
1
+ import { invalidateImageResource } from '@flighthq/image';
2
+ let _floodFillVisited = null;
3
+ /**
4
+ * Fills the rectangular `dest` region with a packed RGBA `color`. Pixels of the
5
+ * region that fall outside the surface are skipped.
6
+ */
7
+ export function fillSurfaceRectangle(dest, color) {
8
+ const r = (color >>> 24) & 0xff;
9
+ const g = (color >> 16) & 0xff;
10
+ const b = (color >> 8) & 0xff;
11
+ const a = color & 0xff;
12
+ for (let py = 0; py < dest.height; py++) {
13
+ const y = dest.y + py;
14
+ if (y < 0 || y >= dest.surface.height)
15
+ continue;
16
+ for (let px = 0; px < dest.width; px++) {
17
+ const x = dest.x + px;
18
+ if (x < 0 || x >= dest.surface.width)
19
+ continue;
20
+ const i = (y * dest.surface.width + x) * 4;
21
+ dest.surface.data[i] = r;
22
+ dest.surface.data[i + 1] = g;
23
+ dest.surface.data[i + 2] = b;
24
+ dest.surface.data[i + 3] = a;
25
+ }
26
+ }
27
+ invalidateImageResource(dest.surface);
28
+ }
29
+ /**
30
+ * Flood-fills a connected region of `out` starting at `(x, y)`. Uses a
31
+ * module-level visited buffer that grows as needed and is reused across calls.
32
+ */
33
+ export function floodFillSurface(out, x, y, color) {
34
+ if (x < 0 || x >= out.width || y < 0 || y >= out.height)
35
+ return;
36
+ const fillR = (color >>> 24) & 0xff;
37
+ const fillG = (color >> 16) & 0xff;
38
+ const fillB = (color >> 8) & 0xff;
39
+ const fillA = color & 0xff;
40
+ const targetI = (y * out.width + x) * 4;
41
+ const targetR = out.data[targetI];
42
+ const targetG = out.data[targetI + 1];
43
+ const targetB = out.data[targetI + 2];
44
+ const targetA = out.data[targetI + 3];
45
+ if (targetR === fillR && targetG === fillG && targetB === fillB && targetA === fillA)
46
+ return;
47
+ const needed = out.width * out.height;
48
+ if (_floodFillVisited === null || _floodFillVisited.length < needed) {
49
+ _floodFillVisited = new Uint8Array(needed);
50
+ }
51
+ else {
52
+ _floodFillVisited.fill(0, 0, needed);
53
+ }
54
+ const visited = _floodFillVisited;
55
+ const stack = [x + y * out.width];
56
+ while (stack.length > 0) {
57
+ const idx = stack.pop();
58
+ if (visited[idx])
59
+ continue;
60
+ visited[idx] = 1;
61
+ const px = idx % out.width;
62
+ const py = Math.floor(idx / out.width);
63
+ const i = idx * 4;
64
+ if (out.data[i] !== targetR ||
65
+ out.data[i + 1] !== targetG ||
66
+ out.data[i + 2] !== targetB ||
67
+ out.data[i + 3] !== targetA) {
68
+ continue;
69
+ }
70
+ out.data[i] = fillR;
71
+ out.data[i + 1] = fillG;
72
+ out.data[i + 2] = fillB;
73
+ out.data[i + 3] = fillA;
74
+ if (px > 0)
75
+ stack.push(idx - 1);
76
+ if (px < out.width - 1)
77
+ stack.push(idx + 1);
78
+ if (py > 0)
79
+ stack.push(idx - out.width);
80
+ if (py < out.height - 1)
81
+ stack.push(idx + out.width);
82
+ }
83
+ invalidateImageResource(out);
84
+ }
85
+ //# sourceMappingURL=surfaceFill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceFill.js","sourceRoot":"","sources":["../src/surfaceFill.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { Surface, SurfaceFingerprint } from '@flighthq/types';
2
+ /**
3
+ * The mean absolute per-channel difference (0..255) between two fingerprints of the same gridSize.
4
+ * Threshold it to gate regressions: ~0 is identical, small single digits are antialiasing/noise,
5
+ * large values mean a real visual change. Throws if the gridSizes differ — they are not comparable.
6
+ */
7
+ export declare function compareSurfaceFingerprints(a: Readonly<SurfaceFingerprint>, b: Readonly<SurfaceFingerprint>): number;
8
+ /**
9
+ * Reduces a surface to a gridSize × gridSize grid of averaged RGB cells. Each cell averages the RGB
10
+ * of the source pixels that fall in it (alpha is ignored — visible colour is what regressions show
11
+ * up in). gridSize controls fidelity vs. text size; the default of 16 catches layout/colour shifts
12
+ * while staying ~1 KB as text. An empty surface yields an all-zero grid.
13
+ */
14
+ export declare function createSurfaceFingerprint(source: Readonly<Surface>, gridSize?: number): SurfaceFingerprint;
15
+ /**
16
+ * Serializes a fingerprint to a compact, git-diffable text line: `<gridSize>:<hex cells>`. This is
17
+ * what gets committed as a regression baseline instead of a screenshot.
18
+ */
19
+ export declare function formatSurfaceFingerprint(fingerprint: Readonly<SurfaceFingerprint>): string;
20
+ /**
21
+ * Parses the text form produced by formatSurfaceFingerprint. Returns `null` for malformed input
22
+ * (wrong shape, odd hex length, or a cell count that is not gridSize × gridSize × 3) so callers can
23
+ * treat a corrupt baseline as "no baseline" rather than crashing.
24
+ */
25
+ export declare function parseSurfaceFingerprint(text: string): SurfaceFingerprint | null;
26
+ //# sourceMappingURL=surfaceFingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceFingerprint.d.ts","sourceRoot":"","sources":["../src/surfaceFingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAQnE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAQnH;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAE,MAAW,GAAG,kBAAkB,CAkC7G;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAO1F;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAiB/E"}
@@ -0,0 +1,99 @@
1
+ // A coarse downscaled RGB fingerprint: the surface is averaged into a gridSize × gridSize grid, so
2
+ // antialiasing jitter that breaks an exact hash washes out while gross changes still register. The
3
+ // committed regression baseline is the hex-encoded form (formatSurfaceFingerprint), not a PNG.
4
+ const HEX = '0123456789abcdef';
5
+ /**
6
+ * The mean absolute per-channel difference (0..255) between two fingerprints of the same gridSize.
7
+ * Threshold it to gate regressions: ~0 is identical, small single digits are antialiasing/noise,
8
+ * large values mean a real visual change. Throws if the gridSizes differ — they are not comparable.
9
+ */
10
+ export function compareSurfaceFingerprints(a, b) {
11
+ if (a.gridSize !== b.gridSize) {
12
+ throw new Error(`compareSurfaceFingerprints: gridSize mismatch (${a.gridSize} vs ${b.gridSize})`);
13
+ }
14
+ if (a.cells.length === 0)
15
+ return 0;
16
+ let sum = 0;
17
+ for (let i = 0; i < a.cells.length; i++)
18
+ sum += Math.abs(a.cells[i] - b.cells[i]);
19
+ return sum / a.cells.length;
20
+ }
21
+ /**
22
+ * Reduces a surface to a gridSize × gridSize grid of averaged RGB cells. Each cell averages the RGB
23
+ * of the source pixels that fall in it (alpha is ignored — visible colour is what regressions show
24
+ * up in). gridSize controls fidelity vs. text size; the default of 16 catches layout/colour shifts
25
+ * while staying ~1 KB as text. An empty surface yields an all-zero grid.
26
+ */
27
+ export function createSurfaceFingerprint(source, gridSize = 16) {
28
+ if (gridSize < 1)
29
+ throw new Error(`createSurfaceFingerprint: gridSize must be >= 1 (got ${gridSize})`);
30
+ const cells = new Uint8Array(gridSize * gridSize * 3);
31
+ const { width, height, data } = source;
32
+ if (width === 0 || height === 0)
33
+ return { gridSize, cells };
34
+ for (let cy = 0; cy < gridSize; cy++) {
35
+ const y0 = Math.floor((cy * height) / gridSize);
36
+ const y1 = Math.max(y0 + 1, Math.floor(((cy + 1) * height) / gridSize));
37
+ for (let cx = 0; cx < gridSize; cx++) {
38
+ const x0 = Math.floor((cx * width) / gridSize);
39
+ const x1 = Math.max(x0 + 1, Math.floor(((cx + 1) * width) / gridSize));
40
+ let sumR = 0;
41
+ let sumG = 0;
42
+ let sumB = 0;
43
+ let count = 0;
44
+ for (let y = y0; y < y1 && y < height; y++) {
45
+ let i = (y * width + x0) * 4;
46
+ for (let x = x0; x < x1 && x < width; x++) {
47
+ sumR += data[i];
48
+ sumG += data[i + 1];
49
+ sumB += data[i + 2];
50
+ count++;
51
+ i += 4;
52
+ }
53
+ }
54
+ const c = (cy * gridSize + cx) * 3;
55
+ cells[c] = count === 0 ? 0 : Math.round(sumR / count);
56
+ cells[c + 1] = count === 0 ? 0 : Math.round(sumG / count);
57
+ cells[c + 2] = count === 0 ? 0 : Math.round(sumB / count);
58
+ }
59
+ }
60
+ return { gridSize, cells };
61
+ }
62
+ /**
63
+ * Serializes a fingerprint to a compact, git-diffable text line: `<gridSize>:<hex cells>`. This is
64
+ * what gets committed as a regression baseline instead of a screenshot.
65
+ */
66
+ export function formatSurfaceFingerprint(fingerprint) {
67
+ const cells = fingerprint.cells;
68
+ let hex = '';
69
+ for (let i = 0; i < cells.length; i++) {
70
+ hex += HEX[(cells[i] >> 4) & 0xf] + HEX[cells[i] & 0xf];
71
+ }
72
+ return `${fingerprint.gridSize}:${hex}`;
73
+ }
74
+ /**
75
+ * Parses the text form produced by formatSurfaceFingerprint. Returns `null` for malformed input
76
+ * (wrong shape, odd hex length, or a cell count that is not gridSize × gridSize × 3) so callers can
77
+ * treat a corrupt baseline as "no baseline" rather than crashing.
78
+ */
79
+ export function parseSurfaceFingerprint(text) {
80
+ const colon = text.indexOf(':');
81
+ if (colon <= 0)
82
+ return null;
83
+ const gridSize = Number.parseInt(text.slice(0, colon), 10);
84
+ if (!Number.isInteger(gridSize) || gridSize < 1)
85
+ return null;
86
+ const hex = text.slice(colon + 1);
87
+ if (hex.length !== gridSize * gridSize * 3 * 2)
88
+ return null;
89
+ const cells = new Uint8Array(hex.length / 2);
90
+ for (let i = 0; i < cells.length; i++) {
91
+ const hi = HEX.indexOf(hex[i * 2]);
92
+ const lo = HEX.indexOf(hex[i * 2 + 1]);
93
+ if (hi < 0 || lo < 0)
94
+ return null;
95
+ cells[i] = (hi << 4) | lo;
96
+ }
97
+ return { gridSize, cells };
98
+ }
99
+ //# sourceMappingURL=surfaceFingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceFingerprint.js","sourceRoot":"","sources":["../src/surfaceFingerprint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { SurfaceRegion } from '@flighthq/types';
2
+ /**
3
+ * Mirrors the `source` region left-to-right into the `dest` region. The mirror
4
+ * size is the overlap of the two regions. Safe to pass the same surface and
5
+ * region in `dest` and `source` for an in-place flip — when aliased, columns
6
+ * are swapped in pairs; otherwise `dest` and `source` must not overlap.
7
+ */
8
+ export declare function flipSurfaceHorizontal(dest: Readonly<SurfaceRegion>, source: Readonly<SurfaceRegion>): void;
9
+ /**
10
+ * Mirrors the `source` region top-to-bottom into the `dest` region. The mirror
11
+ * size is the overlap of the two regions. Safe to pass the same surface and
12
+ * region in `dest` and `source` for an in-place flip — when aliased, rows are
13
+ * swapped in pairs; otherwise `dest` and `source` must not overlap.
14
+ */
15
+ export declare function flipSurfaceVertical(dest: Readonly<SurfaceRegion>, source: Readonly<SurfaceRegion>): void;
16
+ //# sourceMappingURL=surfaceFlip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceFlip.d.ts","sourceRoot":"","sources":["../src/surfaceFlip.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAqB1G;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAqBxG"}