@flighthq/bitmap 0.3.0-next.1021.f5f1ee0
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/dist/bitmap.d.ts +17 -0
- package/dist/bitmap.d.ts.map +1 -0
- package/dist/bitmap.js +87 -0
- package/dist/bitmap.js.map +1 -0
- package/dist/bitmapAffine.d.ts +27 -0
- package/dist/bitmapAffine.d.ts.map +1 -0
- package/dist/bitmapAffine.js +159 -0
- package/dist/bitmapAffine.js.map +1 -0
- package/dist/bitmapAlpha.d.ts +27 -0
- package/dist/bitmapAlpha.d.ts.map +1 -0
- package/dist/bitmapAlpha.js +83 -0
- package/dist/bitmapAlpha.js.map +1 -0
- package/dist/bitmapBevel.d.ts +24 -0
- package/dist/bitmapBevel.d.ts.map +1 -0
- package/dist/bitmapBevel.js +102 -0
- package/dist/bitmapBevel.js.map +1 -0
- package/dist/bitmapBlur.d.ts +57 -0
- package/dist/bitmapBlur.d.ts.map +1 -0
- package/dist/bitmapBlur.js +263 -0
- package/dist/bitmapBlur.js.map +1 -0
- package/dist/bitmapChannel.d.ts +24 -0
- package/dist/bitmapChannel.d.ts.map +1 -0
- package/dist/bitmapChannel.js +127 -0
- package/dist/bitmapChannel.js.map +1 -0
- package/dist/bitmapColorMatrix.d.ts +57 -0
- package/dist/bitmapColorMatrix.d.ts.map +1 -0
- package/dist/bitmapColorMatrix.js +127 -0
- package/dist/bitmapColorMatrix.js.map +1 -0
- package/dist/bitmapCompare.d.ts +18 -0
- package/dist/bitmapCompare.d.ts.map +1 -0
- package/dist/bitmapCompare.js +64 -0
- package/dist/bitmapCompare.js.map +1 -0
- package/dist/bitmapComposite.d.ts +54 -0
- package/dist/bitmapComposite.d.ts.map +1 -0
- package/dist/bitmapComposite.js +262 -0
- package/dist/bitmapComposite.js.map +1 -0
- package/dist/bitmapConvolution.d.ts +17 -0
- package/dist/bitmapConvolution.d.ts.map +1 -0
- package/dist/bitmapConvolution.js +103 -0
- package/dist/bitmapConvolution.js.map +1 -0
- package/dist/bitmapCopy.d.ts +23 -0
- package/dist/bitmapCopy.d.ts.map +1 -0
- package/dist/bitmapCopy.js +78 -0
- package/dist/bitmapCopy.js.map +1 -0
- package/dist/bitmapCoverage.d.ts +11 -0
- package/dist/bitmapCoverage.d.ts.map +1 -0
- package/dist/bitmapCoverage.js +29 -0
- package/dist/bitmapCoverage.js.map +1 -0
- package/dist/bitmapCrop.d.ts +33 -0
- package/dist/bitmapCrop.d.ts.map +1 -0
- package/dist/bitmapCrop.js +175 -0
- package/dist/bitmapCrop.js.map +1 -0
- package/dist/bitmapDisplacement.d.ts +22 -0
- package/dist/bitmapDisplacement.d.ts.map +1 -0
- package/dist/bitmapDisplacement.js +131 -0
- package/dist/bitmapDisplacement.js.map +1 -0
- package/dist/bitmapDissolve.d.ts +21 -0
- package/dist/bitmapDissolve.d.ts.map +1 -0
- package/dist/bitmapDissolve.js +97 -0
- package/dist/bitmapDissolve.js.map +1 -0
- package/dist/bitmapDraw.d.ts +8 -0
- package/dist/bitmapDraw.d.ts.map +1 -0
- package/dist/bitmapDraw.js +14 -0
- package/dist/bitmapDraw.js.map +1 -0
- package/dist/bitmapEncode.d.ts +3 -0
- package/dist/bitmapEncode.d.ts.map +1 -0
- package/dist/bitmapEncode.js +18 -0
- package/dist/bitmapEncode.js.map +1 -0
- package/dist/bitmapFill.d.ts +12 -0
- package/dist/bitmapFill.d.ts.map +1 -0
- package/dist/bitmapFill.js +85 -0
- package/dist/bitmapFill.js.map +1 -0
- package/dist/bitmapFingerprint.d.ts +26 -0
- package/dist/bitmapFingerprint.d.ts.map +1 -0
- package/dist/bitmapFingerprint.js +99 -0
- package/dist/bitmapFingerprint.js.map +1 -0
- package/dist/bitmapFlip.d.ts +16 -0
- package/dist/bitmapFlip.d.ts.map +1 -0
- package/dist/bitmapFlip.js +98 -0
- package/dist/bitmapFlip.js.map +1 -0
- package/dist/bitmapFormat.d.ts +26 -0
- package/dist/bitmapFormat.d.ts.map +1 -0
- package/dist/bitmapFormat.js +80 -0
- package/dist/bitmapFormat.js.map +1 -0
- package/dist/bitmapFrom.d.ts +17 -0
- package/dist/bitmapFrom.d.ts.map +1 -0
- package/dist/bitmapFrom.js +67 -0
- package/dist/bitmapFrom.js.map +1 -0
- package/dist/bitmapGradient.d.ts +36 -0
- package/dist/bitmapGradient.d.ts.map +1 -0
- package/dist/bitmapGradient.js +176 -0
- package/dist/bitmapGradient.js.map +1 -0
- package/dist/bitmapGradientFill.d.ts +32 -0
- package/dist/bitmapGradientFill.d.ts.map +1 -0
- package/dist/bitmapGradientFill.js +120 -0
- package/dist/bitmapGradientFill.js.map +1 -0
- package/dist/bitmapHistogram.d.ts +20 -0
- package/dist/bitmapHistogram.d.ts.map +1 -0
- package/dist/bitmapHistogram.js +61 -0
- package/dist/bitmapHistogram.js.map +1 -0
- package/dist/bitmapImageChannel.d.ts +2 -0
- package/dist/bitmapImageChannel.d.ts.map +1 -0
- package/dist/bitmapImageChannel.js +2 -0
- package/dist/bitmapImageChannel.js.map +1 -0
- package/dist/bitmapMedian.d.ts +14 -0
- package/dist/bitmapMedian.d.ts.map +1 -0
- package/dist/bitmapMedian.js +77 -0
- package/dist/bitmapMedian.js.map +1 -0
- package/dist/bitmapMorphological.d.ts +29 -0
- package/dist/bitmapMorphological.d.ts.map +1 -0
- package/dist/bitmapMorphological.js +81 -0
- package/dist/bitmapMorphological.js.map +1 -0
- package/dist/bitmapNoise.d.ts +48 -0
- package/dist/bitmapNoise.d.ts.map +1 -0
- package/dist/bitmapNoise.js +245 -0
- package/dist/bitmapNoise.js.map +1 -0
- package/dist/bitmapPaletteMap.d.ts +17 -0
- package/dist/bitmapPaletteMap.d.ts.map +1 -0
- package/dist/bitmapPaletteMap.js +45 -0
- package/dist/bitmapPaletteMap.js.map +1 -0
- package/dist/bitmapPixel.d.ts +9 -0
- package/dist/bitmapPixel.d.ts.map +1 -0
- package/dist/bitmapPixel.js +36 -0
- package/dist/bitmapPixel.js.map +1 -0
- package/dist/bitmapPixelate.d.ts +13 -0
- package/dist/bitmapPixelate.d.ts.map +1 -0
- package/dist/bitmapPixelate.js +61 -0
- package/dist/bitmapPixelate.js.map +1 -0
- package/dist/bitmapQuery.d.ts +13 -0
- package/dist/bitmapQuery.d.ts.map +1 -0
- package/dist/bitmapQuery.js +46 -0
- package/dist/bitmapQuery.js.map +1 -0
- package/dist/bitmapRegion.d.ts +20 -0
- package/dist/bitmapRegion.d.ts.map +1 -0
- package/dist/bitmapRegion.js +28 -0
- package/dist/bitmapRegion.js.map +1 -0
- package/dist/bitmapResize.d.ts +17 -0
- package/dist/bitmapResize.d.ts.map +1 -0
- package/dist/bitmapResize.js +191 -0
- package/dist/bitmapResize.js.map +1 -0
- package/dist/bitmapRotate.d.ts +32 -0
- package/dist/bitmapRotate.d.ts.map +1 -0
- package/dist/bitmapRotate.js +143 -0
- package/dist/bitmapRotate.js.map +1 -0
- package/dist/bitmapShadow.d.ts +72 -0
- package/dist/bitmapShadow.d.ts.map +1 -0
- package/dist/bitmapShadow.js +175 -0
- package/dist/bitmapShadow.js.map +1 -0
- package/dist/bitmapSharpen.d.ts +16 -0
- package/dist/bitmapSharpen.d.ts.map +1 -0
- package/dist/bitmapSharpen.js +47 -0
- package/dist/bitmapSharpen.js.map +1 -0
- package/dist/bitmapTone.d.ts +28 -0
- package/dist/bitmapTone.d.ts.map +1 -0
- package/dist/bitmapTone.js +69 -0
- package/dist/bitmapTone.js.map +1 -0
- package/dist/bitmapTransform.d.ts +40 -0
- package/dist/bitmapTransform.d.ts.map +1 -0
- package/dist/bitmapTransform.js +163 -0
- package/dist/bitmapTransform.js.map +1 -0
- package/dist/bitmapWarp.d.ts +48 -0
- package/dist/bitmapWarp.d.ts.map +1 -0
- package/dist/bitmapWarp.js +284 -0
- package/dist/bitmapWarp.js.map +1 -0
- package/dist/contract.d.ts +42 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +42 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/src/bitmap.test.ts +88 -0
- package/src/bitmapAffine.test.ts +98 -0
- package/src/bitmapAlpha.test.ts +110 -0
- package/src/bitmapBevel.test.ts +61 -0
- package/src/bitmapBlur.test.ts +234 -0
- package/src/bitmapChannel.test.ts +97 -0
- package/src/bitmapColorMatrix.test.ts +214 -0
- package/src/bitmapCompare.test.ts +92 -0
- package/src/bitmapComposite.test.ts +259 -0
- package/src/bitmapConvolution.test.ts +130 -0
- package/src/bitmapCopy.test.ts +48 -0
- package/src/bitmapCoverage.test.ts +39 -0
- package/src/bitmapCrop.test.ts +116 -0
- package/src/bitmapDisplacement.test.ts +136 -0
- package/src/bitmapDissolve.test.ts +99 -0
- package/src/bitmapDraw.test.ts +68 -0
- package/src/bitmapEncode.test.ts +18 -0
- package/src/bitmapFill.test.ts +83 -0
- package/src/bitmapFingerprint.test.ts +103 -0
- package/src/bitmapFlip.test.ts +53 -0
- package/src/bitmapFormat.test.ts +124 -0
- package/src/bitmapFrom.test.ts +57 -0
- package/src/bitmapGradient.test.ts +85 -0
- package/src/bitmapGradientFill.test.ts +107 -0
- package/src/bitmapHistogram.test.ts +64 -0
- package/src/bitmapImageChannel.test.ts +19 -0
- package/src/bitmapMedian.test.ts +39 -0
- package/src/bitmapMorphological.test.ts +69 -0
- package/src/bitmapNoise.test.ts +163 -0
- package/src/bitmapPaletteMap.test.ts +49 -0
- package/src/bitmapPixel.test.ts +117 -0
- package/src/bitmapPixelate.test.ts +40 -0
- package/src/bitmapQuery.test.ts +42 -0
- package/src/bitmapRegion.test.ts +34 -0
- package/src/bitmapResize.test.ts +106 -0
- package/src/bitmapRotate.test.ts +108 -0
- package/src/bitmapShadow.test.ts +159 -0
- package/src/bitmapSharpen.test.ts +44 -0
- package/src/bitmapTone.test.ts +104 -0
- package/src/bitmapTransform.test.ts +128 -0
- package/src/bitmapWarp.test.ts +118 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { convolveBitmap } from './bitmapConvolution';
|
|
3
|
+
|
|
4
|
+
function region(bitmap: ReturnType<typeof createBitmap>, x = 0, y = 0, width = bitmap.width, height = bitmap.height) {
|
|
5
|
+
return { bitmap, x, y, width, height };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('convolveBitmap', () => {
|
|
9
|
+
it('applies a convolution matrix to the source region', () => {
|
|
10
|
+
const source = createBitmap(3, 1);
|
|
11
|
+
source.data[0] = 10;
|
|
12
|
+
source.data[4] = 20;
|
|
13
|
+
source.data[8] = 30;
|
|
14
|
+
const out = new Uint8ClampedArray(4);
|
|
15
|
+
convolveBitmap(out, region(source, 1, 0, 1, 1), {
|
|
16
|
+
matrix: [1, 1, 1],
|
|
17
|
+
matrixX: 3,
|
|
18
|
+
matrixY: 1,
|
|
19
|
+
preserveAlpha: false,
|
|
20
|
+
});
|
|
21
|
+
expect(out[0]).toBe(20);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("'clamp' edge mode (default) repeats the nearest edge pixel", () => {
|
|
25
|
+
// 1px source, 3-wide kernel: all three samples hit the single source pixel
|
|
26
|
+
const source = createBitmap(1, 1, 0x606060ff);
|
|
27
|
+
const out = new Uint8ClampedArray(4);
|
|
28
|
+
convolveBitmap(out, region(source), {
|
|
29
|
+
matrix: [1, 1, 1],
|
|
30
|
+
matrixX: 3,
|
|
31
|
+
matrixY: 1,
|
|
32
|
+
divisor: 3,
|
|
33
|
+
preserveAlpha: false,
|
|
34
|
+
});
|
|
35
|
+
expect(out[0]).toBe(0x60);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("'mirror' edge mode reflects at boundaries", () => {
|
|
39
|
+
// 3px source [10, 20, 30]. 5-wide kernel [1, 0, 0, 0, 0] picks 2 positions left.
|
|
40
|
+
// For px=0, offsetX=2: rawSampleX = 0 + 0 + 0 - 2 = -2.
|
|
41
|
+
// mirror(-2, 3) reflects to x=1 (value 20), while clamp(-2) gives x=0 (value 10).
|
|
42
|
+
const source = createBitmap(3, 1);
|
|
43
|
+
source.data[0] = 10;
|
|
44
|
+
source.data[4] = 20;
|
|
45
|
+
source.data[8] = 30;
|
|
46
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
47
|
+
convolveBitmap(out, region(source), {
|
|
48
|
+
edge: 'mirror',
|
|
49
|
+
matrix: [1, 0, 0, 0, 0],
|
|
50
|
+
matrixX: 5,
|
|
51
|
+
matrixY: 1,
|
|
52
|
+
divisor: 1,
|
|
53
|
+
preserveAlpha: false,
|
|
54
|
+
});
|
|
55
|
+
expect(out[0]).toBe(20);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("'transparent' edge mode treats out-of-bounds as transparent black", () => {
|
|
59
|
+
// 1px source (opaque black), 3-wide kernel [1, 0, 0] — leftmost sample
|
|
60
|
+
// is out of bounds and contributes transparent black (all zeros).
|
|
61
|
+
const source = createBitmap(1, 1, 0x000000ff);
|
|
62
|
+
const out = new Uint8ClampedArray(4);
|
|
63
|
+
convolveBitmap(out, region(source), {
|
|
64
|
+
edge: 'transparent',
|
|
65
|
+
divisor: 1,
|
|
66
|
+
matrix: [1, 0, 0],
|
|
67
|
+
matrixX: 3,
|
|
68
|
+
matrixY: 1,
|
|
69
|
+
preserveAlpha: false,
|
|
70
|
+
});
|
|
71
|
+
expect(out[0]).toBe(0);
|
|
72
|
+
expect(out[3]).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("'wrap' edge mode tiles the source toroidally", () => {
|
|
76
|
+
// 3px source [10, 20, 30]. The leftmost kernel position for px=0 wraps to px=2.
|
|
77
|
+
const source = createBitmap(3, 1);
|
|
78
|
+
source.data[0] = 10;
|
|
79
|
+
source.data[4] = 20;
|
|
80
|
+
source.data[8] = 30;
|
|
81
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
82
|
+
convolveBitmap(out, region(source), {
|
|
83
|
+
edge: 'wrap',
|
|
84
|
+
matrix: [1, 0, 0], // picks left neighbor only
|
|
85
|
+
matrixX: 3,
|
|
86
|
+
matrixY: 1,
|
|
87
|
+
divisor: 1,
|
|
88
|
+
preserveAlpha: false,
|
|
89
|
+
});
|
|
90
|
+
// px=0 left neighbor wraps to px=2 → value 30
|
|
91
|
+
expect(out[0]).toBe(30);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('preserves source alpha by default', () => {
|
|
95
|
+
const source = createBitmap(1, 1, 0x00000044);
|
|
96
|
+
const out = new Uint8ClampedArray(4);
|
|
97
|
+
convolveBitmap(out, region(source), {
|
|
98
|
+
bias: 255,
|
|
99
|
+
matrix: [1],
|
|
100
|
+
matrixX: 1,
|
|
101
|
+
matrixY: 1,
|
|
102
|
+
});
|
|
103
|
+
expect(out[3]).toBe(0x44);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('throws when matrix dimensions are not positive', () => {
|
|
107
|
+
const source = createBitmap(1, 1);
|
|
108
|
+
const out = new Uint8ClampedArray(4);
|
|
109
|
+
expect(() => convolveBitmap(out, region(source), { matrix: [], matrixX: 0, matrixY: 1 })).toThrow();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('throws when matrix is too short for its declared dimensions', () => {
|
|
113
|
+
const source = createBitmap(1, 1);
|
|
114
|
+
const out = new Uint8ClampedArray(4);
|
|
115
|
+
expect(() => convolveBitmap(out, region(source), { matrix: [1], matrixX: 3, matrixY: 3 })).toThrow();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('treats an explicit divisor of 0 as passthrough (no division by zero)', () => {
|
|
119
|
+
const source = createBitmap(1, 1, 0x804020ff);
|
|
120
|
+
const out = new Uint8ClampedArray(4);
|
|
121
|
+
convolveBitmap(out, region(source), {
|
|
122
|
+
divisor: 0,
|
|
123
|
+
matrix: [1],
|
|
124
|
+
matrixX: 1,
|
|
125
|
+
matrixY: 1,
|
|
126
|
+
preserveAlpha: false,
|
|
127
|
+
});
|
|
128
|
+
expect(out[0]).toBe(0x80);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { copyBitmapChannel, copyBitmapPixels } from './bitmapCopy';
|
|
3
|
+
import { ImageChannel } from './bitmapImageChannel';
|
|
4
|
+
import { getBitmapPixel, setBitmapPixel } from './bitmapPixel';
|
|
5
|
+
|
|
6
|
+
function region(bitmap: ReturnType<typeof createBitmap>, x = 0, y = 0, width = bitmap.width, height = bitmap.height) {
|
|
7
|
+
return { bitmap, x, y, width, height };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('copyBitmapChannel', () => {
|
|
11
|
+
it('copies the red channel to the blue channel', () => {
|
|
12
|
+
const src = createBitmap(2, 2);
|
|
13
|
+
src.data[0] = 0xab;
|
|
14
|
+
const dst = createBitmap(2, 2);
|
|
15
|
+
copyBitmapChannel(region(dst), ImageChannel.Blue, region(src), ImageChannel.Red);
|
|
16
|
+
expect(dst.data[2]).toBe(src.data[0]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('copies the alpha channel independently', () => {
|
|
20
|
+
const src = createBitmap(1, 1);
|
|
21
|
+
setBitmapPixel(src, 0, 0, 0x000000de);
|
|
22
|
+
const dst = createBitmap(1, 1);
|
|
23
|
+
copyBitmapChannel(region(dst), ImageChannel.Alpha, region(src), ImageChannel.Alpha);
|
|
24
|
+
expect(dst.data[3]).toBe(0xde);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('copyBitmapPixels', () => {
|
|
29
|
+
it('copies a region without alpha blend', () => {
|
|
30
|
+
const src = createBitmap(2, 2, 0xaabbccff);
|
|
31
|
+
const dst = createBitmap(4, 4);
|
|
32
|
+
copyBitmapPixels(region(dst, 1, 1, 2, 2), region(src, 0, 0, 2, 2));
|
|
33
|
+
expect(getBitmapPixel(dst, 1, 1)).toBe(0xaabbccff);
|
|
34
|
+
expect(getBitmapPixel(dst, 0, 0)).toBe(0x00000000);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('alpha-composites when composite is true', () => {
|
|
38
|
+
const src = createBitmap(1, 1);
|
|
39
|
+
setBitmapPixel(src, 0, 0, 0xff000080);
|
|
40
|
+
const dst = createBitmap(1, 1);
|
|
41
|
+
setBitmapPixel(dst, 0, 0, 0x0000ffff);
|
|
42
|
+
copyBitmapPixels(region(dst), region(src), true);
|
|
43
|
+
const result = getBitmapPixel(dst, 0, 0);
|
|
44
|
+
expect(result & 0xff).toBe(0xff); // alpha opaque after composite
|
|
45
|
+
expect((result >>> 24) & 0xff).toBeGreaterThan(0); // red blended in
|
|
46
|
+
expect((result >> 8) & 0xff).toBeGreaterThan(0); // blue retained
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { getBitmapCoverage } from './bitmapCoverage';
|
|
3
|
+
import { setBitmapPixel } from './bitmapPixel';
|
|
4
|
+
|
|
5
|
+
describe('getBitmapCoverage', () => {
|
|
6
|
+
it('returns 0 for a bitmap still entirely the background colour', () => {
|
|
7
|
+
expect(getBitmapCoverage(createBitmap(4, 4, 0x123456ff), 0x123456ff)).toBe(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('returns 0 for a fully transparent bitmap against a transparent background', () => {
|
|
11
|
+
expect(getBitmapCoverage(createBitmap(4, 4, 0x00000000), 0x00000000)).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('counts pixels that differ from the background on any channel', () => {
|
|
15
|
+
const bitmap = createBitmap(2, 2, 0x000000ff);
|
|
16
|
+
setBitmapPixel(bitmap, 0, 0, 0xff0000ff);
|
|
17
|
+
expect(getBitmapCoverage(bitmap, 0x000000ff)).toBe(0.25);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('measures a fully painted bitmap as full coverage', () => {
|
|
21
|
+
const bitmap = createBitmap(2, 2, 0x000000ff);
|
|
22
|
+
setBitmapPixel(bitmap, 0, 0, 0xffffffff);
|
|
23
|
+
setBitmapPixel(bitmap, 1, 0, 0xffffffff);
|
|
24
|
+
setBitmapPixel(bitmap, 0, 1, 0xffffffff);
|
|
25
|
+
setBitmapPixel(bitmap, 1, 1, 0xffffffff);
|
|
26
|
+
expect(getBitmapCoverage(bitmap, 0x000000ff)).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('ignores differences within the channel tolerance', () => {
|
|
30
|
+
const bitmap = createBitmap(2, 1, 0x000000ff);
|
|
31
|
+
setBitmapPixel(bitmap, 0, 0, 0x050505ff); // 5 per channel
|
|
32
|
+
expect(getBitmapCoverage(bitmap, 0x000000ff, 5)).toBe(0);
|
|
33
|
+
expect(getBitmapCoverage(bitmap, 0x000000ff, 4)).toBe(0.5);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns 0 for an empty bitmap', () => {
|
|
37
|
+
expect(getBitmapCoverage(createBitmap(0, 0), 0x000000ff)).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { cropBitmap, extendBitmap, trimBitmap } from './bitmapCrop';
|
|
3
|
+
import { getBitmapPixel, setBitmapPixel } from './bitmapPixel';
|
|
4
|
+
|
|
5
|
+
describe('cropBitmap', () => {
|
|
6
|
+
it('returns a bitmap with the requested dimensions', () => {
|
|
7
|
+
const src = createBitmap(10, 10, 0xff0000ff);
|
|
8
|
+
const out = cropBitmap(src, { x: 2, y: 3, width: 4, height: 5 });
|
|
9
|
+
expect(out.width).toBe(4);
|
|
10
|
+
expect(out.height).toBe(5);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('copies pixels from the source region', () => {
|
|
14
|
+
const src = createBitmap(4, 4);
|
|
15
|
+
setBitmapPixel(src, 2, 1, 0x00ff00ff);
|
|
16
|
+
const out = cropBitmap(src, { x: 2, y: 1, width: 2, height: 2 });
|
|
17
|
+
expect(getBitmapPixel(out, 0, 0)).toBe(0x00ff00ff);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('fills pixels outside the source with transparent black', () => {
|
|
21
|
+
const src = createBitmap(2, 2, 0xffffffff);
|
|
22
|
+
// Crop region extends beyond source.
|
|
23
|
+
const out = cropBitmap(src, { x: 1, y: 1, width: 3, height: 3 });
|
|
24
|
+
// (0,0) is inside source — opaque white.
|
|
25
|
+
expect(getBitmapPixel(out, 0, 0)).toBe(0xffffffff);
|
|
26
|
+
// (2,2) is outside source — transparent black.
|
|
27
|
+
expect(getBitmapPixel(out, 2, 2)).toBe(0x00000000);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('preserves source colorSpace and alphaType', () => {
|
|
31
|
+
const src = createBitmap(2, 2, 0xff0000ff);
|
|
32
|
+
const out = cropBitmap(src, { x: 0, y: 0, width: 2, height: 2 });
|
|
33
|
+
expect(out.colorSpace).toBe(src.colorSpace);
|
|
34
|
+
expect(out.alphaType).toBe(src.alphaType);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns a distinct bitmap object', () => {
|
|
38
|
+
const src = createBitmap(2, 2, 0xff0000ff);
|
|
39
|
+
const out = cropBitmap(src, { x: 0, y: 0, width: 2, height: 2 });
|
|
40
|
+
expect(out).not.toBe(src);
|
|
41
|
+
expect(out.data).not.toBe(src.data);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('extendBitmap', () => {
|
|
46
|
+
it('returns a bitmap padded by the correct number of pixels', () => {
|
|
47
|
+
const src = createBitmap(2, 2, 0x0000ffff);
|
|
48
|
+
const out = extendBitmap(src, 1, 2, 3, 4);
|
|
49
|
+
expect(out.width).toBe(2 + 1 + 3);
|
|
50
|
+
expect(out.height).toBe(2 + 2 + 4);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('copies source pixels into the center region', () => {
|
|
54
|
+
const src = createBitmap(2, 2, 0xabcdefff);
|
|
55
|
+
const out = extendBitmap(src, 1, 1, 1, 1);
|
|
56
|
+
expect(getBitmapPixel(out, 1, 1)).toBe(0xabcdefff);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('transparent edge mode fills padding with transparent black by default', () => {
|
|
60
|
+
const src = createBitmap(2, 2, 0xff0000ff);
|
|
61
|
+
const out = extendBitmap(src, 1, 1, 1, 1);
|
|
62
|
+
expect(getBitmapPixel(out, 0, 0)).toBe(0x00000000);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('transparent edge mode uses fillColor when provided', () => {
|
|
66
|
+
const src = createBitmap(2, 2, 0xff0000ff);
|
|
67
|
+
const out = extendBitmap(src, 1, 1, 1, 1, 'transparent', 0xffffffff);
|
|
68
|
+
expect(getBitmapPixel(out, 0, 0)).toBe(0xffffffff);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('clamp edge mode repeats border pixels in padding', () => {
|
|
72
|
+
const src = createBitmap(2, 2, 0x0000ffff);
|
|
73
|
+
// Set a distinct border pixel.
|
|
74
|
+
setBitmapPixel(src, 0, 0, 0x123456ff);
|
|
75
|
+
const out = extendBitmap(src, 1, 1, 0, 0, 'clamp');
|
|
76
|
+
// Top-left padding comes from source (0,0).
|
|
77
|
+
expect(getBitmapPixel(out, 0, 0)).toBe(0x123456ff);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('wrap edge mode tiles the source in padding', () => {
|
|
81
|
+
const src = createBitmap(2, 2);
|
|
82
|
+
setBitmapPixel(src, 0, 0, 0xff0000ff);
|
|
83
|
+
setBitmapPixel(src, 1, 0, 0x00ff00ff);
|
|
84
|
+
// Extend by 2 on the right → first padding column maps to source col 0.
|
|
85
|
+
const out = extendBitmap(src, 0, 0, 2, 0, 'wrap');
|
|
86
|
+
expect(getBitmapPixel(out, 2, 0)).toBe(0xff0000ff);
|
|
87
|
+
expect(getBitmapPixel(out, 3, 0)).toBe(0x00ff00ff);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('trimBitmap', () => {
|
|
92
|
+
it('removes transparent border rows and columns', () => {
|
|
93
|
+
const src = createBitmap(5, 5);
|
|
94
|
+
// Only pixel (2,2) is non-transparent.
|
|
95
|
+
setBitmapPixel(src, 2, 2, 0xff0000ff);
|
|
96
|
+
const out = trimBitmap(src);
|
|
97
|
+
expect(out.width).toBe(1);
|
|
98
|
+
expect(out.height).toBe(1);
|
|
99
|
+
expect(getBitmapPixel(out, 0, 0)).toBe(0xff0000ff);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns a 1×1 transparent bitmap for a fully transparent input', () => {
|
|
103
|
+
const src = createBitmap(3, 3);
|
|
104
|
+
const out = trimBitmap(src);
|
|
105
|
+
expect(out.width).toBe(1);
|
|
106
|
+
expect(out.height).toBe(1);
|
|
107
|
+
expect(getBitmapPixel(out, 0, 0) & 0xff).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('returns a bitmap equal to the source when there is no transparent border', () => {
|
|
111
|
+
const src = createBitmap(2, 2, 0xff0000ff);
|
|
112
|
+
const out = trimBitmap(src);
|
|
113
|
+
expect(out.width).toBe(2);
|
|
114
|
+
expect(out.height).toBe(2);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { displaceBitmap } from './bitmapDisplacement';
|
|
3
|
+
|
|
4
|
+
function region(bitmap: ReturnType<typeof createBitmap>, x = 0, y = 0, width = bitmap.width, height = bitmap.height) {
|
|
5
|
+
return { bitmap, x, y, width, height };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// A 3x1 strip: red | green | blue.
|
|
9
|
+
function rgbStrip() {
|
|
10
|
+
const s = createBitmap(3, 1);
|
|
11
|
+
s.data.set([255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]);
|
|
12
|
+
return s;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('displaceBitmap', () => {
|
|
16
|
+
it('is a no-op copy when scaleX and scaleY are 0', () => {
|
|
17
|
+
const source = rgbStrip();
|
|
18
|
+
const map = createBitmap(3, 1);
|
|
19
|
+
map.data.fill(128);
|
|
20
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
21
|
+
displaceBitmap(out, region(source), { map: region(map), scaleX: 0, scaleY: 0, mode: 'clamp' });
|
|
22
|
+
// With zero scale, every output pixel samples from its exact position.
|
|
23
|
+
expect(out[0]).toBe(255); // px0 = red
|
|
24
|
+
expect(out[4 + 1]).toBe(255); // px1 = green
|
|
25
|
+
expect(out[8 + 2]).toBe(255); // px2 = blue
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('shifts the sample position by the scaled map displacement', () => {
|
|
29
|
+
// map value 255 → dx = (255/255 - 0.5) * 2 = +1.0 exactly; scaleX=2.
|
|
30
|
+
const source = rgbStrip();
|
|
31
|
+
const map = createBitmap(3, 1);
|
|
32
|
+
map.data[0] = 255;
|
|
33
|
+
map.data[4] = 255;
|
|
34
|
+
map.data[8] = 255;
|
|
35
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
36
|
+
displaceBitmap(out, region(source), {
|
|
37
|
+
map: region(map),
|
|
38
|
+
componentX: 0,
|
|
39
|
+
scaleX: 2,
|
|
40
|
+
scaleY: 0,
|
|
41
|
+
mode: 'clamp',
|
|
42
|
+
});
|
|
43
|
+
// px0←src1 (green), px1←src2 (blue), px2←src3 oob clamped to src2 (blue)
|
|
44
|
+
expect(out[1]).toBe(255); // green channel of px0
|
|
45
|
+
expect(out[4 + 2]).toBe(255); // blue channel of px1
|
|
46
|
+
expect(out[8 + 2]).toBe(255); // blue channel of px2 (clamped)
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("'color' mode fills out-of-range samples with fillColor", () => {
|
|
50
|
+
// Map value 255 with scaleX=4 → dx=+2; px2 samples at x=4 (oob)
|
|
51
|
+
const source = rgbStrip();
|
|
52
|
+
const map = createBitmap(3, 1);
|
|
53
|
+
map.data.fill(255);
|
|
54
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
55
|
+
displaceBitmap(out, region(source), {
|
|
56
|
+
map: region(map),
|
|
57
|
+
scaleX: 4,
|
|
58
|
+
scaleY: 0,
|
|
59
|
+
mode: 'color',
|
|
60
|
+
fillColor: 0xff00ffff,
|
|
61
|
+
});
|
|
62
|
+
// px2 displaces to x=4 (out of range) → magenta fill
|
|
63
|
+
expect(out[2 * 4 + 0]).toBe(0xff);
|
|
64
|
+
expect(out[2 * 4 + 1]).toBe(0);
|
|
65
|
+
expect(out[2 * 4 + 2]).toBe(0xff);
|
|
66
|
+
expect(out[2 * 4 + 3]).toBe(255);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("'ignore' mode keeps the undisplaced source pixel for out-of-range samples", () => {
|
|
70
|
+
// Map value 255 with scaleX=4 → px2 samples at x=4 (oob) → keep blue
|
|
71
|
+
const source = rgbStrip();
|
|
72
|
+
const map = createBitmap(3, 1);
|
|
73
|
+
map.data.fill(255);
|
|
74
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
75
|
+
displaceBitmap(out, region(source), {
|
|
76
|
+
map: region(map),
|
|
77
|
+
scaleX: 4,
|
|
78
|
+
scaleY: 0,
|
|
79
|
+
mode: 'ignore',
|
|
80
|
+
});
|
|
81
|
+
// px2 displaces out of range → keeps src2 (blue) unchanged
|
|
82
|
+
expect(out[2 * 4 + 2]).toBe(255);
|
|
83
|
+
expect(out[2 * 4 + 3]).toBe(255);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("edgeMode 'clamp' clamps out-of-range samples to the edge", () => {
|
|
87
|
+
const source = rgbStrip();
|
|
88
|
+
const map = createBitmap(3, 1);
|
|
89
|
+
map.data[0] = 255;
|
|
90
|
+
map.data[4] = 255;
|
|
91
|
+
map.data[8] = 255;
|
|
92
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
93
|
+
displaceBitmap(out, region(source), {
|
|
94
|
+
map: region(map),
|
|
95
|
+
componentX: 0,
|
|
96
|
+
scaleX: 2,
|
|
97
|
+
scaleY: 0,
|
|
98
|
+
edgeMode: 'clamp',
|
|
99
|
+
});
|
|
100
|
+
// px2 displaces to x=3 (oob) → clamped to x=2 (blue)
|
|
101
|
+
expect(out[8 + 2]).toBe(255);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("edgeMode 'transparent' fills out-of-range samples with transparent black", () => {
|
|
105
|
+
const source = rgbStrip();
|
|
106
|
+
const map = createBitmap(3, 1);
|
|
107
|
+
map.data.fill(255);
|
|
108
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
109
|
+
displaceBitmap(out, region(source), {
|
|
110
|
+
map: region(map),
|
|
111
|
+
scaleX: 4,
|
|
112
|
+
scaleY: 0,
|
|
113
|
+
edgeMode: 'transparent',
|
|
114
|
+
});
|
|
115
|
+
// px2 displaces to x=4 (oob) → transparent black
|
|
116
|
+
expect(out[2 * 4 + 0]).toBe(0);
|
|
117
|
+
expect(out[2 * 4 + 3]).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('samples through the source region offset', () => {
|
|
121
|
+
// 4px [red, green, blue, white]; zero displacement over region (1,0,2,1) copies
|
|
122
|
+
// source pixels 1 and 2 (green, blue), proving the offset is applied.
|
|
123
|
+
const source = createBitmap(4, 1);
|
|
124
|
+
source.data.set([255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]);
|
|
125
|
+
const map = createBitmap(2, 1);
|
|
126
|
+
map.data.fill(128); // neutral — minimal displacement at scale=0
|
|
127
|
+
const out = new Uint8ClampedArray(2 * 4);
|
|
128
|
+
displaceBitmap(out, region(source, 1, 0, 2, 1), {
|
|
129
|
+
map: region(map),
|
|
130
|
+
scaleX: 0,
|
|
131
|
+
scaleY: 0,
|
|
132
|
+
});
|
|
133
|
+
expect(out[1]).toBe(255); // green from source[1]
|
|
134
|
+
expect(out[4 + 2]).toBe(255); // blue from source[2]
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { dissolveBitmapPixels } from './bitmapDissolve';
|
|
3
|
+
import { getBitmapPixel, setBitmapPixel } from './bitmapPixel';
|
|
4
|
+
import { createBitmapRegion } from './bitmapRegion';
|
|
5
|
+
|
|
6
|
+
function countChangedPixels(data: Readonly<Uint8ClampedArray>, original: Readonly<Uint8ClampedArray>): number {
|
|
7
|
+
let changed = 0;
|
|
8
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
9
|
+
if (
|
|
10
|
+
data[i] !== original[i] ||
|
|
11
|
+
data[i + 1] !== original[i + 1] ||
|
|
12
|
+
data[i + 2] !== original[i + 2] ||
|
|
13
|
+
data[i + 3] !== original[i + 3]
|
|
14
|
+
) {
|
|
15
|
+
changed++;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return changed;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('dissolveBitmapPixels', () => {
|
|
22
|
+
it('dissolves at most pixelCount pixels per call', () => {
|
|
23
|
+
const source = createBitmap(4, 4, 0x112233ff);
|
|
24
|
+
const dest = createBitmap(4, 4, 0x00000000);
|
|
25
|
+
const original = dest.data.slice();
|
|
26
|
+
dissolveBitmapPixels(createBitmapRegion(dest), createBitmapRegion(source), 0, 5);
|
|
27
|
+
expect(countChangedPixels(dest.data, original)).toBe(5);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('eventually covers every pixel exactly once so dest matches source', () => {
|
|
31
|
+
const source = createBitmap(5, 3);
|
|
32
|
+
for (let i = 0; i < 5 * 3; i++) setBitmapPixel(source, i % 5, (i / 5) | 0, (0x01010100 * (i + 1)) >>> 0);
|
|
33
|
+
const dest = createBitmap(5, 3, 0x00000000);
|
|
34
|
+
const sourceRegion = createBitmapRegion(source);
|
|
35
|
+
const destRegion = createBitmapRegion(dest);
|
|
36
|
+
|
|
37
|
+
let seed = 0;
|
|
38
|
+
for (let call = 0; call < 8; call++) seed = dissolveBitmapPixels(destRegion, sourceRegion, seed, 2);
|
|
39
|
+
|
|
40
|
+
expect(Array.from(dest.data)).toEqual(Array.from(source.data));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('is deterministic for the same seed sequence', () => {
|
|
44
|
+
const source = createBitmap(6, 6, 0xaabbccff);
|
|
45
|
+
const a = createBitmap(6, 6, 0x00000000);
|
|
46
|
+
const b = createBitmap(6, 6, 0x00000000);
|
|
47
|
+
const sourceRegion = createBitmapRegion(source);
|
|
48
|
+
dissolveBitmapPixels(createBitmapRegion(a), sourceRegion, 3, 7);
|
|
49
|
+
dissolveBitmapPixels(createBitmapRegion(b), sourceRegion, 3, 7);
|
|
50
|
+
expect(Array.from(a.data)).toEqual(Array.from(b.data));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns a terminal seed once fully dissolved that no-ops on reuse', () => {
|
|
54
|
+
const source = createBitmap(4, 4, 0x445566ff);
|
|
55
|
+
const dest = createBitmap(4, 4, 0x00000000);
|
|
56
|
+
const sourceRegion = createBitmapRegion(source);
|
|
57
|
+
const destRegion = createBitmapRegion(dest);
|
|
58
|
+
|
|
59
|
+
let seed = 0;
|
|
60
|
+
for (let call = 0; call < 16; call++) seed = dissolveBitmapPixels(destRegion, sourceRegion, seed, 1);
|
|
61
|
+
expect(Array.from(dest.data)).toEqual(Array.from(source.data));
|
|
62
|
+
|
|
63
|
+
const afterComplete = dissolveBitmapPixels(destRegion, sourceRegion, seed, 4);
|
|
64
|
+
expect(afterComplete).toBe(seed);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('dissolves toward fillColor when source and dest are the same region', () => {
|
|
68
|
+
const bitmap = createBitmap(3, 3, 0x112233ff);
|
|
69
|
+
const region = createBitmapRegion(bitmap);
|
|
70
|
+
let seed = 0;
|
|
71
|
+
for (let call = 0; call < 9; call++) seed = dissolveBitmapPixels(region, region, seed, 1, 0x99887766);
|
|
72
|
+
for (let i = 0; i < 9; i++) expect(getBitmapPixel(bitmap, i % 3, (i / 3) | 0)).toBe(0x99887766);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('ignores fillColor and copies from source when regions differ', () => {
|
|
76
|
+
const source = createBitmap(2, 2, 0x0a0b0c0d);
|
|
77
|
+
const dest = createBitmap(2, 2, 0x00000000);
|
|
78
|
+
let seed = 0;
|
|
79
|
+
for (let call = 0; call < 4; call++) {
|
|
80
|
+
seed = dissolveBitmapPixels(createBitmapRegion(dest), createBitmapRegion(source), seed, 1, 0xffffffff);
|
|
81
|
+
}
|
|
82
|
+
expect(Array.from(dest.data)).toEqual(Array.from(source.data));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns the seed unchanged for a non-positive pixelCount without writing', () => {
|
|
86
|
+
const source = createBitmap(4, 4, 0x112233ff);
|
|
87
|
+
const dest = createBitmap(4, 4, 0x00000000);
|
|
88
|
+
const original = dest.data.slice();
|
|
89
|
+
expect(dissolveBitmapPixels(createBitmapRegion(dest), createBitmapRegion(source), 2, 0)).toBe(2);
|
|
90
|
+
expect(countChangedPixels(dest.data, original)).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('returns the seed for a zero-area region', () => {
|
|
94
|
+
const source = createBitmap(4, 4, 0x112233ff);
|
|
95
|
+
const dest = createBitmap(4, 4, 0x00000000);
|
|
96
|
+
const region = createBitmapRegion(dest, 0, 0, 0, 0);
|
|
97
|
+
expect(dissolveBitmapPixels(region, createBitmapRegion(source), 5, 10)).toBe(5);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { extractBitmapPixels } from './bitmapComposite';
|
|
3
|
+
import { drawBitmap } from './bitmapDraw';
|
|
4
|
+
import { setBitmapPixel } from './bitmapPixel';
|
|
5
|
+
|
|
6
|
+
function region(bitmap: ReturnType<typeof createBitmap>, x = 0, y = 0, width = bitmap.width, height = bitmap.height) {
|
|
7
|
+
return { bitmap, x, y, width, height };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function readExtractedPixel(buf: Uint8ClampedArray, regionWidth: number, x: number, y: number): number {
|
|
11
|
+
const i = (y * regionWidth + x) * 4;
|
|
12
|
+
return ((buf[i] << 24) | (buf[i + 1] << 16) | (buf[i + 2] << 8) | buf[i + 3]) >>> 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('drawBitmap', () => {
|
|
16
|
+
it('does not throw when drawing onto a canvas', () => {
|
|
17
|
+
const src = createBitmap(2, 2, 0x112233ff);
|
|
18
|
+
const canvas = document.createElement('canvas');
|
|
19
|
+
canvas.width = 4;
|
|
20
|
+
canvas.height = 4;
|
|
21
|
+
expect(() => drawBitmap(canvas, region(src), 0, 0)).not.toThrow();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('does not throw when drawing at an offset', () => {
|
|
25
|
+
const src = createBitmap(2, 2, 0x112233ff);
|
|
26
|
+
const canvas = document.createElement('canvas');
|
|
27
|
+
canvas.width = 8;
|
|
28
|
+
canvas.height = 8;
|
|
29
|
+
expect(() => drawBitmap(canvas, region(src), 2, 2)).not.toThrow();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('extracts correct pixels from a filled source', () => {
|
|
33
|
+
const src = createBitmap(4, 4, 0xff0000ff);
|
|
34
|
+
const r = region(src);
|
|
35
|
+
const buf = new Uint8ClampedArray(r.width * r.height * 4);
|
|
36
|
+
extractBitmapPixels(buf, r);
|
|
37
|
+
expect(readExtractedPixel(buf, r.width, 0, 0)).toBe(0xff0000ff);
|
|
38
|
+
expect(readExtractedPixel(buf, r.width, 3, 3)).toBe(0xff0000ff);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('extracts correct pixels from a sub-region', () => {
|
|
42
|
+
const src = createBitmap(4, 4);
|
|
43
|
+
setBitmapPixel(src, 1, 1, 0xaabbccdd);
|
|
44
|
+
setBitmapPixel(src, 2, 2, 0x11223344);
|
|
45
|
+
const r = region(src, 1, 1, 2, 2);
|
|
46
|
+
const buf = new Uint8ClampedArray(r.width * r.height * 4);
|
|
47
|
+
extractBitmapPixels(buf, r);
|
|
48
|
+
expect(readExtractedPixel(buf, r.width, 0, 0)).toBe(0xaabbccdd);
|
|
49
|
+
expect(readExtractedPixel(buf, r.width, 1, 1)).toBe(0x11223344);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('extracts zeroes from an empty bitmap', () => {
|
|
53
|
+
const src = createBitmap(2, 2);
|
|
54
|
+
const r = region(src);
|
|
55
|
+
const buf = new Uint8ClampedArray(r.width * r.height * 4);
|
|
56
|
+
extractBitmapPixels(buf, r);
|
|
57
|
+
expect(readExtractedPixel(buf, r.width, 0, 0)).toBe(0x00000000);
|
|
58
|
+
expect(readExtractedPixel(buf, r.width, 1, 1)).toBe(0x00000000);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('is a no-op for a zero-dimension region', () => {
|
|
62
|
+
const src = createBitmap(2, 2, 0x112233ff);
|
|
63
|
+
const canvas = document.createElement('canvas');
|
|
64
|
+
canvas.width = 4;
|
|
65
|
+
canvas.height = 4;
|
|
66
|
+
expect(() => drawBitmap(canvas, region(src, 0, 0, 0, 0), 0, 0)).not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createBitmap } from './bitmap';
|
|
2
|
+
import { encodeBitmap } from './bitmapEncode';
|
|
3
|
+
|
|
4
|
+
describe('encodeBitmap', () => {
|
|
5
|
+
it('returns a non-empty Uint8Array for a simple image', () => {
|
|
6
|
+
const img = createBitmap(2, 2, 0x112233ff);
|
|
7
|
+
const bytes = encodeBitmap(img);
|
|
8
|
+
expect(bytes).toBeInstanceOf(Uint8Array);
|
|
9
|
+
expect(bytes.length).toBeGreaterThan(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('accepts jpeg format', () => {
|
|
13
|
+
const img = createBitmap(2, 2, 0x112233ff);
|
|
14
|
+
const bytes = encodeBitmap(img, 'jpeg');
|
|
15
|
+
expect(bytes).toBeInstanceOf(Uint8Array);
|
|
16
|
+
expect(bytes.length).toBeGreaterThan(0);
|
|
17
|
+
});
|
|
18
|
+
});
|