@pixerate/editor 0.186.0 → 0.188.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.
@@ -114,15 +114,19 @@ import {
114
114
  parseDataUrl,
115
115
  pngDataUrl,
116
116
  readDeepRegion,
117
+ readPixel,
117
118
  readRegion,
118
119
  setLum,
119
120
  surfaceDepth,
120
121
  surfaceFromDeepPixels,
121
122
  surfaceFromPixels,
123
+ tileCoord,
122
124
  tileKey,
123
125
  tilesInRect,
124
126
  translateSurface,
125
- writeDeepRegion
127
+ uniformTile,
128
+ writeDeepRegion,
129
+ writeRegion
126
130
  } from "./chunk-H367YTXI.js";
127
131
  import {
128
132
  ZlibError,
@@ -750,15 +754,159 @@ function computeSelectionBounds(width, height, data) {
750
754
  if (y0 < 0) return null;
751
755
  return { x: x0, y: y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };
752
756
  }
757
+ var TILE_PIXELS = TILE * TILE;
758
+ var MATERIALIZED_LIMIT = 4;
759
+ var materialized = /* @__PURE__ */ new Map();
760
+ var lastRead = null;
761
+ var lastData = null;
762
+ var TiledSelectionMask = class {
763
+ constructor(width, height, surface, bounds, data) {
764
+ this.width = width;
765
+ this.height = height;
766
+ this.surface = surface;
767
+ this.cachedBounds = bounds;
768
+ if (data) this.remember(data);
769
+ }
770
+ width;
771
+ height;
772
+ surface;
773
+ cachedBounds;
774
+ weak = null;
775
+ get bounds() {
776
+ if (this.cachedBounds === void 0) this.cachedBounds = contentBounds(this.surface);
777
+ return this.cachedBounds;
778
+ }
779
+ get data() {
780
+ if (lastRead === this) return lastData;
781
+ let d = materialized.get(this);
782
+ if (d) {
783
+ materialized.delete(this);
784
+ materialized.set(this, d);
785
+ lastRead = this;
786
+ lastData = d;
787
+ return d;
788
+ }
789
+ d = this.weak?.deref();
790
+ if (!d) {
791
+ const all = readRegion(this.surface, { x: 0, y: 0, width: this.width, height: this.height });
792
+ d = new Uint8Array(all.buffer, all.byteOffset, all.length);
793
+ }
794
+ this.remember(d);
795
+ return d;
796
+ }
797
+ remember(d) {
798
+ lastRead = this;
799
+ lastData = d;
800
+ this.weak = typeof WeakRef === "function" ? new WeakRef(d) : null;
801
+ materialized.set(this, d);
802
+ while (materialized.size > MATERIALIZED_LIMIT) materialized.delete(materialized.keys().next().value);
803
+ }
804
+ };
805
+ function tileGrid(width, height) {
806
+ return { cols: Math.ceil(width / TILE), rows: Math.ceil(height / TILE) };
807
+ }
808
+ function insideTile(tx, ty, width, height) {
809
+ return (tx + 1) * TILE <= width && (ty + 1) * TILE <= height;
810
+ }
811
+ function surfaceFromCoverage(width, height, data) {
812
+ const tiles = /* @__PURE__ */ new Map();
813
+ const { cols, rows } = tileGrid(width, height);
814
+ for (let ty = 0; ty < rows; ty++) {
815
+ for (let tx = 0; tx < cols; tx++) {
816
+ const ox = tx * TILE;
817
+ const oy = ty * TILE;
818
+ const w = Math.min(TILE, width - ox);
819
+ const h = Math.min(TILE, height - oy);
820
+ let lo = 255;
821
+ let hi = 0;
822
+ for (let y = 0; y < h && !(hi > 0 && lo < hi); y++) {
823
+ const row = (oy + y) * width + ox;
824
+ for (let x = 0; x < w; x++) {
825
+ const v = data[row + x];
826
+ if (v < lo) lo = v;
827
+ if (v > hi) hi = v;
828
+ }
829
+ }
830
+ if (hi === 0) continue;
831
+ if (lo === hi && w === TILE && h === TILE) {
832
+ tiles.set(tileKey(tx, ty), uniformTile(1, hi));
833
+ continue;
834
+ }
835
+ const t = new Uint8ClampedArray(TILE_PIXELS);
836
+ for (let y = 0; y < h; y++) {
837
+ const row = (oy + y) * width + ox;
838
+ for (let x = 0; x < w; x++) t[y * TILE + x] = data[row + x];
839
+ }
840
+ tiles.set(tileKey(tx, ty), { data: t, uniform: null });
841
+ }
842
+ }
843
+ return new TiledSurface(1, tiles, 0);
844
+ }
845
+ function clipTile(tile, w, h) {
846
+ const d = tile.data;
847
+ let outside = false;
848
+ for (let y = 0; y < TILE && !outside; y++) {
849
+ for (let x = y < h ? w : 0; x < TILE; x++) {
850
+ if (d[y * TILE + x] !== 0) {
851
+ outside = true;
852
+ break;
853
+ }
854
+ }
855
+ }
856
+ if (!outside) return tile;
857
+ const t = new Uint8ClampedArray(TILE_PIXELS);
858
+ for (let y = 0; y < h; y++) t.set(d.subarray(y * TILE, y * TILE + w), y * TILE);
859
+ return finishTile(t, 1, 0);
860
+ }
861
+ function selectionFromTiles(width, height, surface) {
862
+ checkSize(width, height);
863
+ if (surface.channels !== 1) throw new Error("A selection needs a 1-channel surface");
864
+ if (surface.defaultValue !== 0) {
865
+ const all = readRegion(surface, { x: 0, y: 0, width, height });
866
+ return createSelectionMask(width, height, new Uint8Array(all.buffer, all.byteOffset, all.length));
867
+ }
868
+ const { cols, rows } = tileGrid(width, height);
869
+ let tiles = null;
870
+ for (const [key, tile] of surface.tiles) {
871
+ const { tx, ty } = tileCoord(key);
872
+ let next = tile;
873
+ if (tx < 0 || ty < 0 || tx >= cols || ty >= rows) next = null;
874
+ else if (!insideTile(tx, ty, width, height)) next = clipTile(tile, Math.min(TILE, width - tx * TILE), Math.min(TILE, height - ty * TILE));
875
+ if (next === tile) continue;
876
+ tiles ??= new Map(surface.tiles);
877
+ if (next) tiles.set(key, next);
878
+ else tiles.delete(key);
879
+ }
880
+ return new TiledSelectionMask(width, height, tiles ? new TiledSurface(1, tiles, 0) : surface);
881
+ }
753
882
  function createSelectionMask(width, height, data) {
754
883
  checkSize(width, height);
755
- const bytes = data ?? new Uint8Array(width * height);
756
- if (bytes.length !== width * height) throw new Error("Selection data does not match its size");
757
- return { width, height, data: bytes, bounds: data ? computeSelectionBounds(width, height, bytes) : null };
884
+ if (!data) return new TiledSelectionMask(width, height, new TiledSurface(1, /* @__PURE__ */ new Map(), 0), null);
885
+ if (data.length !== width * height) throw new Error("Selection data does not match its size");
886
+ return new TiledSelectionMask(width, height, surfaceFromCoverage(width, height, data), void 0, data);
887
+ }
888
+ function selectionFromRegion(width, height, rect, data) {
889
+ checkSize(width, height);
890
+ if (rect.x < 0 || rect.y < 0 || rect.x + rect.width > width || rect.y + rect.height > height) throw new Error("The region lies outside the selection");
891
+ return new TiledSelectionMask(width, height, writeRegion(new TiledSurface(1, /* @__PURE__ */ new Map(), 0), rect, data));
758
892
  }
759
893
  function fullSelection(width, height) {
760
894
  checkSize(width, height);
761
- return { width, height, data: new Uint8Array(width * height).fill(255), bounds: { x: 0, y: 0, width, height } };
895
+ const tiles = /* @__PURE__ */ new Map();
896
+ const { cols, rows } = tileGrid(width, height);
897
+ for (let ty = 0; ty < rows; ty++) {
898
+ for (let tx = 0; tx < cols; tx++) {
899
+ if (insideTile(tx, ty, width, height)) {
900
+ tiles.set(tileKey(tx, ty), uniformTile(1, 255));
901
+ continue;
902
+ }
903
+ const t = new Uint8ClampedArray(TILE_PIXELS);
904
+ const w = Math.min(TILE, width - tx * TILE);
905
+ for (let y = 0; y < Math.min(TILE, height - ty * TILE); y++) t.fill(255, y * TILE, y * TILE + w);
906
+ tiles.set(tileKey(tx, ty), { data: t, uniform: null });
907
+ }
908
+ }
909
+ return new TiledSelectionMask(width, height, new TiledSurface(1, tiles, 0), { x: 0, y: 0, width, height });
762
910
  }
763
911
  function isSelectionEmpty(mask) {
764
912
  return !mask || mask.bounds === null;
@@ -767,7 +915,7 @@ function selectionValueAt(mask, x, y) {
767
915
  const xi = Math.floor(x);
768
916
  const yi = Math.floor(y);
769
917
  if (xi < 0 || yi < 0 || xi >= mask.width || yi >= mask.height) return 0;
770
- return mask.data[yi * mask.width + xi] / 255;
918
+ return readPixel(mask.surface, xi, yi)[0] / 255;
771
919
  }
772
920
  function coverageToByte(v) {
773
921
  if (!(v > 0)) return 0;
@@ -788,10 +936,13 @@ function selectionToCoverage(mask) {
788
936
  function selectionArea(mask) {
789
937
  if (!mask || !mask.bounds) return 0;
790
938
  let sum = 0;
791
- const { x, y, width, height } = mask.bounds;
792
- for (let yy = y; yy < y + height; yy++) {
793
- const row = yy * mask.width;
794
- for (let xx = x; xx < x + width; xx++) sum += mask.data[row + xx];
939
+ for (const tile of mask.surface.tiles.values()) {
940
+ if (tile.uniform !== null) {
941
+ sum += tile.uniform * TILE_PIXELS;
942
+ continue;
943
+ }
944
+ const d = tile.data;
945
+ for (let i = 0; i < d.length; i++) sum += d[i];
795
946
  }
796
947
  return sum / 255;
797
948
  }
@@ -803,20 +954,68 @@ function combineSelections(prev, next, mode) {
803
954
  return null;
804
955
  } else {
805
956
  if (prev.width !== next.width || prev.height !== next.height) throw new Error("Selections differ in size");
806
- const a = prev.data;
807
- const b = next.data;
808
- const out = new Uint8Array(a.length);
809
- if (mode === "add") for (let i = 0; i < a.length; i++) out[i] = a[i] > b[i] ? a[i] : b[i];
810
- else if (mode === "subtract") for (let i = 0; i < a.length; i++) out[i] = a[i] > b[i] ? a[i] - b[i] : 0;
811
- else for (let i = 0; i < a.length; i++) out[i] = a[i] < b[i] ? a[i] : b[i];
812
- result = createSelectionMask(prev.width, prev.height, out);
957
+ result = new TiledSelectionMask(prev.width, prev.height, combineSurfaces(prev.surface, next.surface, mode));
813
958
  }
814
959
  return result.bounds ? result : null;
815
960
  }
961
+ var FULL = 255;
962
+ function combineTile(a, b, mode) {
963
+ if (mode === "add") {
964
+ if (!b || a?.uniform === FULL) return a;
965
+ if (!a || b.uniform === FULL) return b;
966
+ } else if (mode === "subtract") {
967
+ if (!a || !b) return a;
968
+ if (b.uniform === FULL) return void 0;
969
+ } else {
970
+ if (!a || !b) return void 0;
971
+ if (a.uniform === FULL) return b;
972
+ if (b.uniform === FULL) return a;
973
+ }
974
+ const x = a.data;
975
+ const y = b.data;
976
+ const out = new Uint8ClampedArray(TILE_PIXELS);
977
+ if (mode === "add") for (let i = 0; i < out.length; i++) out[i] = x[i] > y[i] ? x[i] : y[i];
978
+ else if (mode === "subtract") for (let i = 0; i < out.length; i++) out[i] = x[i] > y[i] ? x[i] - y[i] : 0;
979
+ else for (let i = 0; i < out.length; i++) out[i] = x[i] < y[i] ? x[i] : y[i];
980
+ return finishTile(out, 1, 0) ?? void 0;
981
+ }
982
+ function combineSurfaces(a, b, mode) {
983
+ const tiles = /* @__PURE__ */ new Map();
984
+ for (const key of /* @__PURE__ */ new Set([...a.tiles.keys(), ...b.tiles.keys()])) {
985
+ const tile = combineTile(a.tiles.get(key), b.tiles.get(key), mode);
986
+ if (tile) tiles.set(key, tile);
987
+ }
988
+ return new TiledSurface(1, tiles, 0);
989
+ }
816
990
  function invertSelection(mask) {
817
- const out = new Uint8Array(mask.data.length);
818
- for (let i = 0; i < out.length; i++) out[i] = 255 - mask.data[i];
819
- return createSelectionMask(mask.width, mask.height, out);
991
+ const { width, height } = mask;
992
+ const tiles = /* @__PURE__ */ new Map();
993
+ const { cols, rows } = tileGrid(width, height);
994
+ for (let ty = 0; ty < rows; ty++) {
995
+ for (let tx = 0; tx < cols; tx++) {
996
+ const src = mask.surface.getTile(tx, ty);
997
+ const key = tileKey(tx, ty);
998
+ if (insideTile(tx, ty, width, height)) {
999
+ if (!src) tiles.set(key, uniformTile(1, FULL));
1000
+ else if (src.uniform !== null) {
1001
+ if (src.uniform !== FULL) tiles.set(key, uniformTile(1, FULL - src.uniform));
1002
+ } else {
1003
+ const out2 = new Uint8ClampedArray(TILE_PIXELS);
1004
+ for (let i = 0; i < out2.length; i++) out2[i] = FULL - src.data[i];
1005
+ const t2 = finishTile(out2, 1, 0);
1006
+ if (t2) tiles.set(key, t2);
1007
+ }
1008
+ continue;
1009
+ }
1010
+ const w = Math.min(TILE, width - tx * TILE);
1011
+ const h = Math.min(TILE, height - ty * TILE);
1012
+ const out = new Uint8ClampedArray(TILE_PIXELS);
1013
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) out[y * TILE + x] = FULL - (src ? src.data[y * TILE + x] : 0);
1014
+ const t = finishTile(out, 1, 0);
1015
+ if (t) tiles.set(key, t);
1016
+ }
1017
+ }
1018
+ return new TiledSelectionMask(width, height, new TiledSurface(1, tiles, 0));
820
1019
  }
821
1020
  function resizeSelection(mask, width, height) {
822
1021
  if (mask.width === width && mask.height === height) return mask;
@@ -11640,7 +11839,9 @@ export {
11640
11839
  isSelectionMode,
11641
11840
  parseSelectionMode,
11642
11841
  computeSelectionBounds,
11842
+ selectionFromTiles,
11643
11843
  createSelectionMask,
11844
+ selectionFromRegion,
11644
11845
  fullSelection,
11645
11846
  isSelectionEmpty,
11646
11847
  selectionValueAt,