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/psdReader.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { inflate as inflateSync } from 'pako';
1
+ import { Inflate } from 'pako';
2
2
  import { Psd, Layer, ColorMode, SectionDividerType, LayerAdditionalInfo, ReadOptions, LayerMaskData, Color, PatternInfo, GlobalLayerMaskInfo, RGB, PixelData, PixelArray, ChannelID, Compression } from './psd';
3
- import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, LayerMaskFlags, MaskParams, ColorSpace, RAW_IMAGE_DATA, largeAdditionalInfoKeys, imageDataToCanvas } from './helpers';
3
+ import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, LayerMaskFlags, MaskParams, ColorSpace, largeAdditionalInfoKeys, largeAdditionalInfoKeysObserved, imageDataToCanvas, normalizeLabAB } from './helpers';
4
4
  import { infoHandlersMap } from './additionalInfo';
5
+ import { decodeString } from './utf8';
5
6
  import { InternalImageResources, resourceHandlersMap } from './imageResources';
6
7
 
7
8
  interface ChannelInfo {
@@ -9,7 +10,7 @@ interface ChannelInfo {
9
10
  length: number;
10
11
  }
11
12
 
12
- export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB, ColorMode.Indexed];
13
+ export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB, ColorMode.Indexed, ColorMode.CMYK];
13
14
  const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', '', '', 'multichannel', 'duotone', 'lab'];
14
15
 
