ag-psd 33.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.
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +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/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +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/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +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/e6b99945 +0 -0
- package/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/README_PSD.md +25 -9
- package/TODO.md +13 -84
- package/dist/abr.js +12 -7
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +2 -0
- package/dist/additionalInfo.js +361 -75
- package/dist/additionalInfo.js.map +1 -1
- package/dist/bundle.js +1524 -389
- package/dist/descriptor.d.ts +14 -5
- package/dist/descriptor.js +102 -64
- package/dist/descriptor.js.map +1 -1
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.js +242 -2
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.js +84 -3
- package/dist/imageResources.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/psd.d.ts +144 -2
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.js +206 -110
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +1 -0
- package/dist/psdWriter.js +336 -91
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.d.ts +32 -1
- package/dist/text.js +58 -0
- package/dist/text.js.map +1 -1
- package/dist-es/abr.js +12 -7
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +2 -0
- package/dist-es/additionalInfo.js +361 -75
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/descriptor.d.ts +14 -5
- package/dist-es/descriptor.js +102 -64
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/helpers.d.ts +6 -0
- package/dist-es/helpers.js +242 -2
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.js +84 -3
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.d.ts +1 -0
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/psd.d.ts +144 -2
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.js +206 -110
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +1 -0
- package/dist-es/psdWriter.js +336 -91
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.d.ts +32 -1
- package/dist-es/text.js +58 -0
- package/dist-es/text.js.map +1 -1
- package/package.json +1 -1
- package/perf.js +312 -9
- package/src/abr.ts +14 -7
- package/src/additionalInfo.ts +433 -87
- package/src/descriptor.ts +103 -58
- package/src/helpers.ts +262 -2
- package/src/imageResources.ts +107 -4
- package/src/index.ts +1 -0
- package/src/psd.ts +115 -2
- package/src/psdReader.ts +226 -108
- package/src/psdWriter.ts +345 -90
- package/src/text.ts +76 -1
package/src/descriptor.ts
CHANGED
|
@@ -44,11 +44,6 @@ const unitsMap: Dict = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const unitsMapRev = revMap(unitsMap);
|
|
47
|
-
let logErrors = false;
|
|
48
|
-
|
|
49
|
-
export function setLogErrors(value: boolean) {
|
|
50
|
-
logErrors = value;
|
|
51
|
-
}
|
|
52
47
|
|
|
53
48
|
function makeType(name: string, classID: string) {
|
|
54
49
|
return { name, classID };
|
|
@@ -98,6 +93,7 @@ const fieldToExtType: ExtTypeDict = {
|
|
|
98
93
|
psVersion: nullType,
|
|
99
94
|
docDefaultNewArtboardBackgroundColor: makeType('', 'RGBC'),
|
|
100
95
|
artboardRect: makeType('', 'classFloatRect'),
|
|
96
|
+
framedGroupRect: makeType('', 'classFloatRect'),
|
|
101
97
|
keyOriginRRectRadii: makeType('', 'radii'),
|
|
102
98
|
keyOriginBoxCorners: nullType,
|
|
103
99
|
rectangleCornerA: makeType('', 'Pnt '),
|
|
@@ -185,7 +181,7 @@ const fieldToArrayExtType: ExtTypeDict = {
|
|
|
185
181
|
const typeToField: { [key: string]: string[]; } = {
|
|
186
182
|
'TEXT': [
|
|
187
183
|
'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName', 'exportOption', 'scaleSettings',
|
|
188
|
-
'BackgroundImagePath', 'ImageSubfolderName',
|
|
184
|
+
'BackgroundImagePath', 'ImageSubfolderName', 'SavedIteratedName',
|
|
189
185
|
'presetFileName', 'curvesPresetFileName', 'mixerPresetFileName', 'placed', 'description', 'reason',
|
|
190
186
|
'artboardPresetName', 'json', 'clipID', 'relPath', 'fullPath', 'mediaDescriptor', 'Msge',
|
|
191
187
|
'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath', 'Bk ', 'pathUnicodeName',
|
|
@@ -212,7 +208,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
212
208
|
'meshExpansion', 'meshRigidity', 'VrsM', 'VrsN', 'NmbG', 'WLMn', 'WLMx', 'AmMn', 'AmMx', 'SclH', 'SclV',
|
|
213
209
|
'Lvl ', 'TlNm', 'TlOf', 'FlRs', 'Thsh', 'ShrS', 'ShrE', 'FlRs', 'Vrnc', 'Strg', 'ExtS', 'ExtD',
|
|
214
210
|
'HrzS', 'VrtS', 'NmbR', 'EdgF', 'Ang1', 'Ang2', 'Ang3', 'Ang4', 'lastAppliedComp', 'capturedInfo',
|
|
215
|
-
'pixelSourceType',
|
|
211
|
+
'pixelSourceType', 'framedGroupType',
|
|
216
212
|
],
|
|
217
213
|
'enum': [
|
|
218
214
|
'textGridding', 'Ornt', 'warpStyle', 'warpRotate', 'Inte', 'Bltn', 'ClrS', 'BlrQ',
|
|
@@ -236,7 +232,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
236
232
|
'materialsDisclosed', 'hasMotion', 'muted', 'Effc', 'selected', 'autoScope', 'fillCanvas',
|
|
237
233
|
'cellTextIsHTML', 'Smoo', 'Clsp', 'validAtPosition', 'rigidType', 'hasoptions', 'filterMaskEnable',
|
|
238
234
|
'filterMaskLinked', 'filterMaskExtendWithWhite', 'removeJPEGArtifact', 'Mnch', 'ExtF', 'ExtM',
|
|
239
|
-
'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn',
|
|
235
|
+
'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn', 'LockPPI',
|
|
240
236
|
],
|
|
241
237
|
'doub': [
|
|
242
238
|
'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght', 'Gmm ',
|
|
@@ -244,7 +240,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
244
240
|
'xx', 'xy', 'yx', 'yy', 'tx', 'ty', 'FrGA', 'frameRate', 'audioLevel', 'rotation',
|
|
245
241
|
'X ', 'Y ', 'redFloat', 'greenFloat', 'blueFloat', 'imageResolution',
|
|
246
242
|
'PuX0', 'PuX1', 'PuX2', 'PuX3', 'PuY0', 'PuY1', 'PuY2', 'PuY3',
|
|
247
|
-
'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity',
|
|
243
|
+
'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity', 'PreferredResolution',
|
|
248
244
|
],
|
|
249
245
|
'UntF': [
|
|
250
246
|
'sdwO', 'hglO', 'lagl', 'Lald', 'srgR', 'blur', 'Sftn', 'Opct', 'Dstn', 'Angl',
|
|
@@ -276,6 +272,14 @@ const channels = [
|
|
|
276
272
|
const doubleColorClasses = ['Grsc', 'LbCl', 'GRYC', 'LABC'];
|
|
277
273
|
|
|
278
274
|
// fallback for unknown keys, based on the type of the value
|
|
275
|
+
export const cameraRawFilterClassID = 'Adobe Camera Raw Filter';
|
|
276
|
+
|
|
277
|
+
// types of Camera Raw Filter settings (keys clash with other descriptors so they're typed by class)
|
|
278
|
+
export const cameraRawFilterKeyTypes: Dict = {
|
|
279
|
+
CrVe: 'TEXT', PrVN: 'long', PrVe: 'long', WBal: 'enum', Ex12: 'doub', ShpR: 'doub',
|
|
280
|
+
'Crv ': 'VlLs', CrvR: 'VlLs', CrvG: 'VlLs', CrvB: 'VlLs',
|
|
281
|
+
};
|
|
282
|
+
|
|
279
283
|
function guessOSType(value: any): string | undefined {
|
|
280
284
|
if (typeof value === 'boolean') return 'bool';
|
|
281
285
|
if (typeof value === 'number' && !Number.isInteger(value)) return 'doub';
|
|
@@ -441,7 +445,7 @@ export function readDescriptorStructure(reader: PsdReader, includeClass: boolean
|
|
|
441
445
|
export const outputSettingsRoot = 'outputSettings';
|
|
442
446
|
|
|
443
447
|
export function writeDescriptorStructure(writer: PsdWriter, name: string, classId: string, value: any, root: string) {
|
|
444
|
-
if (
|
|
448
|
+
if (writer.logMissingFeatures && !classId) writer.log('Missing classId for: ', name, classId, value);
|
|
445
449
|
|
|
446
450
|
// write class structure
|
|
447
451
|
writeUnicodeStringWithPadding(writer, name);
|
|
@@ -464,6 +468,11 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
464
468
|
let childRoot = root;
|
|
465
469
|
let extType = fieldToExtType[key];
|
|
466
470
|
|
|
471
|
+
if (classId === cameraRawFilterClassID) {
|
|
472
|
+
type = cameraRawFilterKeyTypes[key] ?? 'long'; // other settings are integer levels
|
|
473
|
+
childRoot = cameraRawFilterClassID; // lists (curves) contain integer levels
|
|
474
|
+
}
|
|
475
|
+
|
|
467
476
|
if (key === 'bounds' && root === 'text') {
|
|
468
477
|
extType = makeType('', 'bounds');
|
|
469
478
|
} else if (key === 'origin') {
|
|
@@ -533,7 +542,7 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
533
542
|
} else if (value[key].Ptrn) {
|
|
534
543
|
extType = makeType('', 'patternLayer');
|
|
535
544
|
} else {
|
|
536
|
-
|
|
545
|
+
writer.logMissingFeatures && writer.log('Invalid strokeStyleContent value', value[key]);
|
|
537
546
|
}
|
|
538
547
|
} else if (key === 'bounds' && root === 'quiltWarp') {
|
|
539
548
|
extType = makeType('', 'classFloatRect');
|
|
@@ -552,7 +561,7 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
552
561
|
writeAsciiStringOrClassId(writer, key);
|
|
553
562
|
writeSignature(writer, type || 'long');
|
|
554
563
|
writeOSType(writer, type || 'long', value[key], key, extType, childRoot);
|
|
555
|
-
if (
|
|
564
|
+
if (writer.logMissingFeatures && !type) writer.log(`Missing descriptor field type for: '${key}' in`, value);
|
|
556
565
|
}
|
|
557
566
|
}
|
|
558
567
|
|
|
@@ -676,10 +685,10 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
676
685
|
writeInt32(writer, value.length);
|
|
677
686
|
|
|
678
687
|
for (let i = 0; i < value.length; i++) {
|
|
679
|
-
const type = fieldToArrayType[key] ?? guessOSType(value[i]);
|
|
688
|
+
const type = root === cameraRawFilterClassID ? 'long' : (fieldToArrayType[key] ?? guessOSType(value[i]));
|
|
680
689
|
writeSignature(writer, type || 'long');
|
|
681
690
|
writeOSType(writer, type || 'long', value[i], `${key}[]`, fieldToArrayExtType[key], root);
|
|
682
|
-
if (
|
|
691
|
+
if (writer.logMissingFeatures && !type) writer.log(`Missing descriptor array type for: '${key}' in`, value);
|
|
683
692
|
}
|
|
684
693
|
break;
|
|
685
694
|
case 'doub': // Double
|
|
@@ -1353,7 +1362,7 @@ function serializeFxObject(stroke: LayerEffectStroke) {
|
|
|
1353
1362
|
return FrFX;
|
|
1354
1363
|
}
|
|
1355
1364
|
|
|
1356
|
-
export function serializeEffects(e: LayerEffectsInfo,
|
|
1365
|
+
export function serializeEffects(e: LayerEffectsInfo, writer: PsdWriter, multi: boolean) {
|
|
1357
1366
|
const info: Lfx2Descriptor & LmfxDescriptor = multi ? {
|
|
1358
1367
|
'Scl ': unitsPercentF(e.scale ?? 1),
|
|
1359
1368
|
masterFXSwitch: !e.disabled,
|
|
@@ -1370,20 +1379,20 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
|
|
|
1370
1379
|
const useMulti = <T>(arr: undefined | T[]): arr is T[] => !!arr && arr.length > 1 && multi;
|
|
1371
1380
|
const useSingle = <T>(arr: undefined | T[]): arr is T[] => !!arr && arr.length >= 1 && (!multi || arr.length === 1);
|
|
1372
1381
|
|
|
1373
|
-
if (useSingle(e.dropShadow)) info.DrSh = serializeEffectObject(e.dropShadow[0], 'dropShadow',
|
|
1374
|
-
if (useMulti(e.dropShadow)) info.dropShadowMulti = e.dropShadow.map(i => serializeEffectObject(i, 'dropShadow',
|
|
1375
|
-
if (useSingle(e.innerShadow)) info.IrSh = serializeEffectObject(e.innerShadow[0], 'innerShadow',
|
|
1376
|
-
if (useMulti(e.innerShadow)) info.innerShadowMulti = e.innerShadow.map(i => serializeEffectObject(i, 'innerShadow',
|
|
1377
|
-
if (e.outerGlow) info.OrGl = serializeEffectObject(e.outerGlow, 'outerGlow',
|
|
1378
|
-
if (useMulti(e.solidFill)) info.solidFillMulti = e.solidFill.map(i => serializeEffectObject(i, 'solidFill',
|
|
1379
|
-
if (useMulti(e.gradientOverlay)) info.gradientFillMulti = e.gradientOverlay.map(i => serializeEffectObject(i, 'gradientOverlay',
|
|
1382
|
+
if (useSingle(e.dropShadow)) info.DrSh = serializeEffectObject(e.dropShadow[0], 'dropShadow', writer);
|
|
1383
|
+
if (useMulti(e.dropShadow)) info.dropShadowMulti = e.dropShadow.map(i => serializeEffectObject(i, 'dropShadow', writer));
|
|
1384
|
+
if (useSingle(e.innerShadow)) info.IrSh = serializeEffectObject(e.innerShadow[0], 'innerShadow', writer);
|
|
1385
|
+
if (useMulti(e.innerShadow)) info.innerShadowMulti = e.innerShadow.map(i => serializeEffectObject(i, 'innerShadow', writer));
|
|
1386
|
+
if (e.outerGlow) info.OrGl = serializeEffectObject(e.outerGlow, 'outerGlow', writer);
|
|
1387
|
+
if (useMulti(e.solidFill)) info.solidFillMulti = e.solidFill.map(i => serializeEffectObject(i, 'solidFill', writer));
|
|
1388
|
+
if (useMulti(e.gradientOverlay)) info.gradientFillMulti = e.gradientOverlay.map(i => serializeEffectObject(i, 'gradientOverlay', writer));
|
|
1380
1389
|
if (useMulti(e.stroke)) info.frameFXMulti = e.stroke.map(i => serializeFxObject(i));
|
|
1381
|
-
if (e.innerGlow) info.IrGl = serializeEffectObject(e.innerGlow, 'innerGlow',
|
|
1382
|
-
if (e.bevel) info.ebbl = serializeEffectObject(e.bevel, 'bevel',
|
|
1383
|
-
if (useSingle(e.solidFill)) info.SoFi = serializeEffectObject(e.solidFill[0], 'solidFill',
|
|
1384
|
-
if (e.patternOverlay) info.patternFill = serializeEffectObject(e.patternOverlay, 'patternOverlay',
|
|
1385
|
-
if (useSingle(e.gradientOverlay)) info.GrFl = serializeEffectObject(e.gradientOverlay[0], 'gradientOverlay',
|
|
1386
|
-
if (e.satin) info.ChFX = serializeEffectObject(e.satin, 'satin',
|
|
1390
|
+
if (e.innerGlow) info.IrGl = serializeEffectObject(e.innerGlow, 'innerGlow', writer);
|
|
1391
|
+
if (e.bevel) info.ebbl = serializeEffectObject(e.bevel, 'bevel', writer);
|
|
1392
|
+
if (useSingle(e.solidFill)) info.SoFi = serializeEffectObject(e.solidFill[0], 'solidFill', writer);
|
|
1393
|
+
if (e.patternOverlay) info.patternFill = serializeEffectObject(e.patternOverlay, 'patternOverlay', writer);
|
|
1394
|
+
if (useSingle(e.gradientOverlay)) info.GrFl = serializeEffectObject(e.gradientOverlay[0], 'gradientOverlay', writer);
|
|
1395
|
+
if (e.satin) info.ChFX = serializeEffectObject(e.satin, 'satin', writer);
|
|
1387
1396
|
if (useSingle(e.stroke)) info.FrFX = serializeFxObject(e.stroke?.[0]);
|
|
1388
1397
|
|
|
1389
1398
|
if (multi) {
|
|
@@ -1406,31 +1415,31 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
|
|
|
1406
1415
|
return info;
|
|
1407
1416
|
}
|
|
1408
1417
|
|
|
1409
|
-
export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor,
|
|
1418
|
+
export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor, reader: PsdReader) {
|
|
1410
1419
|
const effects: LayerEffectsInfo = {};
|
|
1411
1420
|
const { _name, _classID, 'Scl ': scale, masterFXSwitch, DrSh, dropShadowMulti, IrSh, innerShadowMulti, OrGl, IrGl, ebbl, SoFi, solidFillMulti, patternFill, GrFl, gradientFillMulti, ChFX, FrFX, frameFXMulti, numModifyingFX, ...rest } = info as typeof info & { _name?: string; _classID?: string; };
|
|
1412
1421
|
if (!masterFXSwitch) effects.disabled = true;
|
|
1413
1422
|
if (scale) effects.scale = parsePercent(scale);
|
|
1414
|
-
if (DrSh) effects.dropShadow = [parseEffectObject(DrSh,
|
|
1415
|
-
if (dropShadowMulti) effects.dropShadow = dropShadowMulti.map(i => parseEffectObject(i,
|
|
1416
|
-
if (IrSh) effects.innerShadow = [parseEffectObject(IrSh,
|
|
1417
|
-
if (innerShadowMulti) effects.innerShadow = innerShadowMulti.map(i => parseEffectObject(i,
|
|
1418
|
-
if (OrGl) effects.outerGlow = parseEffectObject(OrGl,
|
|
1419
|
-
if (IrGl) effects.innerGlow = parseEffectObject(IrGl,
|
|
1420
|
-
if (ebbl) effects.bevel = parseEffectObject(ebbl,
|
|
1421
|
-
if (SoFi) effects.solidFill = [parseEffectObject(SoFi,
|
|
1422
|
-
if (solidFillMulti) effects.solidFill = solidFillMulti.map(i => parseEffectObject(i,
|
|
1423
|
-
if (patternFill) effects.patternOverlay = parseEffectObject(patternFill,
|
|
1424
|
-
if (GrFl) effects.gradientOverlay = [parseEffectObject(GrFl,
|
|
1425
|
-
if (gradientFillMulti) effects.gradientOverlay = gradientFillMulti.map(i => parseEffectObject(i,
|
|
1426
|
-
if (ChFX) effects.satin = parseEffectObject(ChFX,
|
|
1423
|
+
if (DrSh) effects.dropShadow = [parseEffectObject(DrSh, reader)];
|
|
1424
|
+
if (dropShadowMulti) effects.dropShadow = dropShadowMulti.map(i => parseEffectObject(i, reader));
|
|
1425
|
+
if (IrSh) effects.innerShadow = [parseEffectObject(IrSh, reader)];
|
|
1426
|
+
if (innerShadowMulti) effects.innerShadow = innerShadowMulti.map(i => parseEffectObject(i, reader));
|
|
1427
|
+
if (OrGl) effects.outerGlow = parseEffectObject(OrGl, reader);
|
|
1428
|
+
if (IrGl) effects.innerGlow = parseEffectObject(IrGl, reader);
|
|
1429
|
+
if (ebbl) effects.bevel = parseEffectObject(ebbl, reader, 'bevel');
|
|
1430
|
+
if (SoFi) effects.solidFill = [parseEffectObject(SoFi, reader)];
|
|
1431
|
+
if (solidFillMulti) effects.solidFill = solidFillMulti.map(i => parseEffectObject(i, reader));
|
|
1432
|
+
if (patternFill) effects.patternOverlay = parseEffectObject(patternFill, reader);
|
|
1433
|
+
if (GrFl) effects.gradientOverlay = [parseEffectObject(GrFl, reader)];
|
|
1434
|
+
if (gradientFillMulti) effects.gradientOverlay = gradientFillMulti.map(i => parseEffectObject(i, reader));
|
|
1435
|
+
if (ChFX) effects.satin = parseEffectObject(ChFX, reader);
|
|
1427
1436
|
if (FrFX) effects.stroke = [parseFxObject(FrFX)];
|
|
1428
1437
|
if (frameFXMulti) effects.stroke = frameFXMulti.map(i => parseFxObject(i));
|
|
1429
|
-
if (
|
|
1438
|
+
if (reader.logMissingFeatures && Object.keys(rest).length) reader.log('Unhandled effect keys:', rest);
|
|
1430
1439
|
return effects;
|
|
1431
1440
|
}
|
|
1432
1441
|
|
|
1433
|
-
function parseKeyList(keyList: TimelineKeyDescriptor[],
|
|
1442
|
+
function parseKeyList(keyList: TimelineKeyDescriptor[], reader: PsdReader) {
|
|
1434
1443
|
const keys: TimelineKey[] = [];
|
|
1435
1444
|
|
|
1436
1445
|
for (let j = 0; j < keyList.length; j++) {
|
|
@@ -1454,7 +1463,7 @@ function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: bool
|
|
|
1454
1463
|
break;
|
|
1455
1464
|
case 'keyType.sheetStyle': {
|
|
1456
1465
|
const key: TimelineKey = { interpolation, time, selected, type: 'style' };
|
|
1457
|
-
if (animKey.sheetStyle.Lefx) key.style = parseEffects(animKey.sheetStyle.Lefx,
|
|
1466
|
+
if (animKey.sheetStyle.Lefx) key.style = parseEffects(animKey.sheetStyle.Lefx, reader);
|
|
1458
1467
|
keys.push(key);
|
|
1459
1468
|
break;
|
|
1460
1469
|
}
|
|
@@ -1472,7 +1481,7 @@ function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: bool
|
|
|
1472
1481
|
return keys;
|
|
1473
1482
|
}
|
|
1474
1483
|
|
|
1475
|
-
function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
|
|
1484
|
+
function serializeKeyList(keys: TimelineKey[], writer: PsdWriter): TimelineKeyDescriptor[] {
|
|
1476
1485
|
const keyList: TimelineKeyDescriptor[] = [];
|
|
1477
1486
|
|
|
1478
1487
|
for (let j = 0; j < keys.length; j++) {
|
|
@@ -1493,7 +1502,7 @@ function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
|
|
|
1493
1502
|
break;
|
|
1494
1503
|
case 'style':
|
|
1495
1504
|
animKey = { Type: 'keyType.sheetStyle', sheetStyle: { Vrsn: 1, blendOptions: {} } };
|
|
1496
|
-
if (key.style) animKey.sheetStyle = { Vrsn: 1, Lefx: serializeEffects(key.style,
|
|
1505
|
+
if (key.style) animKey.sheetStyle = { Vrsn: 1, Lefx: serializeEffects(key.style, writer, false), blendOptions: {} };
|
|
1497
1506
|
break;
|
|
1498
1507
|
case 'globalLighting': {
|
|
1499
1508
|
animKey = { Type: 'keyType.globalLighting', gblA: key.globalAngle, globalAltitude: key.globalAltitude };
|
|
@@ -1508,7 +1517,7 @@ function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
|
|
|
1508
1517
|
return keyList;
|
|
1509
1518
|
}
|
|
1510
1519
|
|
|
1511
|
-
export function parseTrackList(trackList: TimelineTrackDescriptor[],
|
|
1520
|
+
export function parseTrackList(trackList: TimelineTrackDescriptor[], reader: PsdReader) {
|
|
1512
1521
|
const tracks: TimelineTrack[] = [];
|
|
1513
1522
|
|
|
1514
1523
|
for (let i = 0; i < trackList.length; i++) {
|
|
@@ -1516,14 +1525,14 @@ export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingF
|
|
|
1516
1525
|
const track: TimelineTrack = {
|
|
1517
1526
|
type: stdTrackID.decode(tr.trackID),
|
|
1518
1527
|
enabled: tr.enab,
|
|
1519
|
-
keys: parseKeyList(tr.keyList,
|
|
1528
|
+
keys: parseKeyList(tr.keyList, reader),
|
|
1520
1529
|
};
|
|
1521
1530
|
|
|
1522
1531
|
if (tr.effectParams) {
|
|
1523
1532
|
track.effectParams = {
|
|
1524
1533
|
fillCanvas: tr.effectParams.fillCanvas,
|
|
1525
1534
|
zoomOrigin: tr.effectParams.zoomOrigin,
|
|
1526
|
-
keys: parseKeyList(tr.effectParams.keyList,
|
|
1535
|
+
keys: parseKeyList(tr.effectParams.keyList, reader),
|
|
1527
1536
|
};
|
|
1528
1537
|
}
|
|
1529
1538
|
|
|
@@ -1533,7 +1542,7 @@ export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingF
|
|
|
1533
1542
|
return tracks;
|
|
1534
1543
|
}
|
|
1535
1544
|
|
|
1536
|
-
export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescriptor[] {
|
|
1545
|
+
export function serializeTrackList(tracks: TimelineTrack[], writer: PsdWriter): TimelineTrackDescriptor[] {
|
|
1537
1546
|
const trackList: TimelineTrackDescriptor[] = [];
|
|
1538
1547
|
|
|
1539
1548
|
for (let i = 0; i < tracks.length; i++) {
|
|
@@ -1545,12 +1554,12 @@ export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescri
|
|
|
1545
1554
|
Effc: !!t.effectParams,
|
|
1546
1555
|
...(t.effectParams ? {
|
|
1547
1556
|
effectParams: {
|
|
1548
|
-
keyList: serializeKeyList(t.effectParams.keys),
|
|
1557
|
+
keyList: serializeKeyList(t.effectParams.keys, writer),
|
|
1549
1558
|
fillCanvas: t.effectParams.fillCanvas,
|
|
1550
1559
|
zoomOrigin: t.effectParams.zoomOrigin,
|
|
1551
1560
|
}
|
|
1552
1561
|
} : {}),
|
|
1553
|
-
keyList: serializeKeyList(t.keys),
|
|
1562
|
+
keyList: serializeKeyList(t.keys, writer),
|
|
1554
1563
|
});
|
|
1555
1564
|
}
|
|
1556
1565
|
|
|
@@ -1561,7 +1570,7 @@ type AllEffects = LayerEffectShadow & LayerEffectsOuterGlow & LayerEffectStroke
|
|
|
1561
1570
|
LayerEffectInnerGlow & LayerEffectBevel & LayerEffectSolidFill &
|
|
1562
1571
|
LayerEffectPatternOverlay & LayerEffectSatin & LayerEffectGradientOverlay;
|
|
1563
1572
|
|
|
1564
|
-
function parseEffectObject(obj: any,
|
|
1573
|
+
function parseEffectObject(obj: any, reader: PsdReader, objName?: string) {
|
|
1565
1574
|
const result: AllEffects = {} as any;
|
|
1566
1575
|
|
|
1567
1576
|
for (const key of Object.keys(obj)) {
|
|
@@ -1636,14 +1645,14 @@ function parseEffectObject(obj: any, reportErrors: boolean, objName?: string) {
|
|
|
1636
1645
|
case '_classID':
|
|
1637
1646
|
break;
|
|
1638
1647
|
default:
|
|
1639
|
-
|
|
1648
|
+
reader.logMissingFeatures && reader.log(`Invalid effect key: '${key}', value:`, val);
|
|
1640
1649
|
}
|
|
1641
1650
|
}
|
|
1642
1651
|
|
|
1643
1652
|
return result;
|
|
1644
1653
|
}
|
|
1645
1654
|
|
|
1646
|
-
function serializeEffectObject(obj: any, objName: string,
|
|
1655
|
+
function serializeEffectObject(obj: any, objName: string, writer: PsdWriter) {
|
|
1647
1656
|
const result: any = {
|
|
1648
1657
|
enab: false,
|
|
1649
1658
|
};
|
|
@@ -1720,7 +1729,7 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1720
1729
|
result[key] = val;
|
|
1721
1730
|
break;
|
|
1722
1731
|
default:
|
|
1723
|
-
|
|
1732
|
+
writer.logMissingFeatures && writer.log(`Invalid effect key: '${key}', value:`, val);
|
|
1724
1733
|
}
|
|
1725
1734
|
}
|
|
1726
1735
|
|
|
@@ -1948,6 +1957,23 @@ function isBookColor(color: any) {
|
|
|
1948
1957
|
}
|
|
1949
1958
|
|
|
1950
1959
|
export function parseColor(color: DescriptorColor): Color {
|
|
1960
|
+
const result = parseColorValues(color);
|
|
1961
|
+
|
|
1962
|
+
if (isBookColor(color)) {
|
|
1963
|
+
const book = color as any as { 'Bk '?: string; 'Nm '?: string; bookID?: number; bookKey?: Uint8Array | string; };
|
|
1964
|
+
const key = book.bookKey;
|
|
1965
|
+
result.book = {
|
|
1966
|
+
book: book['Bk '] ?? '',
|
|
1967
|
+
name: book['Nm '] ?? '',
|
|
1968
|
+
bookID: book.bookID ?? 0,
|
|
1969
|
+
bookKey: typeof key === 'string' ? key : (key ? String.fromCharCode(...Array.from(key)) : ''),
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
return result;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
function parseColorValues(color: DescriptorColor): Color {
|
|
1951
1977
|
if ('H ' in color) {
|
|
1952
1978
|
return { h: parsePercentOrAngle(color['H ']), s: color.Strt / 100, b: color.Brgh / 100 };
|
|
1953
1979
|
} else if ('Rd ' in color) {
|
|
@@ -1966,13 +1992,26 @@ export function parseColor(color: DescriptorColor): Color {
|
|
|
1966
1992
|
} else if ('redFloat' in color) {
|
|
1967
1993
|
return { fr: color.redFloat, fg: color.greenFloat, fb: color.blueFloat };
|
|
1968
1994
|
} else if (isBookColor(color)) {
|
|
1969
|
-
return { r: 0, g: 0, b: 0 }; // book (Pantone)
|
|
1995
|
+
return { r: 0, g: 0, b: 0 }; // book (Pantone) color without color values
|
|
1970
1996
|
} else {
|
|
1971
1997
|
throw new Error('Unsupported color descriptor');
|
|
1972
1998
|
}
|
|
1973
1999
|
}
|
|
1974
2000
|
|
|
1975
2001
|
export function serializeColor(color: Color | undefined): DescriptorColor {
|
|
2002
|
+
const result = serializeColorValues(color);
|
|
2003
|
+
|
|
2004
|
+
if (color?.book) {
|
|
2005
|
+
const { book, name, bookID, bookKey } = color.book;
|
|
2006
|
+
const key = new Uint8Array(bookKey.length);
|
|
2007
|
+
for (let i = 0; i < key.length; i++) key[i] = bookKey.charCodeAt(i) & 0xff;
|
|
2008
|
+
Object.assign(result, { 'Bk ': book, 'Nm ': name, bookID, bookKey: key });
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
return result;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
function serializeColorValues(color: Color | undefined): DescriptorColor {
|
|
1976
2015
|
if (!color) {
|
|
1977
2016
|
return { _name: '', _classID: 'RGBC', 'Rd ': 0, 'Grn ': 0, 'Bl ': 0 };
|
|
1978
2017
|
} else if ('r' in color) {
|
|
@@ -2455,3 +2494,9 @@ export const presetKindType = createEnum<'custom' | 'default'>('presetKindType',
|
|
|
2455
2494
|
custom: 'presetKindCustom',
|
|
2456
2495
|
default: 'presetKindDefault',
|
|
2457
2496
|
});
|
|
2497
|
+
|
|
2498
|
+
export const patternColorHandling = createEnum<'convert' | 'avoid conversion' | 'luminance only'>('', 'convert', {
|
|
2499
|
+
'convert': 'conv',
|
|
2500
|
+
'avoid conversion': 'avod',
|
|
2501
|
+
'luminance only': 'lumi',
|
|
2502
|
+
});
|
package/src/helpers.ts
CHANGED
|
@@ -40,7 +40,7 @@ export const toBlendMode: { [key: string]: BlendMode } = {
|
|
|
40
40
|
Object.keys(toBlendMode).forEach(key => fromBlendMode[toBlendMode[key]] = key);
|
|
41
41
|
|
|
42
42
|
export const layerColors: LayerColor[] = [
|
|
43
|
-
'none', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'gray'
|
|
43
|
+
'none', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'gray', 'seafoam', 'indigo', 'magenta', 'fuchsia',
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
// keys not listed in documentation that Photoshop writes with 64bit length in PSB files (with 8B64 signature)
|
|
@@ -165,9 +165,10 @@ export function clamp(value: number, min: number, max: number) {
|
|
|
165
165
|
|
|
166
166
|
export function hasAlpha(data: PixelData) {
|
|
167
167
|
const size = data.width * data.height * 4;
|
|
168
|
+
const max = data.data instanceof Float32Array ? 1 : (data.data instanceof Uint16Array ? 0xffff : 0xff);
|
|
168
169
|
|
|
169
170
|
for (let i = 3; i < size; i += 4) {
|
|
170
|
-
if (data.data[i] !==
|
|
171
|
+
if (data.data[i] !== max) {
|
|
171
172
|
return true;
|
|
172
173
|
}
|
|
173
174
|
}
|
|
@@ -175,6 +176,154 @@ export function hasAlpha(data: PixelData) {
|
|
|
175
176
|
return false;
|
|
176
177
|
}
|
|
177
178
|
|
|
179
|
+
// Polynomial approximation of U.S. Web Coated (SWOP) v2 CMYK to RGB conversion (default CMYK profile of Photoshop).
|
|
180
|
+
// Coefficients are taken from pdf.js (DeviceCmykCS in src/core/colorspace.js, https://github.com/mozilla/pdf.js),
|
|
181
|
+
// Copyright 2012 Mozilla Foundation, licensed under the Apache License, Version 2.0
|
|
182
|
+
// (http://www.apache.org/licenses/LICENSE-2.0)
|
|
183
|
+
// c, m, y, k - ink values (0 - 1), output - RGB values (0 - 255, not rounded or clamped)
|
|
184
|
+
export function cmykToRgbValues(c: number, m: number, y: number, k: number, out: number[]) {
|
|
185
|
+
out[0] = 255 +
|
|
186
|
+
c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 285.2331026137004) +
|
|
187
|
+
m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 5.497006427196366) +
|
|
188
|
+
y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) +
|
|
189
|
+
k * (-21.86122147463605 * k - 189.48180835922747);
|
|
190
|
+
out[1] = 255 +
|
|
191
|
+
c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 79.2970844816548) +
|
|
192
|
+
m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) +
|
|
193
|
+
y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
|
|
194
|
+
k * (-20.737325471181034 * k - 187.80453709719578);
|
|
195
|
+
out[2] = 255 +
|
|
196
|
+
c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 14.183576799673286) +
|
|
197
|
+
m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) +
|
|
198
|
+
y * (0.03296041114873217 * y + 115.60384449646641 * k - 193.58209356861505) +
|
|
199
|
+
k * (-22.33816807309886 * k - 180.12613974708367);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const clamp01 = (v: number) => v < 0 ? 0 : (v > 1 ? 1 : v);
|
|
203
|
+
|
|
204
|
+
// finds c, m, y (with fixed k) for given RGB color using Gauss-Newton method, returns squared error
|
|
205
|
+
function solveCmy(r: number, g: number, b: number, k: number, x: number[]) {
|
|
206
|
+
const f = [0, 0, 0], f2 = [0, 0, 0], e = [0, 0, 0];
|
|
207
|
+
const J = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
208
|
+
const h = 1e-4;
|
|
209
|
+
|
|
210
|
+
for (let iteration = 0; iteration < 20; iteration++) {
|
|
211
|
+
cmykToRgbValues(x[0], x[1], x[2], k, f);
|
|
212
|
+
e[0] = f[0] - r; e[1] = f[1] - g; e[2] = f[2] - b;
|
|
213
|
+
if ((e[0] * e[0] + e[1] * e[1] + e[2] * e[2]) < 1e-6) break;
|
|
214
|
+
|
|
215
|
+
for (let j = 0; j < 3; j++) {
|
|
216
|
+
const s = x[j];
|
|
217
|
+
x[j] = s + h;
|
|
218
|
+
cmykToRgbValues(x[0], x[1], x[2], k, f2);
|
|
219
|
+
x[j] = s;
|
|
220
|
+
for (let i = 0; i < 3; i++) J[i][j] = (f2[i] - f[i]) / h;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const [[a, b_, c], [d, e_, f_], [g_, h_, i_]] = J;
|
|
224
|
+
const det = a * (e_ * i_ - f_ * h_) - b_ * (d * i_ - f_ * g_) + c * (d * h_ - e_ * g_);
|
|
225
|
+
if (Math.abs(det) < 1e-12) break;
|
|
226
|
+
|
|
227
|
+
const inverse = [
|
|
228
|
+
[(e_ * i_ - f_ * h_), (c * h_ - b_ * i_), (b_ * f_ - c * e_)],
|
|
229
|
+
[(f_ * g_ - d * i_), (a * i_ - c * g_), (c * d - a * f_)],
|
|
230
|
+
[(d * h_ - e_ * g_), (b_ * g_ - a * h_), (a * e_ - b_ * d)],
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
let changed = 0;
|
|
234
|
+
|
|
235
|
+
for (let j = 0; j < 3; j++) {
|
|
236
|
+
const delta = -(inverse[j][0] * e[0] + inverse[j][1] * e[1] + inverse[j][2] * e[2]) / det;
|
|
237
|
+
const value = clamp01(x[j] + delta);
|
|
238
|
+
changed += Math.abs(value - x[j]);
|
|
239
|
+
x[j] = value;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (changed < 1e-9) break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
cmykToRgbValues(x[0], x[1], x[2], k, f);
|
|
246
|
+
return (f[0] - r) * (f[0] - r) + (f[1] - g) * (f[1] - g) + (f[2] - b) * (f[2] - b);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// finds CMYK ink values (0 - 1) for RGB color (0 - 255), out of gamut colors are approximated
|
|
250
|
+
function rgbToCmykExact(r: number, g: number, b: number, out: number[]) {
|
|
251
|
+
const max = Math.max(r, g, b) / 255;
|
|
252
|
+
const blackGeneration = Math.pow(clamp01((1 - max - 0.2) / 0.8), 1.3);
|
|
253
|
+
const x = [0, 0, 0];
|
|
254
|
+
let bestScore = Infinity, bestError = Infinity;
|
|
255
|
+
|
|
256
|
+
const tryBlack = (k: number) => {
|
|
257
|
+
x[0] = clamp01(1 - r / 255 - k);
|
|
258
|
+
x[1] = clamp01(1 - g / 255 - k);
|
|
259
|
+
x[2] = clamp01(1 - b / 255 - k);
|
|
260
|
+
const error = solveCmy(r, g, b, k, x);
|
|
261
|
+
// prefer solutions close to black generation value if the error is small enough
|
|
262
|
+
const score = Math.max(error, 0.1) + Math.abs(k - blackGeneration) * 0.01;
|
|
263
|
+
|
|
264
|
+
if (score < bestScore) {
|
|
265
|
+
bestScore = score;
|
|
266
|
+
bestError = error;
|
|
267
|
+
out[0] = x[0]; out[1] = x[1]; out[2] = x[2]; out[3] = k;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
tryBlack(blackGeneration);
|
|
272
|
+
|
|
273
|
+
if (bestError > 0.1) {
|
|
274
|
+
for (let i = 0; i <= 10; i++) tryBlack(i / 10);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (bestError > 0.1) {
|
|
278
|
+
const k = out[3];
|
|
279
|
+
for (let i = -5; i <= 5; i++) tryBlack(clamp01(k + i / 100));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const cmykLutSize = 33;
|
|
284
|
+
let cmykLut: Float32Array | undefined;
|
|
285
|
+
|
|
286
|
+
function getCmykLut() {
|
|
287
|
+
if (!cmykLut) {
|
|
288
|
+
const n = cmykLutSize;
|
|
289
|
+
const lut = new Float32Array(n * n * n * 4);
|
|
290
|
+
const out = [0, 0, 0, 0];
|
|
291
|
+
|
|
292
|
+
for (let r = 0, o = 0; r < n; r++) {
|
|
293
|
+
for (let g = 0; g < n; g++) {
|
|
294
|
+
for (let b = 0; b < n; b++, o += 4) {
|
|
295
|
+
rgbToCmykExact(r * 255 / (n - 1), g * 255 / (n - 1), b * 255 / (n - 1), out);
|
|
296
|
+
lut[o] = out[0]; lut[o + 1] = out[1]; lut[o + 2] = out[2]; lut[o + 3] = out[3];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
cmykLut = lut;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return cmykLut;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// inverse of cmykToRgbValues, r, g, b - RGB values (0 - 1), output - CMYK ink values (0 - 1)
|
|
308
|
+
export function rgbToCmykValues(r: number, g: number, b: number, out: number[]) {
|
|
309
|
+
const lut = getCmykLut();
|
|
310
|
+
const n1 = cmykLutSize - 1;
|
|
311
|
+
const fr = clamp01(r) * n1, fg = clamp01(g) * n1, fb = clamp01(b) * n1;
|
|
312
|
+
const r0 = Math.min(n1 - 1, Math.floor(fr)), g0 = Math.min(n1 - 1, Math.floor(fg)), b0 = Math.min(n1 - 1, Math.floor(fb));
|
|
313
|
+
const dr = fr - r0, dg = fg - g0, db = fb - b0;
|
|
314
|
+
out[0] = out[1] = out[2] = out[3] = 0;
|
|
315
|
+
|
|
316
|
+
for (let i = 0; i < 8; i++) {
|
|
317
|
+
const ir = i >> 2, ig = (i >> 1) & 1, ib = i & 1;
|
|
318
|
+
const w = (ir ? dr : 1 - dr) * (ig ? dg : 1 - dg) * (ib ? db : 1 - db);
|
|
319
|
+
const o = (((r0 + ir) * cmykLutSize + g0 + ig) * cmykLutSize + b0 + ib) * 4;
|
|
320
|
+
out[0] += w * lut[o];
|
|
321
|
+
out[1] += w * lut[o + 1];
|
|
322
|
+
out[2] += w * lut[o + 2];
|
|
323
|
+
out[3] += w * lut[o + 3];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
178
327
|
export function resetImageData({ data }: PixelData) {
|
|
179
328
|
const alpha = (data instanceof Float32Array) ? 1.0 : ((data instanceof Uint16Array) ? 0xffff : 0xff);
|
|
180
329
|
|
|
@@ -353,6 +502,117 @@ export function writeDataRLE(buffer: Uint8Array, { data, width, height }: PixelD
|
|
|
353
502
|
return buffer.subarray(0, o);
|
|
354
503
|
}
|
|
355
504
|
|
|
505
|
+
// single channel of image data (8bit, 16bit or 32bit values)
|
|
506
|
+
export type PlanarData = Uint8Array | Uint16Array | Float32Array;
|
|
507
|
+
|
|
508
|
+
// converts channel values to big-endian bytes
|
|
509
|
+
export function planarToBytes(values: PlanarData): Uint8Array {
|
|
510
|
+
if (values instanceof Uint8Array) return values;
|
|
511
|
+
|
|
512
|
+
const bytes = new Uint8Array(values.byteLength);
|
|
513
|
+
const view = new DataView(bytes.buffer);
|
|
514
|
+
|
|
515
|
+
if (values instanceof Uint16Array) {
|
|
516
|
+
for (let i = 0; i < values.length; i++) view.setUint16(i * 2, values[i], false);
|
|
517
|
+
} else {
|
|
518
|
+
for (let i = 0; i < values.length; i++) view.setFloat32(i * 4, values[i], false);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return bytes;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// RLE (PackBits) compression of rows of bytes, prefixed with byte counts of each row
|
|
525
|
+
export function writeDataRLEBytes(bytes: Uint8Array, rowBytes: number, height: number, large: boolean) {
|
|
526
|
+
const countSize = large ? 4 : 2;
|
|
527
|
+
const buffer = new Uint8Array(height * (countSize + rowBytes + Math.ceil(rowBytes / 128)));
|
|
528
|
+
let o = countSize * height;
|
|
529
|
+
|
|
530
|
+
for (let y = 0; y < height; y++) {
|
|
531
|
+
const start = o;
|
|
532
|
+
const rowStart = y * rowBytes;
|
|
533
|
+
const rowEnd = rowStart + rowBytes;
|
|
534
|
+
let i = rowStart;
|
|
535
|
+
|
|
536
|
+
while (i < rowEnd) {
|
|
537
|
+
let run = 1;
|
|
538
|
+
while (i + run < rowEnd && run < 128 && bytes[i + run] === bytes[i]) run++;
|
|
539
|
+
|
|
540
|
+
if (run > 1) {
|
|
541
|
+
buffer[o++] = 257 - run;
|
|
542
|
+
buffer[o++] = bytes[i];
|
|
543
|
+
i += run;
|
|
544
|
+
} else {
|
|
545
|
+
// literal bytes until a run of at least 3 equal bytes
|
|
546
|
+
const countIndex = o++;
|
|
547
|
+
let count = 0;
|
|
548
|
+
|
|
549
|
+
while (i < rowEnd && count < 128) {
|
|
550
|
+
if (i + 2 < rowEnd && bytes[i] === bytes[i + 1] && bytes[i] === bytes[i + 2]) break;
|
|
551
|
+
buffer[o++] = bytes[i++];
|
|
552
|
+
count++;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
buffer[countIndex] = count - 1;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const length = o - start;
|
|
560
|
+
|
|
561
|
+
for (let j = 0; j < countSize; j++) {
|
|
562
|
+
buffer[y * countSize + j] = (length >>> ((countSize - j - 1) * 8)) & 0xff;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return buffer.subarray(0, o);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// zip compression with prediction (values are delta encoded per row, 32bit values are split into byte planes first)
|
|
570
|
+
export function writeDataZipWithPrediction(values: PlanarData, width: number, height: number) {
|
|
571
|
+
let bytes: Uint8Array;
|
|
572
|
+
|
|
573
|
+
if (values instanceof Uint8Array) {
|
|
574
|
+
bytes = new Uint8Array(width * height);
|
|
575
|
+
|
|
576
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
577
|
+
for (let x = 0, prev = 0; x < width; x++, i++) {
|
|
578
|
+
bytes[i] = values[i] - prev; // Uint8Array wraps values on assignment
|
|
579
|
+
prev = values[i];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} else if (values instanceof Uint16Array) {
|
|
583
|
+
bytes = new Uint8Array(width * height * 2);
|
|
584
|
+
|
|
585
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
586
|
+
for (let x = 0, prev = 0; x < width; x++, i++) {
|
|
587
|
+
const delta = (values[i] - prev) & 0xffff;
|
|
588
|
+
bytes[i * 2] = delta >> 8;
|
|
589
|
+
bytes[i * 2 + 1] = delta & 0xff;
|
|
590
|
+
prev = values[i];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
const rowBytes = width * 4;
|
|
595
|
+
const source = planarToBytes(values);
|
|
596
|
+
bytes = new Uint8Array(rowBytes * height);
|
|
597
|
+
|
|
598
|
+
for (let y = 0; y < height; y++) {
|
|
599
|
+
const row = y * rowBytes;
|
|
600
|
+
|
|
601
|
+
for (let x = 0; x < width; x++) {
|
|
602
|
+
for (let b = 0; b < 4; b++) {
|
|
603
|
+
bytes[row + b * width + x] = source[row + x * 4 + b];
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
for (let x = rowBytes - 1; x > 0; x--) {
|
|
608
|
+
bytes[row + x] -= bytes[row + x - 1];
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return deflateSync(bytes);
|
|
614
|
+
}
|
|
615
|
+
|
|
356
616
|
export function writeDataZipWithoutPrediction({ data, width, height }: PixelData, offsets: number[]) {
|
|
357
617
|
const size = width * height;
|
|
358
618
|
const channel = new Uint8Array(size);
|