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