ag-psd 31.0.3 → 33.0.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 (159) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/00d0e0b4 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/0cc76730 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/20729831 +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/214a593b +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/25f8b84a +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/27518289 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/330ef2d9 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/37ff16b8 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/3f72a97c +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/75efccd4 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/762f9305 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/7bcfc50a +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/825e9a05 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/87372817 +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  25. package/.v8-cache/v24.7.0-x64-ef5a0af0/891a7c73 +0 -0
  26. package/.v8-cache/v24.7.0-x64-ef5a0af0/8b68a3a2 +0 -0
  27. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  28. package/.v8-cache/v24.7.0-x64-ef5a0af0/a1d12a93 +0 -0
  29. package/.v8-cache/v24.7.0-x64-ef5a0af0/a7e80865 +0 -0
  30. package/.v8-cache/v24.7.0-x64-ef5a0af0/ac317c9c +0 -0
  31. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  32. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  33. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8569d6b +0 -0
  34. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8b073c4 +0 -0
  35. package/.v8-cache/v24.7.0-x64-ef5a0af0/bb489c89 +0 -0
  36. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  37. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  38. package/.v8-cache/v24.7.0-x64-ef5a0af0/c46725e1 +0 -0
  39. package/.v8-cache/v24.7.0-x64-ef5a0af0/c794cb34 +0 -0
  40. package/.v8-cache/v24.7.0-x64-ef5a0af0/cd4ae980 +0 -0
  41. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  42. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  43. package/.v8-cache/v24.7.0-x64-ef5a0af0/d02dd6f7 +0 -0
  44. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  45. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  46. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6735171 +0 -0
  47. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  48. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  49. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  50. package/.v8-cache/v24.7.0-x64-ef5a0af0/f5417276 +0 -0
  51. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  52. package/.v8-cache/v24.7.0-x64-ef5a0af0/f74d3518 +0 -0
  53. package/.v8-cache/v24.7.0-x64-ef5a0af0/f886deac +0 -0
  54. package/.v8-cache/v24.7.0-x64-ef5a0af0/fd0a2987 +0 -0
  55. package/.v8-cache/v24.7.0-x64-ef5a0af0/fe349423 +0 -0
  56. package/CHANGELOG.md +107 -0
  57. package/LICENSE +5 -0
  58. package/README.md +3 -6
  59. package/README_PSD.md +242 -81
  60. package/dist/abr.d.ts +11 -4
  61. package/dist/abr.js +363 -229
  62. package/dist/abr.js.map +1 -1
  63. package/dist/additionalInfo.d.ts +56 -2
  64. package/dist/additionalInfo.js +600 -364
  65. package/dist/additionalInfo.js.map +1 -1
  66. package/dist/ase.d.ts +6 -1
  67. package/dist/ase.js +61 -61
  68. package/dist/ase.js.map +1 -1
  69. package/dist/bundle.js +3096 -1541
  70. package/dist/descriptor.d.ts +25 -6
  71. package/dist/descriptor.js +380 -135
  72. package/dist/descriptor.js.map +1 -1
  73. package/dist/effectsHelpers.js +12 -12
  74. package/dist/effectsHelpers.js.map +1 -1
  75. package/dist/engineData.d.ts +3 -1
  76. package/dist/engineData.js +34 -13
  77. package/dist/engineData.js.map +1 -1
  78. package/dist/engineData2.js +50 -21
  79. package/dist/engineData2.js.map +1 -1
  80. package/dist/helpers.d.ts +6 -3
  81. package/dist/helpers.js +22 -6
  82. package/dist/helpers.js.map +1 -1
  83. package/dist/imageResources.d.ts +15 -3
  84. package/dist/imageResources.js +529 -144
  85. package/dist/imageResources.js.map +1 -1
  86. package/dist/index.js +1 -0
  87. package/dist/index.js.map +1 -1
  88. package/dist/jpeg.js +30 -10
  89. package/dist/jpeg.js.map +1 -1
  90. package/dist/psd.d.ts +154 -18
  91. package/dist/psd.js.map +1 -1
  92. package/dist/psdReader.d.ts +12 -2
  93. package/dist/psdReader.js +720 -421
  94. package/dist/psdReader.js.map +1 -1
  95. package/dist/psdWriter.d.ts +3 -0
  96. package/dist/psdWriter.js +167 -130
  97. package/dist/psdWriter.js.map +1 -1
  98. package/dist/text.js +143 -29
  99. package/dist/text.js.map +1 -1
  100. package/dist/utf8.js +36 -18
  101. package/dist/utf8.js.map +1 -1
  102. package/dist-es/abr.d.ts +11 -4
  103. package/dist-es/abr.js +363 -229
  104. package/dist-es/abr.js.map +1 -1
  105. package/dist-es/additionalInfo.d.ts +56 -2
  106. package/dist-es/additionalInfo.js +600 -364
  107. package/dist-es/additionalInfo.js.map +1 -1
  108. package/dist-es/ase.d.ts +6 -1
  109. package/dist-es/ase.js +61 -61
  110. package/dist-es/ase.js.map +1 -1
  111. package/dist-es/descriptor.d.ts +25 -6
  112. package/dist-es/descriptor.js +380 -135
  113. package/dist-es/descriptor.js.map +1 -1
  114. package/dist-es/effectsHelpers.js +12 -12
  115. package/dist-es/effectsHelpers.js.map +1 -1
  116. package/dist-es/engineData.d.ts +3 -1
  117. package/dist-es/engineData.js +34 -13
  118. package/dist-es/engineData.js.map +1 -1
  119. package/dist-es/engineData2.js +50 -21
  120. package/dist-es/engineData2.js.map +1 -1
  121. package/dist-es/helpers.d.ts +6 -3
  122. package/dist-es/helpers.js +22 -6
  123. package/dist-es/helpers.js.map +1 -1
  124. package/dist-es/imageResources.d.ts +15 -3
  125. package/dist-es/imageResources.js +529 -144
  126. package/dist-es/imageResources.js.map +1 -1
  127. package/dist-es/index.js +1 -0
  128. package/dist-es/index.js.map +1 -1
  129. package/dist-es/jpeg.js +30 -10
  130. package/dist-es/jpeg.js.map +1 -1
  131. package/dist-es/psd.d.ts +154 -18
  132. package/dist-es/psd.js.map +1 -1
  133. package/dist-es/psdReader.d.ts +12 -2
  134. package/dist-es/psdReader.js +720 -421
  135. package/dist-es/psdReader.js.map +1 -1
  136. package/dist-es/psdWriter.d.ts +3 -0
  137. package/dist-es/psdWriter.js +167 -130
  138. package/dist-es/psdWriter.js.map +1 -1
  139. package/dist-es/text.js +143 -29
  140. package/dist-es/text.js.map +1 -1
  141. package/dist-es/utf8.js +36 -18
  142. package/dist-es/utf8.js.map +1 -1
  143. package/package.json +2 -2
  144. package/src/abr.ts +380 -232
  145. package/src/additionalInfo.ts +781 -477
  146. package/src/ase.ts +66 -61
  147. package/src/descriptor.ts +386 -142
  148. package/src/effectsHelpers.ts +9 -9
  149. package/src/engineData.ts +36 -13
  150. package/src/engineData2.ts +468 -432
  151. package/src/helpers.ts +23 -5
  152. package/src/imageResources.ts +658 -167
  153. package/src/index.ts +1 -0
  154. package/src/jpeg.ts +27 -12
  155. package/src/psd.ts +158 -30
  156. package/src/psdReader.ts +732 -414
  157. package/src/psdWriter.ts +186 -132
  158. package/src/text.ts +139 -18
  159. package/src/utf8.ts +35 -21
