webtoon / psd Goto Github PK
View Code? Open in Web Editor NEWFast zero-dependency PSD parser for the web and Node.js
Home Page: https://webtoon.github.io/psd
License: MIT License
Fast zero-dependency PSD parser for the web and Node.js
Home Page: https://webtoon.github.io/psd
License: MIT License
Hi there, great project and the 🚀 is impressive!
I have one question related to masked layers. Basically, I would like to retrieve an image buffer for the mask data (similar to retrieving the composite of the Psd file or the Layer, as an Uint8ClampedArray
).
However, it's not clear how to consume this data. I understand that I can retrieve the masks bounding box reading the layer.maskData
Objects left, right, top, bottom values. I also see that there is an ArrayBuffer inside the layer.layerFrame.userMask.data
Object.
However, the length of said buffer does not match with the size of the mask (as it expected to be width * height * 4 in length). I was thinking that this buffer contains the alpha channel or gray scale data only, however after converting this to fit in an RGBA sized buffer, the rendered data does not reflect the original masks shape, with pixels basically all over the place. I also noticed that the Layer class has an async method getUserMask|getRealUserMask
which seems to return RGBA-sized values, but once more the rendered data does not reflect the masks shape.
As such, I'm not sure how to read and render the mask buffer object. Is there something obvious I'm overlooking with regards to the mask size ?
its possible update a text in layer?
Example..
I have a layer with text "Hello World", I like the change to another text.
Its possible the make this using this lib?
Thanks.
Hi there,
Can someone help me build this feature for a client,
I want a way to get all smart objects inside the psd, and then generate file inputs for each smart object, and when the user uploads an image, i want to display live changes
This is a discussion topic and a work in progress.
We should specify our project goals and/or principles to help us focus on the right things.
Some suggestions based on our work so far:
Ideally, the project goals should be part of the documention, perhaps in a CONTRIBUTING.md
file. We may also put them in the project wiki (currently not enabled).
For now, we can link to this issue when we mention our project goals and/or principles.
Regression of bug that was fixed by f4fc949, but re-emerged after merging #54.
Please see #54 (comment) for details.
sorry,when i use psd to get the font but i cannot get color of font
In
https://github.com/webtoon/psd/blob/main/packages/psd/src/classes/Group.ts#L74
there is a userMask
accessor on Group, but at runtime, I cannot access it.
It's not in the Group
type, but also not accessible after a to any
cast.
So instead of me going to photopea, uploading psd file, selecting layer, adding an image, resizing to fit the layer, exporting to png...
I would like to do this programatically in javascript (server side or client side). I want to write a function that accepts psd file, image file and options. It should output a .png file. Am I in the right place? Can I do this with "webtoon"? I've found libraries like "psd.js" but still not sure how can I achieve that. Can anyone give me an example? Your help is very much appreciated!
Do you plan to expose layerProperties of Groups in the near future? It would be very handy to get information about the Group's name
, opacity
, and hidden
properties through getters.
let nodeFX = node.get('objectEffects').data;
// data is not defined is the node has no fx options.
This property contains all the informations from the FX (also called blending options) from Photoshop. (e.g. Drop Shadow, Inner Shadow ...).
{
data: {
ebbl: [Object] // Bevel and Emboss
FrFX: [Object] // Stroke
IrSh: [Object] // Inner Shadow
IrGl: [Object] // Inner Glow
ChFX: [Object] // Satin
SoFi: [Object] // Color Overlay
GrFl: [Object] // Gradient Overlay
patternFill: [Object] // Pattern Overlay
OrGl: [Object] // Outer Glov
DrSh: [Object] // Drop Shadow
}
}
Firstly guide on how to install all the dependencies and run the project either in contribution.md or any other readme would help new contributors to onboard.
Secondly, guideline on what or how to contribute would be useful.
Hello,
Is it possible to unhide a hidden layer. I have a psd file and I would like to hide and unhide options based on my confirmation file.
Thanks,
Benchmark for ag-psd occasionally fails with the following error: imageDecodeTime must be a nonnegative number
This is probably happening because of the rather fragile (and broken) way we currently use to measure the image decode time, i.e. by subtracting the parse time (measured separately) from the total render time:
psd/packages/benchmark/src/bench.ts
Lines 101 to 102 in 2bda2cc
The benchmark app is long overdue a rewrite anyway; I'm thinking of writing it in React.
I'm trying to know whether a text is capitalized.
I found the layer.layerFrame.layerProperties.textProperties.ResourceDict.StyleSheetSet.StyleSheetData.FontCaps
property but it is always set to 0
.
Whereas on Photoshop, the text is correctly set to capitalized:
Is there anyone who can help to point out where I can get the correct property to see if a text is capitalized or not? 😄
The main in https://github.com/webtoon/psd/blob/main/package.json#L13 points to dist/index.js
, though when installing via yarn I get: node_modules/@webtoon/psd/dist/main.js
It seems that the dist is pointing to main for esm builds?
Currently, packages in our monorepo have the following dependency graph:
psd-decoder psd <-- benchmark, example-browser, example-node, benchmark
When building a package, we must ensure that its dependencies are built first, and in order. Furthermore, when building a package in watch mode, we must launch watchers for the dependencies as well. Managing this with NPM scripts is tricky and error-prone, and I already had to push fixes such as #26 and #27.
* npm-run-all has been abandoned for some time. npm-run-all2 seems to be the most well-maintained fork.
† Since version 5, Lerna bundles and is powered by Nx, so there's little point in installing it for new projects--we should use Nx directly.
I created this PSD file.psd.zip from this PNG file.png.zip, using https://github.com/dlemstra/magick-wasm . That magick library (for the browser) works on converting PNG to JPG/etc., and I saved the bytes output from the selected browser file to a .psd
file on my computer, and that is the contents of file.psd.zip
. That psd seems to render on my Mac (and with CMD+space it shows large):
That png itself was generated from another png (I'm playing with magick-wasm), if that matters. But it renders as a PSD locally if I say the bytes to a .psd
file, but I get this error in Psd.parse(arrayBuffer)
:
my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:397
throw new Le();
^
Le
at wt (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:397:11)
at fn (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:1188:76)
at Rn (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:1490:13)
at Function.parse (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:1982:15)
at <anonymous> (my-app/tmp/psd.ts:162482:21)
at Object.<anonymous> (my-app/tmp/psd.ts:162490:4)
at Module._compile (node:internal/modules/cjs/loader:1376:14)
at Object.F (my-app/node_modules/.pnpm/@[email protected]/node_modules/@esbuild-kit/cjs-loader/dist/index.js:1:941)
at Module.load (node:internal/modules/cjs/loader:1207:32)
at Module._load (node:internal/modules/cjs/loader:1023:12)
Node.js v20.10.0
This is occurring whether or not I'm calling Psd.parse
in Node.js or the browser. So it fails to parse the PSD, and so I can't render the PSD in the browser.
Any ideas what is wrong with this PNG, or why this isn't parsing?
I get the same error when trying to parse a JPEG as well, so not sure.
My messy code, with the PSD functionality temp hacked in for now:
useEffect(() => {
if (!files?.length) {
return
}
const file = files[0]
setInput(file)
// https://www.npmjs.com/package/wasm-imagemagick
file.arrayBuffer().then(async data => {
const ir = new FileReader()
ir.onloadend = () => {
setInputString(ir.result as string)
}
ir.readAsDataURL(file)
if (data.byteLength <= bytes('16mb')) {
if (outputFormat === 'psd') {
const canvasElement = canvasRef.current
if (canvasElement) {
try {
console.log(bufferToString(await file.arrayBuffer()))
function bufferToString(buf: ArrayBuffer) {
var view = new Uint8Array(buf)
var array: Array<string> = []
Array.prototype.forEach.call(view, item => {
array.push(
'0x' +
(item as number).toString(16).padStart(2, '0'),
)
})
return `[ ${array.join(',\n ')} ]`
}
const psdFile = Psd.parse(await file.arrayBuffer())
const context = canvasElement.getContext('2d')
const compositeBuffer = await psdFile.composite()
const imageData = new ImageData(
compositeBuffer as Uint8ClampedArray,
psdFile.width as number,
psdFile.height as number,
)
setShowCanvas(true)
canvasElement.width = psdFile.width
canvasElement.height = psdFile.height
context?.putImageData(imageData, 0, 0)
} catch (e) {
console.log(e)
}
}
} else {
readWithImageMagickCallback(
new Uint8Array(data),
(err, image) => {
if (err) {
console.log(err)
return setInputError(err.message as string)
} else if (image) {
writeWithImageMagick(image, outputFormat)
.then(out => {
setInputError(undefined)
const path = file.name.split('.')
if (path.length > 1) {
path.pop()
}
const blob = new Blob([out])
setOutput(blob)
setOutputFileName(
`${path.join('.')}.${outputFormat.toLowerCase()}`,
)
// downloadBlob(
// blob,
// `${path.join('.')}.${outputFormat.toLowerCase()}`,
// )
const or = new FileReader()
or.onloadend = () => {
setOutputString(or.result as string)
}
or.readAsDataURL(blob)
})
.catch(error => {
setInputError(error.message as string)
})
}
},
)
}
} else {
setInputError(
`File should be less than 16mb so it doesn't crash the browser.`,
)
}
})
}, [files, outputFormat, canvasRef])
In the documentation it says you can export a layer to pixel data. How would you save that to a PNG? I'm using node js.
// Extract the pixel data of a layer, with all layer and layer group effects applied
var layerPixelData = await layer.composite();
Is there any psd file parsing instructions you can refer to?
I have a trouble in parsing the mask layer and applying it.😫
Could you let me know how can I parse the mask layer and apply it? Is it possible at the moment? 🙏
Regards.
One of features available in psd-tools is ICC profile handling.
This is done by extracting relevant tag and then converting image data after extraction.
This in turns uses Python bindings for littlecms
(https://pypi.org/project/littlecms/). In our case this won't fly - closest thing I was able to find are low-level binding based on emscripten.
So the questions are:
When we migrated to WebAssembly in 0.2.0 (#20, #24), we broke Node.js v14 support. This is due to vitejs/vite#8620. Since the next release of Vite will most likely fix this (v3: vitejs/vite#8622, v2: vitejs/vite#8668), let's wait for the new version.
Alternatively, we could monkey-patch Vite using patch-package to fix this now. I'd rather avoid this drastic option.
I'm attempting to import the library into a node js project and I'm getting some errors. I'm working on getting up to speed on all things node js but I am able to use other libraries using requires. I'm also using Typescript.
At the top of module, myclass.ts
I have:
import Psd from "@webtoon/psd";
This generates this error:
[Error [ERR_REQUIRE_ESM]: Must use import to load ES Module:
require() of ES modules is not supported.
require() of /Users/project/node_modules/@webtoon/psd/dist/index.js from /Users/project/MyProjectClass.ts is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /Users/project/node_modules/@webtoon/psd/package.json.
It doesn't make sense to me what to do with that information.
hi
I'm trying to use this library at server.
When I try to composite psd files, atob reference error is occured.
Also when I change atob to buffer, below error is occured.
TypeError: WebAssembly.instantiate(): Import #0 module="./webtoon_psd_decoder_bg.js" error: module is not an object or functiom
How can I use composite?
Thankyou
is it support shape?
editable photoshop shape, circle square ?? etc.
When we replaced the asm.js image decoder with a WebAssembly-based one (#20), we gained performance and resilience† at the expense of increased bundle size and breaking compatibility. This was fine for our use case--but what if people want to use our library in a runtime that does not support WebAssembly (or asm.js for that matter)? What if bundle size is critical?
† asm.js is dead tech and there are development tools (e.g. esbuild) that do not support it. See #20 (comment)
In our internal experiments, a properly optimized vanilla JS decoder was ~15% slower in Chrome (V8), and ~50% slower in Firefox and Safari. Since Node.js uses V8, we may expect similar performance differences in Node.js. These differences may be acceptable to those who want a tiny bundle--or don't have the luxury of WebAssembly.
Perhaps we could provide two decoders (vanilla JS and WebAssembly) and let the user choose?
Create separate bundles for vanilla JS and WebAssembly, possibly exposing them as separate endpoints:
import Psd from '@webtoon/psd' // WebAssembly
import Psd from '@webtoon/psd/vanilla' // Vanilla JS
Create two parse()
methods and export them:
import { parse, parseWithJs } from '@webtoon/psd'
const psd1 = parse(arrayBuffer)
psd1.composite() // WebAssembly
const psd2 = parseWithJs(arrayBuffer)
psd2.composite() // Vanilla JS
Create two composite()
methods:
import { parse, composite, compositeWithJs } from '@webtoon/psd'
const psd = parse(arrayBuffer)
composite(psd) // WebAssembly
compositeWithJs(psd) // Vanilla JS
We got an internal request regarding the "EngineData" field of the Type Tool Object Setting structure, which encodes information about a text layer.
The Type Tool Object Setting structure is part of the Additional Layer Information record, inside the Layer and Mask Information Section of a PSD file.
PSD
Layer and Mask Information
Layer Info
Layer Records
Additional Layer Information
Type Tool Object Setting
text data (Descriptor classId == "TxLr")
EngineData
The EngineData field is encoded in an undocumented format (nicknamed "EngineData format") invented by Adobe. While @webtoon/psd
does not currently parse this segment, we may have to do so in the future, in order to support additional features related to text layers.
Several PSD parsers can handle the EngineData format:
However, we MAY want to write a custom parser in TypeScript, in accordance with the Zero-Dependency design goal (see #5).
The bundle size of version v3.9.0 of PSD.js is only 123KB, and less than 30KB after compression.
It would be nice to expand our test suite and test our library more rigorously. I don't have a clear plan, so I'm open to suggestions.
Some ideas:
Not sure why the code is not getting sourcemapped to TypeScript (unminified), I see the //#sourceMappingURL=index.js.map in the dist/index.js file, is this fixable? See my error from #100:
my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:397
throw new Le();
^
Le
at wt (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:397:11)
at fn (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:1188:76)
at Rn (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:1490:13)
at Function.parse (my-app/node_modules/.pnpm/@[email protected]/node_modules/@webtoon/psd/dist/index.js:1982:15)
at <anonymous> (my-app/tmp/psd.ts:162482:21)
at Object.<anonymous> (my-app/tmp/psd.ts:162490:4)
at Module._compile (node:internal/modules/cjs/loader:1376:14)
at Object.F (my-app/node_modules/.pnpm/@[email protected]/node_modules/@esbuild-kit/cjs-loader/dist/index.js:1:941)
at Module.load (node:internal/modules/cjs/loader:1207:32)
at Module._load (node:internal/modules/cjs/loader:1023:12)
Node.js v20.10.0
I am using Next.js, do I need to do anything fancy do you think, to get your sourcemaps?
Adobe's PSD file format document claims that there are four compression methods for image channels: uncompressed (0), RLE (1), ZIP (2), ZIP with prediction (3). However, I have yet to find a PSD file that uses compression methods 2 and 3. If they do exist, we may have to support them as well.
I'm using the module very well.
Thank you for working on it.
How can I check the layerID?
I think there are GroupID and Regular-ID(?), but I don't have an LayerID to specify the layer.
Is there a way to check it out?
Please check.
Thank you.
H!
Maybe someone can use this in the future.
I have made some animations inside photoshop, within the same document.
each layer is a frame, and have different sizes.
When you export the layer, then you get different sizes.
With this code, you will keep the document sizes, and the correct layer position in the document.
So you can replay the animation again, inside a game or whatever.
import * as fs from "fs"
import Psd from "@webtoon/psd"
import { createCanvas, createImageData } from 'canvas'
const psdData = fs.readFileSync("talk-sideways.psd")
const psdFile = Psd.parse(psdData.buffer)
let layerPixelData = await layer.composite()
const canvas = createCanvas(psdFile.width, psdFile.height)
const ctx = canvas.getContext('2d')
const image = createImageData(layerPixelData, layer.width, layer.height)
ctx.putImageData(image, layer.left, layer.top)
const out = fs.createWriteStream('out.png')
const stream = canvas.createPNGStream()
stream.pipe(out)
out.on('finish', () => console.log('The PNG file was created.'))
A task that parses a large PSD file can often exceed the 16ms budget in web browsers and block UI updates. To avoid this, it's natural to attempt parsing a PSD file in a Web Worker via postMessage()
and sending the results back. It would be nice to support this use case.
Unfortunately, this is currently impossible because the Psd
object cannot be reliably (de)serialized with the structured clone algorithm. Psd
provides instance methods and getters, which are lost when passed to/from a Web Worker.
We have some ideas, but would still like feedback:
Psd
class (and other classes) into plain, structured clone-able objects
Psd.prototype.serialize()
that generates a structured clone-able implementation, as well as Psd.deserialize()
that reconstructs the original Psd
object
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.