@pixerate/editor 0.104.0 → 0.106.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.
Files changed (31) hide show
  1. package/dist/{chunk-NQLQ2JOI.js → chunk-4X5MIVCE.js} +1155 -295
  2. package/dist/chunk-4X5MIVCE.js.map +1 -0
  3. package/dist/{chunk-VSMM7B3P.cjs → chunk-ZID226ZG.cjs} +1169 -309
  4. package/dist/chunk-ZID226ZG.cjs.map +1 -0
  5. package/dist/{compositor-CTMU-B1t.d.cts → compositor-Bc3PNP3C.d.cts} +1 -1
  6. package/dist/{compositor-B1ZrKR89.d.ts → compositor-BsYYllvR.d.ts} +1 -1
  7. package/dist/{export-X9rY79p0.d.cts → export-_m3Es4ru.d.cts} +19 -4
  8. package/dist/{export-BLpMyCYg.d.ts → export-iMrop3Jm.d.ts} +19 -4
  9. package/dist/image-editor/index.cjs +714 -539
  10. package/dist/image-editor/index.cjs.map +1 -1
  11. package/dist/image-editor/index.d.cts +50 -9
  12. package/dist/image-editor/index.d.ts +50 -9
  13. package/dist/image-editor/index.js +228 -53
  14. package/dist/image-editor/index.js.map +1 -1
  15. package/dist/{mixer-DGcH6HLu.d.cts → mixer-C1M1Nua_.d.cts} +1 -1
  16. package/dist/{mixer-DG_9Yu2W.d.ts → mixer-Cu5a9Bzd.d.ts} +1 -1
  17. package/dist/paint/index.d.cts +2 -2
  18. package/dist/paint/index.d.ts +2 -2
  19. package/dist/paint-editor/index.cjs +348 -32
  20. package/dist/paint-editor/index.cjs.map +1 -1
  21. package/dist/paint-editor/index.d.cts +154 -13
  22. package/dist/paint-editor/index.d.ts +154 -13
  23. package/dist/paint-editor/index.js +317 -1
  24. package/dist/paint-editor/index.js.map +1 -1
  25. package/dist/raster/index.d.cts +4 -4
  26. package/dist/raster/index.d.ts +4 -4
  27. package/dist/{surface-AfHCH7BR.d.cts → surface-BBJN9wxg.d.cts} +1 -1
  28. package/dist/{surface-AfHCH7BR.d.ts → surface-BBJN9wxg.d.ts} +1 -1
  29. package/package.json +1 -1
  30. package/dist/chunk-NQLQ2JOI.js.map +0 -1
  31. package/dist/chunk-VSMM7B3P.cjs.map +0 -1