package/src/psdWriter.ts CHANGED
@@ -1,20 +1,22 @@
1
1
  import { Psd, Layer, LayerAdditionalInfo, ColorMode, SectionDividerType, WriteOptions, Color, GlobalLayerMaskInfo, PixelData, LayerMaskData, Compression, ChannelID, PatternInfo } from './psd';
2
- import { hasAlpha, createCanvas, writeDataRLE, LayerChannelData, ChannelData, offsetForChannel, createImageData, fromBlendMode, clamp, LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, RAW_IMAGE_DATA, writeDataZipWithoutPrediction, imageDataToCanvas } from './helpers';
3
- import { ExtendedWriteOptions, infoHandlers } from './additionalInfo';
2
+ import { hasAlpha, createCanvas, writeDataRLE, LayerChannelData, ChannelData, offsetForChannel, createImageData, fromBlendMode, clamp, LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, writeDataZipWithoutPrediction, imageDataToCanvas, denormalizeLabAB } from './helpers';
3
+ import { ExtendedWriteOptions, infoHandlers, infoHandlersMap } from './additionalInfo';
4
4
  import { InternalImageResources, resourceHandlers } from './imageResources';
5
+ import { getCompositeImageData, getLayerImageData, getLayerMaskImageData, getLayerRealMaskImageData } from './psdReader';
5
6
 
6
7
  export interface PsdWriter {
7
8
  offset: number;
8
9
  buffer: ArrayBuffer;
9
10
  view: DataView;
10
11
  tempBuffer: Uint8Array | undefined;
12
+ log(...args: any[]): void;
11
13
  }
12
14
 
13
15
  export function createWriter(size = 4096): PsdWriter {
14
16
  const buffer = new ArrayBuffer(size);
15
17
  const view = new DataView(buffer);
16
18
  const offset = 0;
17
- return { buffer, view, offset, tempBuffer: undefined };
19
+ return { buffer, view, offset, tempBuffer: undefined, log: console.log };
18
20
  }
19
21
 
