@pixerate/editor 0.114.0 → 0.115.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,12 @@ import {
72
72
  tilesInRect,
73
73
  translateSurface
74
74
  } from "./chunk-XK5LZKBD.js";
75
+ import {
76
+ Transform,
77
+ builtinProfile,
78
+ parseProfile,
79
+ srgbProfile
80
+ } from "./chunk-BRLYOPLT.js";
75
81
  import {
76
82
  ZlibError,
77
83
  deflate,
@@ -9321,6 +9327,15 @@ function toRgba8(set) {
9321
9327
  }
9322
9328
  return out;
9323
9329
  }
9330
+ if (mode === MODE_CMYK && set.cmykToRgb) {
9331
+ const ink = new Uint8Array(n * 4);
9332
+ for (let i = 0, o = 0; i < n; i++, o += 4) {
9333
+ for (let c = 0; c < 4; c++) ink[o + c] = Math.round((1 - (planes[c] ? planes[c](i) : 1)) * 255);
9334
+ out[o + 3] = Math.round(alpha(i) * 255);
9335
+ }
9336
+ set.cmykToRgb(ink, out);
9337
+ return out;
9338
+ }
9324
9339
  const palette = set.palette;
9325
9340
  for (let i = 0, o = 0; i < n; i++, o += 4) {
9326
9341
  let r;
@@ -9745,9 +9760,10 @@ function recordPixels(ctx, rec, name) {
9745
9760
  mode: ctx.file.header.colorMode,
9746
9761
  color: planes.slice(0, ctx.colorCount),
9747
9762
  alpha: planes[ctx.colorCount],
9748
- palette: ctx.palette
9763
+ palette: ctx.palette,
9764
+ cmykToRgb: ctx.cmykToRgb
9749
9765
  };
9750
- const deep = toRgbaDeep(set);
9766
+ const deep = set.cmykToRgb ? null : toRgbaDeep(set);
9751
9767
  if (deep) return surfaceFromDeepPixels({ width, height, data: deep }, 4, rec.rect.left, rec.rect.top);
9752
9768
  return surfaceFromPixels({ width, height, data: toRgba8(set) }, 4, rec.rect.left, rec.rect.top);
9753
9769
  }
@@ -10154,9 +10170,10 @@ function mergedLayer(ctx) {
10154
10170
  mode: header.colorMode,
10155
10171
  color: Array.from({ length: cc }, (_, i) => plane(i)),
10156
10172
  alpha: hasAlpha ? plane(cc) : void 0,
10157
- palette: ctx.palette
10173
+ palette: ctx.palette,
10174
+ cmykToRgb: ctx.cmykToRgb
10158
10175
  };
10159
- const deep = hasAlpha ? null : toRgbaDeep(set);
10176
+ const deep = hasAlpha || set.cmykToRgb ? null : toRgbaDeep(set);
10160
10177
  let surface;
10161
10178
  if (deep) surface = surfaceFromDeepPixels({ width, height, data: deep }, 4);
