diff --git a/src/engineData2.ts b/src/engineData2.ts index 8352822..a67801f 100644 --- a/src/engineData2.ts +++ b/src/engineData2.ts @@ -401,7 +401,7 @@ const keysRoot: KeysDict = { }, }; -function decodeObj(obj: any, keys: KeysDict): any { +function decodeObj(obj: EngineData, keys: KeysDict): any { if (obj === null) return obj; if (Array.isArray(obj)) return obj.map(x => decodeObj(x, keys)); if (typeof obj !== 'object') return obj; diff --git a/src/psdReader.ts b/src/psdReader.ts index 981e31b..e5fc181 100644 --- a/src/psdReader.ts +++ b/src/psdReader.ts @@ -3,13 +3,17 @@ import { Psd, Layer, ColorMode, SectionDividerType, LayerAdditionalInfo, ReadOpt import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, ChannelID, Compression, LayerMaskFlags, MaskParams, ColorSpace, RAW_IMAGE_DATA, largeAdditionalInfoKeys, imageDataToCanvas } from './helpers'; import { infoHandlersMap } from './additionalInfo'; import { resourceHandlersMap } from './imageResources'; +import { parseEngineData } from './engineData'; +import { toByteArray } from 'base64-js'; +import { decodeEngineData2 } from './engineData2'; +import type { EngineData } from './text'; interface ChannelInfo { id: ChannelID; length: number; } -export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB, ColorMode.Indexed]; +export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB, ColorMode.Indexed, ColorMode.CMYK]; const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', 'multichannel', 'duotone', 'lab']; function setupGrayscale(data: PixelData) { @@ -345,6 +349,14 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) { // but add option to preserve file color mode (need to return image data instead of canvas in that case) // psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB + if(psd.engineData){ + const byteArray = toByteArray(psd.engineData); + const engineData = parseEngineData(byteArray); + const parsedEngineData = decodeEngineData2(engineData); + + assignGlobalEngineData(psd.children, parsedEngineData); + } + return psd; } @@ -705,6 +717,31 @@ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAddition }, false, u64); } +/** + * There is a Global text engine data outside text element. + * So, we need to pick global engine data and set to per text element. + */ +function assignGlobalEngineData(layers: Layer[] | undefined, globalEngineData: EngineData) { + const resources = globalEngineData?.ResourceDict?.TextFrameSet; + + if (!resources || Object.keys(resources).length === 0 || !layers?.length) { + return; + } + const resourceLength = Object.keys(resources).length; + let textIndex = 0; + layers.forEach((layer) => { + const isText = !!layer.text; + if (isText) { + if (textIndex < resourceLength) { + const resource = resources[textIndex++]; + layer.text!.textPath = resource.TextPath; + } else { + console.warn('Not enough resources for all text layers'); + } + } + }); +} + export function createImageDataBitDepth(width: number, height: number, bitDepth: number, channels = 4): PixelData { if (bitDepth === 1 || bitDepth === 8) { if (channels === 4) { @@ -803,10 +840,11 @@ function readImageData(reader: PsdReader, psd: Psd) { } case ColorMode.CMYK: { if (bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted'); - if (psd.channels !== 4) throw new Error(`Invalid channel count`); + if (psd.channels !== 4 && psd.channels !== 5) throw new Error(`Invalid channel count`); + const channelLen = psd.channels! + 1; const channels = [0, 1, 2, 3]; - if (reader.globalAlpha) channels.push(4); + if (psd.channels === 5 || reader.globalAlpha) channels.push(4); if (compression === Compression.RawData) { throw new Error(`Not implemented`); @@ -818,12 +856,12 @@ function readImageData(reader: PsdReader, psd: Psd) { const cmykImageData: PixelData = { width: imageData.width, height: imageData.height, - data: new Uint8Array(imageData.width * imageData.height * 5), + data: new Uint8Array(imageData.width * imageData.height * channelLen), }; const start = reader.offset; - readDataRLE(reader, cmykImageData, psd.width, psd.height, bitsPerChannel, 5, channels, reader.large); - cmykToRgb(cmykImageData, imageData, true); + readDataRLE(reader, cmykImageData, psd.width, psd.height, bitsPerChannel, channelLen, channels, reader.large); + cmykToRgb(cmykImageData, imageData, false, channelLen); if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start); } else { @@ -860,19 +898,19 @@ function readImageData(reader: PsdReader, psd: Psd) { } } -function cmykToRgb(cmyk: PixelData, rgb: PixelData, reverseAlpha: boolean) { +function cmykToRgb(cmyk: PixelData, rgb: PixelData, reverseAlpha: boolean, channelLen: number = 5) { const size = rgb.width * rgb.height * 4; const srcData = cmyk.data; const dstData = rgb.data; - for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) { - const c = srcData[src]; - const m = srcData[src + 1]; - const y = srcData[src + 2]; - const k = srcData[src + 3]; - dstData[dst] = ((((c * k) | 0) / 255) | 0); - dstData[dst + 1] = ((((m * k) | 0) / 255) | 0); - dstData[dst + 2] = ((((y * k) | 0) / 255) | 0); + for (let src = 0, dst = 0; dst < size; src += channelLen, dst += 4) { + const c = 255 - srcData[src]; + const m = 255 - srcData[src + 1]; + const y = 255 - srcData[src + 2]; + const k = 255 - srcData[src + 3]; + dstData[dst + 0] = ((65535 - (c * (255 - k) + (k << 8))) >> 8) | 0; + dstData[dst + 1] = ((65535 - (m * (255 - k) + (k << 8))) >> 8) | 0; + dstData[dst + 2] = ((65535 - (y * (255 - k) + (k << 8))) >> 8) | 0; dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4]; } diff --git a/src/text.ts b/src/text.ts index 2336001..53f22de 100644 --- a/src/text.ts +++ b/src/text.ts @@ -97,6 +97,7 @@ interface ResourceDict { SubscriptSize: number; SubscriptPosition: number; SmallCapSize: number; + TextFrameSet?: any[]; } interface ParagraphRun {