15
16
  function setupGrayscale(data: PixelData) {
@@ -27,7 +28,44 @@ export interface PsdReader extends ReadOptions {
27
28
  view: DataView;
28
29
  large: boolean;
29
30
  globalAlpha: boolean;
31
+ layersInGlobalInfo?: boolean; // layers were read from Lr16/Lr32/Layr global additional info
30
32
  log(...args: any[]): void;
33
+ // text decoders, created lazily (null if not supported)
34
+ utf8Decoder?: TextDecoder | null;
35
+ utf8StrictDecoder?: TextDecoder | null;
36
+ gbkDecoder?: TextDecoder | null;
37
+ }
38
+
39
+ function createTextDecoder(label: string, options?: TextDecoderOptions) {
40
+ try {
41
+ return typeof TextDecoder !== 'undefined' ? new TextDecoder(label, options) : null;
42
+ } catch {
43
+ return null; // encoding not supported
44
+ }
45
+ }
46
+
47
+ // non-fatal UTF-8 decoder, malformed byte sequences are replaced with U+FFFD
48
+ export function getUtf8Decoder(reader: PsdReader) {
49
+ if (reader.utf8Decoder === undefined) reader.utf8Decoder = createTextDecoder('utf-8', { ignoreBOM: true });
50
+ return reader.utf8Decoder;
51
+ }
52
+
53
+ // UTF-8 decoder that throws on malformed byte sequences
54
+ export function getUtf8StrictDecoder(reader: PsdReader) {
55
+ if (reader.utf8StrictDecoder === undefined) reader.utf8StrictDecoder = createTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
56
+ return reader.utf8StrictDecoder;
57
+ }
58
+
59
+ // GBK decoder that throws on malformed byte sequences
60
+ export function getGbkStrictDecoder(reader: PsdReader) {
61
+ if (reader.gbkDecoder === undefined) reader.gbkDecoder = createTextDecoder('gbk', { fatal: true });
62
+ return reader.gbkDecoder;
63
+ }
64
+
65
+ export function decodeUtf8(reader: PsdReader, buffer: Uint8Array) {
66
+ // native decoder is faster only for longer strings
67
+ const decoder = buffer.byteLength > 1000 ? getUtf8Decoder(reader) : null;
68
+ return decoder ? decoder.decode(buffer) : decodeString(buffer);
31
69
  }
32
70
 
33
71
  export function createReader(buffer: ArrayBufferLike, offset?: number, length?: number): PsdReader {
@@ -99,20 +137,22 @@ export function readFixedPointPath32(reader: PsdReader): number {
99
137
  return readInt32(reader) / (1 << 24);
100
138
  }
101
139
 
140
+ // NOTE: returned array may alias the reader's underlying buffer, never write into it
102
141
  export function readBytes(reader: PsdReader, length: number) {
103
- const start = reader.view.byteOffset + reader.offset;
142
+ const start = reader.offset;
143
+ const viewLength = reader.view.byteLength;
104
144
  reader.offset += length;
105
145
 
106
- if ((start + length) > reader.view.buffer.byteLength) {
146
+ if ((start + length) > viewLength) {
107
147
  // fix for broken PSD files that are missing part of file at the end
108
148
  warnOrThrow(reader, 'Reading bytes exceeding buffer length');
109
149
  if (length > (100 * 1024 * 1024)) throw new Error('Reading past end of file'); // limit to 100MB
110
150
  const result = new Uint8Array(length);
111
- const len = Math.min(length, reader.view.byteLength - start);
112
- if (len > 0) result.set(new Uint8Array(reader.view.buffer, start, len));
151
+ const len = Math.min(length, viewLength - start);
152
+ if (len > 0) result.set(new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, len));
113
153
  return result;
114
154
  } else {
115
- return new Uint8Array(reader.view.buffer, start, length);
155
+ return new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, length);
116
156
  }
117
157
  }
118
158
 
@@ -144,41 +184,65 @@ export function readUnicodeString(reader: PsdReader) {
144
184
  return readUnicodeStringWithLength(reader, length);
145
185
  }
146
186
 
147
- export function readUnicodeStringWithLength(reader: PsdReader, length: number) {
187
+ const STRING_CHUNK_SIZE = 0x2000;
188
+
189
+ function checkReadBounds(reader: PsdReader, size: number) {
190
+ if (reader.offset < 0 || (reader.offset + size) > reader.view.byteLength) {
191
+ throw new RangeError('Offset is outside the bounds of the DataView');
192
+ }
193
+ }
194
+
195
+ function readUtf16String(reader: PsdReader, length: number, littleEndian: boolean) {
196
+ if (!(length > 0)) return '';
197
+
198
+ checkReadBounds(reader, length * 2);
199
+
200
+ const view = reader.view;
201
+ const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
202
+ let offset = reader.offset;
148
203
  let text = '';
149
204
 
150
- while (length--) {
151
- const value = readUint16(reader);
205
+ for (let i = 0; i < length; i += STRING_CHUNK_SIZE) {
206
+ const count = Math.min(STRING_CHUNK_SIZE, length - i);
152
207
 
153
- if (value || length > 0) { // remove trailing \0
154
- text += String.fromCharCode(value);
208
+ for (let j = 0; j < count; j++, offset += 2) {
209
+ codes[j] = view.getUint16(offset, littleEndian);
155
210
  }
211
+
212
+ text += String.fromCharCode.apply(null, (count === codes.length ? codes : codes.subarray(0, count)) as any);
156
213
  }
157
214
 
215
+ reader.offset = offset;
216
+
217
+ // remove trailing \0
218
+ if (text.charCodeAt(text.length - 1) === 0) text = text.substring(0, text.length - 1);
219
+
158
220
  return text;
159
221
  }
160
222
 
161
- export function readUnicodeStringWithLengthLE(reader: PsdReader, length: number) {
223
+ function bytesToString(bytes: Uint8Array) {
162
224
  let text = '';
163
225
 
164
- while (length--) {
165
- const value = readUint16LE(reader);
166
-
167
- if (value || length > 0) { // remove trailing \0
168
- text += String.fromCharCode(value);
169
- }
226
+ for (let i = 0; i < bytes.length; i += STRING_CHUNK_SIZE) {
227
+ text += String.fromCharCode.apply(null, bytes.subarray(i, i + STRING_CHUNK_SIZE) as any);
170
228
  }
171
229
 
172
230
  return text;
173
231
  }
174
232
 
175
- export function readAsciiString(reader: PsdReader, length: number) {
176
- let text = '';
233
+ export function readUnicodeStringWithLength(reader: PsdReader, length: number) {
234
+ return readUtf16String(reader, length, false);
235
+ }
177
236
 
178
- while (length--) {
179
- text += String.fromCharCode(readUint8(reader));
180
- }
237
+ export function readUnicodeStringWithLengthLE(reader: PsdReader, length: number) {
238
+ return readUtf16String(reader, length, true);
239
+ }
181
240
 
241
+ export function readAsciiString(reader: PsdReader, length: number) {
242
+ if (!(length > 0)) return '';
243
+ checkReadBounds(reader, length);
244
+ const text = bytesToString(new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length));
245
+ reader.offset += length;
182
246
  return text;
183
247
  }
184
248
 
@@ -196,14 +260,7 @@ export function checkSignature(reader: PsdReader, a: string, b?: string) {
196
260
  }
197
261
 
198
262
  function readShortString(reader: PsdReader, length: number) {
199
- const buffer = readBytes(reader, length);
200
- let result = '';
201
-
202
- for (let i = 0; i < buffer.length; i++) {
203
- result += String.fromCharCode(buffer[i]);
204
- }
205
-
206
- return result;
263
+ return bytesToString(readBytes(reader, length));
207
264
  }
208
265
 
209
266
  function isValidSignature(sig: string) {
@@ -225,7 +282,8 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
225
282
  const maxSize = version === 1 ? 30000 : 300000;
226
283
 
227
284
  if (width > maxSize || height > maxSize) throw new Error(`Invalid size: ${width}x${height}`);
228
- if (channels > 16) throw new Error(`Invalid channel count: ${channels}`);
285
+ // up to 56 channels plus transparency of composite image (Photoshop CS3 writes 57 channels for 53 alpha channels)
286
+ if (channels < 1 || channels > 57) throw new Error(`Invalid channel count: ${channels}`);
229
287
  if (![1, 8, 16, 32].includes(bitsPerChannel)) throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
230
288
  if (supportedColorModes.indexOf(colorMode) === -1) throw new Error(`Color mode not supported: ${colorModes[colorMode] ?? colorMode}`);
231
289
 
@@ -234,6 +292,7 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
234
292
  Object.assign(reader, readOptions);
235
293
  reader.large = version === 2;
236
294
  reader.globalAlpha = false;
295
+ reader.layersInGlobalInfo = false;
237
296
  if (!('totalMemoryLimit' in reader)) { // setting totalMemoryLimit to undefined explicitly disables memory limit
238
297
  reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
239
298
  }
@@ -261,12 +320,16 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
261
320
  // image resources
262
321
 
263
322
  const imageResources: InternalImageResources = {};
323
+ let unreadResourceBytes = 0;
264
324
 
265
325
  readSection(reader, 1, left => {
266
- while (left() > 0) {
267
- realignWithSignature(reader, isValidSignature);
326
+ // stop if there's not enough data left for resource header (some files have few trailing bytes in this section)
327
+ while (left() >= 12) {
328
+ const signature = realignWithSignature(reader, isValidSignature);
268
329
  const id = readUint16(reader);
269
- readPascalString(reader, 2); // name
330
+ const nameLength = readUint8(reader);
331
+ const name = readBytes(reader, nameLength);
332
+ skipBytes(reader, (nameLength + 1) % 2); // pad to even size
270
333
 
271
334
  readSection(reader, 2, left => {
272
335
  const handler = resourceHandlersMap[id];
@@ -274,20 +337,29 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
274
337
 
275
338
  if (handler && !skip) {
276
339
  try {
277
- handler.read(reader, imageResources, left);
340
+ handler.read(reader, imageResources, left, { signature, id, name, width, height });
278
341
  } catch (e) {
279
342
  if (reader.throwForMissingFeatures) throw e;
280
343
  skipBytes(reader, left());
281
344
  }
282
345
  } else {
283
- // options.logMissingFeatures && console.log(`Unhandled image resource: ${id} (${left()})`);
346
+ reader.logMissingFeatures && reader.log(`Unhandled image resource: ${id} (${left()})`);
284
347
  skipBytes(reader, left());
285
348
  }
286
349
  });
287
350
  }
351
+
352
+ unreadResourceBytes = left();
353
+ skipBytes(reader, left());
288
354
  });
289
355
 
290
- const { layersGroup, layerGroupsEnabledId, ...rest } = imageResources;
356
+ // some files have image resources section length few bytes larger than the data, layer and mask info section
357
+ // starts right after the last resource in that case
358
+ if (unreadResourceBytes && !sectionFits(reader, reader.offset) && sectionFits(reader, reader.offset - unreadResourceBytes)) {
359
+ reader.offset -= unreadResourceBytes;
360
+ }
361
+
362
+ const { layersGroup, layerGroupsEnabledId, savedPathsOrigination, ...rest } = imageResources;
291
363
 
292
364
  if (Object.keys(rest).length) {
293
365
  psd.imageResources = rest;
@@ -331,7 +403,11 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
331
403
 
332
404
  if (!skipComposite) {
333
405
  if (reader.useRawData) {
334
- psd.rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset);
406
+ const rawOffset = Math.min(reader.offset, reader.view.byteLength);
407
+ const rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + rawOffset, reader.view.byteLength - rawOffset);
408
+ psd.rawCompositeData = rawCompositeData;
409
+ psd.rawCompositeDataInfo = { large: reader.large, globalAlpha: reader.globalAlpha };
410
+ if (reader.layersInGlobalInfo) psd.rawCompositeDataInfo.layersInGlobalInfo = true;
335
411
  } else {
336
412
  const imageData = readImageData(reader, psd);
337
413
  if (reader.useImageData) {
@@ -342,10 +418,6 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
342
418
  }
343
419
  }
344
420
 
345
- // TODO: show converted color mode instead of original PSD file color mode
346
- // but add option to preserve file color mode (need to return image data instead of canvas in that case)
347
- // psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB
348
-
349
421
  return psd;
350
422
  }
351
423
 
@@ -393,7 +465,8 @@ export function readLayerInfo(reader: PsdReader, psd: Psd, imageResources: Inter
393
465
  stack[stack.length - 1].children!.unshift(l);
394
466
  stack.push(l);
395
467
  } else if (type === SectionDividerType.BoundingSectionDivider) {
396
- stack.pop();
468
+ // ignore unmatched section dividers, never pop the root
469
+ if (stack.length > 1) stack.pop();
397
470
  // this was workaround because I didn't know what `lsdk` section was, now it's probably not needed anymore
398
471
  // } else if (l.name === '</Layer group>' && !l.sectionDivider && !l.top && !l.left && !l.bottom && !l.right) {
399
472
  // // sometimes layer group terminator doesn't have sectionDivider, so we just guess here (PS bug ?)
@@ -429,8 +502,11 @@ function readLayerRecord(reader: PsdReader, psd: Psd, imageResources: InternalIm
429
502
 
430
503
  checkSignature(reader, '8BIM');
431
504
  const blendMode = readSignature(reader);
432
- if (!toBlendMode[blendMode]) throw new Error(`Invalid blend mode: '${blendMode}'`);
433
- layer.blendMode = toBlendMode[blendMode];
505
+ if (!toBlendMode[blendMode]) {
506
+ if (reader.throwForMissingFeatures) throw new Error(`Invalid blend mode: '${blendMode}'`);
507
+ reader.logMissingFeatures && reader.log(`Unknown blend mode: '${blendMode}', using normal blend mode`);
508
+ }
509
+ layer.blendMode = toBlendMode[blendMode] || 'normal';
434
510
 
435
511
  layer.opacity = readUint8(reader) / 0xff;
436
512
  layer.clipping = readUint8(reader) === 1;
@@ -464,11 +540,20 @@ function readLayerRecord(reader: PsdReader, psd: Psd, imageResources: InternalIm
464
540
  return { layer, channels };
465
541
  }
466
542
 
543
+ // negative sizes are treated as empty (Photoshop writes them for some empty layers, like 0,0,-1,0 bounds of fill layer)
544
+ // checks if length of section (layer and mask info) at given offset fits in the file
545
+ function sectionFits(reader: PsdReader, offset: number) {
546
+ const lengthSize = reader.large ? 8 : 4;
547
+ if (offset < 0 || offset + lengthSize > reader.view.byteLength) return false;
548
+ const length = reader.large ? reader.view.getUint32(offset) * 0x100000000 + reader.view.getUint32(offset + 4) : reader.view.getUint32(offset);
549
+ return offset + lengthSize + length <= reader.view.byteLength;
550
+ }
551
+
467
552
  function isValidBoxSize(box: { top?: number; left?: number; bottom?: number; right?: number; }, reader: PsdReader) {
468
553
  const width = (box.right || 0) - (box.left || 0);
469
554
  const height = (box.bottom || 0) - (box.top || 0);
470
555
  const maxSize = reader.large ? 300000 : 30000;
471
- return width >= 0 && height >= 0 && width <= maxSize && height <= maxSize;
556
+ return width <= maxSize && height <= maxSize;
472
557
  }
473
558
 
474
559
  function readLayerMaskData(reader: PsdReader, layer: Layer) {
@@ -491,7 +576,7 @@ function readLayerMaskData(reader: PsdReader, layer: Layer) {
491
576
  mask.disabled = (flags & LayerMaskFlags.LayerMaskDisabled) !== 0;
492
577
  mask.fromVectorData = (flags & LayerMaskFlags.LayerMaskFromRenderingOtherData) !== 0;
493
578
 
494
- if (left() >= 18) {
579
+ if (hasRealMaskData(reader, flags, left())) {
495
580
  const realMask: LayerMaskData = {};
496
581
  layer.realMask = realMask;
497
582
 
@@ -520,6 +605,44 @@ function readLayerMaskData(reader: PsdReader, layer: Layer) {
520
605
  });
521
606
  }
522
607
 
608
+ function maskParamsSize(params: number) {
609
+ return 1 +
610
+ ((params & MaskParams.UserMaskDensity) ? 1 : 0) +
611
+ ((params & MaskParams.UserMaskFeather) ? 8 : 0) +
612
+ ((params & MaskParams.VectorMaskDensity) ? 1 : 0) +
613
+ ((params & MaskParams.VectorMaskFeather) ? 8 : 0);
614
+ }
615
+
616
+ // checks if data at offset is mask parameters followed by up to 3 bytes of zero padding, ending exactly at offset + left
617
+ function isMaskParamsLayout(view: DataView, offset: number, left: number) {
618
+ if (left < 1) return false;
619
+
620
+ const params = view.getUint8(offset);
621
+ if (params === 0 || (params & ~0x0f)) return false; // only 4 parameter flags exist
622
+
623
+ const size = maskParamsSize(params);
624
+ if (left < size || left - size > 3) return false;
625
+
626
+ for (let i = size; i < left; i++) {
627
+ if (view.getUint8(offset + i) !== 0) return false;
628
+ }
629
+
630
+ return true;
631
+ }
632
+
633
+ // real mask data (18 bytes) is followed by mask parameters (if flag is set) and padding
634
+ function hasRealMaskData(reader: PsdReader, flags: number, left: number) {
635
+ if (left < 18) return false;
636
+ if (!(flags & LayerMaskFlags.MaskHasParametersAppliedToIt)) return true;
637
+
638
+ const { view, offset } = reader;
639
+ if (isMaskParamsLayout(view, offset, left)) return false;
640
+ if (isMaskParamsLayout(view, offset + 18, left - 18)) return true;
641
+
642
+ // unknown layout, assume real mask if there's enough data for it (and mask parameters)
643
+ return left === 18 || left - 18 >= maskParamsSize(view.getUint8(offset + 18));
644
+ }
645
+
523
646
  function readBlendingRange(reader: PsdReader) {
524
647
  return [readUint8(reader), readUint8(reader), readUint8(reader), readUint8(reader)];
525
648
  }
@@ -561,9 +684,9 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
561
684
  compression = readUint16(reader) as Compression;
562
685
  }
563
686
 
564
- // try to fix broken files where there's 1 byte shift of channel
687
+ // try to fix broken files where there's 1 byte shift of channel (in the other direction)
565
688
  if (compression > 3) {
566
- reader.offset -= 3;
689
+ reader.offset -= 4;
567
690
  compression = readUint16(reader) as Compression;
568
691
  }
569
692
 
@@ -599,8 +722,15 @@ export function getCompositeImageData(psd: Psd) {
599
722
  if (!data) return undefined;
600
723
 
601
724
  const reader = createReader(data.buffer, data.byteOffset, data.byteLength);
602
- const imageData = readImageData(reader, psd);
603
- return imageData;
725
+ const info = psd.rawCompositeDataInfo;
726
+
727
+ if (info) {
728
+ reader.large = info.large;
729
+ reader.globalAlpha = info.globalAlpha;
730
+ reader.layersInGlobalInfo = !!info.layersInGlobalInfo;
731
+ }
732
+
733
+ return readImageData(reader, psd);
604
734
  }
605
735
 
606
736
  export function getCompositeCanvas(psd: Psd) {
@@ -649,21 +779,23 @@ export function decodeLayerPixels(layer: Layer, useImageData?: boolean) {
649
779
  decodeLayerImageData(layer, { useImageData });
650
780
  }
651
781
 
652
- function decodeLayerImageData(layer: Layer, options: Pick<ReadOptions, 'useImageData' | 'throwForMissingFeatures' | 'totalMemoryLimit'>) {
782
+ type LogOptions = Pick<ReadOptions, 'debug' | 'log'>;
783
+
784
+ function decodeLayerImageData(layer: Layer, options: Pick<ReadOptions, 'useImageData' | 'throwForMissingFeatures' | 'totalMemoryLimit' | 'debug' | 'log'>) {
653
785
  let { throwForMissingFeatures, useImageData } = options;
654
786
 
655
- const imageData = getDataFromLayer(layer, LayerDataType.Layer, throwForMissingFeatures, options.totalMemoryLimit);
787
+ const imageData = getDataFromLayer(layer, LayerDataType.Layer, throwForMissingFeatures, options.totalMemoryLimit, options);
656
788
  setImageDataOrCanvas(layer, imageData, useImageData);
657
789
  if (options.totalMemoryLimit !== undefined) options.totalMemoryLimit -= imageData?.data.byteLength ?? 0;
658
790
 
659
791
  if (layer.mask) {
660
- const maskData = getDataFromLayer(layer, LayerDataType.Mask, throwForMissingFeatures, options.totalMemoryLimit);
792
+ const maskData = getDataFromLayer(layer, LayerDataType.Mask, throwForMissingFeatures, options.totalMemoryLimit, options);
661
793
  setImageDataOrCanvas(layer.mask, maskData, useImageData);
662
794
  if (options.totalMemoryLimit !== undefined) options.totalMemoryLimit -= maskData?.data.byteLength ?? 0;
663
795
  }
664
796
 
665
797
  if (layer.realMask) {
666
- const maskData = getDataFromLayer(layer, LayerDataType.RealMask, throwForMissingFeatures, options.totalMemoryLimit);
798
+ const maskData = getDataFromLayer(layer, LayerDataType.RealMask, throwForMissingFeatures, options.totalMemoryLimit, options);
667
799
  setImageDataOrCanvas(layer.realMask, maskData, useImageData);
668
800
  if (options.totalMemoryLimit !== undefined) options.totalMemoryLimit -= maskData?.data.byteLength ?? 0;
669
801
  }
@@ -677,7 +809,7 @@ enum LayerDataType {
677
809
  RealMask,
678
810
  }
679
811
 
680
- function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeatures?: boolean, memoryLimit?: number) {
812
+ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeatures?: boolean, memoryLimit?: number, logOptions?: LogOptions) {
681
813
  if (!layer.rawData) return undefined;
682
814
 
683
815
  const { colorMode, bitsPerChannel, channels, large } = layer.rawData;
@@ -691,22 +823,20 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
691
823
 
692
824
  if (layerWidth && layerHeight && read === LayerDataType.Layer) {
693
825
  if (cmyk) {
694
- if (bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
695
- imageData = { width: layerWidth, height: layerHeight, data: new Uint8ClampedArray(layerWidth * layerHeight * 5) } as any as ImageData;
826
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16) throw new Error('bitsPerChannel not supported');
827
+ imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 5, memoryLimit);
696
828
  } else {
697
829
  imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 4, memoryLimit);
698
830
  }
699
831
  }
700
832
 
701
- if (RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
702
- (layer as any).imageDataRaw = [];
703
- (layer as any).imageDataRawCompression = [];
704
- }
705
-
706
833
  for (const { id, compression, data } of channels) {
707
834
  if (!data) continue;
708
835
 
709
836
  const dataReader = createReader(data.buffer, data.byteOffset, data.byteLength);
837
+ dataReader.debug = !!logOptions?.debug;
838
+ if (logOptions?.log) dataReader.log = logOptions.log;
839
+ if (memoryLimit !== undefined) dataReader.totalMemoryLimit = Math.max(0, memoryLimit - (imageData?.data.byteLength ?? 0));
710
840
 
711
841
  if (id === ChannelID.UserMask || id === ChannelID.RealUserMask) {
712
842
  if (id === ChannelID.UserMask && read !== LayerDataType.Mask) continue;
@@ -720,16 +850,8 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
720
850
 
721
851
  if (maskWidth && maskHeight) {
722
852
  maskData = createImageDataBitDepth(maskWidth, maskHeight, bitsPerChannel, 4, memoryLimit);
853
+ if (memoryLimit !== undefined) dataReader.totalMemoryLimit = Math.max(0, dataReader.totalMemoryLimit! - maskData.data.byteLength);
723
854
  readData(dataReader, data.byteLength, maskData, compression, maskWidth, maskHeight, bitsPerChannel, 0, large, 4);
724
- if (RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
725
- if (id === ChannelID.UserMask) {
726
- (layer as any).maskDataRawCompression = compression;
727
- (layer as any).maskDataRaw = data;
728
- } else {
729
- (layer as any).realMaskDataRawCompression = compression;
730
- (layer as any).realMaskDataRaw = data;
731
- }
732
- }
733
855
  setupGrayscale(maskData);
734
856
  resetAlpha(maskData, false);
735
857
  }
@@ -748,15 +870,6 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
748
870
  }
749
871
 
750
872
  readData(dataReader, data.byteLength, targetData, compression, layerWidth, layerHeight, bitsPerChannel, offset, large, cmyk ? 5 : 4);
751
-
752
- if (RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
753
- (layer as any).imageDataRawCompression[id] = compression;
754
- (layer as any).imageDataRaw[id] = data;
755
- }
756
-
757
- if (targetData && colorMode === ColorMode.Grayscale) {
758
- setupGrayscale(targetData);
759
- }
760
873
  }
761
874
 
762
875
  if (id === ChannelID.Transparency) {
@@ -765,11 +878,13 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
765
878
  }
766
879
 
767
880
  if (imageData) {
881
+ if (colorMode === ColorMode.Grayscale) setupGrayscale(imageData);
768
882
  if (!initializedAlpha) resetAlpha(imageData, cmyk);
769
883
 
770
884
  if (cmyk) {
771
885
  const cmykData = imageData;
772
- imageData = createImageData(cmykData.width, cmykData.height);
886
+ const rgbMemoryLimit = memoryLimit === undefined ? undefined : Math.max(0, memoryLimit - cmykData.data.byteLength);
887
+ imageData = createImageDataBitDepth(cmykData.width, cmykData.height, bitsPerChannel, 4, rgbMemoryLimit);
773
888
  cmykToRgb(cmykData, imageData, false);
774
889
  }
775
890
  }
@@ -782,7 +897,7 @@ function readData(reader: PsdReader, length: number, pixels: PixelData | undefin
782
897
 
783
898
  if (compression === Compression.RawData) {
784
899
  if (length !== (width * height * Math.floor(bitDepth / 8))) {
785
- reader.log(`Invalid length (${length}, ${width * height * Math.floor(bitDepth / 8)})`);
900
+ warnOrThrow(reader, `Invalid length (${length}, ${width * height * Math.floor(bitDepth / 8)})`);
786
901
  }
787
902
  const data = readBytes(reader, length);
788
903
  readDataRaw(data, pixels, bitDepth, step, offset);
@@ -791,10 +906,10 @@ function readData(reader: PsdReader, length: number, pixels: PixelData | undefin
791
906
  readDataRLE(reader, pixels, width, height, bitDepth, step, [offset], large);
792
907
  } else if (compression === Compression.ZipWithoutPrediction) {
793
908
  const data = readBytes(reader, length);
794
- readDataZip(data, pixels, width, height, bitDepth, step, offset, false);
909
+ readDataZip(data, pixels, width, height, bitDepth, step, offset, false, reader);
795
910
  } else if (compression === Compression.ZipWithPrediction) {
796
911
  const data = readBytes(reader, length);
797
- readDataZip(data, pixels, width, height, bitDepth, step, offset, true);
912
+ readDataZip(data, pixels, width, height, bitDepth, step, offset, true, reader);
798
913
  } else {
799
914
  throw new Error(`Invalid Compression type: ${compression}`);
800
915
  }
@@ -809,7 +924,7 @@ export function readGlobalLayerMaskInfo(reader: PsdReader) {
809
924
  const colorSpace2 = readUint16(reader);
810
925
  const colorSpace3 = readUint16(reader);
811
926
  const colorSpace4 = readUint16(reader);
812
- const opacity = readUint16(reader) / 0xff;
927
+ const opacity = readUint16(reader) / 100; // 0 - 100
813
928
  const kind = readUint8(reader);
814
929
  skipBytes(reader, left()); // 3 bytes of padding ?
815
930
  return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
@@ -847,7 +962,16 @@ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAddition
847
962
  const key = readSignature(reader);
848
963
 
849
964
  // `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
850
- const u64 = sig === '8B64' || (reader.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
965
+ let u64 = sig === '8B64' || (reader.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
966
+
967
+ // these keys were written by older versions of this library with 32bit length and 8BIM signature,
968
+ // 64bit length always has high 32bits set to 0 (lengths over 4GB are not supported)
969
+ if (reader.large && sig === '8BIM' && largeAdditionalInfoKeysObserved.indexOf(key) !== -1) {
970
+ // 32bit length of 0 is followed by signature of the next block, 64bit length would continue with its low 32bits
971
+ const next = reader.offset + 8 <= reader.view.byteLength ? reader.view.getUint32(reader.offset + 4) : 0;
972
+ const nextIsSignature = next === 0x3842494d /* 8BIM */ || next === 0x38423634 /* 8B64 */;
973
+ u64 = reader.view.getUint32(reader.offset) === 0 && !nextIsSignature;
974
+ }
851
975
 
852
976
  readSection(reader, 2, left => {
853
977
  const handler = infoHandlersMap[key];
@@ -859,7 +983,7 @@ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAddition
859
983
  if (reader.throwForMissingFeatures) throw e;
860
984
  }
861
985
  } else {
862
- reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
986
+ reader.logMissingFeatures && key !== 'GenI' && key !== 'OCIO' && key !== 'CAI ' && reader.log(`Unhandled additional info: ${key}`);
863
987
  skipBytes(reader, left());
864
988
  }
865
989
 
@@ -892,6 +1016,7 @@ function createImageDataBitDepth(width: number, height: number, bitDepth: number
892
1016
  function readImageData(reader: PsdReader, psd: Psd) {
893
1017
  const compression = readUint16(reader) as Compression;
894
1018
  const bitsPerChannel = psd.bitsPerChannel ?? 8;
1019
+ const { width, height } = psd;
895
1020
 
896
1021
  if (supportedColorModes.indexOf(psd.colorMode!) === -1)
897
1022
  throw new Error(`Color mode not supported: ${psd.colorMode}`);
@@ -899,7 +1024,7 @@ function readImageData(reader: PsdReader, psd: Psd) {
899
1024
  if (compression !== Compression.RawData && compression !== Compression.RleCompressed)
900
1025
  throw new Error(`Compression type not supported: ${compression}`);
901
1026
 
902
- const imageData = createImageDataBitDepth(psd.width, psd.height, bitsPerChannel, 4, reader.totalMemoryLimit);
1027
+ const imageData = createImageDataBitDepth(width, height, bitsPerChannel, 4, reader.totalMemoryLimit);
903
1028
  if (reader.totalMemoryLimit !== undefined) reader.totalMemoryLimit -= imageData.data.byteLength;
904
1029
  resetImageData(imageData);
905
1030
 
@@ -907,45 +1032,24 @@ function readImageData(reader: PsdReader, psd: Psd) {
907
1032
  case ColorMode.Bitmap: {
908
1033
  if (bitsPerChannel !== 1) throw new Error('Invalid bitsPerChannel for bitmap color mode');
909
1034
 
1035
+ const rowBytes = Math.ceil(width / 8);
910
1036
  let bytes: Uint8Array;
911
1037
 
912
1038
  if (compression === Compression.RawData) {
913
- bytes = readBytes(reader, Math.ceil(psd.width / 8) * psd.height);
914
- } else if (compression === Compression.RleCompressed) {
915
- bytes = new Uint8Array(psd.width * psd.height);
916
- readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 8, 1, [0], reader.large);
1039
+ bytes = readBytes(reader, rowBytes * height);
917
1040
  } else {
918
- throw new Error(`Compression not supported: ${compression}`);
1041
+ bytes = new Uint8Array(rowBytes * height);
1042
+ readDataRLE(reader, { data: bytes, width: rowBytes, height }, rowBytes, height, 8, 1, [0], reader.large);
919
1043
  }
920
1044
 
921
- decodeBitmap(bytes, imageData.data, psd.width, psd.height);
1045
+ decodeBitmap(bytes, imageData.data, width, height);
922
1046
  break;
923
1047
  }
924
1048
  case ColorMode.RGB:
925
1049
  case ColorMode.Grayscale: {
926
- const channels = psd.colorMode === ColorMode.Grayscale ? [0] : [0, 1, 2];
927
-
928
- if (psd.channels && psd.channels > 3) {
929
- for (let i = 3; i < psd.channels; i++) {
930
- // TODO: store these channels in additional image data
931
- channels.push(i);
932
- }
933
- } else if (reader.globalAlpha) {
934
- channels.push(3);
935
- }
936
-
937
- if (compression === Compression.RawData) {
938
- for (let i = 0; i < channels.length; i++) {
939
- const data = readBytes(reader, psd.width * psd.height * Math.floor(bitsPerChannel / 8));
940
- readDataRaw(data, imageData, bitsPerChannel, 4, channels[i]);
941
- }
942
- } else if (compression === Compression.RleCompressed) {
943
- const start = reader.offset;
944
- readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, reader.large);
945
- if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
946
- } else {
947
- throw new Error(`Compression not supported: ${compression}`);
948
- }
1050
+ const colorChannels = psd.colorMode === ColorMode.Grayscale ? 1 : 3;
1051
+ const offsets = compositeChannelOffsets(reader, psd, colorChannels, 3);
1052
+ readCompositeChannels(reader, compression, imageData, bitsPerChannel, 4, offsets);
949
1053
 
950
1054
  if (psd.colorMode === ColorMode.Grayscale) {
951
1055
  setupGrayscale(imageData);
@@ -953,303 +1057,492 @@ function readImageData(reader: PsdReader, psd: Psd) {
953
1057
  break;
954
1058
  }
955
1059
  case ColorMode.Indexed: {
956
- if (bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
957
- if (psd.channels !== 1) throw new Error('Invalid channel count');
1060
+ if (bitsPerChannel !== 8) throw new Error('bitsPerChannel not supported');
958
1061
  if (!psd.palette) throw new Error('Missing color palette');
959
1062
 
960
- if (compression === Compression.RawData) {
961
- throw new Error(`Compression not supported: ${compression}`);
962
- } else if (compression === Compression.RleCompressed) {
963
- const indexedImageData: PixelData = {
964
- width: imageData.width,
965
- height: imageData.height,
966
- data: new Uint8Array(imageData.width * imageData.height),
967
- };
968
- readDataRLE(reader, indexedImageData, psd.width, psd.height, bitsPerChannel, 1, [0], reader.large);
969
- indexedToRgb(indexedImageData, imageData, psd.palette);
970
- } else {
971
- throw new Error(`Compression not supported: ${compression}`);
972
- }
973
-
1063
+ const offsets = compositeChannelOffsets(reader, psd, 1, -1);
1064
+ const indexedImageData: PixelData = { width, height, data: new Uint8Array(width * height) };
1065
+ readCompositeChannels(reader, compression, indexedImageData, 8, 1, offsets);
1066
+ indexedToRgb(indexedImageData, imageData, psd.palette, psd.imageResources?.transparencyIndex ?? -1);
974
1067
  break;
975
1068
  }
976
1069
  case ColorMode.CMYK: {
977
- if (bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
978
- if (psd.channels !== 4) throw new Error(`Invalid channel count`);
979
-
980
- const channels = [0, 1, 2, 3];
981
- if (reader.globalAlpha) channels.push(4);
1070
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16) throw new Error('bitsPerChannel not supported');
982
1071
 
983
- if (compression === Compression.RawData) {
984
- throw new Error(`Compression not supported: ${compression}`);
985
- // TODO: ...
986
- // for (let i = 0; i < channels.length; i++) {
987
- // readDataRaw(reader, imageData, channels[i], psd.width, psd.height);
988
- // }
989
- } else if (compression === Compression.RleCompressed) {
990
- const cmykImageData: PixelData = {
991
- width: imageData.width,
992
- height: imageData.height,
993
- data: new Uint8Array(imageData.width * imageData.height * 5),
994
- };
995
-
996
- const start = reader.offset;
997
- readDataRLE(reader, cmykImageData, psd.width, psd.height, bitsPerChannel, 5, channels, reader.large);
998
- cmykToRgb(cmykImageData, imageData, true);
999
-
1000
- if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
1001
- } else {
1002
- throw new Error(`Compression not supported: ${compression}`);
1003
- }
1072
+ const offsets = compositeChannelOffsets(reader, psd, 4, 4);
1073
+ const cmykImageData = createImageDataBitDepth(width, height, bitsPerChannel, 5, reader.totalMemoryLimit);
1004
1074
 
1075
+ resetAlpha(cmykImageData, true);
1076
+ readCompositeChannels(reader, compression, cmykImageData, bitsPerChannel, 5, offsets);
1077
+ cmykToRgb(cmykImageData, imageData, !!reader.globalAlpha);
1005
1078
  break;
1006
1079
  }
1007
1080
  default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
1008
1081
  }
1009
1082
 
1010
- // remove weird white matte
1011
- if (reader.globalAlpha) {
1012
- if (psd.bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
1013
- const p = imageData.data;
1014
- const size = imageData.width * imageData.height * 4;
1083
+ // remove weird white matte (already removed when converting from CMYK)
1084
+ if (reader.globalAlpha && psd.colorMode !== ColorMode.CMYK) {
1085
+ removeWhiteMatte(imageData);
1086
+ }
1087
+
1088
+ return imageData;
1089
+ }
1090
+
1091
+ // returns target offset for each of the composite image channels stored in the file, -1 for channels that are skipped
1092
+ function compositeChannelOffsets(reader: PsdReader, psd: Psd, colorChannels: number, alphaOffset: number) {
1093
+ const channelCount = psd.channels ?? (colorChannels + (reader.globalAlpha ? 1 : 0));
1094
+ if (channelCount < colorChannels) throw new Error(`Invalid channel count: ${channelCount}`);
1095
+
1096
+ // First extra channel is transparency if document has global alpha (negative layer count). Some non-Photoshop
1097
+ // applications write transparency channel without setting global alpha, in that case extra channel is not
1098
+ // listed in alpha channel names (Photoshop names all user alpha channels / saved selections).
1099
+ // Photoshop also lists merged transparency as first alpha channel named "Transparency" (in documents marked with
1100
+ // Mtrn/Mt16/Mt32 additional info, or in some 16bit documents with layers stored in Lr16 without any other mark)
1101
+ const alphaNames = psd.imageResources?.alphaChannelNames ?? [];
1102
+ const namedChannels = alphaNames.length;
1103
+ const namedTransparency = !!reader.layersInGlobalInfo && alphaNames[0] === 'Transparency' && namedChannels === channelCount - colorChannels;
1104
+ const hasTransparency = reader.globalAlpha || namedTransparency || (channelCount - colorChannels) > namedChannels;
1105
+ const offsets: number[] = [];
1106
+
1107
+ for (let i = 0; i < channelCount; i++) {
1108
+ if (i < colorChannels) {
1109
+ offsets.push(i);
1110
+ } else if (i === colorChannels && hasTransparency && alphaOffset >= 0) {
1111
+ offsets.push(alphaOffset);
1112
+ } else {
1113
+ // TODO: store these channels (alpha channels / saved selections, spot colors) in additional image data
1114
+ offsets.push(-1);
1115
+ }
1116
+ }
1117
+
1118
+ return offsets;
1119
+ }
1120
+
1121
+ function readCompositeChannels(reader: PsdReader, compression: Compression, pixelData: PixelData, bitsPerChannel: number, step: number, offsets: number[]) {
1122
+ if (compression === Compression.RawData) {
1123
+ const channelSize = pixelData.width * pixelData.height * bytesPerValue(bitsPerChannel);
1124
+
1125
+ for (const offset of offsets) {
1126
+ if (offset < 0) {
1127
+ skipBytes(reader, channelSize);
1128
+ } else {
1129
+ readDataRaw(readBytes(reader, channelSize), pixelData, bitsPerChannel, step, offset);
1130
+ }
1131
+ }
1132
+ } else if (compression === Compression.RleCompressed) {
1133
+ readDataRLE(reader, pixelData, pixelData.width, pixelData.height, bitsPerChannel, step, offsets, reader.large);
1134
+ } else {
1135
+ throw new Error(`Compression not supported: ${compression}`);
1136
+ }
1137
+ }
1138
+
1139
+ // un-premultiplies colors from white matte, used for composite image of documents with global alpha
1140
+ function removeWhiteMatte({ data, width, height }: PixelData) {
1141
+ const size = width * height * 4;
1142
+
1143
+ if (data instanceof Float32Array) {
1144
+ for (let i = 0; i < size; i += 4) {
1145
+ const a = data[i + 3];
1146
+
1147
+ if (a > 0 && a < 1) {
1148
+ const ra = 1 / a;
1149
+ const invA = 1 - ra;
1150
+
1151
+ for (let j = 0; j < 3; j++) {
1152
+ const v = data[i + j] * ra + invA;
1153
+ data[i + j] = v < 0 ? 0 : v;
1154
+ }
1155
+ }
1156
+ }
1157
+ } else if (data instanceof Uint16Array) {
1158
+ for (let i = 0; i < size; i += 4) {
1159
+ const pa = data[i + 3];
1160
+
1161
+ if (pa !== 0 && pa !== 0xffff) {
1162
+ const ra = 0xffff / pa;
1163
+ const invA = 0xffff * (1 - ra);
1164
+
1165
+ for (let j = 0; j < 3; j++) {
1166
+ const v = Math.round(data[i + j] * ra + invA);
1167
+ data[i + j] = v < 0 ? 0 : (v > 0xffff ? 0xffff : v);
1168
+ }
1169
+ }
1170
+ }
1171
+ } else {
1172
+ // Uint8ClampedArray clamps and rounds values on assignment
1015
1173
  for (let i = 0; i < size; i += 4) {
1016
- const pa = p[i + 3];
1174
+ const pa = data[i + 3];
1175
+
1017
1176
  if (pa != 0 && pa != 255) {
1018
1177
  const a = pa / 255;
1019
1178
  const ra = 1 / a;
1020
1179
  const invA = 255 * (1 - ra);
1021
- p[i + 0] = p[i + 0] * ra + invA;
1022
- p[i + 1] = p[i + 1] * ra + invA;
1023
- p[i + 2] = p[i + 2] * ra + invA;
1180
+ data[i + 0] = data[i + 0] * ra + invA;
1181
+ data[i + 1] = data[i + 1] * ra + invA;
1182
+ data[i + 2] = data[i + 2] * ra + invA;
1024
1183
  }
1025
1184
  }
1026
1185
  }
1186
+ }
1027
1187
 
1028
- return imageData;
1188
+ // converts value in 0-255 range to target range, rounding and clamping it
1189
+ function toRange(value: number, max: number) {
1190
+ value = Math.round(value * (max / 255));
1191
+ return value <= 0 ? 0 : (value >= max ? max : value);
1029
1192
  }
1030
1193
 
1031
- function cmykToRgb(cmyk: PixelData, rgb: PixelData, reverseAlpha: boolean) {
1194
+ // Polynomial approximation of U.S. Web Coated (SWOP) v2 CMYK to RGB conversion (default CMYK profile of Photoshop).
1195
+ // Coefficients are taken from pdf.js (DeviceCmykCS in src/core/colorspace.js, https://github.com/mozilla/pdf.js),
1196
+ // Copyright 2012 Mozilla Foundation, licensed under the Apache License, Version 2.0
1197
+ // (http://www.apache.org/licenses/LICENSE-2.0)
1198
+ // cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
1199
+ // removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
1200
+ function cmykToRgb(cmyk: PixelData, rgb: PixelData, removeMatte: boolean) {
1032
1201
  const size = rgb.width * rgb.height * 4;
1033
1202
  const srcData = cmyk.data;
1034
1203
  const dstData = rgb.data;
1204
+ const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
1205
+ const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
1035
1206
 
1036
1207
  for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
1037
- const c = srcData[src];
1038
- const m = srcData[src + 1];
1039
- const y = srcData[src + 2];
1040
- const k = srcData[src + 3];
1041
- dstData[dst] = ((((c * k) | 0) / 255) | 0);
1042
- dstData[dst + 1] = ((((m * k) | 0) / 255) | 0);
1043
- dstData[dst + 2] = ((((y * k) | 0) / 255) | 0);
1044
- dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
1045
- }
1046
-
1047
- // for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
1048
- // const c = 1 - (srcData[src + 0] / 255);
1049
- // const m = 1 - (srcData[src + 1] / 255);
1050
- // const y = 1 - (srcData[src + 2] / 255);
1051
- // // const k = srcData[src + 3] / 255;
1052
- // dstData[dst + 0] = ((1 - c * 0.8) * 255) | 0;
1053
- // dstData[dst + 1] = ((1 - m * 0.8) * 255) | 0;
1054
- // dstData[dst + 2] = ((1 - y * 0.8) * 255) | 0;
1055
- // dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
1056
- // }
1057
- }
1058
-
1059
- function indexedToRgb(indexed: PixelData, rgb: PixelData, palette: RGB[]) {
1208
+ const a = srcData[src + 4];
1209
+
1210
+ // image data stores inverted values (0 = 100% ink)
1211
+ let c = 1 - srcData[src] / srcMax;
1212
+ let m = 1 - srcData[src + 1] / srcMax;
1213
+ let y = 1 - srcData[src + 2] / srcMax;
1214
+ let k = 1 - srcData[src + 3] / srcMax;
1215
+
1216
+ if (removeMatte && a > 0 && a < srcMax) {
1217
+ const ra = srcMax / a;
1218
+ c = Math.min(1, c * ra);
1219
+ m = Math.min(1, m * ra);
1220
+ y = Math.min(1, y * ra);
1221
+ k = Math.min(1, k * ra);
1222
+ }
1223
+
1224
+ dstData[dst] = toRange(255 +
1225
+ c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 285.2331026137004) +
1226
+ m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 5.497006427196366) +
1227
+ y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) +
1228
+ k * (-21.86122147463605 * k - 189.48180835922747), dstMax);
1229
+ dstData[dst + 1] = toRange(255 +
1230
+ c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 79.2970844816548) +
1231
+ m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) +
1232
+ y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
1233
+ k * (-20.737325471181034 * k - 187.80453709719578), dstMax);
1234
+ dstData[dst + 2] = toRange(255 +
1235
+ c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 14.183576799673286) +
1236
+ m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) +
1237
+ y * (0.03296041114873217 * y + 115.60384449646641 * k - 193.58209356861505) +
1238
+ k * (-22.33816807309886 * k - 180.12613974708367), dstMax);
1239
+ dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
1240
+ }
1241
+ }
1242
+
1243
+ // transparencyIndex - palette index of transparent color (-1 if there's none)
1244
+ function indexedToRgb(indexed: PixelData, rgb: PixelData, palette: RGB[], transparencyIndex: number) {
1060
1245
  const size = indexed.width * indexed.height;
1061
1246
  const srcData = indexed.data;
1062
1247
  const dstData = rgb.data;
1063
1248
 
1064
1249
  for (let src = 0, dst = 0; src < size; src++, dst += 4) {
1065
- const c = palette[srcData[src]];
1250
+ const index = srcData[src];
1251
+ const c = palette[index];
1066
1252
  dstData[dst + 0] = c.r;
1067
1253
  dstData[dst + 1] = c.g;
1068
1254
  dstData[dst + 2] = c.b;
1069
- dstData[dst + 3] = 255;
1255
+ dstData[dst + 3] = index === transparencyIndex ? 0 : 255;
1070
1256
  }
1071
1257
  }
1072
1258
 
1073
- function verifyCompatible(a: PixelArray, b: PixelArray) {
1074
- if ((a.byteLength / a.length) !== (b.byteLength / b.length)) {
1075
- throw new Error('Invalid array types');
1259
+ // number of bytes used to store single channel value, 1-bit data is handled as bytes
1260
+ function bytesPerValue(bitDepth: number) {
1261
+ switch (bitDepth) {
1262
+ case 1:
1263
+ case 8: return 1;
1264
+ case 16: return 2;
1265
+ case 32: return 4;
1266
+ default: throw new Error(`Invalid bitDepth (${bitDepth})`);
1076
1267
  }
1077
1268
  }
1078
1269
 
1079
- function bytesToArray(bytes: Uint8Array, bitDepth: number) {
1080
- if (bitDepth === 8) {
1081
- return bytes;
1082
- } else if (bitDepth === 16) {
1083
- // PSD files store 16-bit channel data in big-endian byte order.
1084
- // Swap each pair of bytes so that Uint16Array (native-endian) reads the correct values.
1085
- for (let i = 0; i < bytes.byteLength; i += 2) {
1086
- const tmp = bytes[i];
1087
- bytes[i] = bytes[i + 1];
1088
- bytes[i + 1] = tmp;
1089
- }
1090
- if (bytes.byteOffset % 2) {
1091
- const result = new Uint16Array(bytes.byteLength / 2);
1092
- new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
1093
- return result;
1094
- } else {
1095
- return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
1096
- }
1097
- } else if (bitDepth === 32) {
1098
- if (bytes.byteOffset % 4) {
1099
- const result = new Float32Array(bytes.byteLength / 4);
1100
- new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
1101
- return result;
1102
- } else {
1103
- return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
1104
- }
1105
- } else {
1106
- throw new Error(`Invalid bitDepth (${bitDepth})`)
1270
+ function verifyArrayType(data: PixelArray, bytesPerValue: number) {
1271
+ if (data.BYTES_PER_ELEMENT !== bytesPerValue) {
1272
+ throw new Error('Invalid array types');
1107
1273
  }
1108
1274
  }
1109
1275
 
1110
- function copyChannelToPixelData(pixelData: PixelData, channel: PixelArray, offset: number, step: number) {
1111
- verifyCompatible(pixelData.data, channel);
1112
- const size = pixelData.width * pixelData.height;
1276
+ // NOTE: `buffer` may alias input file data, it must not be modified here
1277
+ function readDataRaw(buffer: Uint8Array, pixelData: PixelData | undefined, bitDepth: number, step: number, offset: number) {
1278
+ const bpv = bytesPerValue(bitDepth);
1279
+
1280
+ if (!pixelData || offset < 0 || offset >= step) return;
1281
+
1113
1282
  const data = pixelData.data;
1114
- for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
1115
- data[p] = channel[i];
1116
- }
1117
- }
1283
+ verifyArrayType(data, bpv);
1284
+ const size = Math.min(pixelData.width * pixelData.height, Math.floor(buffer.byteLength / bpv)) | 0;
1118
1285
 
1119
- function readDataRaw(buffer: Uint8Array, pixelData: PixelData | undefined, bitDepth: number, step: number, offset: number) {
1120
- if (bitDepth == 32) {
1121
- for (let i = 0; i < buffer.byteLength; i += 4) {
1122
- const a = buffer[i + 0];
1123
- const b = buffer[i + 1];
1124
- const c = buffer[i + 2];
1125
- const d = buffer[i + 3];
1126
- buffer[i + 0] = d;
1127
- buffer[i + 1] = c;
1128
- buffer[i + 2] = b;
1129
- buffer[i + 3] = a;
1286
+ if (bpv === 1) {
1287
+ for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
1288
+ data[p] = buffer[i];
1130
1289
  }
1131
- }
1132
-
1133
- const array = bytesToArray(buffer, bitDepth);
1290
+ } else if (bpv === 2) {
1291
+ for (let i = 0, j = 0, p = offset | 0; i < size; i++, j += 2, p = (p + step) | 0) {
1292
+ data[p] = (buffer[j] << 8) | buffer[j + 1];
1293
+ }
1294
+ } else {
1295
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1134
1296
 
1135
- if (pixelData && offset < step) {
1136
- copyChannelToPixelData(pixelData, array, offset, step);
1297
+ for (let i = 0, j = 0, p = offset | 0; i < size; i++, j += 4, p = (p + step) | 0) {
1298
+ data[p] = view.getFloat32(j, false);
1299
+ }
1137
1300
  }
1138
1301
  }
1139
1302
 
1140
- function decodePredicted(data: Uint8Array | Uint16Array, width: number, height: number, mod: number) {
1303
+ function decodePredicted(data: Uint8Array, width: number, height: number) {
1141
1304
  for (let y = 0; y < height; y++) {
1142
1305
  const offset = y * width;
1143
1306
 
1144
1307
  for (let x = 1, o = offset + 1; x < width; x++, o++) {
1145
- data[o] = (data[o - 1] + data[o]) % mod;
1308
+ data[o] = data[o - 1] + data[o]; // Uint8Array wraps values on assignment
1146
1309
  }
1147
1310
  }
1148
1311
  }
1149
1312
 
1150
- export function readDataZip(compressed: Uint8Array, pixelData: PixelData | undefined, width: number, height: number, bitDepth: number, step: number, offset: number, prediction: boolean) {
1151
- const decompressed = inflateSync(compressed);
1313
+ const zipOverflow = new Error('Decompressed data exceeds expected size');
1152
1314
 
1153
- if (pixelData && offset < step) {
1154
- const array = bytesToArray(decompressed, bitDepth);
1315
+ // inflates data, stopping when output exceeds expected size (protects against zip bombs)
1316
+ function inflateLimited(compressed: Uint8Array, expectedSize: number, reader: PsdReader | undefined) {
1317
+ const output = new Uint8Array(expectedSize);
1318
+ let position = 0;
1319
+ let overflow = false;
1155
1320
 
1156
- if (bitDepth === 8) {
1157
- if (prediction) decodePredicted(decompressed, width, height, 0x100);
1158
- copyChannelToPixelData(pixelData, decompressed, offset, step);
1159
- } else if (bitDepth === 16) {
1160
- if (prediction) decodePredicted(array as Uint16Array, width, height, 0x10000);
1161
- copyChannelToPixelData(pixelData, array, offset, step);
1162
- } else if (bitDepth === 32) {
1163
- if (prediction) decodePredicted(decompressed, width * 4, height, 0x100);
1321
+ const inflator = new Inflate({ chunkSize: Math.max(1024, Math.min(expectedSize, 0x10000)) });
1322
+ inflator.onData = (chunk: any) => {
1323
+ const data = chunk as Uint8Array;
1324
+ const count = Math.min(data.length, expectedSize - position);
1325
+ if (count > 0) output.set(count === data.length ? data : data.subarray(0, count), position);
1326
+ position += count;
1164
1327
 
1165
- let di = offset;
1166
- const dst = new Uint32Array(pixelData.data.buffer, pixelData.data.byteOffset, pixelData.data.length);
1328
+ if (count < data.length) {
1329
+ overflow = true;
1330
+ throw zipOverflow;
1331
+ }
1332
+ };
1333
+ inflator.onEnd = (status: number) => {
1334
+ // default handler collects all the chunks into result, we only need the error status
1335
+ inflator.err = status;
1336
+ inflator.msg = (inflator as any).strm?.msg || '';
1337
+ };
1338
+
1339
+ try {
1340
+ inflator.push(compressed, true);
1341
+ } catch (e) {
1342
+ if (e !== zipOverflow) throw e;
1343
+ }
1167
1344
 
1168
- for (let y = 0; y < height; y++) {
1169
- let a = width * 4 * y;
1345
+ if (overflow) {
1346
+ if (reader) warnOrThrow(reader, zipOverflow.message);
1347
+ } else if (inflator.err) {
1348
+ throw new Error(`Invalid zip data: ${inflator.msg}`);
1349
+ }
1170
1350
 
1171
- for (let x = 0; x < width; x++, a++, di += step) {
1172
- const b = a + width;
1173
- const c = b + width;
1174
- const d = c + width;
1175
- dst[di] = ((decompressed[a] << 24) | (decompressed[b] << 16) | (decompressed[c] << 8) | decompressed[d]) >>> 0;
1351
+ return output;
1352
+ }
1353
+
1354
+ export function readDataZip(
1355
+ compressed: Uint8Array, pixelData: PixelData | undefined, width: number, height: number, bitDepth: number, step: number,
1356
+ offset: number, prediction: boolean, reader?: PsdReader
1357
+ ) {
1358
+ const bpv = bytesPerValue(bitDepth);
1359
+
1360
+ if (!pixelData || offset < 0 || offset >= step) return;
1361
+
1362
+ const data = pixelData.data;
1363
+ verifyArrayType(data, bpv);
1364
+
1365
+ const expectedSize = width * height * bpv;
1366
+ if (reader) consumeMemory(reader, expectedSize);
1367
+
1368
+ try {
1369
+ const decompressed = inflateLimited(compressed, expectedSize, reader);
1370
+
1371
+ if (bpv === 1) {
1372
+ for (let y = 0, i = 0; y < height; y++) {
1373
+ let prev = 0;
1374
+
1375
+ for (let x = 0, p = offset + y * width * step; x < width; x++, i++, p += step) {
1376
+ let v = decompressed[i];
1377
+ if (prediction) v = prev = (prev + v) & 0xff;
1378
+ data[p] = v;
1379
+ }
1380
+ }
1381
+ } else if (bpv === 2) {
1382
+ for (let y = 0, i = 0; y < height; y++) {
1383
+ let prev = 0;
1384
+
1385
+ for (let x = 0, p = offset + y * width * step; x < width; x++, i += 2, p += step) {
1386
+ let v = (decompressed[i] << 8) | decompressed[i + 1];
1387
+ if (prediction) v = prev = (prev + v) & 0xffff;
1388
+ data[p] = v;
1176
1389
  }
1177
1390
  }
1178
1391
  } else {
1179
- throw new Error('Invalid bitDepth');
1392
+ const dst = new Uint32Array(data.buffer, data.byteOffset, data.length);
1393
+
1394
+ if (prediction) {
1395
+ // bytes are delta encoded per row and stored in planar order (all 1st bytes, all 2nd bytes, ...)
1396
+ decodePredicted(decompressed, width * 4, height);
1397
+
1398
+ for (let y = 0, di = offset; y < height; y++) {
1399
+ let a = width * 4 * y;
1400
+
1401
+ for (let x = 0; x < width; x++, a++, di += step) {
1402
+ const b = a + width;
1403
+ const c = b + width;
1404
+ const d = c + width;
1405
+ dst[di] = ((decompressed[a] << 24) | (decompressed[b] << 16) | (decompressed[c] << 8) | decompressed[d]) >>> 0;
1406
+ }
1407
+ }
1408
+ } else {
1409
+ const view = new DataView(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength);
1410
+
1411
+ for (let i = 0, j = 0, di = offset, size = width * height; i < size; i++, j += 4, di += step) {
1412
+ dst[di] = view.getUint32(j, false);
1413
+ }
1414
+ }
1180
1415
  }
1416
+ } finally {
1417
+ if (reader) recoverMemory(reader, expectedSize);
1181
1418
  }
1182
1419
  }
1183
1420
 
1184
- export function readDataRLE(reader: PsdReader, pixelData: PixelData | undefined, width: number, height: number, _bitDepth: number, step: number, offsets: number[], large: boolean) {
1185
- const data = pixelData && pixelData.data;
1186
- let lengths: Uint16Array | Uint32Array;
1421
+ // decodes single PackBits row, returns number of decoded values
1422
+ function decodeRLERow(src: Uint8Array, i: number, end: number, dst: PixelArray, p: number, step: number, count: number) {
1423
+ let x = 0;
1187
1424
 
1188
- if (large) {
1189
- consumeMemory(reader, offsets.length * height * 4);
1190
- lengths = new Uint32Array(offsets.length * height);
1425
+ while (i < end && x < count) {
1426
+ const header = src[i++];
1191
1427
 
1192
- for (let o = 0, li = 0; o < offsets.length; o++) {
1193
- for (let y = 0; y < height; y++, li++) {
1194
- lengths[li] = readUint32(reader);
1428
+ if (header > 128) {
1429
+ if (i >= end) break;
1430
+
1431
+ const value = src[i++];
1432
+ let n = 257 - header;
1433
+ if (n > count - x) n = count - x;
1434
+ x += n;
1435
+
1436
+ for (; n > 0; n--, p += step) {
1437
+ dst[p] = value;
1195
1438
  }
1439
+ } else if (header < 128) {
1440
+ let n = header + 1;
1441
+ if (n > count - x) n = count - x;
1442
+ if (n > end - i) n = end - i;
1443
+ x += n;
1444
+
1445
+ for (; n > 0; n--, p += step) {
1446
+ dst[p] = src[i++];
1447
+ }
1448
+ } else {
1449
+ // ignore 128
1450
+ }
1451
+ }
1452
+
1453
+ return x;
1454
+ }
1455
+
1456
+ // converts big-endian 16/32-bit values from bytes and writes them to `data` starting at `p` with `step`
1457
+ function scatterValues(bytes: Uint8Array, count: number, bpv: number, data: PixelArray, p: number, step: number) {
1458
+ if (bpv === 2) {
1459
+ for (let x = 0, i = 0; x < count; x++, i += 2, p += step) {
1460
+ data[p] = (bytes[i] << 8) | bytes[i + 1];
1196
1461
  }
1197
1462
  } else {
1198
- consumeMemory(reader, offsets.length * height * 2);
1199
- lengths = new Uint16Array(offsets.length * height);
1463
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1200
1464
 
1201
- for (let o = 0, li = 0; o < offsets.length; o++) {
1202
- for (let y = 0; y < height; y++, li++) {
1203
- lengths[li] = readUint16(reader);
1204
- }
1465
+ for (let x = 0, i = 0; x < count; x++, i += 4, p += step) {
1466
+ data[p] = view.getFloat32(i, false);
1205
1467
  }
1206
1468
  }
1469
+ }
1207
1470
 
1208
- // if (bitDepth !== 1 && bitDepth !== 8) throw new Error(`Invalid bit depth (${bitDepth})`);
1471
+ // decodes RLE rows of single channel, each row starts at the beginning of the image row
1472
+ function decodeRLEChannel(
1473
+ src: Uint8Array, lengths: Uint16Array | Uint32Array, li: number, pos: number, width: number, height: number,
1474
+ data: PixelArray, offset: number, step: number, bpv: number, row: Uint8Array | undefined
1475
+ ) {
1476
+ const srcEnd = src.length;
1477
+ const rowSize = width * bpv;
1209
1478
 
1210
- const extraLimit = (step - 1) | 0; // 3 for rgb, 4 for cmyk
1479
+ for (let y = 0; y < height; y++, li++) {
1480
+ const rowStart = pos < srcEnd ? pos : srcEnd;
1481
+ pos += lengths[li];
1482
+ const rowEnd = pos < srcEnd ? pos : srcEnd;
1211
1483
 
1212
- for (let c = 0, li = 0; c < offsets.length; c++) {
1213
- const offset = offsets[c] | 0;
1214
- const extra = c > extraLimit || offset > extraLimit;
1484
+ if (rowStart >= rowEnd) continue;
1215
1485
 
1216
- if (!data || extra) {
1217
- for (let y = 0; y < height; y++, li++) {
1218
- skipBytes(reader, lengths[li]);
1219
- }
1486
+ if (bpv === 1) {
1487
+ decodeRLERow(src, rowStart, rowEnd, data, offset + y * width * step, step, width);
1220
1488
  } else {
1221
- for (let y = 0, p = offset | 0; y < height; y++, li++) {
1222
- const length = lengths[li];
1223
- const buffer = readBytes(reader, length);
1224
-
1225
- for (let i = 0, x = 0; i < length; i++) {
1226
- let header = buffer[i];
1227
-
1228
- if (header > 128) {
1229
- const value = buffer[++i];
1230
- header = (256 - header) | 0;
1231
-
1232
- for (let j = 0; j <= header && x < width; j = (j + 1) | 0, x = (x + 1) | 0) {
1233
- data[p] = value;
1234
- p = (p + step) | 0;
1235
- }
1236
- } else if (header < 128) {
1237
- for (let j = 0; j <= header && x < width; j = (j + 1) | 0, x = (x + 1) | 0) {
1238
- data[p] = buffer[++i];
1239
- p = (p + step) | 0;
1240
- }
1241
- } else {
1242
- // ignore 128
1243
- }
1489
+ const decoded = decodeRLERow(src, rowStart, rowEnd, row!, 0, 1, rowSize);
1490
+ scatterValues(row!, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
1491
+ }
1492
+ }
1493
+ }
1244
1494
 
1245
- // This showed up on some images from non-photoshop programs, ignoring it seems to work just fine.
1246
- // if (i >= length) throw new Error(`Invalid RLE data: exceeded buffer size ${i}/${length}`);
1247
- }
1495
+ export function readDataRLE(reader: PsdReader, pixelData: PixelData | undefined, width: number, height: number, bitDepth: number, step: number, offsets: number[], large: boolean) {
1496
+ const data = pixelData && pixelData.data;
1497
+ const rows = offsets.length * height;
1498
+ const lengthSize = large ? 4 : 2;
1499
+
1500
+ consumeMemory(reader, rows * lengthSize);
1501
+
1502
+ try {
1503
+ if ((reader.offset + rows * lengthSize) > reader.view.byteLength) {
1504
+ throw new Error('RLE row lengths exceed buffer length');
1505
+ }
1506
+
1507
+ const lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
1508
+ let total = 0;
1509
+
1510
+ if (large) {
1511
+ for (let i = 0; i < rows; i++) total += (lengths[i] = readUint32(reader));
1512
+ } else {
1513
+ for (let i = 0; i < rows; i++) total += (lengths[i] = readUint16(reader));
1514
+ }
1515
+
1516
+ const start = reader.offset;
1517
+ const srcEnd = reader.view.byteLength;
1518
+
1519
+ if (total > (srcEnd - start)) {
1520
+ // broken or truncated file, decode only data that is available
1521
+ warnOrThrow(reader, 'RLE data exceeds buffer length');
1522
+ }
1523
+
1524
+ const bpv = bytesPerValue(bitDepth);
1525
+ if (data) verifyArrayType(data, bpv);
1526
+
1527
+ const src = new Uint8Array(reader.view.buffer, reader.view.byteOffset, srcEnd);
1528
+ const row = (data && bpv > 1) ? new Uint8Array(width * bpv) : undefined;
1529
+ let pos = start;
1530
+
1531
+ for (let c = 0, li = 0; c < offsets.length; c++, li += height) {
1532
+ const offset = offsets[c] | 0;
1533
+ const channelStart = pos;
1534
+
1535
+ for (let y = 0; y < height; y++) pos += lengths[li + y];
1536
+
1537
+ if (data && offset >= 0 && offset < step) {
1538
+ decodeRLEChannel(src, lengths, li, channelStart, width, height, data, offset, step, bpv, row);
1248
1539
  }
1249
1540
  }
1250
- }
1251
1541
 
1252
- recoverMemory(reader, lengths.byteLength);
1542
+ reader.offset = pos;
1543
+ } finally {
1544
+ recoverMemory(reader, rows * lengthSize);
1545
+ }
1253
1546
  }
1254
1547
 
1255
1548
  export function readSection<T>(
@@ -1303,19 +1596,19 @@ export function readColor(reader: PsdReader): Color {
1303
1596
  skipBytes(reader, 2);
1304
1597
  return { h, s, b };
1305
1598
  }
1306
- case ColorSpace.CMYK: {
1307
- const c = readUint16(reader) / 257;
1308
- const m = readUint16(reader) / 257;
1309
- const y = readUint16(reader) / 257;
1310
- const k = readUint16(reader) / 257;
1599
+ case ColorSpace.CMYK: { // 0 = 100% ink
1600
+ const c = (0xffff - readUint16(reader)) / 257;
1601
+ const m = (0xffff - readUint16(reader)) / 257;
1602
+ const y = (0xffff - readUint16(reader)) / 257;
1603
+ const k = (0xffff - readUint16(reader)) / 257;
1311
1604
  return { c, m, y, k };
1312
1605
  }
1313
1606
  case ColorSpace.Lab: {
1314
1607
  const l = readInt16(reader) / 10000;
1315
1608
  const ta = readInt16(reader);
1316
1609
  const tb = readInt16(reader);
1317
- const a = ta < 0 ? (ta / 12800) : (ta / 12700);
1318
- const b = tb < 0 ? (tb / 12800) : (tb / 12700);
1610
+ const a = normalizeLabAB(ta, 100);
1611
+ const b = normalizeLabAB(tb, 100);
1319
1612
  skipBytes(reader, 2);
1320
1613
  return { l, a, b };
1321
1614
  }
@@ -1329,6 +1622,24 @@ export function readColor(reader: PsdReader): Color {
1329
1622
  }
1330
1623
  }
1331
1624
 
1625
+ const MAX_PATTERN_SIZE = 30000;
1626
+
1627
+ // reads pattern, unsupported or invalid patterns are skipped (returns undefined) unless throwForMissingFeatures is set
1628
+ export function readPatternSafe(reader: PsdReader): PatternInfo | undefined {
1629
+ let length = reader.view.getUint32(reader.offset);
1630
+ while (length % 4) length++;
1631
+ const end = reader.offset + 4 + length;
1632
+
1633
+ try {
1634
+ return readPattern(reader);
1635
+ } catch (e) {
1636
+ if (reader.throwForMissingFeatures) throw e;
1637
+ reader.logMissingFeatures && reader.log(`Skipped pattern: ${(e as Error).message}`);
1638
+ reader.offset = end;
1639
+ return undefined;
1640
+ }
1641
+ }
1642
+
1332
1643
  export function readPattern(reader: PsdReader): PatternInfo {
1333
1644
  let length = readUint32(reader);
1334
1645
  while (length % 4) length++;
@@ -1340,8 +1651,8 @@ export function readPattern(reader: PsdReader): PatternInfo {
1340
1651
  const x = readInt16(reader);
1341
1652
  const y = readInt16(reader);
1342
1653
 
1343
- // we only support RGB and grayscale for now
1344
- if (colorMode !== ColorMode.RGB && colorMode !== ColorMode.Grayscale && colorMode !== ColorMode.Indexed) {
1654
+ // we only support RGB, grayscale, indexed and CMYK for now
1655
+ if (colorMode !== ColorMode.RGB && colorMode !== ColorMode.Grayscale && colorMode !== ColorMode.Indexed && colorMode !== ColorMode.CMYK) {
1345
1656
  throw new Error(`Unsupported pattern color mode: ${colorMode}`);
1346
1657
  }
1347
1658
 
@@ -1358,7 +1669,8 @@ export function readPattern(reader: PsdReader): PatternInfo {
1358
1669
  })
1359
1670
  }
1360
1671
 
1361
- skipBytes(reader, 4); // no idea what this is
1672
+ // some writers follow the palette with 4 extra bytes (unknown)
1673
+ if (reader.view.getUint32(reader.offset) !== 3) skipBytes(reader, 4);
1362
1674
  }
1363
1675
 
1364
1676
  // virtual memory array list
@@ -1373,6 +1685,11 @@ export function readPattern(reader: PsdReader): PatternInfo {
1373
1685
  const channelsCount = readUint32(reader);
1374
1686
  const width = right - left;
1375
1687
  const height = bottom - top;
1688
+ if (width < 0 || height < 0) throw new Error('Invalid pattern bounds');
1689
+ if (width > MAX_PATTERN_SIZE || height > MAX_PATTERN_SIZE) throw new Error(`Pattern too large (${width}x${height})`);
1690
+ if (channelsCount > 64) throw new Error(`Invalid pattern channel count: ${channelsCount}`);
1691
+ // channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
1692
+ if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64) throw new Error('Pattern data out of bounds');
1376
1693
  const size = width * height * 4;
1377
1694
  consumeMemory(reader, size);
1378
1695
  const data = new Uint8Array(size);
@@ -1381,11 +1698,21 @@ export function readPattern(reader: PsdReader): PatternInfo {
1381
1698
  data[i] = 255;
1382
1699
  }
1383
1700
 
1384
- for (let i = 0, ch = 0; i < (channelsCount + 2); i++) {
1701
+ // first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
1702
+ const colorChannels = colorMode === ColorMode.RGB ? 3 : (colorMode === ColorMode.CMYK ? 4 : 1);
1703
+ // CMYK channels are decoded into separate buffer (5 values per pixel, inverted, 0 = 100% ink) and converted to RGB at the end
1704
+ const cmyk = colorMode === ColorMode.CMYK ? new Uint8Array(width * height * 5).fill(255) : undefined;
1705
+ if (cmyk) consumeMemory(reader, cmyk.byteLength);
1706
+ const alphaTarget = cmyk ? 4 : 3;
1707
+
1708
+ for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
1385
1709
  const has = readUint32(reader);
1386
1710
  if (!has) continue;
1387
1711
 
1388
1712
  const length = readUint32(reader);
1713
+ if (!length) continue;
1714
+ if (length < 23) throw new Error('Invalid pattern channel length');
1715
+
1389
1716
  const pixelDepth = readUint32(reader);
1390
1717
  const ctop = readUint32(reader);
1391
1718
  const cleft = readUint32(reader);
@@ -1393,82 +1720,75 @@ export function readPattern(reader: PsdReader): PatternInfo {
1393
1720
  const cright = readUint32(reader);
1394
1721
  const pixelDepth2 = readUint16(reader);
1395
1722
  const compressionMode = readUint8(reader); // 0 - raw, 1 - rle
1396
- const dataLength = length - (4 + 16 + 2 + 1);
1397
- const cdata = readBytes(reader, dataLength);
1723
+ const cdata = readBytes(reader, length - 23);
1724
+ const depth = (pixelDepth2 === 8 || pixelDepth2 === 16 || pixelDepth2 === 32) ? pixelDepth2 : pixelDepth;
1398
1725
 
1399
- if (pixelDepth !== 8 || pixelDepth2 !== 8) {
1400
- throw new Error('16bit pixel depth not supported for patterns');
1726
+ if (depth !== 8 && depth !== 16 && depth !== 32) throw new Error(`Unsupported pattern pixel depth: ${depth}`);
1727
+
1728
+ if (ctop < top || cleft < left || cbottom > bottom || cright > right || cbottom < ctop || cright < cleft) {
1729
+ throw new Error('Pattern channel bounds outside of pattern bounds');
1401
1730
  }
1402
1731
 
1732
+ const target = ch < colorChannels ? ch : (ch === colorChannels ? alphaTarget : -1);
1733
+ ch++;
1734
+
1735
+ if (target === -1) continue;
1736
+
1403
1737
  const w = cright - cleft;
1404
1738
  const h = cbottom - ctop;
1405
- const ox = cleft - left;
1406
- const oy = ctop - top;
1739
+ const rowBytes = w * (depth / 8);
1740
+ const channelSize = rowBytes * h;
1741
+ let bytes: Uint8Array;
1407
1742
 
1408
1743
  if (compressionMode === 0) {
1409
- if (colorMode === ColorMode.RGB && ch < 3) {
1410
- for (let y = 0; y < h; y++) {
1411
- for (let x = 0; x < w; x++) {
1412
- const src = x + y * w;
1413
- const dst = (ox + x + (y + oy) * width) * 4;
1414
- data[dst + ch] = cdata[src];
1415
- }
1416
- }
1417
- } else if (colorMode === ColorMode.Grayscale && ch < 1) {
1418
- for (let y = 0; y < h; y++) {
1419
- for (let x = 0; x < w; x++) {
1420
- const src = x + y * w;
1421
- const dst = (ox + x + (y + oy) * width) * 4;
1422
- const value = cdata[src];
1423
- data[dst + 0] = value;
1424
- data[dst + 1] = value;
1425
- data[dst + 2] = value;
1426
- }
1427
- }
1428
- } else if (colorMode === ColorMode.Indexed) {
1429
- for (let y = 0; y < h; y++) {
1430
- for (let x = 0; x < w; x++) {
1431
- const src = x + y * w;
1432
- const dst = (ox + x + (y + oy) * width) * 4;
1433
- const index = cdata[src];
1434
- const color = palette[index];
1435
- data[dst + 0] = color.r;
1436
- data[dst + 1] = color.g;
1437
- data[dst + 2] = color.b;
1438
- }
1439
- }
1440
- } else {
1441
- if (reader.throwForMissingFeatures) throw new Error('Invalid color pattern');
1442
- }
1744
+ if (cdata.byteLength < channelSize) throw new Error('Pattern channel data out of bounds');
1745
+ bytes = cdata;
1443
1746
  } else if (compressionMode === 1) {
1444
- consumeMemory(reader, w * h);
1747
+ // each 2 bytes of RLE data can produce at most 128 bytes
1748
+ if (channelSize > cdata.byteLength * 64) throw new Error('Pattern channel data out of bounds');
1445
1749
 
1446
- const pixelData: PixelData = { data, width, height };
1447
- const tempData: PixelData = { data: new Uint8Array(w * h), width: w, height: h };
1750
+ consumeMemory(reader, channelSize);
1751
+ bytes = new Uint8Array(channelSize);
1448
1752
  const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
1753
+ readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
1754
+ } else {
1755
+ throw new Error('Invalid pattern compression mode');
1756
+ }
1449
1757
 
1450
- if (colorMode === ColorMode.RGB && ch < 3) {
1451
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1452
- copyChannelToRGBA(tempData, pixelData, ox, oy, ch);
1453
- }
1454
-
1455
- if (colorMode === ColorMode.Grayscale && ch < 1) {
1456
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1457
- copyChannelToRGBA(tempData, pixelData, ox, oy, 0);
1458
- setupGrayscale(pixelData);
1459
- }
1758
+ const ox = cleft - left;
1759
+ const oy = ctop - top;
1460
1760
 
1461
- if (colorMode === ColorMode.Indexed) {
1462
- // TODO:
1463
- throw new Error('Indexed pattern color mode not implemented');
1761
+ for (let y = 0; y < h; y++) {
1762
+ for (let x = 0; x < w; x++) {
1763
+ const src = x + y * w;
1764
+ const dst = (ox + x + (y + oy) * width) * 4;
1765
+ const value = depth === 8 ? bytes[src] : patternValueTo8bit(bytes, src, depth);
1766
+
1767
+ if (cmyk) {
1768
+ cmyk[(ox + x + (y + oy) * width) * 5 + target] = value;
1769
+ } else if (target === 3) {
1770
+ data[dst + 3] = value;
1771
+ } else if (colorMode === ColorMode.Indexed) {
1772
+ const color = palette[value];
1773
+ data[dst + 0] = color.r;
1774
+ data[dst + 1] = color.g;
1775
+ data[dst + 2] = color.b;
1776
+ } else if (colorMode === ColorMode.Grayscale) {
1777
+ data[dst + 0] = value;
1778
+ data[dst + 1] = value;
1779
+ data[dst + 2] = value;
1780
+ } else {
1781
+ data[dst + target] = value;
1782
+ }
1464
1783
  }
1465
-
1466
- recoverMemory(reader, w * h);
1467
- } else {
1468
- throw new Error('Invalid pattern compression mode');
1469
1784
  }
1470
1785
 
1471
- ch++;
1786
+ if (compressionMode === 1) recoverMemory(reader, channelSize);
1787
+ }
1788
+
1789
+ if (cmyk) {
1790
+ cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
1791
+ recoverMemory(reader, cmyk.byteLength);
1472
1792
  }
1473
1793
 
1474
1794
  reader.offset = end;
@@ -1476,22 +1796,20 @@ export function readPattern(reader: PsdReader): PatternInfo {
1476
1796
  return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
1477
1797
  }
1478
1798
 
1479
- function copyChannelToRGBA(srcData: PixelData, dstData: PixelData, ox: number, oy: number, offset: number) {
1480
- const w = srcData.width;
1481
- const h = srcData.height;
1482
- const width = dstData.width;
1483
-
1484
- for (let y = 0; y < h; y++) {
1485
- for (let x = 0; x < w; x++) {
1486
- const src = x + y * w;
1487
- const dst = (ox + x + (y + oy) * width) * 4;
1488
- const value = srcData.data[src];
1489
- dstData.data[dst + offset] = value;
1490
- }
1799
+ // converts big-endian 16bit or 32bit (float) value at given index to 8bit value
1800
+ function patternValueTo8bit(bytes: Uint8Array, index: number, depth: number) {
1801
+ if (depth === 16) {
1802
+ return Math.round(((bytes[index * 2] << 8) | bytes[index * 2 + 1]) / 257);
1803
+ } else {
1804
+ const view = new DataView(bytes.buffer, bytes.byteOffset + index * 4, 4);
1805
+ const value = view.getFloat32(0, false);
1806
+ return value > 0 ? (value < 1 ? Math.round(value * 255) : 255) : 0;
1491
1807
  }
1492
1808
  }
1493
1809
 
1494
- function consumeMemory(reader: PsdReader, size: number) {
1810
+ export function consumeMemory(reader: PsdReader, size: number) {
1811
+ if (!(size >= 0)) throw new Error('Invalid size');
1812
+
1495
1813
  if (reader.totalMemoryLimit !== undefined) {
1496
1814
  if (reader.totalMemoryLimit < size) throw new Error('Exceeded memory limit');
1497
1815
  reader.totalMemoryLimit -= size;