@flighthq/bitmap-wasm 0.4.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/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@flighthq/bitmap-wasm",
3
+ "version": "0.4.0",
4
+ "author": "Joshua Granick and other contributors",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/flighthq/flight-rs.git",
9
+ "directory": "packages/bitmap-wasm"
10
+ },
11
+ "type": "module",
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src/**/*.test.ts",
26
+ "!dist/**/*.test.js",
27
+ "!dist/**/*.test.d.ts",
28
+ "!dist/**/*.test.js.map",
29
+ "!dist/**/*.test.d.ts.map"
30
+ ],
31
+ "scripts": {
32
+ "wasm": "npm run wasm --prefix ../..",
33
+ "build": "npm run wasm && tsc -b && tsx ./scripts/copy-wasm-glue.ts",
34
+ "clean": "tsc -b --clean",
35
+ "test": "vitest run --config vitest.config.ts",
36
+ "test:watch": "vitest --watch --config vitest.config.ts",
37
+ "prepack": "npm run clean && npm run clean:dist && npm run build",
38
+ "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
+ },
40
+ "dependencies": {
41
+ "@flighthq/bitmap": "^0.4.0",
42
+ "@flighthq/types": "^0.4.0"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "^5.3.0"
46
+ },
47
+ "description": "wasm-backed drop-in for @flighthq/bitmap: identical API, bulk pixel ops run in Rust",
48
+ "sideEffects": false,
49
+ "flightWasmSubstitute": {
50
+ "authoritativePackage": "@flighthq/bitmap",
51
+ "crate": "flighthq-bitmap",
52
+ "kind": "rust-wasm",
53
+ "rustBacked": true,
54
+ "supportLevel": "experimental"
55
+ }
56
+ }
@@ -0,0 +1,210 @@
1
+ import { createBitmap, createBitmapRegion } from '@flighthq/bitmap';
2
+ import * as reference from '@flighthq/bitmap';
3
+ import type { Bitmap, BitmapRegion } from '@flighthq/types';
4
+
5
+ import * as rs from './bitmapWasm';
6
+
7
+ function paintBitmap(width: number, height: number): Bitmap {
8
+ const bitmap = createBitmap(width, height, 0);
9
+ for (let index = 0; index < width * height; index += 1) {
10
+ bitmap.data[index * 4] = (index * 37 + 11) & 0xff;
11
+ bitmap.data[index * 4 + 1] = (index * 53 + 7) & 0xff;
12
+ bitmap.data[index * 4 + 2] = (index * 97 + 3) & 0xff;
13
+ bitmap.data[index * 4 + 3] = (index * 17 + 1) & 0xff;
14
+ }
15
+ return bitmap;
16
+ }
17
+
18
+ function fullRegion(bitmap: Bitmap): BitmapRegion {
19
+ return createBitmapRegion(bitmap, 0, 0, bitmap.width, bitmap.height);
20
+ }
21
+
22
+ function cloneBitmap(source: Bitmap): Bitmap {
23
+ const clone = createBitmap(source.width, source.height, 0);
24
+ clone.data.set(source.data);
25
+ return clone;
26
+ }
27
+
28
+ describe('generated wasm boundary', () => {
29
+ it('initializes synchronously and idempotently', () => {
30
+ rs.initBitmapWasm();
31
+ rs.initBitmapWasm();
32
+ });
33
+
34
+ it('matches all selected color-matrix builders and composition', () => {
35
+ const actual = Array.from({ length: 20 }, () => 0);
36
+ const expected = Array.from({ length: 20 }, () => 0);
37
+ const cases: Array<[(out: number[]) => void, (out: number[]) => void]> = [
38
+ [
39
+ (out) => rs.buildBitmapBrightnessColorMatrix(out, 1.25),
40
+ (out) => reference.buildBitmapBrightnessColorMatrix(out, 1.25),
41
+ ],
42
+ [
43
+ (out) => rs.buildBitmapContrastColorMatrix(out, 0.75),
44
+ (out) => reference.buildBitmapContrastColorMatrix(out, 0.75),
45
+ ],
46
+ [rs.buildBitmapGrayscaleColorMatrix, reference.buildBitmapGrayscaleColorMatrix],
47
+ [
48
+ (out) => rs.buildBitmapHueRotationColorMatrix(out, 42),
49
+ (out) => reference.buildBitmapHueRotationColorMatrix(out, 42),
50
+ ],
51
+ [rs.buildBitmapInvertColorMatrix, reference.buildBitmapInvertColorMatrix],
52
+ [
53
+ (out) => rs.buildBitmapSaturationColorMatrix(out, 1.5),
54
+ (out) => reference.buildBitmapSaturationColorMatrix(out, 1.5),
55
+ ],
56
+ [rs.buildBitmapSepiaColorMatrix, reference.buildBitmapSepiaColorMatrix],
57
+ [rs.setBitmapColorMatrixIdentity, reference.setBitmapColorMatrixIdentity],
58
+ ];
59
+ for (const [generated, cultivated] of cases) {
60
+ generated(actual);
61
+ cultivated(expected);
62
+ expect(actual).toEqual(expected);
63
+ }
64
+
65
+ const first = actual.slice();
66
+ reference.buildBitmapContrastColorMatrix(expected, 1.2);
67
+ const second = expected.slice();
68
+ rs.concatBitmapColorMatrix(actual, first, second);
69
+ reference.concatBitmapColorMatrix(expected, first, second);
70
+ expect(actual).toEqual(expected);
71
+ });
72
+
73
+ it('matches selected output-buffer kernels', () => {
74
+ const source = paintBitmap(4, 3);
75
+ const region = fullRegion(source);
76
+ const actual = new Uint8ClampedArray(source.data.length);
77
+ const expected = new Uint8ClampedArray(source.data.length);
78
+ const matrix = Array.from({ length: 20 }, () => 0);
79
+ reference.buildBitmapSaturationColorMatrix(matrix, 0.4);
80
+
81
+ rs.colorMatrixBitmap(actual, region, matrix);
82
+ reference.colorMatrixBitmap(expected, region, matrix);
83
+ expect(actual).toEqual(expected);
84
+
85
+ const options = {
86
+ edge: 'mirror' as const,
87
+ matrix: [1, 2, 1, 2, 4, 2, 1, 2, 1],
88
+ matrixX: 3,
89
+ matrixY: 3,
90
+ preserveAlpha: false,
91
+ };
92
+ rs.convolveBitmap(actual, region, options);
93
+ reference.convolveBitmap(expected, region, options);
94
+ expect(actual).toEqual(expected);
95
+
96
+ rs.dilateBitmap(actual, region, 1);
97
+ reference.dilateBitmap(expected, region, 1);
98
+ expect(actual).toEqual(expected);
99
+
100
+ rs.erodeBitmap(actual, region, 1);
101
+ reference.erodeBitmap(expected, region, 1);
102
+ expect(actual).toEqual(expected);
103
+
104
+ rs.pixelateBitmap(actual, region, 2);
105
+ reference.pixelateBitmap(expected, region, 2);
106
+ expect(actual).toEqual(expected);
107
+
108
+ rs.premultiplyBitmapPixels(actual, source.data, source.data.length);
109
+ reference.premultiplyBitmapPixels(expected, source.data, source.data.length);
110
+ expect(actual).toEqual(expected);
111
+
112
+ rs.unpremultiplyBitmapPixels(actual, source.data, source.data.length);
113
+ reference.unpremultiplyBitmapPixels(expected, source.data, source.data.length);
114
+ expect(actual).toEqual(expected);
115
+ });
116
+
117
+ it('matches mutating region operations, clipping, and invalidation', () => {
118
+ const source = paintBitmap(3, 2);
119
+ const actualBitmap = paintBitmap(3, 2);
120
+ const expectedBitmap = cloneBitmap(actualBitmap);
121
+ const sourceRegion = createBitmapRegion(source, 0, 0, 3, 2);
122
+ const actual = createBitmapRegion(actualBitmap, -1, 0, 3, 2);
123
+ const expected = createBitmapRegion(expectedBitmap, -1, 0, 3, 2);
124
+
125
+ rs.copyBitmapPixels(actual, sourceRegion);
126
+ reference.copyBitmapPixels(expected, sourceRegion);
127
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
128
+ expect(actualBitmap.version).toBe(expectedBitmap.version);
129
+
130
+ rs.fillBitmapRectangle(actual, 0xaabbccdd);
131
+ reference.fillBitmapRectangle(expected, 0xaabbccdd);
132
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
133
+
134
+ rs.multiplyBitmapAlpha(actual, 0.5);
135
+ reference.multiplyBitmapAlpha(expected, 0.5);
136
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
137
+
138
+ rs.setBitmapAlpha(actual, 123);
139
+ reference.setBitmapAlpha(expected, 123);
140
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
141
+
142
+ rs.copyBitmapAlpha(actual, sourceRegion);
143
+ reference.copyBitmapAlpha(expected, sourceRegion);
144
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
145
+ expect(actualBitmap.version).toBe(expectedBitmap.version);
146
+
147
+ const inverted = Array.from({ length: 256 }, (_, value) => 255 - value);
148
+ rs.applyBitmapPaletteMap(actual, sourceRegion, inverted, null, null, null);
149
+ reference.applyBitmapPaletteMap(expected, sourceRegion, inverted, null, null, null);
150
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
151
+ expect(actualBitmap.version).toBe(expectedBitmap.version);
152
+
153
+ const byteInverted = Uint8Array.from(inverted);
154
+ rs.applyBitmapCurve(actual, sourceRegion, byteInverted, null, null, null);
155
+ reference.applyBitmapCurve(expected, sourceRegion, byteInverted, null, null, null);
156
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
157
+ expect(actualBitmap.version).toBe(expectedBitmap.version);
158
+
159
+ rs.applyBitmapLevels(actual, sourceRegion, 12, 240, 0.75);
160
+ reference.applyBitmapLevels(expected, sourceRegion, 12, 240, 0.75);
161
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
162
+ expect(actualBitmap.version).toBe(expectedBitmap.version);
163
+
164
+ const comparison = cloneBitmap(actualBitmap);
165
+ comparison.data[5] = (comparison.data[5]! + 128) & 0xff;
166
+ expect(rs.getBitmapMismatch(actualBitmap, comparison, 10)).toEqual(
167
+ reference.getBitmapMismatch(expectedBitmap, comparison, 10),
168
+ );
169
+
170
+ const actualFingerprint = rs.createBitmapFingerprint(actualBitmap, 2);
171
+ const expectedFingerprint = reference.createBitmapFingerprint(expectedBitmap, 2);
172
+ expect(actualFingerprint.gridSize).toBe(expectedFingerprint.gridSize);
173
+ expect(actualFingerprint.cells).toEqual(expectedFingerprint.cells);
174
+ const comparisonFingerprint = reference.createBitmapFingerprint(comparison, 2);
175
+ expect(rs.compareBitmapFingerprints(actualFingerprint, comparisonFingerprint)).toBe(
176
+ reference.compareBitmapFingerprints(expectedFingerprint, comparisonFingerprint),
177
+ );
178
+
179
+ rs.mergeBitmapChannels(actual, sourceRegion, sourceRegion, sourceRegion, sourceRegion);
180
+ reference.mergeBitmapChannels(expected, sourceRegion, sourceRegion, sourceRegion, sourceRegion);
181
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
182
+ expect(actualBitmap.version).toBe(expectedBitmap.version);
183
+
184
+ expect(rs.getBitmapColorBoundsRectangle(actual, 0xffffff00, 0xaabbcc00)).toEqual(
185
+ reference.getBitmapColorBoundsRectangle(expected, 0xffffff00, 0xaabbcc00),
186
+ );
187
+ expect(rs.getBitmapHistogram(actual)).toEqual(reference.getBitmapHistogram(expected));
188
+ });
189
+
190
+ it('matches deterministic noise variants and coverage', () => {
191
+ const actualBitmap = createBitmap(4, 3, 0);
192
+ const expectedBitmap = createBitmap(4, 3, 0);
193
+ const actual = fullRegion(actualBitmap);
194
+ const expected = fullRegion(expectedBitmap);
195
+
196
+ rs.fillBitmapNoise(actual, 123, 10, 240, true);
197
+ reference.fillBitmapNoise(expected, 123, 10, 240, true);
198
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
199
+
200
+ rs.fillBitmapPerlinNoise(actual, 3, 4, 2, 99, false, true, 0xf);
201
+ reference.fillBitmapPerlinNoise(expected, 3, 4, 2, 99, false, true, 0xf);
202
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
203
+
204
+ rs.fillBitmapTurbulence(actual, 3, 4, 2, 101, true, false, 0x7);
205
+ reference.fillBitmapTurbulence(expected, 3, 4, 2, 101, true, false, 0x7);
206
+ expect(actualBitmap.data).toEqual(expectedBitmap.data);
207
+
208
+ expect(rs.getBitmapCoverage(actualBitmap, 0, 2)).toBe(reference.getBitmapCoverage(expectedBitmap, 0, 2));
209
+ });
210
+ });