ag-psd 32.0.0 → 34.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 (117) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  25. package/CHANGELOG.md +100 -0
  26. package/LICENSE +5 -0
  27. package/README.md +4 -7
  28. package/README_PSD.md +261 -84
  29. package/TODO.md +13 -84
  30. package/dist/abr.d.ts +11 -4
  31. package/dist/abr.js +362 -230
  32. package/dist/abr.js.map +1 -1
  33. package/dist/additionalInfo.d.ts +56 -2
  34. package/dist/additionalInfo.js +828 -361
  35. package/dist/additionalInfo.js.map +1 -1
  36. package/dist/ase.d.ts +6 -1
  37. package/dist/ase.js +61 -61
  38. package/dist/ase.js.map +1 -1
  39. package/dist/bundle.js +3321 -1364
  40. package/dist/descriptor.d.ts +28 -13
  41. package/dist/descriptor.js +295 -241
  42. package/dist/descriptor.js.map +1 -1
  43. package/dist/helpers.d.ts +7 -1
  44. package/dist/helpers.js +246 -4
  45. package/dist/helpers.js.map +1 -1
  46. package/dist/imageResources.d.ts +15 -3
  47. package/dist/imageResources.js +586 -144
  48. package/dist/imageResources.js.map +1 -1
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/jpeg.js +8 -8
  53. package/dist/jpeg.js.map +1 -1
  54. package/dist/psd.d.ts +292 -19
  55. package/dist/psd.js.map +1 -1
  56. package/dist/psdReader.d.ts +10 -0
  57. package/dist/psdReader.js +424 -216
  58. package/dist/psdReader.js.map +1 -1
  59. package/dist/psdWriter.d.ts +2 -0
  60. package/dist/psdWriter.js +373 -113
  61. package/dist/psdWriter.js.map +1 -1
  62. package/dist/text.d.ts +32 -1
  63. package/dist/text.js +75 -6
  64. package/dist/text.js.map +1 -1
  65. package/dist/utf8.js +0 -3
  66. package/dist/utf8.js.map +1 -1
  67. package/dist-es/abr.d.ts +11 -4
  68. package/dist-es/abr.js +362 -230
  69. package/dist-es/abr.js.map +1 -1
  70. package/dist-es/additionalInfo.d.ts +56 -2
  71. package/dist-es/additionalInfo.js +828 -361
  72. package/dist-es/additionalInfo.js.map +1 -1
  73. package/dist-es/ase.d.ts +6 -1
  74. package/dist-es/ase.js +61 -61
  75. package/dist-es/ase.js.map +1 -1
  76. package/dist-es/descriptor.d.ts +28 -13
  77. package/dist-es/descriptor.js +295 -241
  78. package/dist-es/descriptor.js.map +1 -1
  79. package/dist-es/helpers.d.ts +7 -1
  80. package/dist-es/helpers.js +246 -4
  81. package/dist-es/helpers.js.map +1 -1
  82. package/dist-es/imageResources.d.ts +15 -3
  83. package/dist-es/imageResources.js +586 -144
  84. package/dist-es/imageResources.js.map +1 -1
  85. package/dist-es/index.d.ts +1 -0
  86. package/dist-es/index.js +1 -0
  87. package/dist-es/index.js.map +1 -1
  88. package/dist-es/jpeg.js +8 -8
  89. package/dist-es/jpeg.js.map +1 -1
  90. package/dist-es/psd.d.ts +292 -19
  91. package/dist-es/psd.js.map +1 -1
  92. package/dist-es/psdReader.d.ts +10 -0
  93. package/dist-es/psdReader.js +424 -216
  94. package/dist-es/psdReader.js.map +1 -1
  95. package/dist-es/psdWriter.d.ts +2 -0
  96. package/dist-es/psdWriter.js +373 -113
  97. package/dist-es/psdWriter.js.map +1 -1
  98. package/dist-es/text.d.ts +32 -1
  99. package/dist-es/text.js +75 -6
  100. package/dist-es/text.js.map +1 -1
  101. package/dist-es/utf8.js +0 -3
  102. package/dist-es/utf8.js.map +1 -1
  103. package/package.json +2 -2
  104. package/perf.js +312 -9
  105. package/src/abr.ts +382 -235
  106. package/src/additionalInfo.ts +1071 -482
  107. package/src/ase.ts +66 -61
  108. package/src/descriptor.ts +291 -242
  109. package/src/helpers.ts +266 -3
  110. package/src/imageResources.ts +736 -169
  111. package/src/index.ts +1 -0
  112. package/src/jpeg.ts +8 -8
  113. package/src/psd.ts +271 -32
  114. package/src/psdReader.ts +440 -221
  115. package/src/psdWriter.ts +402 -110
  116. package/src/text.ts +93 -7
  117. package/src/utf8.ts +0 -4
package/src/psdWriter.ts CHANGED
@@ -1,20 +1,24 @@
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, denormalizeLabAB } 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, PlanarData, planarToBytes, writeDataRLEBytes, writeDataZipWithPrediction, rgbToCmykValues } from './helpers';
3
+ import { deflate as deflateSync } from 'pako';
4
+ import { ExtendedWriteOptions, infoHandlers, infoHandlersMap } from './additionalInfo';
4
5
  import { InternalImageResources, resourceHandlers } from './imageResources';
6
+ import { getCompositeImageData, getLayerImageData, getLayerMaskImageData, getLayerRealMaskImageData } from './psdReader';
5
7
 
6
8
  export interface PsdWriter {
7
9
  offset: number;
8
10
  buffer: ArrayBuffer;
9
11
  view: DataView;
10
12
  tempBuffer: Uint8Array | undefined;
13
+ logMissingFeatures?: boolean;
14
+ log(...args: any[]): void;
11
15
  }