20
22
  export function getWriterBuffer(writer: PsdWriter) {
@@ -153,39 +155,45 @@ export function writeUnicodeStringWithPadding(writer: PsdWriter, text: string) {
153
155
  writeUint16(writer, 0);
154
156
  }
155
157
 
156
- function getLargestLayerSize(layers: Layer[] = []): number {
158
+ // Worst-case size of writeDataRLE output for a single channel: per row the byte count (2 bytes, 4 for PSB)
159
+ // plus PackBits data, which in the worst case is all literal runs of up to 128 bytes, each with 1 header byte.
160
+ export function getRLEChannelBound(width: number, height: number, large: boolean) {
161
+ return height * ((large ? 4 : 2) + width + Math.ceil(width / 128));
162
+ }
163
+
164
+ function getLargestLayerSize(layers: Layer[] = [], large: boolean): number {
157
165
  let max = 0;
158
166
 
159
167
  for (const layer of layers) {
160
- const { width, height } = getLayerDimentions(layer);
161
- max = Math.max(max, 2 * height + 2 * width * height);
162
-
163
- if (layer.mask) {
164
- const { width, height } = getLayerDimentions(layer.mask);
165
- max = Math.max(max, 2 * height + 2 * width * height);
166
- }
167
-
168
- if (layer.realMask) {
169
- const { width, height } = getLayerDimentions(layer.realMask);
170
- max = Math.max(max, 2 * height + 2 * width * height);
168
+ for (const target of [layer, layer.mask, layer.realMask]) {
169
+ if (target) {
170
+ const { width, height } = getLayerDimentions(target);
171
+ max = Math.max(max, getRLEChannelBound(width, height, large));
172
+ }
171
173
  }
172
174
 
173
175
  if (layer.children) {
174
- max = Math.max(max, getLargestLayerSize(layer.children));
176
+ max = Math.max(max, getLargestLayerSize(layer.children, large));
175
177
  }
176
178
  }
177
179
 
178
180
  return max;
179
181
  }
180
182
 
183
+ export function writeUint64(writer: PsdWriter, value: number) {
184
+ writeUint32(writer, Math.floor(value / 0x100000000));
185
+ writeUint32(writer, value >>> 0);
186
+ }
187
+
181
188
  export function writeSection(writer: PsdWriter, round: number, func: () => void, writeTotalLength = false, large = false) {
182
- if (large) writeUint32(writer, 0);
183
189
  const offset = writer.offset;
190
+ if (large) writeUint32(writer, 0);
184
191
  writeUint32(writer, 0);
192
+ const start = writer.offset;
185
193
 
186
194
  func();
187
195
 
188
- let length = writer.offset - offset - 4;
196
+ let length = writer.offset - start;
189
197
  let len = length;
190
198
 
191
199
  while (len % round) {
@@ -193,32 +201,34 @@ export function writeSection(writer: PsdWriter, round: number, func: () => void,
193
201
  len++;
194
202
  }
195
203
 
196
- // while (writer.offset % round) {
197
- // writeUint8(writer, 0);
198
- // len++;
199
- // }
200
-
201
204
  if (writeTotalLength) {
202
205
  length = len;
203
206
  }
204
207
 
205
- writer.view.setUint32(offset, length, false);
208
+ if (large) {
209
+ writer.view.setUint32(offset, Math.floor(length / 0x100000000), false);
210
+ writer.view.setUint32(offset + 4, length >>> 0, false);
211
+ } else {
212
+ if (length > 0xffffffff) throw new Error('Section too large, use PSB format instead');
213
+ writer.view.setUint32(offset, length, false);
214
+ }
215
+ }
216
+
217
+ function verifyPixelData(data: PixelData | undefined, name: string) {
218
+ // check element size instead of instanceof, so arrays from other realms (iframes, workers, vm) are accepted
219
+ const bytesPerElement = data && (data.data as any).BYTES_PER_ELEMENT;
220
+ if (bytesPerElement !== undefined && bytesPerElement !== 1) {
221
+ throw new Error(`${name} has incorrect bitDepth (only 8-bit Uint8Array or Uint8ClampedArray data is supported for writing)`);
222
+ }
206
223
  }
207
224
 
208
225
  function verifyBitCount(target: Psd | Layer) {
209
226
  target.children?.forEach(verifyBitCount);
210
227
 
211
- const data = target.imageData;
212
- if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
213
- throw new Error('imageData has incorrect bitDepth');
214
- }
228
+ verifyPixelData(target.imageData, 'imageData');
215
229
 
216
- if ('mask' in target && target.mask) {
217
- const data = target.mask.imageData;
218
- if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
219
- throw new Error('mask imageData has incorrect bitDepth');
220
- }
221
- }
230
+ if ('mask' in target) verifyPixelData(target.mask?.imageData, 'mask imageData');
231
+ if ('realMask' in target) verifyPixelData(target.realMask?.imageData, 'realMask imageData');
222
232
  }
223
233
 
224
234
  export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}) {
@@ -233,11 +243,29 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
233
243
  if (bitsPerChannel !== 8)
234
244
  throw new Error('bitsPerChannel other than 8 are not supported for writing');
235
245
 
246
+ if (options.log) writer.log = options.log;
247
+
248
+ const layers: Layer[] = [];
249
+ addChildren(layers, psd.children);
250
+ if (!layers.length) layers.push({});
251
+
236
252
  verifyBitCount(psd);
237
253
 
238
254
  const imageResources: InternalImageResources = { ...psd.imageResources };
239
255
  const opt: ExtendedWriteOptions = { ...options, layerIds: new Set(), layerToId: new Map() };
240
256
 
257
+ // document is always written in RGB color mode, skip resources specific to original color mode
258
+ if (psd.colorMode !== undefined && psd.colorMode !== ColorMode.RGB) {
259
+ delete imageResources.iccProfile;
260
+ delete imageResources.indexedColorCount;
261
+ delete imageResources.transparencyIndex;
262
+ delete imageResources.alphaChannelDisplayInfo;
263
+ delete imageResources.colorHalftoneScreens;
264
+ delete imageResources.grayscaleHalftoneScreens;
265
+ delete imageResources.colorTransferFunctions;
266
+ delete imageResources.grayscaleTransferFunctions;
267
+ }
268
+
241
269
  if (opt.generateThumbnail) {
242
270
  imageResources.thumbnail = createThumbnail(psd);
243
271
  }
@@ -248,12 +276,16 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
248
276
  imageData = psd.canvas.getContext('2d')!.getImageData(0, 0, psd.canvas.width, psd.canvas.height);
249
277
  }