10162
10179
  else {
@@ -10194,6 +10211,28 @@ function psdToLayers(file, options = {}) {
10194
10211
  depth: options.depth ?? 0,
10195
10212
  dpi: resolution(file) ?? 72
10196
10213
  };
10214
+ let cmykProfile = null;
10215
+ if (header.colorMode === MODE_CMYK && !ctx.skipPixels) {
10216
+ const icc = resource(file, 1039);
10217
+ let src = null;
10218
+ if (icc) {
10219
+ try {
10220
+ const p = parseProfile(icc);
10221
+ if (p.colorSpace === "cmyk") {
10222
+ src = p;
10223
+ cmykProfile = { name: p.description || "Embedded CMYK profile", icc: icc.slice() };
10224
+ } else warn.extra.push(`The CMYK file embeds a ${p.colorSpace.toUpperCase()} profile; the default coated CMYK profile was used.`);
10225
+ } catch (e) {
10226
+ warn.extra.push(`The CMYK profile could not be read (${e instanceof Error ? e.message : String(e)}); the default coated CMYK profile was used.`);
10227
+ }
10228
+ }
10229
+ if (!src) {
10230
+ src = builtinProfile("coated-cmyk");
10231
+ cmykProfile = { builtin: "coated-cmyk", name: src.description };
10232
+ }
10233
+ const t = Transform.create(src, srgbProfile(), { intent: "relative", bpc: true });
10234
+ ctx.cmykToRgb = (ink, out) => t.convert8(ink, 4, out, 4, false);
10235
+ }
10197
10236
  for (const block of file.globalBlocks) {
10198
10237
  if (LINKED_FILE_KEYS.includes(block.key)) {
10199
10238
  for (const f of parseLinkedFiles(block.data)) if (!ctx.linked.has(f.uuid)) ctx.linked.set(f.uuid, f);
@@ -10218,7 +10257,8 @@ function psdToLayers(file, options = {}) {
10218
10257
  if (mode !== "rgb" && header.depth > 8 && header.colorMode !== MODE_INDEXED && header.colorMode !== MODE_BITMAP) {
10219
10258
  warn.extra.push(`The file is ${header.depth}-bit ${mode}; it was converted to ${header.depth}-bit RGB.`);
10220
10259
  } else if (mode !== "rgb" || header.depth !== 8 && header.depth !== 16 && header.depth !== 32) {
10221
- warn.extra.push(`The file is ${header.depth}-bit ${mode}; it was converted to 8-bit RGB.`);
10260
+ const how = header.colorMode === MODE_CMYK ? ` through ${cmykProfile?.name ?? "its profile"}` : "";
10261
+ warn.extra.push(`The file is ${header.depth}-bit ${mode}; it was converted to 8-bit RGB${how}.`);
10222
10262
  }
10223
10263
  let layers = [];
10224
10264
  if (file.layers.length) {
@@ -10253,6 +10293,7 @@ function psdToLayers(file, options = {}) {
10253
10293
  patterns: [...ctx.patterns.values()],
10254
10294
  ...angle !== void 0 || altitude !== void 0 ? { globalLight: { angle: Math.max(-360, Math.min(360, angle ?? 120)), altitude: Math.max(0, Math.min(90, altitude ?? 30)) } } : {},
10255
10295
  info: { psb: header.psb, depth: header.depth, colorMode: mode, channels: header.channels },
10296
+ ...cmykProfile ? { sourceMode: { mode: "cmyk", profile: cmykProfile } } : header.colorMode === MODE_LAB ? { sourceMode: { mode: "lab" } } : {},
10256
10297
  ...resource(file, 1039) ? { iccProfile: resource(file, 1039).slice() } : {},
10257
10298
  bitDepth: (header.depth === 16 || header.depth === 32) && header.colorMode !== MODE_INDEXED && header.colorMode !== MODE_BITMAP ? header.depth : 8
10258
10299
  };
@@ -11051,6 +11092,21 @@ function exportPsd(input) {
11051
11092
  const { width, height } = input;
11052
11093
  const depth = input.depth ?? 8;
11053
11094
  if (width < 1 || height < 1) throw new PsdError("The document is empty.");
11095
+ const mode = input.colorMode ?? "rgb";
11096
+ if (mode !== "rgb" && !input.colorPlanes) throw new PsdError(`A ${mode.toUpperCase()} file needs colorPlanes to convert the pixels.`);
11097
+ if (mode !== "rgb" && depth !== 8) throw new PsdError(`${mode.toUpperCase()} files are written at 8 bits per channel.`);
11098
+ const colorIds = mode === "cmyk" ? [0, 1, 2, 3] : [0, 1, 2];
11099
+ const colorPlanes = (rgba, n2) => mode === "rgb" ? rgbaToPlanes(rgba, n2).slice(0, 3) : input.colorPlanes(rgba, n2);
11100
+ const pixelChannels = (rgba, w, h) => {
11101
+ const n2 = w * h;
11102
+ const alpha = rgbaToPlanes(rgba, n2)[3];
11103
+ return [rleChannel(CHANNEL_ALPHA, alpha, w, h, psb), ...colorPlanes(rgba, n2).map((p, i) => rleChannel(i, p, w, h, psb))];
11104
+ };
11105
+ const surfaceChannels = (surface, bounds) => {
11106
+ if (mode !== "rgb") return pixelChannels(readRegion(surface, bounds), bounds.width, bounds.height);
11107
+ const [r, g, b, a2] = surfacePlanes(surface, bounds, depth);
11108
+ return [CHANNEL_ALPHA, 0, 1, 2].map((id, i) => rleChannel(id, [a2, r, g, b][i], bounds.width, bounds.height, psb, depth));
11109
+ };
11054
11110
  const psb = width > PSD_MAX_DIMENSION || height > PSD_MAX_DIMENSION;
11055
11111
  const records = [];
11056
11112
  let nextId = 0;
@@ -11094,7 +11150,7 @@ function exportPsd(input) {
11094
11150
  case "group": {
11095
11151
  records.push({
11096
11152
  rect: EMPTY_RECT,
11097
- channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),
11153
+ channels: [CHANNEL_ALPHA, ...colorIds].map(emptyChannel),
11098
11154
  blendKey: "norm",
11099
11155
  opacity: 255,
11100
11156
  clipping: 0,
@@ -11111,7 +11167,7 @@ function exportPsd(input) {
11111
11167
  const rec = {
11112
11168
  ...base,
11113
11169
  rect: EMPTY_RECT,
11114
- channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),
11170
+ channels: [CHANNEL_ALPHA, ...colorIds].map(emptyChannel),
11115
11171
  blendKey,
11116
11172
  flags: base.flags | FLAG_PIXELS_IRRELEVANT
11117
11173
  };
@@ -11128,7 +11184,7 @@ function exportPsd(input) {
11128
11184
  const rec = {
11129
11185
  ...common2(layer, [{ key: block.key, data: block.data }]),
11130
11186
  rect: EMPTY_RECT,
11131
- channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),
11187
+ channels: [CHANNEL_ALPHA, ...colorIds].map(emptyChannel),
11132
11188
  blendKey: BLEND_TO_PSD[layer.blend] ?? "norm"
11133
11189
  };
11134
11190
  if (layer.mask) {
@@ -11149,16 +11205,10 @@ function exportPsd(input) {
11149
11205
  const rec = { ...common2(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
11150
11206
  const bounds = contentBounds(layer.surface);
11151
11207
  if (bounds) {
11152
- const [r2, g2, b2, a2] = surfacePlanes(layer.surface, bounds, depth);
11153
11208
  rec.rect = toRect({ ...bounds, x: bounds.x + layer.offset.x, y: bounds.y + layer.offset.y });
11154
- rec.channels = [
11155
- rleChannel(CHANNEL_ALPHA, a2, bounds.width, bounds.height, psb, depth),
11156
- rleChannel(0, r2, bounds.width, bounds.height, psb, depth),
11157
- rleChannel(1, g2, bounds.width, bounds.height, psb, depth),
11158
- rleChannel(2, b2, bounds.width, bounds.height, psb, depth)
11159
- ];
11209
+ rec.channels = surfaceChannels(layer.surface, bounds);
11160
11210
  } else {
11161
- rec.channels = [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel);
11211
+ rec.channels = [CHANNEL_ALPHA, ...colorIds].map(emptyChannel);
11162
11212
  }
11163
11213
  if (layer.mask) {
11164
11214
  const m = maskChannel(layer.mask, layer.offset.x, layer.offset.y, psb, depth);
@@ -11199,16 +11249,10 @@ function exportPsd(input) {
11199
11249
  const rec = { ...common2(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
11200
11250
  const bounds = contentBounds(layer.surface);
11201
11251
  if (bounds) {
11202
- const [r2, g2, b2, a2] = surfacePlanes(layer.surface, bounds, depth);
11203
11252
  rec.rect = toRect(bounds);
11204
- rec.channels = [
11205
- rleChannel(CHANNEL_ALPHA, a2, bounds.width, bounds.height, psb, depth),
11206
- rleChannel(0, r2, bounds.width, bounds.height, psb, depth),
11207
- rleChannel(1, g2, bounds.width, bounds.height, psb, depth),
11208
- rleChannel(2, b2, bounds.width, bounds.height, psb, depth)
11209
- ];
11253
+ rec.channels = surfaceChannels(layer.surface, bounds);
11210
11254
  } else {
11211
- rec.channels = [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel);
11255
+ rec.channels = [CHANNEL_ALPHA, ...colorIds].map(emptyChannel);
11212
11256
  }
11213
11257
  if (layer.mask) {
11214
11258
  const m = maskChannel(layer.mask, 0, 0, psb, depth);
@@ -11226,7 +11270,7 @@ function exportPsd(input) {
11226
11270
  }
11227
11271
  if (layer.type === "shape") {
11228
11272
  const losses = /* @__PURE__ */ new Set();
11229
- for (const b2 of shapeBlocks(layer, width, height, input.resolution ?? 72, losses)) extra.push({ key: b2.key, data: padEven(b2.data) });
11273
+ for (const b of shapeBlocks(layer, width, height, input.resolution ?? 72, losses)) extra.push({ key: b.key, data: padEven(b.data) });
11230
11274
  for (const n2 of losses) note(n2, layer.name);
11231
11275
  if (layer.fill?.type === "pattern") patternIds2.add(layer.fill.patternId);
11232
11276
  if (layer.stroke?.paint.type === "pattern") patternIds2.add(layer.stroke.paint.patternId);
@@ -11236,16 +11280,10 @@ function exportPsd(input) {
11236
11280
  const rec = { ...common2(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
11237
11281
  const bounds = contentBounds(layer.surface);
11238
11282
  if (bounds) {
11239
- const [r2, g2, b2, a2] = surfacePlanes(layer.surface, bounds, depth);
11240
11283
  rec.rect = toRect(bounds);
11241
- rec.channels = [
11242
- rleChannel(CHANNEL_ALPHA, a2, bounds.width, bounds.height, psb, depth),
11243
- rleChannel(0, r2, bounds.width, bounds.height, psb, depth),
11244
- rleChannel(1, g2, bounds.width, bounds.height, psb, depth),
11245
- rleChannel(2, b2, bounds.width, bounds.height, psb, depth)
11246
- ];
11284
+ rec.channels = surfaceChannels(layer.surface, bounds);
11247
11285
  } else {
11248
- rec.channels = [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel);
11286
+ rec.channels = [CHANNEL_ALPHA, ...colorIds].map(emptyChannel);
11249
11287
  }
11250
11288
  if (layer.mask) {
11251
11289
  const m = maskChannel(layer.mask, 0, 0, psb, depth);
@@ -11263,9 +11301,10 @@ function exportPsd(input) {
11263
11301
  if (records.length > 32767) throw new PsdError("A PSD holds at most 32767 layer records.");
11264
11302
  const n = width * height;
11265
11303
  if (input.merged.length < n * 4) throw new PsdError(`The merged image has ${input.merged.length} bytes, expected ${n * 4}.`);
11266
- const [r, g, b, a] = rgbaToPlanes(matteWhite(input.merged), n);
11304
+ const matted = matteWhite(input.merged);
11305
+ const a = rgbaToPlanes(matted, n)[3];
11267
11306
  const transparent = a.some((v) => v !== 255);
11268
- let planes = (transparent ? [r, g, b, a] : [r, g, b]).map((p) => planeAtDepth(p, depth));
11307
+ let planes = [...colorPlanes(matted, n), ...transparent ? [a] : []].map((p) => planeAtDepth(p, depth));
11269
11308
  const deep = input.mergedDeep;
11270
11309
  if (deep && depth !== 8 && deep.length >= n * 4 && (deep instanceof Uint16Array ? depth === 16 : depth === 32)) {
11271
11310
  const dp = deepToPlanes(matteWhiteDeep(deep), n);
@@ -11324,7 +11363,7 @@ function exportPsd(input) {
11324
11363
  if (filterItems.length) globalBlocks.push({ key: "FEid", data: writeFilterEffects(filterItems) });
11325
11364
  const bytes = writePsd(
11326
11365
  {
11327
- header: { psb, channels: planes.length, width, height, depth, colorMode: 3 },
11366
+ header: { psb, channels: planes.length, width, height, depth, colorMode: mode === "cmyk" ? 4 : mode === "lab" ? 9 : 3 },
11328
11367
  colorModeData: new Uint8Array(0),
11329
11368
  resources,
11330
11369
  layers: records,
@@ -11696,4 +11735,4 @@ export {
11696
11735
  smartContentsFile,
11697
11736
  exportPsd
11698
11737
  };
11699
- //# sourceMappingURL=chunk-EUVSQ25B.js.map
11738
+ //# sourceMappingURL=chunk-XICA4PVO.js.map