12
16
 
13
17
  export function createWriter(size = 4096): PsdWriter {
14
18
  const buffer = new ArrayBuffer(size);
15
19
  const view = new DataView(buffer);
16
20
  const offset = 0;
17
- return { buffer, view, offset, tempBuffer: undefined };
21
+ return { buffer, view, offset, tempBuffer: undefined, log: console.log };
18
22
  }
19
23
 
20
24
  export function getWriterBuffer(writer: PsdWriter) {
@@ -215,11 +219,38 @@ export function writeSection(writer: PsdWriter, round: number, func: () => void,
215
219
  function verifyPixelData(data: PixelData | undefined, name: string) {
216
220
  // check element size instead of instanceof, so arrays from other realms (iframes, workers, vm) are accepted
217
221
  const bytesPerElement = data && (data.data as any).BYTES_PER_ELEMENT;
218
- if (bytesPerElement !== undefined && bytesPerElement !== 1) {
219
- throw new Error(`${name} has incorrect bitDepth (only 8-bit Uint8Array or Uint8ClampedArray data is supported for writing)`);
222
+ if (bytesPerElement !== undefined && bytesPerElement !== 1 && bytesPerElement !== 2 && !(data!.data instanceof Float32Array)) {
223
+ throw new Error(`${name} has incorrect bitDepth (only Uint8Array, Uint8ClampedArray, Uint16Array and Float32Array data is supported for writing)`);
220
224
  }
221
225
  }
222
226
 
227
+ // supported bit depths for each color mode
228
+ const writeBitDepths: { [colorMode: number]: number[] | undefined; } = {
229
+ [ColorMode.Bitmap]: [1],
230
+ [ColorMode.Grayscale]: [8, 16, 32],
231
+ [ColorMode.Indexed]: [8],
232
+ [ColorMode.RGB]: [8, 16, 32],
233
+ [ColorMode.CMYK]: [8, 16],
234
+ };
235
+
236
+ const colorModeNames: { [colorMode: number]: string; } = {
237
+ [ColorMode.Bitmap]: 'Bitmap', [ColorMode.Grayscale]: 'Grayscale', [ColorMode.Indexed]: 'Indexed', [ColorMode.RGB]: 'RGB',
238
+ [ColorMode.CMYK]: 'CMYK', [ColorMode.Multichannel]: 'Multichannel', [ColorMode.Duotone]: 'Duotone', [ColorMode.Lab]: 'Lab',
239
+ };
240
+
241
+ function colorChannelsCount(colorMode: ColorMode) {
242
+ return colorMode === ColorMode.RGB ? 3 : (colorMode === ColorMode.CMYK ? 4 : 1);
243
+ }
244
+
245
+ // HDR toning settings that Photoshop stores in color mode data of 32bit documents (default values of new document),
246
+ // Photoshop refuses to open 32bit documents without them
247
+ const hdrToningData = [
248
+ 104, 100, 114, 116, 0, 0, 0, 3, 62, 107, 133, 31, 0, 0, 0, 2, 0, 0, 0, 7, 0, 76, 0, 105, 0, 110, 0, 101, 0, 97, 0, 114, 0, 0,
249
+ 0, 2, 0, 2, 0, 0, 0, 0, 0, 255, 0, 255, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 65, 128, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 63, 128, 0, 0,
250
+ 104, 100, 114, 97, 0, 0, 0, 6, 0, 0, 0, 0, 65, 160, 0, 0, 65, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 128, 0, 0, 0, 0, 0, 0,
251
+ 0, 0,
252
+ ];
253
+
223
254
  function verifyBitCount(target: Psd | Layer) {
224
255
  target.children?.forEach(verifyBitCount);
225
256
 
@@ -236,19 +267,38 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
236
267
  if ((psd.width > 30000 || psd.height > 30000) && !options.psb)
237
268
  throw new Error('Document size is too large (max is 30000x30000, use PSB format instead)');
238
269
 
239
- const bitsPerChannel = psd.bitsPerChannel ?? 8;
270
+ const colorMode = psd.colorMode ?? ColorMode.RGB;
271
+ const bitsPerChannel = psd.bitsPerChannel ?? (colorMode === ColorMode.Bitmap ? 1 : 8);
272
+ const bitDepths = writeBitDepths[colorMode];
273
+
274
+ if (!bitDepths)
275
+ throw new Error(`${colorModeNames[colorMode] ?? colorMode} color mode is not supported for writing`);
276
+
277
+ if (bitDepths.indexOf(bitsPerChannel) === -1)
278
+ throw new Error(`bitsPerChannel ${bitsPerChannel} is not supported for writing in ${colorModeNames[colorMode]} color mode`);
279
+
280
+ // Photoshop doesn't support layers in bitmap and indexed color modes
281
+ const layersAllowed = colorMode !== ColorMode.Bitmap && colorMode !== ColorMode.Indexed;
282
+
283
+ if (!layersAllowed && psd.children?.length)
284
+ throw new Error(`Layers are not supported in ${colorModeNames[colorMode]} color mode`);
240
285
 
241
- if (bitsPerChannel !== 8)
242
- throw new Error('bitsPerChannel other than 8 are not supported for writing');
286
+ if (colorMode === ColorMode.Indexed && !psd.palette)
287
+ throw new Error('Missing color palette for Indexed color mode');
288
+
289
+ if (options.log) writer.log = options.log;
290
+ if (options.logMissingFeatures) writer.logMissingFeatures = true;
243
291
 
244
292
  const layers: Layer[] = [];
245
293
  addChildren(layers, psd.children);
246
- if (!layers.length) layers.push({});
294
+ if (!layers.length && layersAllowed) layers.push({});
247
295
 
248
296
  verifyBitCount(psd);
249
297
 
250
298
  const imageResources: InternalImageResources = { ...psd.imageResources };
251
- const opt: ExtendedWriteOptions = { ...options, layerIds: new Set(), layerToId: new Map() };
299
+ const opt: ExtendedWriteOptions = { ...options, layerIds: new Set(), layerToId: new Map(), colorMode, bitsPerChannel };
300
+ // 16 and 32 bit documents store layers in Lr16 / Lr32 section of global additional info
301
+ const layersInGlobalInfo = bitsPerChannel === 16 || bitsPerChannel === 32;
252
302
 
253
303
  if (opt.generateThumbnail) {
254
304
  imageResources.thumbnail = createThumbnail(psd);
@@ -260,10 +310,14 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
260
310
  imageData = psd.canvas.getContext('2d')!.getImageData(0, 0, psd.canvas.width, psd.canvas.height);
261
311
  }
262
312
 
313
+ if (!imageData && psd.rawCompositeData) { // read with `useRawData` option
314
+ imageData = getCompositeImageData(psd);
315
+ }
316
+
263
317
  if (imageData && (psd.width !== imageData.width || psd.height !== imageData.height))
264
318
  throw new Error('Document canvas must have the same size as document');
265
319
 
266
- const globalAlpha = !!imageData && hasAlpha(imageData);
320
+ const globalAlpha = !!imageData && layersAllowed && hasAlpha(imageData);
267
321
  // only used for encoding a single layer channel at a time, composite image is encoded directly into the writer buffer
268
322
  writer.tempBuffer = new Uint8Array(getLargestLayerSize(psd.children, !!options.psb));
269
323
 
@@ -271,21 +325,22 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
271
325
  writeSignature(writer, '8BPS');
272
326
  writeUint16(writer, options.psb ? 2 : 1); // version
273
327
  writeZeros(writer, 6);
274
- writeUint16(writer, globalAlpha ? 4 : 3); // channels
328
+ writeUint16(writer, colorChannelsCount(colorMode) + (globalAlpha ? 1 : 0)); // channels
275
329
  writeUint32(writer, psd.height);
276
330
  writeUint32(writer, psd.width);
277
331
  writeUint16(writer, bitsPerChannel); // bits per channel
278
- writeUint16(writer, ColorMode.RGB); // we only support saving RGB right now
332
+ writeUint16(writer, colorMode);
279
333
 
280
334
  // color mode data
281
335
  writeSection(writer, 1, () => {
282
- if (psd.palette) {
283
- for (let i = 0; i < 256; i++) writeUint8(writer, psd.palette[i]?.r || 0);
284
- for (let i = 0; i < 256; i++) writeUint8(writer, psd.palette[i]?.g || 0);
285
- for (let i = 0; i < 256; i++) writeUint8(writer, psd.palette[i]?.b || 0);
336
+ if (colorMode === ColorMode.Indexed) {
337
+ const palette = psd.palette!;
338
+ for (let i = 0; i < 256; i++) writeUint8(writer, palette[i]?.r ?? 0);
339
+ for (let i = 0; i < 256; i++) writeUint8(writer, palette[i]?.g ?? 0);
340
+ for (let i = 0; i < 256; i++) writeUint8(writer, palette[i]?.b ?? 0);
341
+ } else if (bitsPerChannel === 32) {
342
+ writeBytes(writer, new Uint8Array(hdrToningData));
286
343
  }
287
-
288
- // TODO: other data?
289
344
  });
290
345
 
291
346
  // image resources
@@ -298,30 +353,42 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
298
353
  const has = handler.has(imageResources);
299
354
  const count = has === false ? 0 : (has === true ? 1 : has);
300
355
  for (let i = 0; i < count; i++) {
356
+ const id = handler.keyCount > 1 ? handler.key + i : handler.key;
357
+ const name = handler.name ? handler.name(imageResources, i) : '';
301
358
  writeSignature(writer, '8BIM');
302
- writeUint16(writer, handler.key);
303
- writePascalString(writer, '', 2);
304
- writeSection(writer, 2, () => handler.write(writer, imageResources, i));
359
+ writeUint16(writer, id);
360
+ writePascalString(writer, name, 2);
361
+ writeSection(writer, 2, () => handler.write(writer, imageResources, i, psd));
305
362
  }
306
363
  }
307
364
  });
308
365
 
309
366
  // layer and mask info
310
367
  writeSection(writer, 2, () => {
311
- writeLayerInfo(writer, layers, psd, globalAlpha, opt);
368
+ if (layersInGlobalInfo || !layersAllowed) {
369
+ writeSection(writer, 4, () => { }, true, opt.psb); // empty layer info
370
+ } else {
371
+ writeLayerInfo(writer, layers, psd, globalAlpha, opt);
372
+ }
373
+
312
374
  writeGlobalLayerMaskInfo(writer, psd.globalLayerMaskInfo);
375
+
376
+ if (layersInGlobalInfo) {
377
+ if (globalAlpha) writeGlobalInfoSection(writer, bitsPerChannel === 16 ? 'Mt16' : 'Mt32', !!opt.psb, () => { });
378
+ writeGlobalInfoSection(writer, bitsPerChannel === 16 ? 'Lr16' : 'Lr32', !!opt.psb, () => {
379
+ writeLayerInfoData(writer, layers, psd, globalAlpha, opt);
380
+ });
381
+ }
382
+
313
383
  writeAdditionalLayerInfo(writer, psd, psd, opt);
314
384
  }, undefined, !!opt.psb);
315
385
 
316
386
  writer.tempBuffer = undefined;
317
387
 
318
388
  // image data
319
- writeUint16(writer, Compression.RleCompressed); // Photoshop doesn't support zip compression of composite image data
389
+ if (colorMode === ColorMode.RGB && bitsPerChannel === 8 && (!imageData || is8bit(imageData))) {
390
+ writeUint16(writer, Compression.RleCompressed); // Photoshop doesn't support zip compression of composite image data
320
391
 
321
- if (RAW_IMAGE_DATA && (psd as any).imageDataRaw) {
322
- console.log('writing raw image data');
323
- writeBytes(writer, (psd as any).imageDataRaw);
324
- } else {
325
392
  const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
326
393
  const data = getCompositeData(imageData, psd.width, psd.height, globalAlpha);
327
394
 
@@ -330,9 +397,187 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
330
397
  ensureSize(writer, writer.offset + maxSize);
331
398
  const buffer = new Uint8Array(writer.buffer, writer.offset, maxSize);
332
399
  writer.offset += writeDataRLE(buffer, data, channels, !!options.psb).length;
400
+ } else {
401
+ writeCompositeImageData(writer, psd, imageData, colorMode, bitsPerChannel, globalAlpha, !!options.psb);
333
402
  }
334
403
  }
335
404
 
405
+ function writeGlobalInfoSection(writer: PsdWriter, key: string, psb: boolean, func: () => void) {
406
+ writeSignature(writer, psb ? '8B64' : '8BIM');
407
+ writeSignature(writer, key);
408
+ writeSection(writer, 4, func, false, psb);
409
+ }
410
+
411
+ function is8bit(data: PixelData) {
412
+ return (data.data as any).BYTES_PER_ELEMENT === 1;
413
+ }
414
+
415
+ // converts image data (8, 16 or 32 bit RGBA data) to channels of the document color mode and bit depth
416
+ function getPlanarChannels(imageData: PixelData, colorMode: ColorMode, bitsPerChannel: number, matte: boolean) {
417
+ const { data, width, height } = imageData;
418
+ const size = width * height;
419
+ const srcFloat = data instanceof Float32Array;
420
+ const srcMax = srcFloat ? 1 : (data instanceof Uint16Array ? 0xffff : 0xff);
421
+ const dstFloat = bitsPerChannel === 32;
422
+ const dstMax = dstFloat ? 1 : (bitsPerChannel === 16 ? 0xffff : 0xff);
423
+ const colorCount = colorChannelsCount(colorMode);
424
+ const create = () => dstFloat ? new Float32Array(size) : (bitsPerChannel === 16 ? new Uint16Array(size) : new Uint8Array(size));
425
+ const colors: PlanarData[] = [];
426
+ for (let i = 0; i < colorCount; i++) colors.push(create());
427
+ const alpha = create();
428
+
429
+ // color values are gamma corrected when converting between float (linear) and integer values (same as imageDataToCanvas)
430
+ const toNormalized = (v: number) => srcFloat ? Math.pow(clamp(v, 0, 1), 1 / 2.2) : v / srcMax;
431
+ const fromNormalized = (v: number) => dstFloat ? Math.pow(v, 2.2) : Math.round(clamp(v, 0, 1) * dstMax);
432
+ const sameType = srcFloat === dstFloat && srcMax === dstMax;
433
+ const convertColor = (v: number) => sameType ? (dstFloat ? v : Math.round(v)) : fromNormalized(toNormalized(v));
434
+ const convertAlpha = (v: number) => sameType ? v : (dstFloat ? v / srcMax : Math.round(clamp(v / srcMax, 0, 1) * dstMax));
435
+ const cmyk = [0, 0, 0, 0];
436
+
437
+ for (let i = 0, p = 0; i < size; i++, p += 4) {
438
+ const a = data[p + 3];
439
+ const alphaValue = convertAlpha(a);
440
+ alpha[i] = alphaValue;
441
+ // composite image colors are blended with white (or no ink) for documents with transparency
442
+ const blend = matte && a !== srcMax && a > 0 ? clamp(a / srcMax, 0, 1) : 1;
443
+
444
+ if (colorMode === ColorMode.CMYK) {
445
+ rgbToCmykValues(toNormalized(data[p]), toNormalized(data[p + 1]), toNormalized(data[p + 2]), cmyk);
446
+
447
+ for (let j = 0; j < 4; j++) {
448
+ colors[j][i] = Math.round((1 - clamp(cmyk[j] * blend, 0, 1)) * dstMax); // stored inverted (0 = 100% ink)
449
+ }
450
+ } else {
451
+ for (let j = 0; j < colorCount; j++) {
452
+ let value: number;
453
+
454
+ if (colorMode === ColorMode.RGB) {
455
+ value = convertColor(data[p + j]);
456
+ } else {
457
+ const r = data[p], g = data[p + 1], b = data[p + 2];
458
+ value = convertColor((r === g && g === b) ? r : (0.299 * r + 0.587 * g + 0.114 * b));
459
+ }
460
+
461
+ if (blend !== 1) value = dstFloat ? value * blend + 1 - blend : Math.round(value * blend + dstMax * (1 - blend));
462
+ colors[j][i] = value;
463
+ }
464
+ }
465
+ }
466
+
467
+ return { colors, alpha };
468
+ }
469
+
470
+ function encodeChannel(values: PlanarData, width: number, height: number, options: WriteOptions) {
471
+ if (!(values instanceof Uint8Array)) {
472
+ return { data: writeDataZipWithPrediction(values, width, height), compression: Compression.ZipWithPrediction };
473
+ } else if (options.compress) {
474
+ return { data: deflateSync(values), compression: Compression.ZipWithoutPrediction };
475
+ } else {
476
+ return { data: writeDataRLEBytes(values, width, height, !!options.psb), compression: Compression.RleCompressed };
477
+ }
478
+ }
479
+
480
+ function writeCompositeImageData(
481
+ writer: PsdWriter, psd: Psd, imageData: PixelData | undefined, colorMode: ColorMode, bitsPerChannel: number, globalAlpha: boolean, psb: boolean
482
+ ) {
483
+ const { width, height } = psd;
484
+ const data = imageData ?? { data: new Uint8Array(width * height * 4), width, height };
485
+ const parts: Uint8Array[] = [];
486
+
487
+ if (colorMode === ColorMode.Bitmap || colorMode === ColorMode.Indexed) {
488
+ const values = colorMode === ColorMode.Bitmap ?
489
+ getBitmapData(data) :
490
+ getIndexedData(data, psd.palette!, psd.imageResources?.transparencyIndex);
491
+ const rowBytes = colorMode === ColorMode.Bitmap ? Math.ceil(width / 8) : width;
492
+
493
+ if (colorMode === ColorMode.Bitmap) {
494
+ writeUint16(writer, Compression.RawData);
495
+ parts.push(values);
496
+ } else {
497
+ writeUint16(writer, Compression.RleCompressed);
498
+ parts.push(writeDataRLEBytes(values, rowBytes, height, psb));
499
+ }
500
+ } else {
501
+ const { colors, alpha } = getPlanarChannels(data, colorMode, bitsPerChannel, globalAlpha);
502
+ const channels = globalAlpha ? [...colors, alpha] : colors;
503
+
504
+ if (bitsPerChannel === 8) {
505
+ // row byte counts for all channels are stored before data of all channels
506
+ const encoded = channels.map(c => writeDataRLEBytes(c as Uint8Array, width, height, psb));
507
+ const countsSize = height * (psb ? 4 : 2);
508
+ writeUint16(writer, Compression.RleCompressed);
509
+ for (const e of encoded) parts.push(e.subarray(0, countsSize));
510
+ for (const e of encoded) parts.push(e.subarray(countsSize));
511
+ } else {
512
+ writeUint16(writer, Compression.RawData); // Photoshop writes 16 and 32 bit composite image without compression
513
+ for (const c of channels) parts.push(planarToBytes(c));
514
+ }
515
+ }
516
+
517
+ for (const part of parts) writeBytes(writer, part);
518
+ }
519
+
520
+ function getBitmapData({ data, width, height }: PixelData) {
521
+ const rowBytes = Math.ceil(width / 8);
522
+ const result = new Uint8Array(rowBytes * height);
523
+ const max = data instanceof Float32Array ? 1 : (data instanceof Uint16Array ? 0xffff : 0xff);
524
+
525
+ for (let y = 0, p = 0; y < height; y++) {
526
+ for (let x = 0; x < width; x++, p += 4) {
527
+ const a = data[p + 3] / max;
528
+ const value = ((0.299 * data[p] + 0.587 * data[p + 1] + 0.114 * data[p + 2]) / max) * a + (1 - a); // blend with white
529
+ if (value < 0.5) result[y * rowBytes + (x >> 3)] |= 0x80 >> (x & 7); // 1 = black
530
+ }
531
+ }
532
+
533
+ return result;
534
+ }
535
+
536
+ function getIndexedData({ data, width, height }: PixelData, palette: { r: number; g: number; b: number; }[], transparencyIndex: number | undefined) {
537
+ const size = width * height;
538
+ const result = new Uint8Array(size);
539
+ const shift = data instanceof Uint16Array ? 8 : 0;
540
+ const cache = new Map<number, number>();
541
+
542
+ for (let i = 0; i < palette.length && i < 256; i++) {
543
+ const key = (palette[i].r << 16) | (palette[i].g << 8) | palette[i].b;
544
+ if (i !== transparencyIndex && !cache.has(key)) cache.set(key, i);
545
+ }
546
+
547
+ for (let i = 0, p = 0; i < size; i++, p += 4) {
548
+ const read = (v: number) => data instanceof Float32Array ? Math.round(Math.pow(clamp(v, 0, 1), 1 / 2.2) * 255) : (v >>> shift);
549
+ const a = data instanceof Float32Array ? data[p + 3] * 255 : (data[p + 3] >>> shift);
550
+
551
+ if (transparencyIndex !== undefined && transparencyIndex >= 0 && a < 128) {
552
+ result[i] = transparencyIndex;
553
+ continue;
554
+ }
555
+
556
+ const r = read(data[p]), g = read(data[p + 1]), b = read(data[p + 2]);
557
+ const key = (r << 16) | (g << 8) | b;
558
+ let index = cache.get(key);
559
+
560
+ if (index === undefined) {
561
+ // use closest color from the palette
562
+ let best = Infinity;
563
+ index = 0;
564
+
565
+ for (let j = 0; j < palette.length && j < 256; j++) {
566
+ if (j === transparencyIndex) continue;
567
+ const dr = palette[j].r - r, dg = palette[j].g - g, db = palette[j].b - b;
568
+ const distance = dr * dr + dg * dg + db * db;
569
+ if (distance < best) { best = distance; index = j; }
570
+ }
571
+
572
+ cache.set(key, index);
573
+ }
574
+
575
+ result[i] = index;
576
+ }
577
+
578
+ return result;
579
+ }
580
+
336
581
  function getCompositeData(imageData: PixelData | undefined, width: number, height: number, globalAlpha: boolean): PixelData {
337
582
  if (!imageData) return { data: new Uint8Array(width * height * 4), width, height };
338
583
 
@@ -359,67 +604,76 @@ function getCompositeData(imageData: PixelData | undefined, width: number, heigh
359
604
  }
360
605
 
361
606
  function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlpha: boolean, options: ExtendedWriteOptions) {
362
- writeSection(writer, 4, () => {
363
- writeInt16(writer, globalAlpha ? -layers.length : layers.length);
607
+ writeSection(writer, 4, () => writeLayerInfoData(writer, layers, psd, globalAlpha, options), true, options.psb);
608
+ }
364
609
 
365
- const layersData = layers.map((l, i) => getChannels(writer.tempBuffer!, l, i === 0, options));
610
+ function writeLayerInfoData(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlpha: boolean, options: ExtendedWriteOptions) {
611
+ writeInt16(writer, globalAlpha ? -layers.length : layers.length);
366
612
 
367
- // layer records
368
- for (const layerData of layersData) {
369
- const { layer, top, left, bottom, right, channels } = layerData;
613
+ const layersData = layers.map((l, i) => getChannels(writer, l, i === 0, options));
370
614
 
371
- writeInt32(writer, top);
372
- writeInt32(writer, left);
373
- writeInt32(writer, bottom);
374
- writeInt32(writer, right);
375
- writeUint16(writer, channels.length);
615
+ // layer records
616
+ for (const layerData of layersData) {
617
+ const { layer, top, left, bottom, right, channels } = layerData;
376
618
 
377
- for (const c of channels) {
378
- writeInt16(writer, c.id);
379
- if (options.psb) {
380
- writeUint64(writer, c.length);
381
- } else {
382
- writeUint32(writer, c.length);
383
- }
619
+ writeInt32(writer, top);
620
+ writeInt32(writer, left);
621
+ writeInt32(writer, bottom);
622
+ writeInt32(writer, right);
623
+ writeUint16(writer, channels.length);
624
+
625
+ for (const c of channels) {
626
+ writeInt16(writer, c.id);
627
+ if (options.psb) {
628
+ writeUint64(writer, c.length);
629
+ } else {
630
+ writeUint32(writer, c.length);
384
631
  }
632
+ }
385
633
 
386
- writeSignature(writer, '8BIM');
387
- writeSignature(writer, fromBlendMode[layer.blendMode!] || 'norm');
388
- writeUint8(writer, Math.round(clamp(layer.opacity ?? 1, 0, 1) * 255));
389
- writeUint8(writer, layer.clipping ? 1 : 0);
634
+ writeSignature(writer, '8BIM');
635
+ writeSignature(writer, fromBlendMode[layer.blendMode!] || 'norm');
636
+ writeUint8(writer, Math.round(clamp(layer.opacity ?? 1, 0, 1) * 255));
637
+ writeUint8(writer, layer.clipping ? 1 : 0);
390
638
 
391
- let flags = 0x08; // 1 for Photoshop 5.0 and later, tells if bit 4 has useful information
392
- if (layer.transparencyProtected) flags |= 0x01;
393
- if (layer.hidden) flags |= 0x02;
394
- if (layer.vectorMask || (layer.sectionDivider && layer.sectionDivider.type !== SectionDividerType.Other) || layer.adjustment) {
395
- flags |= 0x10; // pixel data irrelevant to appearance of document
396
- }
397
- if (layer.effectsOpen) flags |= 0x20;
398
-
399
- writeUint8(writer, flags);
400
- writeUint8(writer, 0); // filler
401
- writeSection(writer, 1, () => {
402
- writeLayerMaskData(writer, layer, layerData);
403
- writeLayerBlendingRanges(writer, layer);
404
- writePascalString(writer, (layer.name || '').substring(0, 255), 4);
405
- writeAdditionalLayerInfo(writer, layer, psd, options);
406
- });
639
+ let flags = 0x08; // 1 for Photoshop 5.0 and later, tells if bit 4 has useful information
640
+ if (layer.transparencyProtected) flags |= 0x01;
641
+ if (layer.hidden) flags |= 0x02;
642
+ if (layer.vectorMask || (layer.sectionDivider && layer.sectionDivider.type !== SectionDividerType.Other) || layer.adjustment) {
643
+ flags |= 0x10; // pixel data irrelevant to appearance of document
407
644
  }
645
+ if (layer.effectsOpen) flags |= 0x20;
408
646
 
409
- // layer channel image data
410
- for (const layerData of layersData) {
411
- for (const channel of layerData.channels) {
412
- writeUint16(writer, channel.compression);
647
+ writeUint8(writer, flags);
648
+ writeUint8(writer, 0); // filler
649
+ writeSection(writer, 1, () => {
650
+ writeLayerMaskData(writer, layer, layerData);
651
+ writeLayerBlendingRanges(writer, layer);
652
+ writePascalString(writer, (layer.name || '').substring(0, 255), 4);
653
+ writeAdditionalLayerInfo(writer, layer, psd, options);
654
+ });
655
+ }
413
656
 
414
- if (channel.data) {
415
- writeBytes(writer, channel.data);
416
- }
657
+ // layer channel image data
658
+ for (const layerData of layersData) {
659
+ for (const channel of layerData.channels) {
660
+ writeUint16(writer, channel.compression);
661
+
662
+ if (channel.data) {
663
+ writeBytes(writer, channel.data);
417
664
  }
418
665
  }
419
- }, true, options.psb);
666
+ }
420
667
  }
421
668
 
422
- function writeLayerMaskData(writer: PsdWriter, { mask, realMask }: Layer, layerData: LayerChannelData) {
669
+ // real mask is only stored separately if layer has both vector and pixel masks, otherwise it's stored as the main mask
670
+ function getMasks(layer: Layer) {
671
+ return layer.mask ? { mask: layer.mask, realMask: layer.realMask } : { mask: layer.realMask, realMask: undefined };
672
+ }
673
+
674
+ function writeLayerMaskData(writer: PsdWriter, layer: Layer, layerData: LayerChannelData) {
675
+ const { mask, realMask } = getMasks(layer);
676
+
423
677
  writeSection(writer, 4, () => {
424
678
  if (!mask && !realMask) return;
425
679
 
@@ -512,13 +766,13 @@ function writeAdditionalLayerInfo(writer: PsdWriter, target: LayerAdditionalInfo
512
766
  let key = handler.key;
513
767
 
514
768
  if (key === 'Txt2' && options.invalidateTextLayers) continue;
515
- if (key === 'vmsk' && options.psb) key = 'vsms';
769
+ if (key === 'vmsk' && infoHandlersMap['vscg'].has(target)) key = 'vsms'; // Photoshop uses 'vsms' for layers with vector stroke
516
770
 
517
771
  if (handler.has(target)) {
518
772
  const large = options.psb && largeAdditionalInfoKeys.indexOf(key) !== -1;
519
773
  const writeTotalLength = key !== 'Txt2' && key !== 'cinf' && key !== 'extn' && key !== 'CAI ' && key !== 'OCIO';
520
774
  const fourBytes = key === 'levl' || key === 'Txt2' || key === 'luni' || key === 'vmsk' || key === 'artb' || key === 'artd' ||
521
- key === 'vogk' || key === 'SoLd' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
775
+ key === 'vogk' || key === 'SoLd' || key === 'SoLE' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
522
776
  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';
523
777
 
524
778
  writeSignature(writer, large ? '8B64' : '8BIM');
@@ -624,7 +878,7 @@ function createThumbnail(psd: Psd) {
624
878
  return canvas;
625
879
  }
626
880
 
627
- function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, layer: Layer, mask: LayerMaskData, options: WriteOptions, realMask: boolean) {
881
+ function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, mask: LayerMaskData, options: WriteOptions, realMask: boolean) {
628
882
  let top = (mask.top as any) | 0;
629
883
  let left = (mask.left as any) | 0;
630
884
  let right = (mask.right as any) | 0;
@@ -645,13 +899,14 @@ function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, la
645
899
 
646
900
  let buffer: Uint8Array;
647
901
  let compression: Compression;
902
+ const bitsPerChannel = (options as ExtendedWriteOptions).bitsPerChannel ?? 8;
648
903
 
649
- if (RAW_IMAGE_DATA && (layer as any)[realMask ? 'realMaskDataRaw' : 'maskDataRaw']) {
650
- buffer = (layer as any)[realMask ? 'realMaskDataRaw' : 'maskDataRaw'];
651
- compression = (layer as any)[realMask ? 'realMaskDataRawCompression' : 'maskDataRawCompression'];
652
- } else if (!imageData) {
904
+ if (!imageData) {
653
905
  buffer = new Uint8Array(0);
654
906
  compression = Compression.RleCompressed;
907
+ } else if (bitsPerChannel !== 8 || !is8bit(imageData)) {
908
+ const { colors } = getPlanarChannels(imageData, ColorMode.RGB, bitsPerChannel, false);
909
+ ({ data: buffer, compression } = encodeChannel(colors[0], width, height, options));
655
910
  } else if (options.compress) {
656
911
  buffer = writeDataZipWithoutPrediction(imageData, [0]);
657
912
  compression = Compression.ZipWithoutPrediction;
@@ -674,20 +929,46 @@ function bounds(obj: { top?: number; left?: number; bottom?: number; right?: num
674
929
  } : undefined;
675
930
  }
676
931
 
677
- function getChannels(tempBuffer: Uint8Array, layer: Layer, background: boolean, options: WriteOptions): LayerChannelData {
932
+ function getChannels(writer: PsdWriter, layer: Layer, background: boolean, options: WriteOptions): LayerChannelData {
933
+ let source = layer;
934
+
678
935
  if (layer.rawData) {
679
- return {
680
- layer,
681
- channels: layer.rawData.channels.map(c => ({ ...c, length: 2 + (c.data?.byteLength ?? 0) })),
682
- ...bounds(layer)!,
683
- mask: bounds(layer.mask),
684
- realMask: bounds(layer.realMask),
936
+ const { colorMode, bitsPerChannel, large } = layer.rawData;
937
+ const opt = options as ExtendedWriteOptions;
938
+
939
+ if (large === !!options.psb && colorMode === (opt.colorMode ?? ColorMode.RGB) && bitsPerChannel === (opt.bitsPerChannel ?? 8)) {
940
+ // same mask mapping as in writeLayerMaskData
941
+ const { mask, realMask } = getMasks(layer);
942
+ return {
943
+ layer,
944
+ channels: layer.rawData.channels.map(c => ({ ...c, length: 2 + (c.data?.byteLength ?? 0) })),
945
+ ...bounds(layer)!,
946
+ mask: bounds(mask),
947
+ realMask: bounds(realMask),
948
+ };
949
+ }
950
+
951
+ // raw data is in different format than the one we're writing, decode it so it can be re-encoded
952
+ source = {
953
+ ...layer,
954
+ rawData: undefined,
955
+ canvas: undefined,
956
+ imageData: getLayerImageData(layer),
957
+ mask: layer.mask && { ...layer.mask, canvas: undefined, imageData: getLayerMaskImageData(layer) },
958
+ realMask: layer.realMask && { ...layer.realMask, canvas: undefined, imageData: getLayerRealMaskImageData(layer) },
685
959
  };
960
+
961
+ // temp buffer is sized only for layers with image data, grow it and keep it for following layers
962
+ const size = getLargestLayerSize([{ ...source, children: undefined }], !!options.psb);
963
+ if (writer.tempBuffer!.byteLength < size) writer.tempBuffer = new Uint8Array(size);
686
964
  }
687
965
 
688
- const layerData = getLayerChannels(tempBuffer, layer, background, options);
689
- if (layer.mask) getMaskChannels(tempBuffer, layerData, layer, layer.mask, options, false);
690
- if (layer.realMask) getMaskChannels(tempBuffer, layerData, layer, layer.realMask, options, true);
966
+ const tempBuffer = writer.tempBuffer!;
967
+ const { mask, realMask } = getMasks(source);
968
+ const layerData = getLayerChannels(tempBuffer, source, background, options);
969
+ if (mask) getMaskChannels(tempBuffer, layerData, mask, options, false);
970
+ if (realMask) getMaskChannels(tempBuffer, layerData, realMask, options, true);
971
+ layerData.layer = layer;
691
972
  return layerData;
692
973
  }
693
974
 
@@ -699,11 +980,8 @@ function getLayerDimentions({ canvas, imageData }: Layer): { width: number; heig
699
980
  }
700
981
 
701
982
  function cropImageData(data: PixelData, left: number, top: number, width: number, height: number) {
702
- if (data.data instanceof Uint32Array || data.data instanceof Uint16Array) {
703
- throw new Error('imageData has incorrect bit depth');
704
- }
705
-
706
- const croppedData = createImageData(width, height);
983
+ const croppedData: PixelData = is8bit(data) ? createImageData(width, height) :
984
+ { width, height, data: new (data.data.constructor as any)(width * height * 4) };
707
985
  const srcData = data.data;
708
986
  const dstData = croppedData.data;
709
987
 
@@ -735,8 +1013,9 @@ function getLayerChannels(tempBuffer: Uint8Array, layer: Layer, background: bool
735
1013
  let { width, height } = getLayerDimentions(layer);
736
1014
 
737
1015
  if (!(layer.canvas || layer.imageData) || !width || !height) {
738
- right = left;
739
- bottom = top;
1016
+ // layer without image data is empty, keep inverted (negative size) bounds from some files as they are
1017
+ if (layer.right === undefined || right > left) right = left;
1018
+ if (layer.bottom === undefined || bottom > top) bottom = top;
740
1019
  return { layer, top, left, right, bottom, channels };
741
1020
  }
742
1021
 
@@ -768,20 +1047,32 @@ function getLayerChannels(tempBuffer: Uint8Array, layer: Layer, background: bool
768
1047
  ChannelID.Color2,
769
1048
  ];
770
1049
 
771
- if (!background || options.noBackground || layer.mask || hasAlpha(imageData) || (RAW_IMAGE_DATA && (layer as any).imageDataRaw?.['-1'])) {
1050
+ if (!background || options.noBackground || layer.mask || hasAlpha(imageData)) {
772
1051
  channelIds.unshift(ChannelID.Transparency);
773
1052
  }
774
1053
 
1054
+ const colorMode = (options as ExtendedWriteOptions).colorMode ?? ColorMode.RGB;
1055
+ const bitsPerChannel = (options as ExtendedWriteOptions).bitsPerChannel ?? 8;
1056
+
1057
+ if (colorMode !== ColorMode.RGB || bitsPerChannel !== 8 || !is8bit(imageData)) {
1058
+ const { colors, alpha } = getPlanarChannels(imageData, colorMode, bitsPerChannel, false);
1059
+ const ids = channelIds[0] === ChannelID.Transparency ? [ChannelID.Transparency] : [];
1060
+ for (let i = 0; i < colors.length; i++) ids.push(i as ChannelID);
1061
+
1062
+ channels = ids.map(id => {
1063
+ const { data, compression } = encodeChannel(id === ChannelID.Transparency ? alpha : colors[id], width, height, options);
1064
+ return { id, compression, data, length: 2 + data.length };
1065
+ });
1066
+
1067
+ return { layer, top, left, right, bottom, channels };
1068
+ }
1069
+
775
1070
  channels = channelIds.map(id => {
776
- const offset = offsetForChannel(id, false); // TODO: psd.colorMode === ColorMode.CMYK);
1071
+ const offset = offsetForChannel(id, false); // image data is always RGBA
777
1072
  let data: Uint8Array;
778
1073
  let compression: Compression;
779
1074
 
780
- if (RAW_IMAGE_DATA && (layer as any).imageDataRaw) {
781
- // console.log('written raw layer image data');
782
- data = (layer as any).imageDataRaw[id];
783
- compression = (layer as any).imageDataRawCompression[id];
784
- } else if (options.compress) {
1075
+ if (options.compress) {
785
1076
  data = writeDataZipWithoutPrediction(imageData, [offset]);
786
1077
  compression = Compression.ZipWithoutPrediction;
787
1078
  } else {
@@ -868,11 +1159,12 @@ export function writeColor(writer: PsdWriter, color: Color | undefined) {
868
1159
  writeUint16(writer, Math.round(color.b * 0xffff));
869
1160
  writeUint16(writer, 0);
870
1161
  } else if ('c' in color) {
1162
+ // 0 = 100% ink
871
1163
  writeUint16(writer, ColorSpace.CMYK);
872
- writeUint16(writer, Math.round(color.c * 257));
873
- writeUint16(writer, Math.round(color.m * 257));
874
- writeUint16(writer, Math.round(color.y * 257));
875
- writeUint16(writer, Math.round(color.k * 257));
1164
+ writeUint16(writer, 0xffff - Math.round(clamp(color.c, 0, 255) * 257));
1165
+ writeUint16(writer, 0xffff - Math.round(clamp(color.m, 0, 255) * 257));
1166
+ writeUint16(writer, 0xffff - Math.round(clamp(color.y, 0, 255) * 257));
1167
+ writeUint16(writer, 0xffff - Math.round(clamp(color.k, 0, 255) * 257));
876
1168
  } else {
877
1169
  writeUint16(writer, ColorSpace.Grayscale);
878
1170
  writeUint16(writer, Math.round(color.k * 10000 / 255));