250
278
 
279
+ if (!imageData && psd.rawCompositeData) { // read with `useRawData` option
280
+ imageData = getCompositeImageData(psd);
281
+ }
282
+
251
283
  if (imageData && (psd.width !== imageData.width || psd.height !== imageData.height))
252
284
  throw new Error('Document canvas must have the same size as document');
253
285
 
254
286
  const globalAlpha = !!imageData && hasAlpha(imageData);
255
- const maxBufferSize = Math.max(getLargestLayerSize(psd.children), 4 * 2 * psd.width * psd.height + 2 * psd.height);
256
- writer.tempBuffer = new Uint8Array(maxBufferSize);
287
+ // only used for encoding a single layer channel at a time, composite image is encoded directly into the writer buffer
288
+ writer.tempBuffer = new Uint8Array(getLargestLayerSize(psd.children, !!options.psb));
257
289
 
258
290
  // header
259
291
  writeSignature(writer, '8BPS');
@@ -265,20 +297,8 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
265
297
  writeUint16(writer, bitsPerChannel); // bits per channel
266
298
  writeUint16(writer, ColorMode.RGB); // we only support saving RGB right now
267
299
 
268
- // color mode data
269
- writeSection(writer, 1, () => {
270
- if (psd.palette) {
271
- for (let i = 0; i < 256; i++) writeUint8(writer, psd.palette[i]?.r || 0);
272
- for (let i = 0; i < 256; i++) writeUint8(writer, psd.palette[i]?.g || 0);
273
- for (let i = 0; i < 256; i++) writeUint8(writer, psd.palette[i]?.b || 0);
274
- }
275
-
276
- // TODO: other data?
277
- });
278
-
279
- const layers: Layer[] = [];
280
- addChildren(layers, psd.children);
281
- if (!layers.length) layers.push({});
300
+ // color mode data (empty for RGB color mode)
301
+ writeUint32(writer, 0);
282
302
 
283
303
  // image resources
284
304
 
@@ -290,10 +310,12 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
290
310
  const has = handler.has(imageResources);
291
311
  const count = has === false ? 0 : (has === true ? 1 : has);
292
312
  for (let i = 0; i < count; i++) {
313
+ const id = handler.keyCount > 1 ? handler.key + i : handler.key;
314
+ const name = handler.name ? handler.name(imageResources, i) : '';
293
315
  writeSignature(writer, '8BIM');
294
- writeUint16(writer, handler.key);
295
- writePascalString(writer, '', 2);
296
- writeSection(writer, 2, () => handler.write(writer, imageResources, i));
316
+ writeUint16(writer, id);
317
+ writePascalString(writer, name, 2);
318
+ writeSection(writer, 2, () => handler.write(writer, imageResources, i, psd));
297
319
  }
298
320
  }
299
321
  });
@@ -305,45 +327,51 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
305
327
  writeAdditionalLayerInfo(writer, psd, psd, opt);
306
328
  }, undefined, !!opt.psb);
307
329
 
308
- // image data
309
- const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
310
- const width = imageData ? imageData.width : psd.width;
311
- const height = imageData ? imageData.height : psd.height;
312
- const data: PixelData = { data: new Uint8Array(width * height * 4), width, height };
330
+ writer.tempBuffer = undefined;
313
331
 