@@ -10,6 +10,7 @@ import {
10
10
  ADJUSTMENT_KINDS,
11
11
  ADJUSTMENT_LUT_SIZE,
12
12
  AS_SHOT_KELVIN,
13
+ BROWSER_IMAGE_TYPES,
13
14
  D50,
14
15
  D65,
15
16
  DEFAULT_ADJUSTMENTS_VALUES,
@@ -244,6 +245,7 @@ import {
244
245
  setSmartFiltersEnabled,
245
246
  setSmartPlacement,
246
247
  shiftCoverage,
248
+ smartContentsFile,
247
249
  smartObjectLayers,
248
250
  smoothCoverage,
249
251
  srgbToLinear,
@@ -277,7 +279,7 @@ import {
277
279
  writePsd,
278
280
  xyToTempTint,
279
281
  xyToUv
280
- } from "../chunk-NQLQ2JOI.js";
282
+ } from "../chunk-4X5MIVCE.js";
281
283
  import {
282
284
  DEFAULT_GLOBAL_LIGHT,
283
285
  DIRECT_GAUSSIAN_MAX_SIGMA,
@@ -2992,39 +2994,6 @@ var FreeTransformSession = class {
2992
2994
  };
2993
2995
 
2994
2996
  // src/image-editor/controller.ts
2995
- function prepareNestedForPsd(layers) {
2996
- const out = [];
2997
- for (const l of layers) {
2998
- if (l.type === "annotation") continue;
2999
- let next = l;
3000
- if (l.type === "group") next = { ...l, children: prepareNestedForPsd(l.children) };
3001
- else if (l.type === "smart") next = rasterizeSmartObject([l], l.id).layer;
3002
- else if (l.type === "pixel" && l.filters?.length) {
3003
- const p = { ...l };
3004
- if (l.filters.some(isActiveFilter)) {
3005
- const b = l.surface.bounds;
3006
- if (b) {
3007
- const r = { x: b.x + l.offset.x - 64, y: b.y + l.offset.y - 64, width: b.width + 128, height: b.height + 128 };
3008
- const plain3 = { ...l, visible: true, opacity: 1, blend: "normal" };
3009
- delete plain3.mask;
3010
- delete plain3.clipped;
3011
- delete plain3.effects;
3012
- delete plain3.fillOpacity;
3013
- const data = new LayerCompositor().composite([plain3], r, { width: r.x + r.width, height: r.y + r.height });
3014
- p.surface = surfaceFromPixels({ width: r.width, height: r.height, data }, 4, r.x - l.offset.x, r.y - l.offset.y);
3015
- }
3016
- }
3017
- delete p.filters;
3018
- next = p;
3019
- }
3020
- if (next.mask?.source) {
3021
- next = { ...next };
3022
- delete next.mask;
3023
- }
3024
- out.push(next);
3025
- }
3026
- return out;
3027
- }
3028
2997
  var IMAGE_EDITOR_SCHEMA_VERSION = LAYERED_SCHEMA_VERSION;
3029
2998
  function paintCoverageInto(base, coverage, apply, rects) {
3030
2999
  const tiles = new Map(base.tiles);
@@ -3650,6 +3619,17 @@ var ImageEditorController = class {
3650
3619
  }
3651
3620
  return b ? { x: b.x + layer.offset.x, y: b.y + layer.offset.y, width: b.width, height: b.height } : null;
3652
3621
  }
3622
+ /**
3623
+ * A tight base64 PNG data URL of a pixel or smart layer's content, or `null` for an unknown or
3624
+ * non-pixel layer, or when the layer is completely transparent.
3625
+ */
3626
+ getLayerDataURL(id) {
3627
+ const layer = this.getLayer(id);
3628
+ if (!layer || layer.type !== "pixel" && layer.type !== "smart") return null;
3629
+ const surface = this.surfaceOf(layer);
3630
+ const enc = encodeSurface(surface);
3631
+ return enc ? enc.url : null;
3632
+ }
3653
3633
  /**
3654
3634
  * Composites the layer stack in document space without a canvas (annotation layers need one
3655
3635
  * and are left out): `rect` (default: the canvas) as RGBA, straight alpha. Crop, rotation and
@@ -4392,20 +4372,13 @@ var ImageEditorController = class {
4392
4372
  const c = layer.contents;
4393
4373
  const base = c.name.replace(/\.\w+$/, "") || "Contents";
4394
4374
  if (c.file) {
4395
- const mime = { psd: "image/vnd.adobe.photoshop", psb: "image/vnd.adobe.photoshop", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", tif: "image/tiff", tiff: "image/tiff", gif: "image/gif" };
4375
+ const mime = { psd: "image/vnd.adobe.photoshop", psb: "image/vnd.adobe.photoshop", pdf: "application/pdf", ...BROWSER_IMAGE_TYPES };
4396
4376
  const ext = c.file.type.toLowerCase();
4397
4377
  return { name: /\.\w+$/.test(c.name) ? c.name : `${base}.${ext}`, mimeType: mime[ext] ?? "application/octet-stream", bytes: c.file.bytes };
4398
4378
  }
4399
4379
  if (!isContentsDecoded(c)) throw new LayerOperationError(`The contents of "${layer.name || id}" are not available here.`);
4400
- if (c.kind === "raster") {
4401
- const data = readRegion(contentsSurface(c), { x: 0, y: 0, width: c.width, height: c.height });
4402
- return { name: `${base}.png`, mimeType: "image/png", bytes: encodePng(c.width, c.height, data) };
4403
- }
4404
- const doc = c.document;
4405
- const merged = readRegion(contentsSurface(c), { x: 0, y: 0, width: c.width, height: c.height });
4406
- const prepared = prepareNestedForPsd(doc.layers);
4407
- const res = exportPsd({ width: c.width, height: c.height, layers: prepared, merged, patterns: doc.patterns, globalLight: doc.globalLight });
4408
- return { name: `${base}.${res.psb ? "psb" : "psd"}`, mimeType: "image/vnd.adobe.photoshop", bytes: res.bytes };
4380
+ const f = smartContentsFile(c);
4381
+ return { name: f.name, mimeType: f.name.endsWith(".png") ? "image/png" : "image/vnd.adobe.photoshop", bytes: f.bytes };
4409
4382
  }
4410
4383
  /**
4411
4384
  * Layer › Smart Objects › New Smart Object via Copy (`linked: false`: independent contents) or a
@@ -4485,11 +4458,13 @@ var ImageEditorController = class {
4485
4458
  */
4486
4459
  async prepareSmartObjects() {
4487
4460
  const pending = /* @__PURE__ */ new Map();
4488
- for (const l of flattenLayers(this.state.layers)) if (l.type === "smart" && !isContentsDecoded(l.contents) && l.contents.file) pending.set(l.contents.id, l.contents);
4461
+ for (const l of flattenLayers(this.state.layers)) {
4462
+ if (l.type === "smart" && !isContentsDecoded(l.contents) && l.contents.file && BROWSER_IMAGE_TYPES[l.contents.file.type]) pending.set(l.contents.id, l.contents);
4463
+ }
4489
4464
  const left = [];
4490
4465
  for (const [cid, c] of pending) {
4491
4466
  try {
4492
- const url = `data:${c.file.type === "png" ? "image/png" : c.file.type === "gif" ? "image/gif" : c.file.type === "tif" || c.file.type === "tiff" ? "image/tiff" : "image/jpeg"};base64,${bytesToBase64(c.file.bytes)}`;
4467
+ const url = `data:${BROWSER_IMAGE_TYPES[c.file.type]};base64,${bytesToBase64(c.file.bytes)}`;
4493
4468
  const px2 = await this.decodeImage(url);
4494
4469
  const decoded = { ...c, kind: "raster", raster: rasterContents(px2, c.name, cid).raster, width: px2.width, height: px2.height };
4495
4470
  const swap = (layers) => {
@@ -4841,6 +4816,15 @@ var ImageEditorController = class {
4841
4816
  byKey.set(key, thumb);
4842
4817
  return thumb;
4843
4818
  }
4819
+ /**
4820
+ * A small PNG data URL preview of a pixel or smart layer over the canvas (or of its mask with `mask: true`),
4821
+ * longest side `maxSide` px; `null` for layers without pixels.
4822
+ */
4823
+ getLayerThumbnailDataURL(id, options = {}) {
4824
+ const thumb = this.getLayerThumbnail(id, options);
4825
+ if (!thumb) return null;
4826
+ return pngDataUrl(thumb.width, thumb.height, thumb.data, 4);
4827
+ }
4844
4828
  // --- Painting ---
4845
4829
  /** Brush settings, colours and tool options of the paint tools (view state, no undo). */
4846
4830
  setPaintOptions(options) {
@@ -6057,11 +6041,11 @@ var ImageEditorController = class {
6057
6041
  return out2;
6058
6042
  }
6059
6043
  if (layer.type === "smart") {
6060
- add2("Smart objects were written as pixel layers (their rendered pixels)", layer.name);
6061
- const px2 = rasterizeSmartObject([layer], layer.id).layer;
6062
- const mask2 = bakeMask(px2);
6063
- if (mask2) px2.mask = mask2;
6064
- return px2;
6044
+ const out2 = { ...layer };
6045
+ const mask2 = bakeMask(layer);
6046
+ if (mask2) out2.mask = mask2;
6047
+ else delete out2.mask;
6048
+ return out2;
6065
6049
  }
6066
6050
  let out = { ...layer };
6067
6051
  if (layer.source === "document") {
@@ -6914,6 +6898,150 @@ function readAdjustmentControls(adjustments) {
6914
6898
  }
6915
6899
  return out;
6916
6900
  }
6901
+ var IMAGE_FILTER_PRESETS = [
6902
+ {
6903
+ id: "none",
6904
+ label: "Original",
6905
+ adjustments: {
6906
+ brightness: 0,
6907
+ contrast: 0,
6908
+ saturation: 0,
6909
+ exposure: 0,
6910
+ temperature: 0,
6911
+ tint: 0,
6912
+ blur: 0,
6913
+ opacity: 1
6914
+ }
6915
+ },
6916
+ {
6917
+ id: "dramatic",
6918
+ label: "Dramatic",
6919
+ adjustments: {
6920
+ brightness: -5,
6921
+ contrast: 45,
6922
+ saturation: 20,
6923
+ exposure: 5,
6924
+ temperature: -10,
6925
+ tint: 0
6926
+ }
6927
+ },
6928
+ {
6929
+ id: "cyberpunk",
6930
+ label: "Cyberpunk",
6931
+ adjustments: {
6932
+ brightness: 5,
6933
+ contrast: 35,
6934
+ saturation: 55,
6935
+ exposure: 0,
6936
+ temperature: -25,
6937
+ tint: 40
6938
+ }
6939
+ },
6940
+ {
6941
+ id: "vintage",
6942
+ label: "Vintage",
6943
+ adjustments: {
6944
+ brightness: 8,
6945
+ contrast: -15,
6946
+ saturation: -35,
6947
+ exposure: 5,
6948
+ temperature: 35,
6949
+ tint: 10
6950
+ }
6951
+ },
6952
+ {
6953
+ id: "warm",
6954
+ label: "Warm",
6955
+ adjustments: {
6956
+ brightness: 5,
6957
+ contrast: 10,
6958
+ saturation: 15,
6959
+ exposure: 0,
6960
+ temperature: 45,
6961
+ tint: 5
6962
+ }
6963
+ },
6964
+ {
6965
+ id: "cool",
6966
+ label: "Cool",
6967
+ adjustments: {
6968
+ brightness: 0,
6969
+ contrast: 15,
6970
+ saturation: 10,
6971
+ exposure: 0,
6972
+ temperature: -45,
6973
+ tint: -10
6974
+ }
6975
+ },
6976
+ {
6977
+ id: "grayscale",
6978
+ label: "B&W",
6979
+ adjustments: {
6980
+ brightness: 0,
6981
+ contrast: 20,
6982
+ saturation: -100,
6983
+ exposure: 0,
6984
+ temperature: 0,
6985
+ tint: 0
6986
+ }
6987
+ },
6988
+ {
6989
+ id: "noir",
6990
+ label: "Noir",
6991
+ adjustments: {
6992
+ brightness: -15,
6993
+ contrast: 65,
6994
+ saturation: -100,
6995
+ exposure: -5,
6996
+ temperature: 0,
6997
+ tint: 0
6998
+ }
6999
+ },
7000
+ {
7001
+ id: "fade",
7002
+ label: "Fade",
7003
+ adjustments: {
7004
+ brightness: 15,
7005
+ contrast: -25,
7006
+ saturation: -20,
7007
+ exposure: 10,
7008
+ temperature: 10,
7009
+ tint: 0
7010
+ }
7011
+ }
7012
+ ];
7013
+ var IMAGE_CROP_PRESETS = [
7014
+ { id: "free", label: "Free", aspectRatio: null },
7015
+ { id: "1:1", label: "1:1", aspectRatio: 1 },
7016
+ { id: "16:9", label: "16:9", aspectRatio: 16 / 9 },
7017
+ { id: "4:3", label: "4:3", aspectRatio: 4 / 3 },
7018
+ { id: "4:5", label: "4:5", aspectRatio: 4 / 5 },
7019
+ { id: "9:16", label: "9:16", aspectRatio: 9 / 16 }
7020
+ ];
7021
+ var DEPTH_MASK_CONTROLS = [
7022
+ {
7023
+ id: "near",
7024
+ label: "Near Threshold",
7025
+ section: "effects",
7026
+ min: 0,
7027
+ max: 255,
7028
+ step: 1,
7029
+ default: 0,
7030
+ decimals: 0,
7031
+ track: plain
7032
+ },
7033
+ {
7034
+ id: "far",
7035
+ label: "Far Threshold",
7036
+ section: "effects",
7037
+ min: 0,
7038
+ max: 255,
7039
+ step: 1,
7040
+ default: 255,
7041
+ decimals: 0,
7042
+ track: plain
7043
+ }
7044
+ ];
6917
7045
 
6918
7046
  // src/image-editor/selection/tool-options.ts
6919
7047
  var SELECTION_TOOLS = [
@@ -8747,7 +8875,7 @@ function createImagePsdTools(controller) {
8747
8875
  const open = {
8748
8876
  name: "import_psd",
8749
8877
  title: "Open a PSD as the document",
8750
- description: "Opens a PSD / PSB as the document, replacing the current layers, crop and rotation (one undo step; undo brings the previous document back). Pixel, group and adjustment layers, masks, blend modes, opacity, fill, clipping, visibility, locks, layer effects (with the patterns and global light of the file) map directly; type, smart-object, fill and shape layers come in as their pixels; unsupported adjustments are left out. Returns the new layer tree with ids, the size and the warnings (each names the affected layers).",
8878
+ description: "Opens a PSD / PSB as the document, replacing the current layers, crop and rotation (one undo step; undo brings the previous document back). Pixel, group and adjustment layers, masks, blend modes, opacity, fill, clipping, visibility, locks, layer effects (with the patterns and global light of the file) map directly; smart objects come in as smart objects (embedded contents, placement and warp, smart filters: Box Blur is drawn, other Photoshop filters are kept but not drawn, filter mask; linked contents as their stored pixels); type, fill and shape layers come in as their pixels; unsupported adjustments are left out. Returns the new layer tree with ids, the size and the warnings (each names the affected layers).",
8751
8879
  inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
8752
8880
  annotations: { title: "Open a PSD as the document", ...DESTRUCTIVE4 },
8753
8881
  handler: ({ data }) => {
@@ -8766,7 +8894,7 @@ function createImagePsdTools(controller) {
8766
8894
  const save = {
8767
8895
  name: "export_psd",
8768
8896
  title: "Export the document as a PSD",
8769
- description: "Writes the document as an 8-bit RGB PSD (a PSB above 30000 px) with real layers, groups, adjustment layers, masks and layer effects (an effects block opened from a PSD is written back unchanged while its effects are; the patterns effects use are embedded), in the source frame (crop and rotation are not applied). Layer filters and depth masks are baked, annotation layers are rasterized (in a browser; elsewhere left out), and the merged image is the editor's composite. Changes nothing. Returns the file as base64, its size in bytes, whether it is a PSB, and warnings for everything that was changed or left out.",
8897
+ description: "Writes the document as an 8-bit RGB PSD (a PSB above 30000 px) with real layers, groups, adjustment layers, masks and layer effects (an effects block opened from a PSD is written back unchanged while its effects are; the patterns effects use are embedded), in the source frame (crop and rotation are not applied). Smart objects are written as Photoshop smart objects (contents embedded once per linked set, placement, warp, smart filters and filter mask; a Develop smart filter has no Photoshop filter and is left out of the list). Layer filters and depth masks are baked, annotation layers are rasterized (in a browser; elsewhere left out), and the merged image is the editor's composite. Changes nothing. Returns the file as base64, its size in bytes, whether it is a PSB, and warnings for everything that was changed or left out.",
8770
8898
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
8771
8899
  annotations: { title: "Export the document as a PSD", ...READ5 },
8772
8900
  handler: async () => {
@@ -10103,6 +10231,49 @@ function smartFilterRows(layer) {
10103
10231
  }));
10104
10232
  return rows.reverse();
10105
10233
  }
10234
+ function extractNormalizedLayerItems(controller, fallbackImageUrl = "") {
10235
+ const state = controller.getState();
10236
+ const ctrlLayers = controller.getLayers();
10237
+ if (!ctrlLayers || ctrlLayers.length === 0) return [];
10238
+ const serialized = controller.toJSON();
10239
+ const assets = serialized.assets ?? {};
10240
+ const allSerializedLayers = [];
10241
+ const collectSerialized = (list) => {
10242
+ for (const sl of list) {
10243
+ allSerializedLayers.push(sl);
10244
+ if (sl.children && Array.isArray(sl.children)) {
10245
+ collectSerialized(sl.children);
10246
+ }
10247
+ }
10248
+ };
10249
+ collectSerialized(serialized.layers ?? []);
10250
+ const sLayerMap = new Map(allSerializedLayers.map((sl) => [sl.id, sl]));
10251
+ const imgW = state.imageDimensions?.width || 1;
10252
+ const imgH = state.imageDimensions?.height || 1;
10253
+ return ctrlLayers.map((l) => {
10254
+ const bounds = controller.getLayerBounds(l.id) || {
10255
+ x: 0,
10256
+ y: 0,
10257
+ width: imgW,
10258
+ height: imgH
10259
+ };
10260
+ const sLayer = sLayerMap.get(l.id);
10261
+ const assetKey = sLayer?.asset;
10262
+ const layerDataUrl = assetKey && assets[assetKey] || assets[l.id] || (sLayer?.source === "document" ? fallbackImageUrl : "");
10263
+ return {
10264
+ id: l.id,
10265
+ name: l.name || "Untitled Layer",
10266
+ imageUrl: layerDataUrl || fallbackImageUrl,
10267
+ thumbnailUrl: layerDataUrl || void 0,
10268
+ visible: l.visible !== false,
10269
+ opacity: l.opacity ?? 1,
10270
+ x: imgW ? bounds.x / imgW : 0,
10271
+ y: imgH ? bounds.y / imgH : 0,
10272
+ width: imgW ? bounds.width / imgW : 1,
10273
+ height: imgH ? bounds.height / imgH : 1
10274
+ };
10275
+ });
10276
+ }
10106
10277
 
10107
10278
  // src/image-editor/layers/effects-ui.ts
10108
10279
  function layerEffectsPanel(layer) {
@@ -10903,6 +11074,7 @@ export {
10903
11074
  DEFAULT_REFINE_EDGE_PARAMS,
10904
11075
  DEFAULT_SELECTION_TOOL_OPTIONS,
10905
11076
  DEFAULT_TRANSFORM,
11077
+ DEPTH_MASK_CONTROLS,
10906
11078
  DEVELOP_FILTER_ID,
10907
11079
  DIRECT_GAUSSIAN_MAX_SIGMA,
10908
11080
  DabWalker,
@@ -10919,8 +11091,10 @@ export {
10919
11091
  HUE_RANGE_LABELS,
10920
11092
  IDENTITY_MATRIX3,
10921
11093
  IMAGE_ADJUSTMENT_CONTROLS,
11094
+ IMAGE_CROP_PRESETS,
10922
11095
  IMAGE_EDITOR_SCHEMA_VERSION,
10923
11096
  IMAGE_EDITOR_STATE_VERSION,
11097
+ IMAGE_FILTER_PRESETS,
10924
11098
  INTERPOLATIONS,
10925
11099
  ImageEditorController,
10926
11100
  ImageEditorRenderer,
@@ -11133,6 +11307,7 @@ export {
11133
11307
  evaluateAdjustmentLayers,
11134
11308
  expandCoverage,
11135
11309
  exportPsd,
11310
+ extractNormalizedLayerItems,
11136
11311
  featherCoverage,
11137
11312
  fillRect,
11138
11313
  filterMaskCoverage,