pixel-data-js 0.17.1 → 0.18.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.
@@ -72,6 +72,7 @@ __export(src_exports, {
72
72
  fileToImageData: () => fileToImageData,
73
73
  fillPixelData: () => fillPixelData,
74
74
  floodFillSelection: () => floodFillSelection,
75
+ getCircleBrushBounds: () => getCircleBrushBounds,
75
76
  getImageDataFromClipboard: () => getImageDataFromClipboard,
76
77
  getIndexedImageColorCounts: () => getIndexedImageColorCounts,
77
78
  getRectBrushBounds: () => getRectBrushBounds,
@@ -1925,6 +1926,148 @@ var PixelEngineConfig = class {
1925
1926
  }
1926
1927
  };
1927
1928
 
1929
+ // src/PixelData/applyCircleBrushToPixelData.ts
1930
+ function applyCircleBrushToPixelData(target, color, centerX, centerY, brushSize, alpha = 255, fallOff, blendFn = sourceOverPerfect, bounds) {
1931
+ const targetWidth = target.width;
1932
+ const targetHeight = target.height;
1933
+ const b = bounds ?? getCircleBrushBounds(
1934
+ centerX,
1935
+ centerY,
1936
+ brushSize,
1937
+ targetWidth,
1938
+ targetHeight
1939
+ );
1940
+ if (b.w <= 0 || b.h <= 0) return;
1941
+ const data32 = target.data32;
1942
+ const r = brushSize / 2;
1943
+ const rSqr = r * r;
1944
+ const invR = 1 / r;
1945
+ const centerOffset = brushSize % 2 === 0 ? 0.5 : 0;
1946
+ const baseColor = color & 16777215;
1947
+ const constantSrc = (alpha << 24 | baseColor) >>> 0;
1948
+ const endX = b.x + b.w;
1949
+ const endY = b.y + b.h;
1950
+ const fCenterX = Math.floor(centerX);
1951
+ const fCenterY = Math.floor(centerY);
1952
+ for (let cy = b.y; cy < endY; cy++) {
1953
+ const relY = cy - fCenterY + centerOffset;
1954
+ const dySqr = relY * relY;
1955
+ const rowOffset = cy * targetWidth;
1956
+ for (let cx = b.x; cx < endX; cx++) {
1957
+ const relX = cx - fCenterX + centerOffset;
1958
+ const dSqr = relX * relX + dySqr;
1959
+ if (dSqr <= rSqr) {
1960
+ const idx = rowOffset + cx;
1961
+ if (fallOff) {
1962
+ const strength = fallOff(Math.sqrt(dSqr) * invR);
1963
+ const fAlpha = alpha * strength & 255;
1964
+ const src = (fAlpha << 24 | baseColor) >>> 0;
1965
+ data32[idx] = blendFn(src, data32[idx]);
1966
+ } else {
1967
+ data32[idx] = blendFn(constantSrc, data32[idx]);
1968
+ }
1969
+ }
1970
+ }
1971
+ }
1972
+ }
1973
+ function getCircleBrushBounds(centerX, centerY, brushSize, targetWidth, targetHeight, out) {
1974
+ const r = brushSize / 2;
1975
+ const minOffset = -Math.ceil(r - 0.5);
1976
+ const maxOffset = Math.floor(r - 0.5);
1977
+ const startX = Math.floor(centerX + minOffset);
1978
+ const startY = Math.floor(centerY + minOffset);
1979
+ const endX = Math.floor(centerX + maxOffset) + 1;
1980
+ const endY = Math.floor(centerY + maxOffset) + 1;
1981
+ const res = out ?? {
1982
+ x: 0,
1983
+ y: 0,
1984
+ w: 0,
1985
+ h: 0
1986
+ };
1987
+ const cStartX = targetWidth !== void 0 ? Math.max(0, startX) : startX;
1988
+ const cStartY = targetHeight !== void 0 ? Math.max(0, startY) : startY;
1989
+ const cEndX = targetWidth !== void 0 ? Math.min(targetWidth, endX) : endX;
1990
+ const cEndY = targetHeight !== void 0 ? Math.min(targetHeight, endY) : endY;
1991
+ res.x = cStartX;
1992
+ res.y = cStartY;
1993
+ res.w = Math.max(0, cEndX - cStartX);
1994
+ res.h = Math.max(0, cEndY - cStartY);
1995
+ return res;
1996
+ }
1997
+
1998
+ // src/History/PixelWriter.ts
1999
+ var PixelWriter = class {
2000
+ target;
2001
+ historyManager;
2002
+ accumulator;
2003
+ config;
2004
+ mutator;
2005
+ constructor(target, mutatorFactory, {
2006
+ tileSize = 256,
2007
+ maxHistorySteps = 50,
2008
+ historyManager = new HistoryManager(maxHistorySteps)
2009
+ } = {}) {
2010
+ this.target = target;
2011
+ this.config = new PixelEngineConfig(tileSize);
2012
+ this.historyManager = historyManager;
2013
+ this.accumulator = new PixelAccumulator(target, this.config);
2014
+ this.mutator = mutatorFactory(this);
2015
+ }
2016
+ withHistory(cb) {
2017
+ cb(this.mutator);
2018
+ this.captureHistory();
2019
+ }
2020
+ captureHistory() {
2021
+ const beforeTiles = this.accumulator.beforeTiles;
2022
+ if (beforeTiles.length === 0) return;
2023
+ const afterTiles = this.accumulator.extractAfterTiles();
2024
+ const patch = {
2025
+ beforeTiles,
2026
+ afterTiles
2027
+ };
2028
+ const target = this.target;
2029
+ const tileSize = this.config.tileSize;
2030
+ const accumulator = this.accumulator;
2031
+ const action = {
2032
+ undo: () => applyPatchTiles(target, patch.beforeTiles, tileSize),
2033
+ redo: () => applyPatchTiles(target, patch.afterTiles, tileSize),
2034
+ dispose: () => accumulator.recyclePatch(patch)
2035
+ };
2036
+ this.historyManager.commit(action);
2037
+ this.accumulator.reset();
2038
+ }
2039
+ };
2040
+
2041
+ // src/History/PixelMutator/mutatorApplyCircleBrush.ts
2042
+ var boundsOut = { x: 0, y: 0, w: 0, h: 0 };
2043
+ function mutatorApplyCircleBrush(writer) {
2044
+ return {
2045
+ applyCircleBrush(color, centerX, centerY, brushSize, alpha = 255, fallOff, blendFn) {
2046
+ const circleBounds = getCircleBrushBounds(
2047
+ centerX,
2048
+ centerY,
2049
+ brushSize,
2050
+ writer.target.width,
2051
+ writer.target.height,
2052
+ boundsOut
2053
+ );
2054
+ const { x, y, w, h } = circleBounds;
2055
+ writer.accumulator.storeRegionBeforeState(x, y, w, h);
2056
+ applyCircleBrushToPixelData(
2057
+ writer.target,
2058
+ color,
2059
+ centerX,
2060
+ centerY,
2061
+ brushSize,
2062
+ alpha,
2063
+ fallOff,
2064
+ blendFn,
2065
+ circleBounds
2066
+ );
2067
+ }
2068
+ };
2069
+ }
2070
+
1928
2071
  // src/PixelData/applyMaskToPixelData.ts
1929
2072
  function applyMaskToPixelData(dst, mask, opts = {}) {
1930
2073
  const {
@@ -2012,46 +2155,6 @@ function applyMaskToPixelData(dst, mask, opts = {}) {
2012
2155
  }
2013
2156
  }
2014
2157
 
2015
- // src/History/PixelWriter.ts
2016
- var PixelWriter = class {
2017
- target;
2018
- historyManager;
2019
- accumulator;
2020
- config;
2021
- mutator;
2022
- constructor(target, mutatorFactory, {
2023
- tileSize = 256,
2024
- maxHistorySteps = 50,
2025
- historyManager = new HistoryManager(maxHistorySteps)
2026
- } = {}) {
2027
- this.target = target;
2028
- this.config = new PixelEngineConfig(tileSize);
2029
- this.historyManager = historyManager;
2030
- this.accumulator = new PixelAccumulator(target, this.config);
2031
- this.mutator = mutatorFactory(this);
2032
- }
2033
- withHistory(cb) {
2034
- cb(this.mutator);
2035
- const beforeTiles = this.accumulator.beforeTiles;
2036
- if (beforeTiles.length === 0) return;
2037
- const afterTiles = this.accumulator.extractAfterTiles();
2038
- const patch = {
2039
- beforeTiles,
2040
- afterTiles
2041
- };
2042
- const target = this.target;
2043
- const tileSize = this.config.tileSize;
2044
- const accumulator = this.accumulator;
2045
- const action = {
2046
- undo: () => applyPatchTiles(target, patch.beforeTiles, tileSize),
2047
- redo: () => applyPatchTiles(target, patch.afterTiles, tileSize),
2048
- dispose: () => accumulator.recyclePatch(patch)
2049
- };
2050
- this.historyManager.commit(action);
2051
- this.accumulator.reset();
2052
- }
2053
- };
2054
-
2055
2158
  // src/History/PixelMutator/mutatorApplyMask.ts
2056
2159
  function mutatorApplyMask(writer) {
2057
2160
  return {
@@ -2069,6 +2172,143 @@ function mutatorApplyMask(writer) {
2069
2172
  };
2070
2173
  }
2071
2174
 
2175
+ // src/ImageData/imageDataToUInt32Array.ts
2176
+ function imageDataToUInt32Array(imageData) {
2177
+ return new Uint32Array(
2178
+ imageData.data.buffer,
2179
+ imageData.data.byteOffset,
2180
+ // Shift right by 2 is a fast bitwise division by 4.
2181
+ imageData.data.byteLength >> 2
2182
+ );
2183
+ }
2184
+
2185
+ // src/PixelData/PixelData.ts
2186
+ var PixelData = class _PixelData {
2187
+ data32;
2188
+ imageData;
2189
+ get width() {
2190
+ return this.imageData.width;
2191
+ }
2192
+ get height() {
2193
+ return this.imageData.height;
2194
+ }
2195
+ constructor(imageData) {
2196
+ this.data32 = imageDataToUInt32Array(imageData);
2197
+ this.imageData = imageData;
2198
+ }
2199
+ set(imageData) {
2200
+ this.imageData = imageData;
2201
+ this.data32 = imageDataToUInt32Array(imageData);
2202
+ }
2203
+ /**
2204
+ * Creates a deep copy of the PixelData using the environment's ImageData constructor.
2205
+ */
2206
+ copy() {
2207
+ const buffer = new Uint8ClampedArray(this.imageData.data);
2208
+ const ImageConstructor = typeof ImageData !== "undefined" ? ImageData : this.imageData.constructor;
2209
+ const newImageData = new ImageConstructor(
2210
+ buffer,
2211
+ this.width,
2212
+ this.height
2213
+ );
2214
+ return new _PixelData(newImageData);
2215
+ }
2216
+ };
2217
+
2218
+ // src/PixelData/applyRectBrushToPixelData.ts
2219
+ function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect, bounds) {
2220
+ const targetWidth = target.width;
2221
+ const targetHeight = target.height;
2222
+ const b = bounds ?? getRectBrushBounds(
2223
+ centerX,
2224
+ centerY,
2225
+ brushWidth,
2226
+ brushHeight,
2227
+ targetWidth,
2228
+ targetHeight
2229
+ );
2230
+ if (b.w <= 0 || b.h <= 0) return;
2231
+ const data32 = target.data32;
2232
+ const baseColor = color & 16777215;
2233
+ const constantSrc = (alpha << 24 | baseColor) >>> 0;
2234
+ const invHalfW = 1 / (brushWidth / 2);
2235
+ const invHalfH = 1 / (brushHeight / 2);
2236
+ const endX = b.x + b.w;
2237
+ const endY = b.y + b.h;
2238
+ for (let py = b.y; py < endY; py++) {
2239
+ const rowOffset = py * targetWidth;
2240
+ const dy = fallOff ? Math.abs(py + 0.5 - centerY) * invHalfH : 0;
2241
+ for (let px = b.x; px < endX; px++) {
2242
+ const idx = rowOffset + px;
2243
+ if (fallOff) {
2244
+ const dx = Math.abs(px + 0.5 - centerX) * invHalfW;
2245
+ const dist = dx > dy ? dx : dy;
2246
+ const strength = fallOff(dist);
2247
+ const fAlpha = alpha * strength | 0;
2248
+ const src = (fAlpha << 24 | baseColor) >>> 0;
2249
+ data32[idx] = blendFn(src, data32[idx]);
2250
+ } else {
2251
+ data32[idx] = blendFn(constantSrc, data32[idx]);
2252
+ }
2253
+ }
2254
+ }
2255
+ }
2256
+ function getRectBrushBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight, out) {
2257
+ const startX = Math.floor(centerX - brushWidth / 2);
2258
+ const startY = Math.floor(centerY - brushHeight / 2);
2259
+ const endX = startX + brushWidth;
2260
+ const endY = startY + brushHeight;
2261
+ const res = out ?? {
2262
+ x: 0,
2263
+ y: 0,
2264
+ w: 0,
2265
+ h: 0
2266
+ };
2267
+ const cStartX = targetWidth !== void 0 ? Math.max(0, startX) : startX;
2268
+ const cStartY = targetHeight !== void 0 ? Math.max(0, startY) : startY;
2269
+ const cEndX = targetWidth !== void 0 ? Math.min(targetWidth, endX) : endX;
2270
+ const cEndY = targetHeight !== void 0 ? Math.min(targetHeight, endY) : endY;
2271
+ const w = cEndX - cStartX;
2272
+ const h = cEndY - cStartY;
2273
+ res.x = cStartX;
2274
+ res.y = cStartY;
2275
+ res.w = w < 0 ? 0 : w;
2276
+ res.h = h < 0 ? 0 : h;
2277
+ return res;
2278
+ }
2279
+
2280
+ // src/History/PixelMutator/mutatorApplyRectBrush.ts
2281
+ var boundsOut2 = { x: 0, y: 0, w: 0, h: 0 };
2282
+ function mutatorApplyRectBrush(writer) {
2283
+ return {
2284
+ applyRectBrush(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn) {
2285
+ const bounds = getRectBrushBounds(
2286
+ centerX,
2287
+ centerY,
2288
+ brushWidth,
2289
+ brushHeight,
2290
+ writer.target.width,
2291
+ writer.target.height,
2292
+ boundsOut2
2293
+ );
2294
+ const { x, y, w, h } = bounds;
2295
+ writer.accumulator.storeRegionBeforeState(x, y, w, h);
2296
+ applyRectBrushToPixelData(
2297
+ writer.target,
2298
+ color,
2299
+ centerX,
2300
+ centerY,
2301
+ brushWidth,
2302
+ brushHeight,
2303
+ alpha,
2304
+ fallOff,
2305
+ blendFn,
2306
+ bounds
2307
+ );
2308
+ }
2309
+ };
2310
+ }
2311
+
2072
2312
  // src/PixelData/blendColorPixelData.ts
2073
2313
  function blendColorPixelData(dst, color, opts = {}) {
2074
2314
  const {
@@ -2514,21 +2754,21 @@ function makeFullPixelMutator(writer) {
2514
2754
  ...mutatorBlendColor(writer),
2515
2755
  ...mutatorBlendPixel(writer),
2516
2756
  ...mutatorFill(writer),
2517
- ...mutatorInvert(writer)
2757
+ ...mutatorInvert(writer),
2758
+ ...mutatorApplyCircleBrush(writer),
2759
+ ...mutatorApplyRectBrush(writer)
2518
2760
  };
2519
2761
  }
2520
2762
 
2521
2763
  // src/ImageData/ReusableImageData.ts
2522
2764
  function makeReusableImageData() {
2523
2765
  let imageData = null;
2524
- let buffer = null;
2525
2766
  return function getReusableImageData(width, height) {
2526
2767
  const hasInstance = !!imageData;
2527
2768
  const widthMatches = hasInstance && imageData.width === width;
2528
2769
  const heightMatches = hasInstance && imageData.height === height;
2529
2770
  if (!widthMatches || !heightMatches) {
2530
- const buffer2 = new Uint8ClampedArray(width * height * 4);
2531
- imageData = new ImageData(buffer2, width, height);
2771
+ imageData = new ImageData(width, height);
2532
2772
  }
2533
2773
  return imageData;
2534
2774
  };
@@ -2592,16 +2832,6 @@ function imageDataToDataUrl(imageData) {
2592
2832
  }
2593
2833
  imageDataToDataUrl.reset = get.reset;
2594
2834
 
2595
- // src/ImageData/imageDataToUInt32Array.ts
2596
- function imageDataToUInt32Array(imageData) {
2597
- return new Uint32Array(
2598
- imageData.data.buffer,
2599
- imageData.data.byteOffset,
2600
- // Shift right by 2 is a fast bitwise division by 4.
2601
- imageData.data.byteLength >> 2
2602
- );
2603
- }
2604
-
2605
2835
  // src/ImageData/invertImageData.ts
2606
2836
  function invertImageData(imageData) {
2607
2837
  const data = imageData.data;
@@ -3135,123 +3365,6 @@ function mergeMasks(dst, dstWidth, src, opts) {
3135
3365
  }
3136
3366
  }
3137
3367
 
3138
- // src/PixelData/PixelData.ts
3139
- var PixelData = class _PixelData {
3140
- data32;
3141
- imageData;
3142
- get width() {
3143
- return this.imageData.width;
3144
- }
3145
- get height() {
3146
- return this.imageData.height;
3147
- }
3148
- constructor(imageData) {
3149
- this.data32 = imageDataToUInt32Array(imageData);
3150
- this.imageData = imageData;
3151
- }
3152
- set(imageData) {
3153
- this.imageData = imageData;
3154
- this.data32 = imageDataToUInt32Array(imageData);
3155
- }
3156
- /**
3157
- * Creates a deep copy of the PixelData using the environment's ImageData constructor.
3158
- */
3159
- copy() {
3160
- const buffer = new Uint8ClampedArray(this.imageData.data);
3161
- const ImageConstructor = typeof ImageData !== "undefined" ? ImageData : this.imageData.constructor;
3162
- const newImageData = new ImageConstructor(
3163
- buffer,
3164
- this.width,
3165
- this.height
3166
- );
3167
- return new _PixelData(newImageData);
3168
- }
3169
- };
3170
-
3171
- // src/PixelData/applyCircleBrushToPixelData.ts
3172
- function applyCircleBrushToPixelData(target, color, centerX, centerY, brushSize, alpha = 255, fallOff, blendFn = sourceOverPerfect) {
3173
- const r = brushSize / 2;
3174
- const rSqr = r * r;
3175
- const centerOffset = brushSize % 2 === 0 ? 0.5 : 0;
3176
- const xStart = Math.max(0, Math.ceil(centerX - r));
3177
- const xEnd = Math.min(target.width - 1, Math.floor(centerX + r));
3178
- const yStart = Math.max(0, Math.ceil(centerY - r));
3179
- const yEnd = Math.min(target.height - 1, Math.floor(centerY + r));
3180
- const data32 = target.data32;
3181
- const targetWidth = target.width;
3182
- const baseColor = color & 16777215;
3183
- const invR = 1 / r;
3184
- const constantSrc = (alpha << 24 | baseColor) >>> 0;
3185
- for (let cy = yStart; cy <= yEnd; cy++) {
3186
- const dy = cy - centerY + centerOffset;
3187
- const dySqr = dy * dy;
3188
- const rowOffset = cy * targetWidth;
3189
- for (let cx = xStart; cx <= xEnd; cx++) {
3190
- const dx = cx - centerX + centerOffset;
3191
- const dSqr = dx * dx + dySqr;
3192
- if (dSqr <= rSqr) {
3193
- const idx = rowOffset + cx;
3194
- if (fallOff) {
3195
- const strength = fallOff(Math.sqrt(dSqr) * invR);
3196
- const fAlpha = alpha * strength & 255;
3197
- const src = (fAlpha << 24 | baseColor) >>> 0;
3198
- data32[idx] = blendFn(src, data32[idx]);
3199
- } else {
3200
- data32[idx] = blendFn(constantSrc, data32[idx]);
3201
- }
3202
- }
3203
- }
3204
- }
3205
- }
3206
-
3207
- // src/PixelData/applyRectBrushToPixelData.ts
3208
- function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect) {
3209
- const targetWidth = target.width;
3210
- const targetHeight = target.height;
3211
- const data32 = target.data32;
3212
- const rawStartX = Math.floor(centerX - brushWidth / 2);
3213
- const rawStartY = Math.floor(centerY - brushHeight / 2);
3214
- const endX = Math.min(targetWidth, rawStartX + brushWidth);
3215
- const endY = Math.min(targetHeight, rawStartY + brushHeight);
3216
- const startX = Math.max(0, rawStartX);
3217
- const startY = Math.max(0, rawStartY);
3218
- const baseColor = color & 16777215;
3219
- const constantSrc = (alpha << 24 | baseColor) >>> 0;
3220
- const invHalfW = 1 / (brushWidth / 2);
3221
- const invHalfH = 1 / (brushHeight / 2);
3222
- for (let py = startY; py < endY; py++) {
3223
- const rowOffset = py * targetWidth;
3224
- const dy = Math.abs(py + 0.5 - centerY) * invHalfH;
3225
- for (let px = startX; px < endX; px++) {
3226
- if (fallOff) {
3227
- const dx = Math.abs(px + 0.5 - centerX) * invHalfW;
3228
- const dist = dx > dy ? dx : dy;
3229
- const fAlpha = alpha * fallOff(dist) | 0;
3230
- const src = (fAlpha << 24 | baseColor) >>> 0;
3231
- data32[rowOffset + px] = blendFn(src, data32[rowOffset + px]);
3232
- } else {
3233
- data32[rowOffset + px] = blendFn(constantSrc, data32[rowOffset + px]);
3234
- }
3235
- }
3236
- }
3237
- }
3238
- function getRectBrushBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight) {
3239
- const rawStartX = Math.floor(centerX - brushWidth / 2);
3240
- const rawStartY = Math.floor(centerY - brushHeight / 2);
3241
- const rawEndX = rawStartX + brushWidth;
3242
- const rawEndY = rawStartY + brushHeight;
3243
- const startX = targetWidth !== void 0 ? Math.max(0, rawStartX) : rawStartX;
3244
- const startY = targetHeight !== void 0 ? Math.max(0, rawStartY) : rawStartY;
3245
- const endX = targetWidth !== void 0 ? Math.min(targetWidth, rawEndX) : rawEndX;
3246
- const endY = targetHeight !== void 0 ? Math.min(targetHeight, rawEndY) : rawEndY;
3247
- return {
3248
- x: startX,
3249
- y: startY,
3250
- w: endX - startX,
3251
- h: endY - startY
3252
- };
3253
- }
3254
-
3255
3368
  // src/PixelData/clearPixelData.ts
3256
3369
  function clearPixelData(dst, rect) {
3257
3370
  fillPixelData(dst, 0, rect);
@@ -3472,6 +3585,7 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
3472
3585
  fileToImageData,
3473
3586
  fillPixelData,
3474
3587
  floodFillSelection,
3588
+ getCircleBrushBounds,
3475
3589
  getImageDataFromClipboard,
3476
3590
  getIndexedImageColorCounts,
3477
3591
  getRectBrushBounds,