@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.
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/surface.d.ts +16 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +84 -0
- package/dist/surface.js.map +1 -0
- package/dist/surfaceAffine.d.ts +27 -0
- package/dist/surfaceAffine.d.ts.map +1 -0
- package/dist/surfaceAffine.js +159 -0
- package/dist/surfaceAffine.js.map +1 -0
- package/dist/surfaceAlpha.d.ts +27 -0
- package/dist/surfaceAlpha.d.ts.map +1 -0
- package/dist/surfaceAlpha.js +83 -0
- package/dist/surfaceAlpha.js.map +1 -0
- package/dist/surfaceBevel.d.ts +42 -0
- package/dist/surfaceBevel.d.ts.map +1 -0
- package/dist/surfaceBevel.js +102 -0
- package/dist/surfaceBevel.js.map +1 -0
- package/dist/surfaceBlur.d.ts +62 -0
- package/dist/surfaceBlur.d.ts.map +1 -0
- package/dist/surfaceBlur.js +263 -0
- package/dist/surfaceBlur.js.map +1 -0
- package/dist/surfaceChannel.d.ts +24 -0
- package/dist/surfaceChannel.d.ts.map +1 -0
- package/dist/surfaceChannel.js +126 -0
- package/dist/surfaceChannel.js.map +1 -0
- package/dist/surfaceColorMatrix.d.ts +57 -0
- package/dist/surfaceColorMatrix.d.ts.map +1 -0
- package/dist/surfaceColorMatrix.js +127 -0
- package/dist/surfaceColorMatrix.js.map +1 -0
- package/dist/surfaceCompare.d.ts +18 -0
- package/dist/surfaceCompare.d.ts.map +1 -0
- package/dist/surfaceCompare.js +64 -0
- package/dist/surfaceCompare.js.map +1 -0
- package/dist/surfaceComposite.d.ts +53 -0
- package/dist/surfaceComposite.d.ts.map +1 -0
- package/dist/surfaceComposite.js +230 -0
- package/dist/surfaceComposite.js.map +1 -0
- package/dist/surfaceConvolution.d.ts +27 -0
- package/dist/surfaceConvolution.d.ts.map +1 -0
- package/dist/surfaceConvolution.js +103 -0
- package/dist/surfaceConvolution.js.map +1 -0
- package/dist/surfaceCopy.d.ts +23 -0
- package/dist/surfaceCopy.d.ts.map +1 -0
- package/dist/surfaceCopy.js +78 -0
- package/dist/surfaceCopy.js.map +1 -0
- package/dist/surfaceCoverage.d.ts +11 -0
- package/dist/surfaceCoverage.d.ts.map +1 -0
- package/dist/surfaceCoverage.js +29 -0
- package/dist/surfaceCoverage.js.map +1 -0
- package/dist/surfaceCrop.d.ts +33 -0
- package/dist/surfaceCrop.d.ts.map +1 -0
- package/dist/surfaceCrop.js +174 -0
- package/dist/surfaceCrop.js.map +1 -0
- package/dist/surfaceDisplacement.d.ts +48 -0
- package/dist/surfaceDisplacement.d.ts.map +1 -0
- package/dist/surfaceDisplacement.js +131 -0
- package/dist/surfaceDisplacement.js.map +1 -0
- package/dist/surfaceDissolve.d.ts +21 -0
- package/dist/surfaceDissolve.d.ts.map +1 -0
- package/dist/surfaceDissolve.js +97 -0
- package/dist/surfaceDissolve.js.map +1 -0
- package/dist/surfaceDraw.d.ts +8 -0
- package/dist/surfaceDraw.d.ts.map +1 -0
- package/dist/surfaceDraw.js +14 -0
- package/dist/surfaceDraw.js.map +1 -0
- package/dist/surfaceEncode.d.ts +3 -0
- package/dist/surfaceEncode.d.ts.map +1 -0
- package/dist/surfaceEncode.js +18 -0
- package/dist/surfaceEncode.js.map +1 -0
- package/dist/surfaceFill.d.ts +12 -0
- package/dist/surfaceFill.d.ts.map +1 -0
- package/dist/surfaceFill.js +85 -0
- package/dist/surfaceFill.js.map +1 -0
- package/dist/surfaceFingerprint.d.ts +26 -0
- package/dist/surfaceFingerprint.d.ts.map +1 -0
- package/dist/surfaceFingerprint.js +99 -0
- package/dist/surfaceFingerprint.js.map +1 -0
- package/dist/surfaceFlip.d.ts +16 -0
- package/dist/surfaceFlip.d.ts.map +1 -0
- package/dist/surfaceFlip.js +98 -0
- package/dist/surfaceFlip.js.map +1 -0
- package/dist/surfaceFormat.d.ts +26 -0
- package/dist/surfaceFormat.d.ts.map +1 -0
- package/dist/surfaceFormat.js +80 -0
- package/dist/surfaceFormat.js.map +1 -0
- package/dist/surfaceFrom.d.ts +14 -0
- package/dist/surfaceFrom.d.ts.map +1 -0
- package/dist/surfaceFrom.js +87 -0
- package/dist/surfaceFrom.js.map +1 -0
- package/dist/surfaceGradient.d.ts +57 -0
- package/dist/surfaceGradient.d.ts.map +1 -0
- package/dist/surfaceGradient.js +176 -0
- package/dist/surfaceGradient.js.map +1 -0
- package/dist/surfaceGradientFill.d.ts +32 -0
- package/dist/surfaceGradientFill.d.ts.map +1 -0
- package/dist/surfaceGradientFill.js +120 -0
- package/dist/surfaceGradientFill.js.map +1 -0
- package/dist/surfaceHistogram.d.ts +20 -0
- package/dist/surfaceHistogram.d.ts.map +1 -0
- package/dist/surfaceHistogram.js +61 -0
- package/dist/surfaceHistogram.js.map +1 -0
- package/dist/surfaceImageChannel.d.ts +2 -0
- package/dist/surfaceImageChannel.d.ts.map +1 -0
- package/dist/surfaceImageChannel.js +2 -0
- package/dist/surfaceImageChannel.js.map +1 -0
- package/dist/surfaceMedian.d.ts +14 -0
- package/dist/surfaceMedian.d.ts.map +1 -0
- package/dist/surfaceMedian.js +77 -0
- package/dist/surfaceMedian.js.map +1 -0
- package/dist/surfaceMorphological.d.ts +29 -0
- package/dist/surfaceMorphological.d.ts.map +1 -0
- package/dist/surfaceMorphological.js +81 -0
- package/dist/surfaceMorphological.js.map +1 -0
- package/dist/surfaceNoise.d.ts +48 -0
- package/dist/surfaceNoise.d.ts.map +1 -0
- package/dist/surfaceNoise.js +245 -0
- package/dist/surfaceNoise.js.map +1 -0
- package/dist/surfacePaletteMap.d.ts +17 -0
- package/dist/surfacePaletteMap.d.ts.map +1 -0
- package/dist/surfacePaletteMap.js +45 -0
- package/dist/surfacePaletteMap.js.map +1 -0
- package/dist/surfacePixel.d.ts +9 -0
- package/dist/surfacePixel.d.ts.map +1 -0
- package/dist/surfacePixel.js +36 -0
- package/dist/surfacePixel.js.map +1 -0
- package/dist/surfacePixelate.d.ts +13 -0
- package/dist/surfacePixelate.d.ts.map +1 -0
- package/dist/surfacePixelate.js +61 -0
- package/dist/surfacePixelate.js.map +1 -0
- package/dist/surfaceQuery.d.ts +13 -0
- package/dist/surfaceQuery.d.ts.map +1 -0
- package/dist/surfaceQuery.js +46 -0
- package/dist/surfaceQuery.js.map +1 -0
- package/dist/surfaceRegion.d.ts +20 -0
- package/dist/surfaceRegion.d.ts.map +1 -0
- package/dist/surfaceRegion.js +28 -0
- package/dist/surfaceRegion.js.map +1 -0
- package/dist/surfaceResize.d.ts +27 -0
- package/dist/surfaceResize.d.ts.map +1 -0
- package/dist/surfaceResize.js +191 -0
- package/dist/surfaceResize.js.map +1 -0
- package/dist/surfaceRotate.d.ts +32 -0
- package/dist/surfaceRotate.d.ts.map +1 -0
- package/dist/surfaceRotate.js +143 -0
- package/dist/surfaceRotate.js.map +1 -0
- package/dist/surfaceShadow.d.ts +107 -0
- package/dist/surfaceShadow.d.ts.map +1 -0
- package/dist/surfaceShadow.js +173 -0
- package/dist/surfaceShadow.js.map +1 -0
- package/dist/surfaceSharpen.d.ts +25 -0
- package/dist/surfaceSharpen.d.ts.map +1 -0
- package/dist/surfaceSharpen.js +47 -0
- package/dist/surfaceSharpen.js.map +1 -0
- package/dist/surfaceTone.d.ts +28 -0
- package/dist/surfaceTone.d.ts.map +1 -0
- package/dist/surfaceTone.js +69 -0
- package/dist/surfaceTone.js.map +1 -0
- package/dist/surfaceTransform.d.ts +40 -0
- package/dist/surfaceTransform.d.ts.map +1 -0
- package/dist/surfaceTransform.js +163 -0
- package/dist/surfaceTransform.js.map +1 -0
- package/dist/surfaceWarp.d.ts +48 -0
- package/dist/surfaceWarp.d.ts.map +1 -0
- package/dist/surfaceWarp.js +284 -0
- package/dist/surfaceWarp.js.map +1 -0
- package/package.json +39 -0
- package/src/surface.test.ts +78 -0
- package/src/surfaceAffine.test.ts +98 -0
- package/src/surfaceAlpha.test.ts +113 -0
- package/src/surfaceBevel.test.ts +67 -0
- package/src/surfaceBlur.test.ts +240 -0
- package/src/surfaceChannel.test.ts +97 -0
- package/src/surfaceColorMatrix.test.ts +220 -0
- package/src/surfaceCompare.test.ts +92 -0
- package/src/surfaceComposite.test.ts +256 -0
- package/src/surfaceConvolution.test.ts +136 -0
- package/src/surfaceCopy.test.ts +54 -0
- package/src/surfaceCoverage.test.ts +39 -0
- package/src/surfaceCrop.test.ts +116 -0
- package/src/surfaceDisplacement.test.ts +142 -0
- package/src/surfaceDissolve.test.ts +99 -0
- package/src/surfaceDraw.test.ts +38 -0
- package/src/surfaceEncode.test.ts +18 -0
- package/src/surfaceFill.test.ts +89 -0
- package/src/surfaceFingerprint.test.ts +103 -0
- package/src/surfaceFlip.test.ts +59 -0
- package/src/surfaceFormat.test.ts +124 -0
- package/src/surfaceFrom.test.ts +62 -0
- package/src/surfaceGradient.test.ts +91 -0
- package/src/surfaceGradientFill.test.ts +107 -0
- package/src/surfaceHistogram.test.ts +70 -0
- package/src/surfaceImageChannel.test.ts +19 -0
- package/src/surfaceMedian.test.ts +45 -0
- package/src/surfaceMorphological.test.ts +75 -0
- package/src/surfaceNoise.test.ts +169 -0
- package/src/surfacePaletteMap.test.ts +55 -0
- package/src/surfacePixel.test.ts +99 -0
- package/src/surfacePixelate.test.ts +46 -0
- package/src/surfaceQuery.test.ts +48 -0
- package/src/surfaceRegion.test.ts +34 -0
- package/src/surfaceResize.test.ts +112 -0
- package/src/surfaceRotate.test.ts +119 -0
- package/src/surfaceShadow.test.ts +165 -0
- package/src/surfaceSharpen.test.ts +50 -0
- package/src/surfaceTone.test.ts +104 -0
- package/src/surfaceTransform.test.ts +134 -0
- package/src/surfaceWarp.test.ts +124 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createSurface } from './surface';
|
|
2
|
+
import { getSurfacePixel } from './surfacePixel';
|
|
3
|
+
import {
|
|
4
|
+
rotateSurface,
|
|
5
|
+
rotateSurface180,
|
|
6
|
+
rotateSurfaceClockwise,
|
|
7
|
+
rotateSurfaceCounterClockwise,
|
|
8
|
+
} from './surfaceRotate';
|
|
9
|
+
|
|
10
|
+
function region(
|
|
11
|
+
surface: ReturnType<typeof createSurface>,
|
|
12
|
+
x = 0,
|
|
13
|
+
y = 0,
|
|
14
|
+
width = surface.width,
|
|
15
|
+
height = surface.height,
|
|
16
|
+
) {
|
|
17
|
+
return { surface, x, y, width, height };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 2x1 surface: pixel (0,0) = A, pixel (1,0) = B.
|
|
21
|
+
function ab() {
|
|
22
|
+
const surface = createSurface(2, 1);
|
|
23
|
+
surface.data.set([0xa0, 0, 0, 255], 0);
|
|
24
|
+
surface.data.set([0xb0, 0, 0, 255], 4);
|
|
25
|
+
return surface;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('rotateSurface', () => {
|
|
29
|
+
it('with angle 0 copies source into dest', () => {
|
|
30
|
+
const source = createSurface(3, 3, 0xff0000ff);
|
|
31
|
+
const out = createSurface(3, 3);
|
|
32
|
+
rotateSurface(region(out), region(source), 0);
|
|
33
|
+
expect(out.data[0]).toBe(0xff);
|
|
34
|
+
expect(out.data[3]).toBe(0xff);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('with angle π reverses source similar to rotateSurface180', () => {
|
|
38
|
+
const source = ab();
|
|
39
|
+
const out = createSurface(2, 1);
|
|
40
|
+
rotateSurface(region(out), region(source), Math.PI);
|
|
41
|
+
// Bilinear introduces slight blending, so just check direction
|
|
42
|
+
expect(out.data[0]).toBeGreaterThan(out.data[4]); // B-side (brighter) on left
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('clamp edge mode (default) repeats edge pixels for out-of-bounds', () => {
|
|
46
|
+
const source = createSurface(1, 1, 0xff0000ff);
|
|
47
|
+
const out = createSurface(5, 5);
|
|
48
|
+
rotateSurface(region(out), region(source), 0);
|
|
49
|
+
// With clamp, all pixels map to the single source pixel
|
|
50
|
+
expect(out.data[(0 * 5 + 0) * 4 + 3]).toBe(0xff);
|
|
51
|
+
expect(out.data[(2 * 5 + 2) * 4 + 3]).toBe(0xff);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('transparent edge mode writes transparent black for out-of-bounds', () => {
|
|
55
|
+
const source = createSurface(1, 1, 0xff0000ff);
|
|
56
|
+
const out = createSurface(5, 5);
|
|
57
|
+
rotateSurface(region(out), region(source), 0, undefined, undefined, 'transparent');
|
|
58
|
+
// Only center pixel maps back to source; corners are out of bounds
|
|
59
|
+
expect(out.data[(0 * 5 + 0) * 4 + 3]).toBe(0);
|
|
60
|
+
expect(out.data[(2 * 5 + 2) * 4 + 3]).toBe(0xff);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('wrap edge mode tiles the source', () => {
|
|
64
|
+
const source = createSurface(2, 2, 0xff0000ff);
|
|
65
|
+
const out = createSurface(4, 4);
|
|
66
|
+
rotateSurface(region(out), region(source), 0, undefined, undefined, 'wrap');
|
|
67
|
+
// All pixels should be opaque red (wraps the 2x2 source)
|
|
68
|
+
expect(out.data[3]).toBe(0xff);
|
|
69
|
+
expect(out.data[(3 * 4 + 3) * 4 + 3]).toBe(0xff);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('nearest sample mode uses nearest-neighbor interpolation', () => {
|
|
73
|
+
const source = createSurface(2, 1);
|
|
74
|
+
source.data.set([0, 0, 0, 255], 0);
|
|
75
|
+
source.data.set([200, 200, 200, 255], 4);
|
|
76
|
+
const out = createSurface(2, 1);
|
|
77
|
+
rotateSurface(region(out), region(source), 0, undefined, undefined, 'clamp', 'nearest');
|
|
78
|
+
// Nearest should produce exact pixel values, no blending
|
|
79
|
+
expect(out.data[0]).toBe(0);
|
|
80
|
+
expect(out.data[4]).toBe(200);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('rotateSurface180', () => {
|
|
85
|
+
it('reverses pixel order with matching dimensions', () => {
|
|
86
|
+
const source = ab();
|
|
87
|
+
const out = createSurface(2, 1);
|
|
88
|
+
rotateSurface180(region(out), region(source));
|
|
89
|
+
expect(out.data[0]).toBe(0xb0);
|
|
90
|
+
expect(out.data[4]).toBe(0xa0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('rotates in place when dest and source are the same region', () => {
|
|
94
|
+
const surface = ab();
|
|
95
|
+
rotateSurface180(region(surface), region(surface));
|
|
96
|
+
expect(surface.data[0]).toBe(0xb0);
|
|
97
|
+
expect(surface.data[4]).toBe(0xa0);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('rotateSurfaceClockwise', () => {
|
|
102
|
+
it('rotates a row into a column, left element on top', () => {
|
|
103
|
+
const source = ab();
|
|
104
|
+
const out = createSurface(1, 2);
|
|
105
|
+
rotateSurfaceClockwise(region(out), region(source));
|
|
106
|
+
expect(getSurfacePixel(out, 0, 0)).toBe(getSurfacePixel(source, 0, 0));
|
|
107
|
+
expect(getSurfacePixel(out, 0, 1)).toBe(getSurfacePixel(source, 1, 0));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('rotateSurfaceCounterClockwise', () => {
|
|
112
|
+
it('rotates a row into a column, right element on top', () => {
|
|
113
|
+
const source = ab();
|
|
114
|
+
const out = createSurface(1, 2);
|
|
115
|
+
rotateSurfaceCounterClockwise(region(out), region(source));
|
|
116
|
+
expect(getSurfacePixel(out, 0, 0)).toBe(getSurfacePixel(source, 1, 0));
|
|
117
|
+
expect(getSurfacePixel(out, 0, 1)).toBe(getSurfacePixel(source, 0, 0));
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createSurface } from './surface';
|
|
2
|
+
import { compositeSurfacePixels, compositeSurfaceRegion } from './surfaceComposite';
|
|
3
|
+
import { dropShadowSurface, glowSurface, innerGlowSurface, innerShadowSurface } from './surfaceShadow';
|
|
4
|
+
|
|
5
|
+
function region(
|
|
6
|
+
surface: ReturnType<typeof createSurface>,
|
|
7
|
+
x = 0,
|
|
8
|
+
y = 0,
|
|
9
|
+
width = surface.width,
|
|
10
|
+
height = surface.height,
|
|
11
|
+
) {
|
|
12
|
+
return { surface, x, y, width, height };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('dropShadowSurface', () => {
|
|
16
|
+
it('produces a tinted alpha mask in out', () => {
|
|
17
|
+
const source = createSurface(1, 1, 0xffffffff);
|
|
18
|
+
const out = new Uint8ClampedArray(4);
|
|
19
|
+
const scratch = new Uint8ClampedArray(4);
|
|
20
|
+
dropShadowSurface(out, scratch, region(source), { radiusX: 0, radiusY: 0, color: 0x0000ffff });
|
|
21
|
+
expect(out[0]).toBe(0);
|
|
22
|
+
expect(out[2]).toBe(0xff);
|
|
23
|
+
expect(out[3]).toBe(0xff);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('compositing out at an offset places the shadow correctly', () => {
|
|
27
|
+
const source = createSurface(1, 1, 0xffffffff);
|
|
28
|
+
const dest = createSurface(4, 4);
|
|
29
|
+
const out = new Uint8ClampedArray(4);
|
|
30
|
+
const scratch = new Uint8ClampedArray(4);
|
|
31
|
+
dropShadowSurface(out, scratch, region(source), { radiusX: 0, radiusY: 0, color: 0x0000ffff });
|
|
32
|
+
const angle = (0 * Math.PI) / 180;
|
|
33
|
+
const offsetX = Math.round(Math.cos(angle) * 1);
|
|
34
|
+
const offsetY = Math.round(Math.sin(angle) * 1);
|
|
35
|
+
compositeSurfacePixels(region(dest, 1 + offsetX, 1 + offsetY, 1, 1), out);
|
|
36
|
+
const i = (1 * dest.width + 2) * 4;
|
|
37
|
+
expect(dest.data[i + 2]).toBe(0xff);
|
|
38
|
+
expect(dest.data[i + 3]).toBe(0xff);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('compositing shadow then source produces source-over-shadow', () => {
|
|
42
|
+
const source = createSurface(1, 1, 0xff0000ff);
|
|
43
|
+
const dest = createSurface(2, 1);
|
|
44
|
+
const out = new Uint8ClampedArray(4);
|
|
45
|
+
const scratch = new Uint8ClampedArray(4);
|
|
46
|
+
dropShadowSurface(out, scratch, region(source), { radiusX: 0, radiusY: 0, color: 0x0000ffff });
|
|
47
|
+
compositeSurfacePixels(region(dest, 1, 0, 1, 1), out);
|
|
48
|
+
compositeSurfaceRegion(region(dest, 0, 0, 1, 1), region(source));
|
|
49
|
+
expect(dest.data[0]).toBe(0xff);
|
|
50
|
+
expect(dest.data[3]).toBe(0xff);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('source.surface.data can be used as out for a full-surface region', () => {
|
|
54
|
+
const surface = createSurface(1, 1, 0xffffffff);
|
|
55
|
+
const scratch = new Uint8ClampedArray(4);
|
|
56
|
+
dropShadowSurface(surface.data, scratch, region(surface), { radiusX: 0, radiusY: 0, color: 0x0000ffff });
|
|
57
|
+
expect(surface.data[2]).toBe(0xff); // tinted blue
|
|
58
|
+
expect(surface.data[3]).toBe(0xff); // alpha carried from source
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('glowSurface', () => {
|
|
63
|
+
it('produces a tinted alpha mask in out', () => {
|
|
64
|
+
const source = createSurface(1, 1, 0xffffffff);
|
|
65
|
+
const out = new Uint8ClampedArray(4);
|
|
66
|
+
const scratch = new Uint8ClampedArray(4);
|
|
67
|
+
glowSurface(out, scratch, region(source), { radiusX: 0, radiusY: 0, color: 0x00ff00ff });
|
|
68
|
+
expect(out[1]).toBe(0xff);
|
|
69
|
+
expect(out[3]).toBe(0xff);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('compositing out places the glow at the same position', () => {
|
|
73
|
+
const source = createSurface(1, 1, 0xffffffff);
|
|
74
|
+
const dest = createSurface(1, 1);
|
|
75
|
+
const out = new Uint8ClampedArray(4);
|
|
76
|
+
const scratch = new Uint8ClampedArray(4);
|
|
77
|
+
glowSurface(out, scratch, region(source), { radiusX: 0, radiusY: 0, color: 0x00ff00ff });
|
|
78
|
+
compositeSurfacePixels(region(dest), out);
|
|
79
|
+
expect(dest.data[1]).toBe(0xff);
|
|
80
|
+
expect(dest.data[3]).toBe(0xff);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('compositing source over glow produces source-over-glow', () => {
|
|
84
|
+
const source = createSurface(1, 1, 0xff0000ff);
|
|
85
|
+
const dest = createSurface(1, 1);
|
|
86
|
+
const out = new Uint8ClampedArray(4);
|
|
87
|
+
const scratch = new Uint8ClampedArray(4);
|
|
88
|
+
glowSurface(out, scratch, region(source), { radiusX: 0, radiusY: 0, color: 0x00ff00ff });
|
|
89
|
+
compositeSurfacePixels(region(dest), out);
|
|
90
|
+
compositeSurfaceRegion(region(dest), region(source));
|
|
91
|
+
expect(dest.data[0]).toBe(0xff);
|
|
92
|
+
expect(dest.data[3]).toBe(0xff);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('source.surface.data can be used as out for a full-surface region', () => {
|
|
96
|
+
const surface = createSurface(1, 1, 0xffffffff);
|
|
97
|
+
const scratch = new Uint8ClampedArray(4);
|
|
98
|
+
glowSurface(surface.data, scratch, region(surface), { radiusX: 0, radiusY: 0, color: 0x00ff00ff });
|
|
99
|
+
expect(surface.data[1]).toBe(0xff);
|
|
100
|
+
expect(surface.data[3]).toBe(0xff);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('innerGlowSurface', () => {
|
|
105
|
+
it('clips the glow to inside the shape and tints it', () => {
|
|
106
|
+
// 3x1: transparent | opaque | transparent. The inner glow appears only on
|
|
107
|
+
// the opaque pixel; transparent pixels (outside the shape) stay at 0 alpha.
|
|
108
|
+
const source = createSurface(3, 1);
|
|
109
|
+
source.data[1 * 4 + 3] = 255;
|
|
110
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
111
|
+
const scratch = new Uint8ClampedArray(3 * 4);
|
|
112
|
+
innerGlowSurface(out, scratch, region(source), { radiusX: 2, radiusY: 0, color: 0x00ff00ff });
|
|
113
|
+
expect(out[0 * 4 + 3]).toBe(0);
|
|
114
|
+
expect(out[2 * 4 + 3]).toBe(0);
|
|
115
|
+
expect(out[1 * 4 + 3]).toBe(170);
|
|
116
|
+
expect(out[1 * 4 + 0]).toBe(0);
|
|
117
|
+
expect(out[1 * 4 + 1]).toBe(0xff);
|
|
118
|
+
expect(out[1 * 4 + 2]).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('produces no inner glow when blur is zero', () => {
|
|
122
|
+
const source = createSurface(1, 1, 0x0000ffff);
|
|
123
|
+
const out = new Uint8ClampedArray(4);
|
|
124
|
+
const scratch = new Uint8ClampedArray(4);
|
|
125
|
+
innerGlowSurface(out, scratch, region(source), { radiusX: 0, radiusY: 0 });
|
|
126
|
+
expect(out[3]).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('innerShadowSurface', () => {
|
|
131
|
+
it('defaults to a black tint', () => {
|
|
132
|
+
const source = createSurface(3, 1);
|
|
133
|
+
source.data[1 * 4 + 3] = 255;
|
|
134
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
135
|
+
const scratch = new Uint8ClampedArray(3 * 4);
|
|
136
|
+
innerShadowSurface(out, scratch, region(source), { radiusX: 2, radiusY: 0 });
|
|
137
|
+
expect(out[1 * 4 + 0]).toBe(0);
|
|
138
|
+
expect(out[1 * 4 + 1]).toBe(0);
|
|
139
|
+
expect(out[1 * 4 + 2]).toBe(0);
|
|
140
|
+
expect(out[1 * 4 + 3]).toBe(170);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('gathers the shadow toward one edge when given a directional offset', () => {
|
|
144
|
+
// A 3-px-wide filled bar (px 1..3) inside a 5-px row; px 0 and 4 are exterior.
|
|
145
|
+
const source = createSurface(5, 1);
|
|
146
|
+
for (let px = 1; px <= 3; px++) source.data[px * 4 + 3] = 255;
|
|
147
|
+
const alphaAt = (offsetX: number): number[] => {
|
|
148
|
+
const out = new Uint8ClampedArray(5 * 4);
|
|
149
|
+
const scratch = new Uint8ClampedArray(5 * 4);
|
|
150
|
+
innerShadowSurface(out, scratch, region(source), { offsetX, radiusX: 1, radiusY: 0 });
|
|
151
|
+
return [out[1 * 4 + 3], out[2 * 4 + 3], out[3 * 4 + 3]];
|
|
152
|
+
};
|
|
153
|
+
// Zero offset rings the boundary evenly: the two interior edges match.
|
|
154
|
+
const centered = alphaAt(0);
|
|
155
|
+
expect(centered[0]).toBe(centered[2]);
|
|
156
|
+
// A positive offset pulls the exterior in from the left, so the shadow gathers on the left edge.
|
|
157
|
+
const shifted = alphaAt(2);
|
|
158
|
+
expect(shifted[0]).toBeGreaterThan(shifted[2]);
|
|
159
|
+
// The opposite offset mirrors it.
|
|
160
|
+
const mirrored = alphaAt(-2);
|
|
161
|
+
expect(mirrored[2]).toBeGreaterThan(mirrored[0]);
|
|
162
|
+
expect(mirrored[0]).toBe(shifted[2]);
|
|
163
|
+
expect(mirrored[2]).toBe(shifted[0]);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createSurface } from './surface';
|
|
2
|
+
import { sharpenSurface } from './surfaceSharpen';
|
|
3
|
+
|
|
4
|
+
function region(
|
|
5
|
+
surface: ReturnType<typeof createSurface>,
|
|
6
|
+
x = 0,
|
|
7
|
+
y = 0,
|
|
8
|
+
width = surface.width,
|
|
9
|
+
height = surface.height,
|
|
10
|
+
) {
|
|
11
|
+
return { surface, x, y, width, height };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('sharpenSurface', () => {
|
|
15
|
+
it('amount 0 returns the source RGB and preserves alpha', () => {
|
|
16
|
+
const source = createSurface(3, 1);
|
|
17
|
+
source.data.set([10, 20, 30, 120], 4);
|
|
18
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
19
|
+
const scratch = new Uint8ClampedArray(3 * 4);
|
|
20
|
+
sharpenSurface(out, scratch, region(source), { amount: 0 });
|
|
21
|
+
expect(out[4]).toBe(10);
|
|
22
|
+
expect(out[5]).toBe(20);
|
|
23
|
+
expect(out[6]).toBe(30);
|
|
24
|
+
expect(out[7]).toBe(120);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('accentuates a bright center pixel above its original value', () => {
|
|
28
|
+
const source = createSurface(5, 1);
|
|
29
|
+
source.data[2 * 4] = 100;
|
|
30
|
+
for (let i = 0; i < 5; i++) source.data[i * 4 + 3] = 255;
|
|
31
|
+
const out = new Uint8ClampedArray(5 * 4);
|
|
32
|
+
const scratch = new Uint8ClampedArray(5 * 4);
|
|
33
|
+
sharpenSurface(out, scratch, region(source), { amount: 1, radiusX: 4, radiusY: 0 });
|
|
34
|
+
expect(out[2 * 4]).toBeGreaterThan(100);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('preserves source alpha while sharpening RGB', () => {
|
|
38
|
+
const source = createSurface(3, 1);
|
|
39
|
+
for (let i = 0; i < 3; i++) {
|
|
40
|
+
source.data[i * 4] = i * 40;
|
|
41
|
+
source.data[i * 4 + 3] = 77;
|
|
42
|
+
}
|
|
43
|
+
const out = new Uint8ClampedArray(3 * 4);
|
|
44
|
+
const scratch = new Uint8ClampedArray(3 * 4);
|
|
45
|
+
sharpenSurface(out, scratch, region(source), { amount: 2 });
|
|
46
|
+
expect(out[3]).toBe(77);
|
|
47
|
+
expect(out[7]).toBe(77);
|
|
48
|
+
expect(out[11]).toBe(77);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createSurface } from './surface';
|
|
2
|
+
import { getSurfacePixel, setSurfacePixel } from './surfacePixel';
|
|
3
|
+
import { createSurfaceRegion } from './surfaceRegion';
|
|
4
|
+
import { applySurfaceCurve, applySurfaceLevels } from './surfaceTone';
|
|
5
|
+
|
|
6
|
+
function buildIdentityLut() {
|
|
7
|
+
return Uint8Array.from({ length: 256 }, (_, i) => i);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function buildInvertLut() {
|
|
11
|
+
return Uint8Array.from({ length: 256 }, (_, i) => 255 - i);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('applySurfaceCurve', () => {
|
|
15
|
+
it('identity LUT leaves pixels unchanged', () => {
|
|
16
|
+
const surf = createSurface(2, 2, 0x11223344);
|
|
17
|
+
const lut = buildIdentityLut();
|
|
18
|
+
applySurfaceCurve(createSurfaceRegion(surf), createSurfaceRegion(surf), lut, lut, lut, lut);
|
|
19
|
+
expect(getSurfacePixel(surf, 0, 0)).toBe(0x11223344);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('invert LUT inverts RGB channels', () => {
|
|
23
|
+
const src = createSurface(1, 1);
|
|
24
|
+
setSurfacePixel(src, 0, 0, 0x00_ff_80_ff);
|
|
25
|
+
const dst = createSurface(1, 1);
|
|
26
|
+
const lut = buildInvertLut();
|
|
27
|
+
applySurfaceCurve(createSurfaceRegion(dst), createSurfaceRegion(src), lut, lut, lut, null);
|
|
28
|
+
const p = getSurfacePixel(dst, 0, 0);
|
|
29
|
+
expect((p >>> 24) & 0xff).toBe(0xff); // 0x00 inverted
|
|
30
|
+
expect((p >> 16) & 0xff).toBe(0x00); // 0xff inverted
|
|
31
|
+
expect((p >> 8) & 0xff).toBe(0x7f); // 0x80 inverted
|
|
32
|
+
expect(p & 0xff).toBe(0xff); // alpha passed through
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('null LUT passes channel through unchanged', () => {
|
|
36
|
+
const src = createSurface(1, 1, 0x80_80_80_80);
|
|
37
|
+
const dst = createSurface(1, 1);
|
|
38
|
+
const lut = buildInvertLut();
|
|
39
|
+
// Only invert R, leave G, B, A through.
|
|
40
|
+
applySurfaceCurve(createSurfaceRegion(dst), createSurfaceRegion(src), lut, null, null, null);
|
|
41
|
+
const p = getSurfacePixel(dst, 0, 0);
|
|
42
|
+
expect((p >>> 24) & 0xff).toBe(0x7f); // R inverted
|
|
43
|
+
expect((p >> 16) & 0xff).toBe(0x80); // G unchanged
|
|
44
|
+
expect((p >> 8) & 0xff).toBe(0x80); // B unchanged
|
|
45
|
+
expect(p & 0xff).toBe(0x80); // A unchanged
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('is alias-safe when out aliases source', () => {
|
|
49
|
+
const surf = createSurface(2, 1, 0xff_00_00_ff);
|
|
50
|
+
const lut = buildInvertLut();
|
|
51
|
+
applySurfaceCurve(createSurfaceRegion(surf), createSurfaceRegion(surf), lut, null, null, null);
|
|
52
|
+
// R (0xff) inverted to 0x00; others unchanged.
|
|
53
|
+
const p = getSurfacePixel(surf, 0, 0);
|
|
54
|
+
expect((p >>> 24) & 0xff).toBe(0x00);
|
|
55
|
+
expect((p >> 8) & 0xff).toBe(0x00);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('applySurfaceLevels', () => {
|
|
60
|
+
it('default black/white/gamma is an identity', () => {
|
|
61
|
+
const surf = createSurface(1, 1, 0x80_80_80_ff);
|
|
62
|
+
applySurfaceLevels(createSurfaceRegion(surf), createSurfaceRegion(surf));
|
|
63
|
+
expect((getSurfacePixel(surf, 0, 0) >>> 24) & 0xff).toBeCloseTo(0x80, -1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('black and white points clip the range', () => {
|
|
67
|
+
const surf = createSurface(1, 1);
|
|
68
|
+
setSurfacePixel(surf, 0, 0, 0x00_00_00_ff); // black pixel
|
|
69
|
+
applySurfaceLevels(createSurfaceRegion(surf), createSurfaceRegion(surf), 0, 128);
|
|
70
|
+
// A value at black point maps to 0 — stays 0.
|
|
71
|
+
expect((getSurfacePixel(surf, 0, 0) >>> 24) & 0xff).toBe(0);
|
|
72
|
+
setSurfacePixel(surf, 0, 0, 0x80_80_80_ff); // mid pixel at half of range
|
|
73
|
+
applySurfaceLevels(createSurfaceRegion(surf), createSurfaceRegion(surf), 0, 128);
|
|
74
|
+
// 128/128 = 1.0 → output 255.
|
|
75
|
+
expect((getSurfacePixel(surf, 0, 0) >>> 24) & 0xff).toBe(255);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('gamma < 1 lightens midtones', () => {
|
|
79
|
+
const src = createSurface(1, 1);
|
|
80
|
+
setSurfacePixel(src, 0, 0, 0x80_80_80_ff);
|
|
81
|
+
const dst = createSurface(1, 1);
|
|
82
|
+
applySurfaceLevels(createSurfaceRegion(dst), createSurfaceRegion(src), 0, 255, 0.5);
|
|
83
|
+
// gamma=0.5 → pow(0.5, 2) = 0.25 — darker than 0.5.
|
|
84
|
+
const v = (getSurfacePixel(dst, 0, 0) >>> 24) & 0xff;
|
|
85
|
+
expect(v).toBeLessThan(0x80);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('gamma > 1 darkens midtones', () => {
|
|
89
|
+
const src = createSurface(1, 1);
|
|
90
|
+
setSurfacePixel(src, 0, 0, 0x80_80_80_ff);
|
|
91
|
+
const dst = createSurface(1, 1);
|
|
92
|
+
applySurfaceLevels(createSurfaceRegion(dst), createSurfaceRegion(src), 0, 255, 2);
|
|
93
|
+
// gamma=2 → pow(0.5, 0.5) ≈ 0.707 → lighter.
|
|
94
|
+
const v = (getSurfacePixel(dst, 0, 0) >>> 24) & 0xff;
|
|
95
|
+
expect(v).toBeGreaterThan(0x80);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('alpha channel is passed through unchanged', () => {
|
|
99
|
+
const src = createSurface(1, 1, 0x80_80_80_aa);
|
|
100
|
+
const dst = createSurface(1, 1);
|
|
101
|
+
applySurfaceLevels(createSurfaceRegion(dst), createSurfaceRegion(src), 0, 255, 1);
|
|
102
|
+
expect(getSurfacePixel(dst, 0, 0) & 0xff).toBe(0xaa);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createSurface } from './surface';
|
|
2
|
+
import { getSurfacePixel, setSurfacePixel } from './surfacePixel';
|
|
3
|
+
import { applySurfaceColorTransform, applySurfaceThreshold, mergeSurface, scrollSurface } from './surfaceTransform';
|
|
4
|
+
|
|
5
|
+
function region(
|
|
6
|
+
surface: ReturnType<typeof createSurface>,
|
|
7
|
+
x = 0,
|
|
8
|
+
y = 0,
|
|
9
|
+
width = surface.width,
|
|
10
|
+
height = surface.height,
|
|
11
|
+
) {
|
|
12
|
+
return { surface, x, y, width, height };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const identity = {
|
|
16
|
+
redMultiplier: 1,
|
|
17
|
+
greenMultiplier: 1,
|
|
18
|
+
blueMultiplier: 1,
|
|
19
|
+
alphaMultiplier: 1,
|
|
20
|
+
redOffset: 0,
|
|
21
|
+
greenOffset: 0,
|
|
22
|
+
blueOffset: 0,
|
|
23
|
+
alphaOffset: 0,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('applySurfaceColorTransform', () => {
|
|
27
|
+
it('applies multiplier', () => {
|
|
28
|
+
const img = createSurface(2, 2, 0x808080ff);
|
|
29
|
+
applySurfaceColorTransform(region(img), region(img), { ...identity, redMultiplier: 0 });
|
|
30
|
+
expect(img.data[0]).toBe(0);
|
|
31
|
+
expect(img.data[1]).toBe(0x80);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('applies offset', () => {
|
|
35
|
+
const img = createSurface(1, 1);
|
|
36
|
+
setSurfacePixel(img, 0, 0, 0x000000ff);
|
|
37
|
+
applySurfaceColorTransform(region(img), region(img), { ...identity, redOffset: 100 });
|
|
38
|
+
expect(img.data[0]).toBe(100);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('clamps to 0-255', () => {
|
|
42
|
+
const img = createSurface(1, 1, 0x808080ff);
|
|
43
|
+
applySurfaceColorTransform(region(img), region(img), { ...identity, redMultiplier: 10, redOffset: 100 });
|
|
44
|
+
expect(img.data[0]).toBe(255);
|
|
45
|
+
applySurfaceColorTransform(region(img), region(img), { ...identity, redMultiplier: 0, redOffset: -100 });
|
|
46
|
+
expect(img.data[0]).toBe(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('only affects the specified rect', () => {
|
|
50
|
+
const img = createSurface(2, 2, 0x808080ff);
|
|
51
|
+
applySurfaceColorTransform(region(img, 0, 0, 1, 1), region(img, 0, 0, 1, 1), { ...identity, redMultiplier: 0 });
|
|
52
|
+
expect(img.data[0]).toBe(0);
|
|
53
|
+
expect(img.data[4]).toBe(0x80);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('can write to a separate output surface', () => {
|
|
57
|
+
const source = createSurface(1, 1, 0x808080ff);
|
|
58
|
+
const out = createSurface(1, 1);
|
|
59
|
+
applySurfaceColorTransform(region(out), region(source), { ...identity, redMultiplier: 0 });
|
|
60
|
+
expect(out.data[0]).toBe(0);
|
|
61
|
+
expect(out.data[1]).toBe(0x80);
|
|
62
|
+
expect(source.data[0]).toBe(0x80); // source unchanged
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('applySurfaceThreshold', () => {
|
|
67
|
+
it('replaces pixels that pass the test', () => {
|
|
68
|
+
const src = createSurface(2, 1);
|
|
69
|
+
setSurfacePixel(src, 0, 0, 0x808080ff);
|
|
70
|
+
setSurfacePixel(src, 1, 0, 0x404040ff);
|
|
71
|
+
const dst = createSurface(2, 1);
|
|
72
|
+
const count = applySurfaceThreshold(region(dst), region(src), '>', 0x607060ff, 0xffffffff);
|
|
73
|
+
expect(count).toBe(1);
|
|
74
|
+
expect(getSurfacePixel(dst, 0, 0)).toBe(0xffffffff);
|
|
75
|
+
expect(getSurfacePixel(dst, 1, 0)).toBe(0x00000000);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('copies source when copySource is true and test fails', () => {
|
|
79
|
+
const src = createSurface(1, 1, 0x112233ff);
|
|
80
|
+
const dst = createSurface(1, 1);
|
|
81
|
+
applySurfaceThreshold(region(dst), region(src), '>', 0xffffffff, 0xffffffff, 0xffffffff, true);
|
|
82
|
+
expect(getSurfacePixel(dst, 0, 0)).toBe(0x112233ff);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns zero when no pixels pass', () => {
|
|
86
|
+
const src = createSurface(2, 2, 0x000000ff);
|
|
87
|
+
const dst = createSurface(2, 2);
|
|
88
|
+
const count = applySurfaceThreshold(region(dst), region(src), '>', 0xffffffff);
|
|
89
|
+
expect(count).toBe(0);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('mergeSurface', () => {
|
|
94
|
+
it('with multiplier 1 copies source', () => {
|
|
95
|
+
const src = createSurface(1, 1, 0xff0000ff);
|
|
96
|
+
const dst = createSurface(1, 1, 0x0000ffff);
|
|
97
|
+
mergeSurface(region(dst), region(src), 1, 1, 1, 1);
|
|
98
|
+
expect(dst.data[0]).toBe(0xff);
|
|
99
|
+
expect(dst.data[2]).toBe(0x00);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('with multiplier 0 keeps destination', () => {
|
|
103
|
+
const src = createSurface(1, 1, 0xff0000ff);
|
|
104
|
+
const dst = createSurface(1, 1, 0x0000ffff);
|
|
105
|
+
mergeSurface(region(dst), region(src), 0, 0, 0, 0);
|
|
106
|
+
expect(dst.data[0]).toBe(0x00);
|
|
107
|
+
expect(dst.data[2]).toBe(0xff);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('with multiplier 0.5 blends evenly', () => {
|
|
111
|
+
const src = createSurface(1, 1, 0x200000ff);
|
|
112
|
+
const dst = createSurface(1, 1, 0x000020ff);
|
|
113
|
+
mergeSurface(region(dst), region(src), 0.5, 0, 0.5, 0);
|
|
114
|
+
expect(dst.data[0]).toBeCloseTo(16, 0);
|
|
115
|
+
expect(dst.data[2]).toBeCloseTo(16, 0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('scrollSurface', () => {
|
|
120
|
+
it('shifts content right with wrapping', () => {
|
|
121
|
+
const img = createSurface(4, 1);
|
|
122
|
+
setSurfacePixel(img, 0, 0, 0xff0000ff);
|
|
123
|
+
scrollSurface(img, 1, 0);
|
|
124
|
+
expect(getSurfacePixel(img, 1, 0)).toBe(0xff0000ff);
|
|
125
|
+
expect(getSurfacePixel(img, 0, 0)).toBe(0x00000000);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('wraps pixels around the edge', () => {
|
|
129
|
+
const img = createSurface(4, 1);
|
|
130
|
+
setSurfacePixel(img, 3, 0, 0xaabbccff);
|
|
131
|
+
scrollSurface(img, 1, 0);
|
|
132
|
+
expect(getSurfacePixel(img, 0, 0)).toBe(0xaabbccff);
|
|
133
|
+
});
|
|
134
|
+
});
|