332
+ // image data
314
333
  writeUint16(writer, Compression.RleCompressed); // Photoshop doesn't support zip compression of composite image data
315
334
 
316
- if (RAW_IMAGE_DATA && (psd as any).imageDataRaw) {
317
- console.log('writing raw image data');
318
- writeBytes(writer, (psd as any).imageDataRaw);
319
- } else {
320
- if (imageData) data.data.set(new Uint8Array(imageData.data.buffer, imageData.data.byteOffset, imageData.data.byteLength));
321
-
322
- // add weird white matte
323
- if (globalAlpha) {
324
- const size = data.width * data.height * 4;
325
- const p = data.data;
326
- for (let i = 0; i < size; i += 4) {
327
- const pa = p[i + 3];
328
- if (pa != 0 && pa != 255) {
329
- const a = pa / 255;
330
- const ra = 255 * (1 - a);
331
- p[i + 0] = p[i + 0] * a + ra;
332
- p[i + 1] = p[i + 1] * a + ra;
333
- p[i + 2] = p[i + 2] * a + ra;
334
- }
335
+ const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
336
+ const data = getCompositeData(imageData, psd.width, psd.height, globalAlpha);
337
+
338
+ // encode directly into the writer buffer
339
+ const maxSize = channels.length * getRLEChannelBound(data.width, data.height, !!options.psb);
340
+ ensureSize(writer, writer.offset + maxSize);
341
+ const buffer = new Uint8Array(writer.buffer, writer.offset, maxSize);
342
+ writer.offset += writeDataRLE(buffer, data, channels, !!options.psb).length;
343
+ }
344
+
345
+ function getCompositeData(imageData: PixelData | undefined, width: number, height: number, globalAlpha: boolean): PixelData {
346
+ if (!imageData) return { data: new Uint8Array(width * height * 4), width, height };
347
+
348
+ const size = width * height * 4;
349
+ const src = imageData.data;
350
+ let dst: Uint8Array | undefined = undefined;
351
+
352
+ // add weird white matte (copy the data only if there are semi-transparent pixels)
353
+ if (globalAlpha) {
354
+ for (let i = 0; i < size; i += 4) {
355
+ const pa = src[i + 3];
356
+ if (pa != 0 && pa != 255) {
357
+ if (!dst) dst = new Uint8Array(src.buffer, src.byteOffset, size).slice();
358
+ const a = pa / 255;
359
+ const ra = 255 * (1 - a);
360
+ dst[i + 0] = src[i + 0] * a + ra;
361
+ dst[i + 1] = src[i + 1] * a + ra;
362
+ dst[i + 2] = src[i + 2] * a + ra;
335
363
  }
336
364
  }
337
-
338
- writeBytes(writer, writeDataRLE(writer.tempBuffer, data, channels, !!options.psb));
339
365
  }
366
+
367
+ return dst ? { data: dst, width, height } : imageData;
340
368
  }
341
369
 
342
370
  function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlpha: boolean, options: ExtendedWriteOptions) {
343
371
  writeSection(writer, 4, () => {
344
372
  writeInt16(writer, globalAlpha ? -layers.length : layers.length);
345
373
 
346
- const layersData = layers.map((l, i) => getChannels(writer.tempBuffer!, l, i === 0, options));
374
+ const layersData = layers.map((l, i) => getChannels(writer, l, i === 0, options));
347
375
 
348
376
  // layer records
349
377
  for (const layerData of layersData) {
@@ -357,8 +385,11 @@ function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlph
357
385
 
358
386
  for (const c of channels) {
359
387
  writeInt16(writer, c.id);
360
- if (options.psb) writeUint32(writer, 0);
361
- writeUint32(writer, c.length);
388
+ if (options.psb) {
389
+ writeUint64(writer, c.length);
390
+ } else {
391
+ writeUint32(writer, c.length);
392
+ }
362
393
  }
363
394
 
364
395
  writeSignature(writer, '8BIM');
@@ -397,8 +428,15 @@ function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlph
397
428
  }, true, options.psb);
398
429
  }
399
430
 
400
- function writeLayerMaskData(writer: PsdWriter, { mask, realMask }: Layer, layerData: LayerChannelData) {
401
- writeSection(writer, 1, () => {
431
+ // real mask is only stored separately if layer has both vector and pixel masks, otherwise it's stored as the main mask
432
+ function getMasks(layer: Layer) {
433
+ return layer.mask ? { mask: layer.mask, realMask: layer.realMask } : { mask: layer.realMask, realMask: undefined };
434
+ }
435
+
436
+ function writeLayerMaskData(writer: PsdWriter, layer: Layer, layerData: LayerChannelData) {
437
+ const { mask, realMask } = getMasks(layer);
438
+
439
+ writeSection(writer, 4, () => {
402
440
  if (!mask && !realMask) return;
403
441
 
404
442
  let params = 0, flags = 0, realFlags = 0;
@@ -444,9 +482,7 @@ function writeLayerMaskData(writer: PsdWriter, { mask, realMask }: Layer, layerD
444
482
  if (mask.vectorMaskDensity !== undefined) writeUint8(writer, Math.round(mask.vectorMaskDensity * 0xff));
445
483
  if (mask.vectorMaskFeather !== undefined) writeFloat64(writer, mask.vectorMaskFeather);
446
484
  }
447
-
448
- writeZeros(writer, 2);
449
- });
485
+ }, true); // photoshop pads mask data to multiple of 4 bytes and includes padding in the length
450
486
  }
451
487
 
452
488
  function writerBlendingRange(writer: PsdWriter, range: number[]) {
@@ -480,7 +516,7 @@ function writeGlobalLayerMaskInfo(writer: PsdWriter, info: GlobalLayerMaskInfo |
480
516
  writeUint16(writer, info.colorSpace2);
481
517
  writeUint16(writer, info.colorSpace3);
482
518
  writeUint16(writer, info.colorSpace4);
483
- writeUint16(writer, Math.round(info.opacity * 0xff));
519
+ writeUint16(writer, Math.round(clamp(info.opacity, 0, 1) * 100)); // 0-100
484
520
  writeUint8(writer, info.kind);
485
521
  writeZeros(writer, 3);
486
522
  }
@@ -492,13 +528,13 @@ function writeAdditionalLayerInfo(writer: PsdWriter, target: LayerAdditionalInfo
492
528
  let key = handler.key;
493
529
 
494
530
  if (key === 'Txt2' && options.invalidateTextLayers) continue;
495
- if (key === 'vmsk' && options.psb) key = 'vsms';
531
+ if (key === 'vmsk' && infoHandlersMap['vscg'].has(target)) key = 'vsms'; // Photoshop uses 'vsms' for layers with vector stroke
496
532
 
497
533
  if (handler.has(target)) {
498
534
  const large = options.psb && largeAdditionalInfoKeys.indexOf(key) !== -1;
499
535
  const writeTotalLength = key !== 'Txt2' && key !== 'cinf' && key !== 'extn' && key !== 'CAI ' && key !== 'OCIO';
500
536
  const fourBytes = key === 'levl' || key === 'Txt2' || key === 'luni' || key === 'vmsk' || key === 'artb' || key === 'artd' ||
501
- key === 'vogk' || key === 'SoLd' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
537
+ key === 'vogk' || key === 'SoLd' || key === 'SoLE' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
502
538
  key === 'lmfx' || key === 'lrFX' || key === 'cinf' || key === 'PlLd' || key === 'Anno' || key === 'CAI ' || key === 'OCIO' || key === 'GenI' || key === 'FEid' || key === 'curv' || key === 'CgEd' || key === 'vibA' || key === 'blwh' || key === 'grdm';
503
539
 
504
540
  writeSignature(writer, large ? '8B64' : '8BIM');
@@ -555,11 +591,8 @@ function addChildren(layers: Layer[], children: Layer[] | undefined) {
555
591
  }
556
592
 
557
593
  function resizeBuffer(writer: PsdWriter, size: number) {
558
- let newLength = writer.buffer.byteLength;
559
-
560
- do {
561
- newLength *= 2;
562
- } while (size > newLength);
594
+ // grow at least 2x to keep small writes fast, but don't overshoot large reservations (like composite image data)
595
+ const newLength = Math.max(writer.buffer.byteLength * 2, size);
563
596
 
564
597
  const newBuffer = new ArrayBuffer(newLength);
565
598
  const newBytes = new Uint8Array(newBuffer);
@@ -587,11 +620,11 @@ function createThumbnail(psd: Psd) {
587
620
 
588
621
  if (psd.width > psd.height) {
589
622
  canvas.width = 160;
590
- canvas.height = Math.floor(psd.height * (canvas.width / psd.width));
623
+ canvas.height = Math.max(1, Math.floor(psd.height * (canvas.width / psd.width)));
591
624
  scale = canvas.width / psd.width;
592
625
  } else {
593
626
  canvas.height = 160;
594
- canvas.width = Math.floor(psd.width * (canvas.height / psd.height));
627
+ canvas.width = Math.max(1, Math.floor(psd.width * (canvas.height / psd.height)));
595
628
  scale = canvas.height / psd.height;
596
629
  }
597
630
 
@@ -607,7 +640,7 @@ function createThumbnail(psd: Psd) {
607
640
  return canvas;
608
641
  }
609
642
 
610
- function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, layer: Layer, mask: LayerMaskData, options: WriteOptions, realMask: boolean) {
643
+ function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, mask: LayerMaskData, options: WriteOptions, realMask: boolean) {
611
644
  let top = (mask.top as any) | 0;
612
645
  let left = (mask.left as any) | 0;
613
646
  let right = (mask.right as any) | 0;
@@ -629,17 +662,14 @@ function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, la
629
662
  let buffer: Uint8Array;
630
663
  let compression: Compression;
631
664
 
632
- if (RAW_IMAGE_DATA && (layer as any)[realMask ? 'realMaskDataRaw' : 'maskDataRaw']) {
633
- buffer = (layer as any)[realMask ? 'realMaskDataRaw' : 'maskDataRaw'];
634
- compression = (layer as any)[realMask ? 'realMaskDataRawCompression' : 'maskDataRawCompression'];
635
- } else if (!imageData) {
665
+ if (!imageData) {
636
666
  buffer = new Uint8Array(0);
637
667
  compression = Compression.RleCompressed;
638
668
  } else if (options.compress) {
639
669
  buffer = writeDataZipWithoutPrediction(imageData, [0]);
640
670
  compression = Compression.ZipWithoutPrediction;
641
671
  } else {
642
- buffer = writeDataRLE(tempBuffer, imageData, [0], !!options.psb)!;
672
+ buffer = writeDataRLE(tempBuffer, imageData, [0], !!options.psb).slice(); // copy, temp buffer is reused
643
673
  compression = Compression.RleCompressed;
644
674
  }
645
675
 
@@ -657,20 +687,47 @@ function bounds(obj: { top?: number; left?: number; bottom?: number; right?: num
657
687
  } : undefined;
658
688
  }
659
689
 
660
- function getChannels(tempBuffer: Uint8Array, layer: Layer, background: boolean, options: WriteOptions): LayerChannelData {
690
+ function getChannels(writer: PsdWriter, layer: Layer, background: boolean, options: WriteOptions): LayerChannelData {
691
+ let source = layer;
692
+
661
693
  if (layer.rawData) {
662
- return {
663
- layer,
664
- channels: layer.rawData.channels.map(c => ({ ...c, length: 2 + (c.data?.byteLength ?? 0) })),
665
- ...bounds(layer)!,
666
- mask: bounds(layer.mask),
667
- realMask: bounds(layer.realMask),
694
+ const { colorMode, bitsPerChannel, large } = layer.rawData;
695
+
696
+ if (large === !!options.psb && colorMode === ColorMode.RGB && bitsPerChannel === 8) {
697
+ // same mask mapping as in writeLayerMaskData
698
+ const { mask, realMask } = getMasks(layer);
699
+ return {
700
+ layer,
701
+ channels: layer.rawData.channels.map(c => ({ ...c, length: 2 + (c.data?.byteLength ?? 0) })),
702
+ ...bounds(layer)!,
703
+ mask: bounds(mask),
704
+ realMask: bounds(realMask),
705
+ };
706
+ }
707
+
708
+ if (bitsPerChannel !== 8) throw new Error('Layer raw data with bitsPerChannel other than 8 is not supported for writing');
709
+
710
+ // raw data is in different format than the one we're writing, decode it so it can be re-encoded
711
+ source = {
712
+ ...layer,
713
+ rawData: undefined,
714
+ canvas: undefined,
715
+ imageData: getLayerImageData(layer),
716
+ mask: layer.mask && { ...layer.mask, canvas: undefined, imageData: getLayerMaskImageData(layer) },
717
+ realMask: layer.realMask && { ...layer.realMask, canvas: undefined, imageData: getLayerRealMaskImageData(layer) },
668
718
  };
719
+
720
+ // temp buffer is sized only for layers with image data, grow it and keep it for following layers
721
+ const size = getLargestLayerSize([{ ...source, children: undefined }], !!options.psb);
722
+ if (writer.tempBuffer!.byteLength < size) writer.tempBuffer = new Uint8Array(size);
669
723
  }
670
724
 
671
- const layerData = getLayerChannels(tempBuffer, layer, background, options);
672
- if (layer.mask) getMaskChannels(tempBuffer, layerData, layer, layer.mask, options, false);
673
- if (layer.realMask) getMaskChannels(tempBuffer, layerData, layer, layer.realMask, options, true);
725
+ const tempBuffer = writer.tempBuffer!;
726
+ const { mask, realMask } = getMasks(source);
727
+ const layerData = getLayerChannels(tempBuffer, source, background, options);
728
+ if (mask) getMaskChannels(tempBuffer, layerData, mask, options, false);
729
+ if (realMask) getMaskChannels(tempBuffer, layerData, realMask, options, true);
730
+ layerData.layer = layer;
674
731
  return layerData;
675
732
  }
676
733
 
@@ -751,24 +808,20 @@ function getLayerChannels(tempBuffer: Uint8Array, layer: Layer, background: bool
751
808
  ChannelID.Color2,
752
809
  ];
753
810
 
754
- if (!background || options.noBackground || layer.mask || hasAlpha(imageData) || (RAW_IMAGE_DATA && (layer as any).imageDataRaw?.['-1'])) {
811
+ if (!background || options.noBackground || layer.mask || hasAlpha(imageData)) {
755
812
  channelIds.unshift(ChannelID.Transparency);
756
813
  }
757
814
 
758
815
  channels = channelIds.map(id => {
759
- const offset = offsetForChannel(id, false); // TODO: psd.colorMode === ColorMode.CMYK);
816
+ const offset = offsetForChannel(id, false); // image data is always RGBA
760
817
  let data: Uint8Array;
761
818
  let compression: Compression;
762
819
 
763
- if (RAW_IMAGE_DATA && (layer as any).imageDataRaw) {
764
- // console.log('written raw layer image data');
765
- data = (layer as any).imageDataRaw[id];
766
- compression = (layer as any).imageDataRawCompression[id];
767
- } else if (options.compress) {
820
+ if (options.compress) {
768
821
  data = writeDataZipWithoutPrediction(imageData, [offset]);
769
822
  compression = Compression.ZipWithoutPrediction;
770
823
  } else {
771
- data = writeDataRLE(tempBuffer, imageData, [offset], !!options.psb)!;
824
+ data = writeDataRLE(tempBuffer, imageData, [offset], !!options.psb).slice(); // copy, temp buffer is reused
772
825
  compression = Compression.RleCompressed;
773
826
  }
774
827
 
@@ -841,8 +894,8 @@ export function writeColor(writer: PsdWriter, color: Color | undefined) {
841
894
  } else if ('l' in color) {
842
895
  writeUint16(writer, ColorSpace.Lab);
843
896
  writeInt16(writer, Math.round(color.l * 10000));
844
- writeInt16(writer, Math.round(color.a < 0 ? (color.a * 12800) : (color.a * 12700)));
845
- writeInt16(writer, Math.round(color.b < 0 ? (color.b * 12800) : (color.b * 12700)));
897
+ writeInt16(writer, Math.round(denormalizeLabAB(color.a, 100)));
898
+ writeInt16(writer, Math.round(denormalizeLabAB(color.b, 100)));
846
899
  writeUint16(writer, 0);
847
900
  } else if ('h' in color) {
848
901
  writeUint16(writer, ColorSpace.HSB);
@@ -851,11 +904,12 @@ export function writeColor(writer: PsdWriter, color: Color | undefined) {
851
904
  writeUint16(writer, Math.round(color.b * 0xffff));
852
905
  writeUint16(writer, 0);
853
906
  } else if ('c' in color) {
907
+ // 0 = 100% ink
854
908
  writeUint16(writer, ColorSpace.CMYK);
855
- writeUint16(writer, Math.round(color.c * 257));
856
- writeUint16(writer, Math.round(color.m * 257));
857
- writeUint16(writer, Math.round(color.y * 257));
858
- writeUint16(writer, Math.round(color.k * 257));
909
+ writeUint16(writer, 0xffff - Math.round(clamp(color.c, 0, 255) * 257));
910
+ writeUint16(writer, 0xffff - Math.round(clamp(color.m, 0, 255) * 257));
911
+ writeUint16(writer, 0xffff - Math.round(clamp(color.y, 0, 255) * 257));
912
+ writeUint16(writer, 0xffff - Math.round(clamp(color.k, 0, 255) * 257));
859
913
  } else {
860
914
  writeUint16(writer, ColorSpace.Grayscale);
861
915
  writeUint16(writer, Math.round(color.k * 10000 / 255));
@@ -899,6 +953,10 @@ export function writePattern(writer: PsdWriter, pattern: PatternInfo) {
899
953
 
900
954
  writeUint32(writer, 24); // channels count
901
955
 
956
+ // ponytail: pattern channels always use small (2-byte) RLE scanline headers, matching
957
+ // readPattern which reads them as non-large
958
+ const buffer = new Uint8Array(getRLEChannelBound(width, height, false));
959
+
902
960
  // channels: RGB at indices 0,1,2 and alpha at index 25
903
961
  for (let i = 0; i < 24 + 2; i++) {
904
962
  const offset = i < 3 ? i : (i === 25 ? 3 : -1);
@@ -908,11 +966,7 @@ export function writePattern(writer: PsdWriter, pattern: PatternInfo) {
908
966
  continue;
909
967
  }
910
968
 
911
- // worst-case RLE size for a single channel: width + 1 per scanline, plus scanline length headers
912
- const buffer = new Uint8Array(width * height + 2 * height + 2 * width + 16);
913
- // ponytail: pattern channels always use small (2-byte) RLE scanline headers, matching
914
- // readPattern which reads them as non-large; `large` here only affects the header width.
915
- const data = writeDataRLE(buffer, pixelData, [offset], false)!;
969
+ const data = writeDataRLE(buffer, pixelData, [offset], false);
916
970
 
917
971
  writeUint32(writer, 1); // has
918
972
  writeUint32(writer, data.length + 4 + 16 + 2 + 1); // length