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/abr.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BlnM, DescriptorUnitsValue, parseAngle, parsePercent, parseUnitsToNumber, readVersionAndDescriptor } from './descriptor';
|
|
2
2
|
import { BlendMode, PatternInfo } from './psd';
|
|
3
|
-
import { checkSignature, createReader, readBytes, readDataRLE, readInt16, readInt32, readPascalString,
|
|
3
|
+
import { checkSignature, consumeMemory, createReader, PsdReader, readBytes, readDataRLE, readInt16, readInt32, readPascalString, readPatternSafe, readSignature, readUint16, readUint32, readUint8, readUnicodeString, skipBytes } from './psdReader';
|
|
4
4
|
|
|
5
5
|
export interface Abr {
|
|
6
6
|
brushes: Brush[];
|
|
@@ -99,6 +99,7 @@ interface DynamicBrushShape {
|
|
|
99
99
|
|
|
100
100
|
export interface Brush {
|
|
101
101
|
name: string;
|
|
102
|
+
group?: string[]; // names of brush groups (folders in brushes panel) the brush is in, from outermost to innermost
|
|
102
103
|
shape: BrushShape;
|
|
103
104
|
shapeDynamics?: {
|
|
104
105
|
sizeDynamics: BrushDynamics;
|
|
@@ -152,8 +153,8 @@ export interface Brush {
|
|
|
152
153
|
transfer?: {
|
|
153
154
|
flowDynamics: BrushDynamics;
|
|
154
155
|
opacityDynamics: BrushDynamics;
|
|
155
|
-
wetnessDynamics
|
|
156
|
-
mixDynamics
|
|
156
|
+
wetnessDynamics?: BrushDynamics; // missing in brushes from older Photoshop versions
|
|
157
|
+
mixDynamics?: BrushDynamics;
|
|
157
158
|
};
|
|
158
159
|
brushPose?: {
|
|
159
160
|
overrideAngle: boolean;
|
|
@@ -167,8 +168,8 @@ export interface Brush {
|
|
|
167
168
|
};
|
|
168
169
|
noise: boolean;
|
|
169
170
|
wetEdges: boolean;
|
|
170
|
-
|
|
171
|
-
//
|
|
171
|
+
buildUp: boolean;
|
|
172
|
+
// smoothing option is stored in toolOptions.smoothing
|
|
172
173
|
protectTexture?: boolean;
|
|
173
174
|
spacing: number;
|
|
174
175
|
brushGroup?: undefined; // ?
|
|
@@ -210,7 +211,10 @@ export interface Brush {
|
|
|
210
211
|
// internal
|
|
211
212
|
|
|
212
213
|
interface PhryDescriptor {
|
|
213
|
-
hierarchy
|
|
214
|
+
hierarchy?: ({
|
|
215
|
+
_classID: 'preset' | 'groupEnd';
|
|
216
|
+
} | {
|
|
217
|
+
_classID: 'Grup';
|
|
214
218
|
'Nm ': string;
|
|
215
219
|
zuid: string;
|
|
216
220
|
})[];
|
|
@@ -403,6 +407,9 @@ interface DescDescriptor {
|
|
|
403
407
|
}[];
|
|
404
408
|
}
|
|
405
409
|
|
|
410
|
+
// sanity limits for brush sample dimensions (Photoshop brushes are limited to 5000px)
|
|
411
|
+
const MAX_BRUSH_SIZE = 10000;
|
|
412
|
+
|
|
406
413
|
const toBrushType: { [key: string]: 'brush' | 'mixer brush' | 'smudge brush'; } = {
|
|
407
414
|
_: 'brush',
|
|
408
415
|
MixB: 'mixer brush',
|
|
@@ -513,262 +520,403 @@ function parseHeightmap(array: Uint8Array) {
|
|
|
513
520
|
// return result;
|
|
514
521
|
}
|
|
515
522
|
|
|
516
|
-
export
|
|
523
|
+
export interface ReadAbrOptions {
|
|
524
|
+
logMissingFeatures?: boolean;
|
|
525
|
+
/** Throw on unsupported or invalid parts of the file instead of skipping them */
|
|
526
|
+
throwForMissingFeatures?: boolean;
|
|
527
|
+
/** Memory limit for decoded data in bytes (2GB by default), set to `undefined` explicitly to disable */
|
|
528
|
+
totalMemoryLimit?: number;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function readAbr(buffer: ArrayBufferView, options: ReadAbrOptions = {}): Abr {
|
|
517
532
|
const reader = createReader(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
533
|
+
reader.logMissingFeatures = options.logMissingFeatures;
|
|
534
|
+
reader.throwForMissingFeatures = options.throwForMissingFeatures;
|
|
535
|
+
reader.totalMemoryLimit = 'totalMemoryLimit' in options ? options.totalMemoryLimit : 2 * 1024 * 1024 * 1024;
|
|
536
|
+
|
|
537
|
+
// skips invalid or unsupported part of the file
|
|
538
|
+
const skip = (e: unknown, what: string) => {
|
|
539
|
+
if (options.throwForMissingFeatures) throw e;
|
|
540
|
+
options.logMissingFeatures && reader.log(`Skipped ${what}: ${(e as Error).message}`);
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
if (reader.view.byteLength < 4) throw new Error('Invalid ABR file');
|
|
544
|
+
|
|
518
545
|
const version = readInt16(reader);
|
|
519
546
|
const samples: SampleInfo[] = [];
|
|
520
547
|
const brushes: Brush[] = [];
|
|
521
548
|
const patterns: PatternInfo[] = [];
|
|
549
|
+
const brushByPreset: (Brush | undefined)[] = []; // brush for each preset in 'desc' section (brushes that fail to parse are skipped)
|
|
550
|
+
let hierarchy: PhryDescriptor['hierarchy'];
|
|
522
551
|
|
|
523
552
|
if (version === 1 || version === 2) {
|
|
524
|
-
|
|
553
|
+
readLegacyBrushes(reader, version, samples, brushes, skip);
|
|
525
554
|
} else if (version === 6 || version === 7 || version === 9 || version === 10) {
|
|
526
555
|
const minorVersion = readInt16(reader);
|
|
527
556
|
if (minorVersion !== 1 && minorVersion !== 2) throw new Error('Unsupported ABR minor version');
|
|
528
557
|
|
|
529
|
-
|
|
558
|
+
// section header is 12 bytes, ignore any shorter trailing data
|
|
559
|
+
while (reader.offset + 12 <= reader.view.byteLength) {
|
|
530
560
|
checkSignature(reader, '8BIM');
|
|
531
561
|
const type = readSignature(reader) as 'samp' | 'desc' | 'patt' | 'phry';
|
|
532
|
-
|
|
533
|
-
const end = reader.offset + size;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const y = readInt32(reader);
|
|
549
|
-
const x = readInt32(reader);
|
|
550
|
-
const h = readInt32(reader) - y;
|
|
551
|
-
const w = readInt32(reader) - x;
|
|
552
|
-
if (w <= 0 || h <= 0) throw new Error('Invalid bounds');
|
|
553
|
-
|
|
554
|
-
const bithDepth = readInt16(reader);
|
|
555
|
-
const compression = readUint8(reader); // 0 - raw, 1 - RLE
|
|
556
|
-
const alpha = new Uint8Array(w * h);
|
|
557
|
-
|
|
558
|
-
if (bithDepth === 8) {
|
|
559
|
-
if (compression === 0) {
|
|
560
|
-
alpha.set(readBytes(reader, alpha.byteLength));
|
|
561
|
-
} else if (compression === 1) {
|
|
562
|
-
readDataRLE(reader, { width: w, height: h, data: alpha }, w, h, bithDepth, 1, [0], false);
|
|
563
|
-
} else {
|
|
564
|
-
throw new Error('Invalid compression');
|
|
562
|
+
const size = readUint32(reader);
|
|
563
|
+
const end = Math.min(reader.offset + size, reader.view.byteLength);
|
|
564
|
+
|
|
565
|
+
try {
|
|
566
|
+
switch (type) {
|
|
567
|
+
case 'samp': {
|
|
568
|
+
while (reader.offset + 4 <= end) {
|
|
569
|
+
let brushLength = readUint32(reader);
|
|
570
|
+
while (brushLength & 0b11) brushLength++; // pad to 4 byte alignment
|
|
571
|
+
const brushEnd = Math.min(reader.offset + brushLength, end);
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
const sample = readSample(reader, brushEnd, minorVersion);
|
|
575
|
+
samples.push(sample);
|
|
576
|
+
} catch (e) {
|
|
577
|
+
skip(e, 'brush sample');
|
|
565
578
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
579
|
+
|
|
580
|
+
reader.offset = brushEnd;
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
case 'desc': {
|
|
585
|
+
const desc: DescDescriptor = readVersionAndDescriptor(reader, true);
|
|
586
|
+
|
|
587
|
+
for (const [index, brush] of (desc.Brsh || []).entries()) {
|
|
588
|
+
try {
|
|
589
|
+
const parsed = parseBrush(brush);
|
|
590
|
+
brushes.push(parsed);
|
|
591
|
+
brushByPreset[index] = parsed;
|
|
592
|
+
} catch (e) {
|
|
593
|
+
skip(e, `brush "${brush['Nm ']}"`);
|
|
575
594
|
}
|
|
576
|
-
} else {
|
|
577
|
-
throw new Error('Invalid depth');
|
|
578
595
|
}
|
|
579
|
-
|
|
580
|
-
samples.push({ id, bounds: { x, y, w, h }, alpha });
|
|
581
|
-
reader.offset = brushEnd;
|
|
596
|
+
break;
|
|
582
597
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
// console.log(require('util').inspect(desc, false, 99, true));
|
|
588
|
-
// require('fs').writeFileSync('test.log', require('util').inspect(desc, false, 99, false), 'utf8');
|
|
589
|
-
|
|
590
|
-
for (const brush of desc.Brsh) {
|
|
591
|
-
const b: Brush = {
|
|
592
|
-
name: brush['Nm '],
|
|
593
|
-
shape: parseBrushShape(brush.Brsh),
|
|
594
|
-
spacing: parsePercent(brush.Spcn),
|
|
595
|
-
// TODO: brushGroup ???
|
|
596
|
-
wetEdges: brush.Wtdg,
|
|
597
|
-
noise: brush.Nose,
|
|
598
|
-
// TODO: TxtC ??? smoothing / build-up ?
|
|
599
|
-
// TODO: 'Rpt ' ???
|
|
600
|
-
useBrushSize: brush.useBrushSize, // ???
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
if (brush.interpretation != null) b.interpretation = brush.interpretation;
|
|
604
|
-
if (brush.protectTexture != null) b.protectTexture = brush.protectTexture;
|
|
605
|
-
|
|
606
|
-
if (brush.useTipDynamics) {
|
|
607
|
-
b.shapeDynamics = {
|
|
608
|
-
tiltScale: parsePercent(brush.tiltScale),
|
|
609
|
-
sizeDynamics: parseDynamics(brush.szVr),
|
|
610
|
-
angleDynamics: parseDynamics(brush.angleDynamics),
|
|
611
|
-
roundnessDynamics: parseDynamics(brush.roundnessDynamics),
|
|
612
|
-
flipX: brush.flipX,
|
|
613
|
-
flipY: brush.flipY,
|
|
614
|
-
brushProjection: brush.brushProjection,
|
|
615
|
-
minimumDiameter: parsePercent(brush.minimumDiameter),
|
|
616
|
-
minimumRoundness: parsePercent(brush.minimumRoundness),
|
|
617
|
-
};
|
|
598
|
+
case 'patt': {
|
|
599
|
+
while (reader.offset + 4 <= end) {
|
|
600
|
+
const pattern = readPatternSafe(reader);
|
|
601
|
+
if (pattern) patterns.push(pattern);
|
|
618
602
|
}
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case 'phry': { // brush groups (folders in brushes panel)
|
|
606
|
+
// flat list of group starts, presets (in the same order as in 'desc' section) and group ends
|
|
607
|
+
const desc: PhryDescriptor = readVersionAndDescriptor(reader, true);
|
|
608
|
+
hierarchy = desc.hierarchy;
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
default:
|
|
612
|
+
// 'lPdc' (tool presets) and other unknown sections
|
|
613
|
+
options.logMissingFeatures && reader.log(`Unhandled brush section: ${type}`);
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
} catch (e) {
|
|
617
|
+
skip(e, `brush section ${type}`);
|
|
618
|
+
}
|
|
619
619
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
620
|
+
// skip any unread data in the section and align to 4 bytes
|
|
621
|
+
reader.offset = end;
|
|
622
|
+
while (reader.offset % 4) reader.offset++;
|
|
623
|
+
}
|
|
624
|
+
} else {
|
|
625
|
+
throw new Error(`Unsupported ABR version (${version})`);
|
|
626
|
+
}
|
|
628
627
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
628
|
+
if (hierarchy) {
|
|
629
|
+
const groups: string[] = [];
|
|
630
|
+
let presetIndex = 0;
|
|
631
|
+
|
|
632
|
+
for (const item of hierarchy) {
|
|
633
|
+
if (item._classID === 'Grup') {
|
|
634
|
+
groups.push(item['Nm ']);
|
|
635
|
+
} else if (item._classID === 'groupEnd') {
|
|
636
|
+
groups.pop();
|
|
637
|
+
} else if (item._classID === 'preset') {
|
|
638
|
+
const brush = brushByPreset[presetIndex];
|
|
639
|
+
if (brush && groups.length) brush.group = groups.slice();
|
|
640
|
+
presetIndex++;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
644
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
b.dualBrush = {
|
|
648
|
-
flip: db.Flip,
|
|
649
|
-
shape: parseBrushShape(db.Brsh),
|
|
650
|
-
blendMode: BlnM.decode(db.BlnM),
|
|
651
|
-
useScatter: db.useScatter,
|
|
652
|
-
spacing: parsePercent(db.Spcn),
|
|
653
|
-
count: db['Cnt '],
|
|
654
|
-
bothAxes: db.bothAxes,
|
|
655
|
-
countDynamics: parseDynamics(db.countDynamics),
|
|
656
|
-
scatterDynamics: parseDynamics(db.scatterDynamics),
|
|
657
|
-
};
|
|
658
|
-
}
|
|
645
|
+
return { samples, patterns, brushes };
|
|
646
|
+
}
|
|
659
647
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
foregroundBackground: parseDynamics(brush.clVr!),
|
|
663
|
-
hue: parsePercent(brush['H ']!),
|
|
664
|
-
saturation: parsePercent(brush.Strt!),
|
|
665
|
-
brightness: parsePercent(brush.Brgh!),
|
|
666
|
-
purity: parsePercent(brush.purity!),
|
|
667
|
-
perTip: brush.colorDynamicsPerTip!,
|
|
668
|
-
};
|
|
669
|
-
}
|
|
648
|
+
function readSample(reader: PsdReader, brushEnd: number, minorVersion: number): SampleInfo {
|
|
649
|
+
const id = readPascalString(reader, 1);
|
|
670
650
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
opacityDynamics: parseDynamics(brush.opVr!),
|
|
675
|
-
wetnessDynamics: parseDynamics(brush.wtVr!),
|
|
676
|
-
mixDynamics: parseDynamics(brush.mxVr!),
|
|
677
|
-
};
|
|
678
|
-
}
|
|
651
|
+
// v1 - Skip the Int16 bounds rectangle and the unknown Int16.
|
|
652
|
+
// v2 - Skip the unknown bytes.
|
|
653
|
+
skipBytes(reader, minorVersion === 1 ? 10 : 264);
|
|
679
654
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
overrideAngle: brush.overridePoseAngle!,
|
|
683
|
-
overrideTiltX: brush.overridePoseTiltX!,
|
|
684
|
-
overrideTiltY: brush.overridePoseTiltY!,
|
|
685
|
-
overridePressure: brush.overridePosePressure!,
|
|
686
|
-
pressure: parsePercent(brush.brushPosePressure!),
|
|
687
|
-
tiltX: brush.brushPoseTiltX!,
|
|
688
|
-
tiltY: brush.brushPoseTiltY!,
|
|
689
|
-
angle: brush.brushPoseAngle!,
|
|
690
|
-
};
|
|
691
|
-
}
|
|
655
|
+
return readSampleBitmap(reader, brushEnd, id);
|
|
656
|
+
}
|
|
692
657
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
smoothingValue: to.smoothingValue || 0,
|
|
704
|
-
smoothingRadiusMode: !!to.smoothingRadiusMode,
|
|
705
|
-
smoothingCatchup: !!to.smoothingCatchup,
|
|
706
|
-
smoothingCatchupAtEnd: !!to.smoothingCatchupAtEnd,
|
|
707
|
-
smoothingZoomCompensation: !!to.smoothingZoomCompensation,
|
|
708
|
-
pressureSmoothing: !!to.pressureSmoothing,
|
|
709
|
-
usePressureOverridesSize: !!to.usePressureOverridesSize,
|
|
710
|
-
usePressureOverridesOpacity: !!to.usePressureOverridesOpacity,
|
|
711
|
-
useLegacy: !!to.useLegacy,
|
|
712
|
-
};
|
|
713
|
-
|
|
714
|
-
if (to.prVr) b.toolOptions.flowDynamics = parseDynamics(to.prVr);
|
|
715
|
-
if (to.opVr) b.toolOptions.opacityDynamics = parseDynamics(to.opVr);
|
|
716
|
-
if (to.szVr) b.toolOptions.sizeDynamics = parseDynamics(to.szVr);
|
|
717
|
-
if ('wetness' in to) b.toolOptions.wetness = to.wetness;
|
|
718
|
-
if ('dryness' in to) b.toolOptions.dryness = to.dryness;
|
|
719
|
-
if ('mix' in to) b.toolOptions.mix = to.mix;
|
|
720
|
-
if ('autoFill' in to) b.toolOptions.autoFill = to.autoFill;
|
|
721
|
-
if ('autoClean' in to) b.toolOptions.autoClean = to.autoClean;
|
|
722
|
-
if ('loadSolidColorOnly' in to) b.toolOptions.loadSolidColorOnly = to.loadSolidColorOnly;
|
|
723
|
-
if ('sampleAllLayers' in to) b.toolOptions.sampleAllLayers = to.sampleAllLayers;
|
|
724
|
-
if ('SmdF' in to) b.toolOptions.smudgeFingerPainting = to.SmdF;
|
|
725
|
-
if ('SmdS' in to) b.toolOptions.smudgeSampleAllLayers = to.SmdS;
|
|
726
|
-
if ('Prs ' in to) b.toolOptions.strength = to['Prs '];
|
|
727
|
-
}
|
|
658
|
+
// bounds, bit depth, compression and image data of brush sample
|
|
659
|
+
function readSampleBitmap(reader: PsdReader, brushEnd: number, id: string): SampleInfo {
|
|
660
|
+
const y = readInt32(reader);
|
|
661
|
+
const x = readInt32(reader);
|
|
662
|
+
const h = readInt32(reader) - y;
|
|
663
|
+
const w = readInt32(reader) - x;
|
|
664
|
+
if (w <= 0 || h <= 0) throw new Error('Invalid bounds');
|
|
665
|
+
if (w > MAX_BRUSH_SIZE || h > MAX_BRUSH_SIZE) {
|
|
666
|
+
throw new Error(`Brush sample too large (${w}x${h})`);
|
|
667
|
+
}
|
|
728
668
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const desc: PhryDescriptor = readVersionAndDescriptor(reader);
|
|
743
|
-
// example:
|
|
744
|
-
// hierarchy: [
|
|
745
|
-
// {
|
|
746
|
-
// 'Nm ': 'PRE_EXPORT ',
|
|
747
|
-
// zuid: '965209f2-6f35-9a40-aa43-485684382172'
|
|
748
|
-
// },
|
|
749
|
-
// {},
|
|
750
|
-
// ...
|
|
751
|
-
// ]
|
|
752
|
-
if (options.logMissingFeatures) {
|
|
753
|
-
if (desc.hierarchy?.length) {
|
|
754
|
-
// console.log('unhandled phry section', desc);
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
break;
|
|
758
|
-
}
|
|
759
|
-
default:
|
|
760
|
-
throw new Error(`Invalid brush type: ${type}`);
|
|
761
|
-
}
|
|
669
|
+
const bithDepth = readInt16(reader);
|
|
670
|
+
const compression = readUint8(reader); // 0 - raw, 1 - RLE
|
|
671
|
+
if (bithDepth !== 8 && bithDepth !== 16) throw new Error('Invalid depth');
|
|
672
|
+
if (compression !== 0 && compression !== 1) throw new Error('Invalid compression');
|
|
673
|
+
const rowSize = w * (bithDepth === 16 ? 2 : 1);
|
|
674
|
+
// RLE needs at least 2 bytes for row length and 2 bytes per each 128 byte run
|
|
675
|
+
const minDataSize = compression === 0 ? h * rowSize : h * (2 + Math.ceil(rowSize / 128) * 2);
|
|
676
|
+
if (minDataSize > brushEnd - reader.offset) {
|
|
677
|
+
throw new Error('Brush sample data out of bounds');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
consumeMemory(reader, w * h * (compression === 1 && bithDepth === 16 ? 3 : 1));
|
|
681
|
+
const alpha = new Uint8Array(w * h);
|
|
762
682
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
683
|
+
if (bithDepth === 8) {
|
|
684
|
+
if (compression === 0) {
|
|
685
|
+
alpha.set(readBytes(reader, alpha.byteLength));
|
|
686
|
+
} else if (compression === 1) {
|
|
687
|
+
readDataRLE(reader, { width: w, height: h, data: alpha }, w, h, bithDepth, 1, [0], false);
|
|
688
|
+
} else {
|
|
689
|
+
throw new Error('Invalid compression');
|
|
690
|
+
}
|
|
691
|
+
} else if (bithDepth === 16) {
|
|
692
|
+
if (compression === 0) {
|
|
693
|
+
for (let i = 0; i < alpha.byteLength; i++) {
|
|
694
|
+
alpha[i] = readUint16(reader) >> 8; // convert to 8bit values
|
|
695
|
+
}
|
|
696
|
+
} else if (compression === 1) {
|
|
697
|
+
const data = new Uint16Array(w * h);
|
|
698
|
+
readDataRLE(reader, { width: w, height: h, data }, w, h, bithDepth, 1, [0], false);
|
|
699
|
+
for (let i = 0; i < alpha.byteLength; i++) {
|
|
700
|
+
alpha[i] = data[i] >> 8; // convert to 8bit values
|
|
767
701
|
}
|
|
702
|
+
} else {
|
|
703
|
+
throw new Error('Invalid compression');
|
|
768
704
|
}
|
|
769
705
|
} else {
|
|
770
|
-
throw new Error(
|
|
706
|
+
throw new Error('Invalid depth');
|
|
771
707
|
}
|
|
772
708
|
|
|
773
|
-
return {
|
|
709
|
+
return { id, bounds: { x, y, w, h }, alpha };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ABR versions 1 and 2 (Photoshop 6 and earlier) contain a list of brushes with brush tips in them
|
|
713
|
+
function readLegacyBrushes(reader: PsdReader, version: number, samples: SampleInfo[], brushes: Brush[], skip: (e: unknown, what: string) => void) {
|
|
714
|
+
const count = readUint16(reader);
|
|
715
|
+
|
|
716
|
+
for (let i = 0; i < count; i++) {
|
|
717
|
+
const type = readUint16(reader);
|
|
718
|
+
const size = readUint32(reader);
|
|
719
|
+
const end = reader.offset + size;
|
|
720
|
+
|
|
721
|
+
if (end > reader.view.byteLength) { // truncated file
|
|
722
|
+
skip(new Error('Brush data out of bounds'), `brush ${i + 1}`);
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
try {
|
|
727
|
+
if (type === 1) { // computed brush
|
|
728
|
+
skipBytes(reader, 4); // misc
|
|
729
|
+
const spacing = readUint16(reader);
|
|
730
|
+
const diameter = readUint16(reader);
|
|
731
|
+
const roundness = readUint16(reader);
|
|
732
|
+
const angle = readInt16(reader);
|
|
733
|
+
const hardness = readUint16(reader);
|
|
734
|
+
if (reader.offset > end) throw new Error('Brush data out of bounds');
|
|
735
|
+
|
|
736
|
+
brushes.push(createLegacyBrush('', {
|
|
737
|
+
type: 'computed',
|
|
738
|
+
size: diameter,
|
|
739
|
+
angle,
|
|
740
|
+
roundness: roundness / 100,
|
|
741
|
+
spacingOn: spacing !== 0,
|
|
742
|
+
spacing: spacing / 100,
|
|
743
|
+
flipX: false,
|
|
744
|
+
flipY: false,
|
|
745
|
+
hardness: hardness / 100,
|
|
746
|
+
}));
|
|
747
|
+
} else if (type === 2) { // sampled brush
|
|
748
|
+
skipBytes(reader, 4); // misc
|
|
749
|
+
const spacing = readUint16(reader);
|
|
750
|
+
const name = version === 2 ? readUnicodeString(reader) : '';
|
|
751
|
+
skipBytes(reader, 1 + 8); // antialiasing, Int16 bounds rectangle (same as Int32 one)
|
|
752
|
+
|
|
753
|
+
// legacy brushes don't have sample ids
|
|
754
|
+
const sample = readSampleBitmap(reader, end, `brush-${i + 1}`);
|
|
755
|
+
samples.push(sample);
|
|
756
|
+
brushes.push(createLegacyBrush(name, {
|
|
757
|
+
type: 'sampled',
|
|
758
|
+
size: Math.max(sample.bounds.w, sample.bounds.h),
|
|
759
|
+
angle: 0,
|
|
760
|
+
roundness: 1,
|
|
761
|
+
spacingOn: spacing !== 0,
|
|
762
|
+
spacing: spacing / 100,
|
|
763
|
+
flipX: false,
|
|
764
|
+
flipY: false,
|
|
765
|
+
name,
|
|
766
|
+
sampledData: sample.id,
|
|
767
|
+
}));
|
|
768
|
+
} else {
|
|
769
|
+
throw new Error(`Unknown brush type: ${type}`);
|
|
770
|
+
}
|
|
771
|
+
} catch (e) {
|
|
772
|
+
skip(e, `brush ${i + 1}`);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
reader.offset = end;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function createLegacyBrush(name: string, shape: BrushShape): Brush {
|
|
780
|
+
return { name, shape, spacing: 1, wetEdges: false, noise: false, buildUp: false, useBrushSize: true };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function parseBrush(brush: DescDescriptor['Brsh'][number]): Brush {
|
|
784
|
+
const b: Brush = {
|
|
785
|
+
name: brush['Nm '],
|
|
786
|
+
shape: parseBrushShape(brush.Brsh),
|
|
787
|
+
spacing: parsePercent(brush.Spcn),
|
|
788
|
+
wetEdges: brush.Wtdg,
|
|
789
|
+
noise: brush.Nose,
|
|
790
|
+
buildUp: !!brush['Rpt '],
|
|
791
|
+
useBrushSize: brush.useBrushSize, // ???
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
if (brush.interpretation != null) b.interpretation = brush.interpretation;
|
|
795
|
+
if (brush.protectTexture != null) b.protectTexture = brush.protectTexture;
|
|
796
|
+
|
|
797
|
+
if (brush.useTipDynamics) {
|
|
798
|
+
b.shapeDynamics = {
|
|
799
|
+
tiltScale: parsePercent(brush.tiltScale),
|
|
800
|
+
sizeDynamics: parseDynamics(brush.szVr),
|
|
801
|
+
angleDynamics: parseDynamics(brush.angleDynamics),
|
|
802
|
+
roundnessDynamics: parseDynamics(brush.roundnessDynamics),
|
|
803
|
+
flipX: brush.flipX,
|
|
804
|
+
flipY: brush.flipY,
|
|
805
|
+
brushProjection: brush.brushProjection,
|
|
806
|
+
minimumDiameter: parsePercent(brush.minimumDiameter),
|
|
807
|
+
minimumRoundness: parsePercent(brush.minimumRoundness),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (brush.useScatter) {
|
|
812
|
+
b.scatter = {
|
|
813
|
+
count: brush['Cnt '],
|
|
814
|
+
bothAxes: brush.bothAxes,
|
|
815
|
+
countDynamics: parseDynamics(brush.countDynamics),
|
|
816
|
+
scatterDynamics: parseDynamics(brush.scatterDynamics),
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (brush.useTexture && brush.Txtr) {
|
|
821
|
+
b.texture = {
|
|
822
|
+
id: brush.Txtr.Idnt,
|
|
823
|
+
name: brush.Txtr['Nm '],
|
|
824
|
+
blendMode: BlnM.decode(brush.textureBlendMode),
|
|
825
|
+
depth: parsePercent(brush.textureDepth),
|
|
826
|
+
depthMinimum: parsePercent(brush.minimumDepth),
|
|
827
|
+
depthDynamics: parseDynamics(brush.textureDepthDynamics),
|
|
828
|
+
scale: parsePercent(brush.textureScale),
|
|
829
|
+
invert: brush.InvT,
|
|
830
|
+
brightness: brush.textureBrightness,
|
|
831
|
+
contrast: brush.textureContrast,
|
|
832
|
+
textureEachTip: !!brush.TxtC,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const db = brush.dualBrush;
|
|
837
|
+
if (db && db.useDualBrush) {
|
|
838
|
+
b.dualBrush = {
|
|
839
|
+
flip: db.Flip,
|
|
840
|
+
shape: parseBrushShape(db.Brsh),
|
|
841
|
+
blendMode: BlnM.decode(db.BlnM),
|
|
842
|
+
useScatter: db.useScatter,
|
|
843
|
+
spacing: parsePercent(db.Spcn),
|
|
844
|
+
count: db['Cnt '],
|
|
845
|
+
bothAxes: db.bothAxes,
|
|
846
|
+
countDynamics: parseDynamics(db.countDynamics),
|
|
847
|
+
scatterDynamics: parseDynamics(db.scatterDynamics),
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (brush.useColorDynamics) {
|
|
852
|
+
b.colorDynamics = {
|
|
853
|
+
foregroundBackground: parseDynamics(brush.clVr!),
|
|
854
|
+
hue: parsePercent(brush['H ']!),
|
|
855
|
+
saturation: parsePercent(brush.Strt!),
|
|
856
|
+
brightness: parsePercent(brush.Brgh!),
|
|
857
|
+
purity: parsePercent(brush.purity!),
|
|
858
|
+
perTip: brush.colorDynamicsPerTip!,
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (brush.usePaintDynamics) {
|
|
863
|
+
b.transfer = {
|
|
864
|
+
flowDynamics: parseDynamics(brush.prVr!),
|
|
865
|
+
opacityDynamics: parseDynamics(brush.opVr!),
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
if (brush.wtVr) b.transfer.wetnessDynamics = parseDynamics(brush.wtVr);
|
|
869
|
+
if (brush.mxVr) b.transfer.mixDynamics = parseDynamics(brush.mxVr);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (brush.useBrushPose) {
|
|
873
|
+
b.brushPose = {
|
|
874
|
+
overrideAngle: brush.overridePoseAngle!,
|
|
875
|
+
overrideTiltX: brush.overridePoseTiltX!,
|
|
876
|
+
overrideTiltY: brush.overridePoseTiltY!,
|
|
877
|
+
overridePressure: brush.overridePosePressure!,
|
|
878
|
+
pressure: parsePercent(brush.brushPosePressure!),
|
|
879
|
+
tiltX: brush.brushPoseTiltX!,
|
|
880
|
+
tiltY: brush.brushPoseTiltY!,
|
|
881
|
+
angle: brush.brushPoseAngle!,
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const to = brush.toolOptions;
|
|
886
|
+
if (to) {
|
|
887
|
+
b.toolOptions = {
|
|
888
|
+
type: toBrushType[to._classID] || 'brush',
|
|
889
|
+
brushPreset: to.brushPreset,
|
|
890
|
+
flow: to.flow ?? 100,
|
|
891
|
+
smooth: to.Smoo ?? 0,
|
|
892
|
+
mode: BlnM.decode(to['Md '] || 'BlnM.Nrml'), // sometimes mode is missing
|
|
893
|
+
opacity: to.Opct ?? 100,
|
|
894
|
+
smoothing: !!to.smoothing,
|
|
895
|
+
smoothingValue: to.smoothingValue || 0,
|
|
896
|
+
smoothingRadiusMode: !!to.smoothingRadiusMode,
|
|
897
|
+
smoothingCatchup: !!to.smoothingCatchup,
|
|
898
|
+
smoothingCatchupAtEnd: !!to.smoothingCatchupAtEnd,
|
|
899
|
+
smoothingZoomCompensation: !!to.smoothingZoomCompensation,
|
|
900
|
+
pressureSmoothing: !!to.pressureSmoothing,
|
|
901
|
+
usePressureOverridesSize: !!to.usePressureOverridesSize,
|
|
902
|
+
usePressureOverridesOpacity: !!to.usePressureOverridesOpacity,
|
|
903
|
+
useLegacy: !!to.useLegacy,
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
if (to.prVr) b.toolOptions.flowDynamics = parseDynamics(to.prVr);
|
|
907
|
+
if (to.opVr) b.toolOptions.opacityDynamics = parseDynamics(to.opVr);
|
|
908
|
+
if (to.szVr) b.toolOptions.sizeDynamics = parseDynamics(to.szVr);
|
|
909
|
+
if ('wetness' in to) b.toolOptions.wetness = to.wetness;
|
|
910
|
+
if ('dryness' in to) b.toolOptions.dryness = to.dryness;
|
|
911
|
+
if ('mix' in to) b.toolOptions.mix = to.mix;
|
|
912
|
+
if ('autoFill' in to) b.toolOptions.autoFill = to.autoFill;
|
|
913
|
+
if ('autoClean' in to) b.toolOptions.autoClean = to.autoClean;
|
|
914
|
+
if ('loadSolidColorOnly' in to) b.toolOptions.loadSolidColorOnly = to.loadSolidColorOnly;
|
|
915
|
+
if ('sampleAllLayers' in to) b.toolOptions.sampleAllLayers = to.sampleAllLayers;
|
|
916
|
+
if ('SmdF' in to) b.toolOptions.smudgeFingerPainting = to.SmdF;
|
|
917
|
+
if ('SmdS' in to) b.toolOptions.smudgeSampleAllLayers = to.SmdS;
|
|
918
|
+
if ('Prs ' in to) b.toolOptions.strength = to['Prs '];
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return b;
|
|
774
922
|
}
|