A lightweight Adobe Photoshop .psd/.psb file parser in typescript with zero-dependency for WebBrowser and NodeJS

Overview

@webtoon/psd

A lightweight Adobe Photoshop .psd/.psb file parser in typescript with zero-dependency for WebBrowser and NodeJS

Browser Support

Chrome Firefox Safari Internet Explorer
38 20 10.1 Not Supported

Installation

$ npm install @webtoon/psd

Features

Supported

  • Support large format (.psb)
  • Image / Layer information (size, offset, etc.)
  • Image / Layer pixel data
  • Unicode layer names
  • Image / Layer opacity
  • Text layers string value
  • Guides
  • Slices

🚧 Work in progress

  • Layer effects (shadow, overlays, etc.)

Unsupported

  • Photoshop metadata not directly related to the image

Usage

@webtoon/psd is provided as a pure ECMAScript module.

Web Browsers

Check out the live demo and the the source for web browser.

@webtoon/psd must be bundled with a bundler such as Webpack or Rollup.

@webtoon/psd reads a PSD file as an ArrayBuffer. You can use FileReader or File to load a PSD file:

import Psd from "@webtoon/psd";

const inputEl: HTMLInputElement = document.querySelector("input[type='file']");
inputEl.addEventListener("change", async () => {
  const file = inputEl.files[0];

  const result = await file.arrayBuffer();
  const psdFile = Psd.parse(result);

  const canvasElement = document.createElement("canvas");
  const context = canvasElement.getContext("2d");
  const imageData = new ImageData(
    psdFile.composite(),
    psdFile.width,
    psdFile.height
  );

  canvasElement.width = psdFile.width;
  canvasElement.height = psdFile.height;

  context.putImageData(imageData, 0, 0);
  document.body.append(canvasElement);
});

For performance, we recommend parsing PSD files in a Web Worker rather than the main thread.

NodeJS

@webtoon/psd does not support the Node.js Buffer. You must explicitly supply the underlying ArrayBuffer.

import * as fs from "fs";
import Psd from "@webtoon/psd";

const psdData = fs.readFileSync("./my-file.psd");
// Pass the ArrayBuffer instance inside the Buffer
const psdFile = Psd.parse(psdData.buffer);

Since @webtoon/psd is provided as an ES module, you must use dynamic import() or a bundler to run it in CommonJS code:

const Psd = await import("webtoon/psd.ts");

API Docs

This library provides the Psd class as the default export.

Opening a file

Psd.parse(ArrayBuffer) takes an ArrayBuffer containing a PSD or PSB file and returns a new Psd object.

const psdFile = Psd.parse(myBuffer);

Traversing layers

A Psd object contains a tree of Layer and Group (i.e. layer group) objects.

  • The Psd object provides a children property, which is an array of top-level Layers and Groups.
  • Each Group object provides a children property, which is an array of Layers and Groups that belong immediately under the current layer group .
import Psd, {Group, Layer, Node} from "@webtoon/psd";

// Recursively traverse layers and layer groups
function traverseNode(node: Node) {
  if (node instanceof Group) {
    for (const child of node.children) {
      traverseNode(child);
    }
  } else if (node instanceof Layer) {
    // Do something with layer
  } else {
    throw new Error("Invalid node type");
  }
}

for (const node of psdFile.children) {
  traverseNode(node);
}

The Psd object also provides the layers property, which is an array of all Layers in the image (including nested).

for (const layer of psdFile.layers) {
  doSomething(layer);
}

Retrieving image data

Use Psd.prototype.composite() and Layer.prototype.composite() to retrieve the pixel information for the entire image or an individual layer.

// Extract the pixel data of the entire image
pixelData = psd.composite();

// Extract the pixel data of a layer, with all layer and layer group effects applied
// (currently, only the opacity is supported)
layerPixelData = layer.composite();

// Extract the pixel data of a layer, with only the layer's own effects applied
layerPixelData = layer.composite(true, false);

// Extract the pixel data of a layer, without any effects
layerPixelData = layer.composite(false);

License

@webtoon/psd is released under the MIT license.

Copyright 2021-present NAVER WEBTOON

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Comments
  • feature: Implement reading mask data

    feature: Implement reading mask data

    Hey,

    Over the past month we implemented some features in our fork for the upcoming OpenDesign new release. We didn't have time to discuss them with you properly, so let me know if you want these features or not - in case you do, I'll properly refactor the code and add tests :)

    This is PR 3/3; implementing layer mask properties and decoding.

    mask.psd comes from psd-tools tests (https://github.com/psd-tools/psd-tools/blob/main/tests/psd_files/masks.psd): Python package, released under MIT license.

    opened by scoiatael 9
  • feature: implement EngineData parsing

    feature: implement EngineData parsing

    fixes https://github.com/webtoon/psd/issues/6

    This feature should be more or less finished - during the code review I'll run it on sample designs to verify parsing works consistently.

    opened by scoiatael 6
  • Opening a PSD file throws

    Opening a PSD file throws "invalid typed array length" error

    I'm trying to open a PSD file i haved created from Photopea into the demo and after selecting the file, the preview did not worked and instead the demo threw an error:

    image

    When dissecting the stack trace, it leads me to this code which is in line ~~253~~ 254: image

    Hope it helps :wink: hoping to use it for a project

    opened by nedpals 4
  • visible always true ?

    visible always true ?

    Hi 👋 I'm trying to get information from .psb about whether the layer is disabled or not, for some reason I always get true, is this normal? 2022-04-26_03-38 2022-04-26_03-39 or am I looking somewhere wrong ?

    opened by ServOKio 4
  • ICC profile

    ICC profile

    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:

    • should this library handle the conversion or only expose information for ICC (and let users worry about converting themselves)? If former, is importing emscripten littlecms an ok solution? I'd rather create bindings ourselves than import npm library of dubious quality.
    • can I start working on it? :) I suppose the first step would be to look into alternative libraries for conversion.
    opened by scoiatael 3
  • [api change] Make additionalProperties a Record

    [api change] Make additionalProperties a Record

    Sorry I didn't make this work when introducing this feature, but I didn't know enough TypeScript. Anyhow, Record type is better here, since it makes checking keys much more efficient.

    Also, a couple of additional quality-of-life-changes:

    • expose AliKey as part of public API,
    • expose additionalProperties on Group (things like ArtboardData are on Group, not Layer).

    Let me know WDYT :)

    type: enhancement 
    opened by scoiatael 2
  • Regression: Additional Layer Info blocks are parsed incorrectly

    Regression: Additional Layer Info blocks are parsed incorrectly

    Regression of bug that was fixed by f4fc9497d2826aeaed17910194dc9e8f7130ebe9, but re-emerged after merging #54.

    Please see https://github.com/webtoon/psd/pull/54#discussion_r999429884 for details.

    type: bug 
    opened by pastelmind 2
  • Proper instructions for contributions is needed

    Proper instructions for contributions is needed

    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.

    type: documentation 
    opened by sifatul 2
  • Psd.composite() note about

    Psd.composite() note about "Maximize Compatibility"

    AFAIK only the pixel data of the entire image needs the PSD/PSB to be saved with "Maximize Compatibility". I've tested this.

    I didn't test for layer.composite(). I suspect layers don't need it.

    By the way: beautiful library!! I've just tested it with ZMOK coming from psd.js and made really noticeable speed improvements. Impressive work 🎉

    opened by Arecsu 2
  • fix: flag used for locked/visible layers

    fix: flag used for locked/visible layers

    This fixes the flag used for locked vs visible layers, I have a basic psd (PS CC latest) with three layers, one locked, one visible, one not visible. The flags returned look like the following:

    
    Locked layer flags:  00001001 
    Visible layer flags: 00001000 
    Hidden layer flags:  00001010 
    

    Currently it's looking for the visible flag in position 7, rather than 6.

    opened by stevelacy 2
  • How can I check the LayerId?

    How can I check the LayerId?

    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.

    opened by juntf 2
  • I need to check if group is hidden.  Layer has a isHidden proeprty, but group does not.

    I need to check if group is hidden. Layer has a isHidden proeprty, but group does not.

    I need to check if group is hidden.

    Layer has a hidden attribute, but group does not.

    // Layer
      get isHidden(): boolean {
        return this.layerFrame.layerProperties.hidden;
      }
    //  I want in Group
      get isHidden(): boolean {
        return this.layerFrame?.layerProperties.hidden ?? true;
      }
    
    image
    opened by daybrush 1
  • Keep the document width and height , when exporting a layer (animations)

    Keep the document width and height , when exporting a layer (animations)

    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.'))
    
    opened by gcmartijn 0
  • Help importing project into node js project

    Help importing project into node js project

    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.

    opened by velara3 1
  • How to save a layer to a image file

    How to save a layer to a image file

    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();
    
    opened by velara3 2
  • upload psd parse error

    upload psd parse error

    image image

    image

    I uploaded a psd file and reported an error when parsing. The error screenshot is shown above. After I open the source code to modify the code and recompile, it can be used normally. The location of the modified source code is as follows image

    Here is my psd file

    test_psd.zip

    opened by more-strive 0
  • layerProperties of the Group are not accessible

    layerProperties of the Group are not accessible

    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.

    opened by Maciej001 0
Releases(0.3.0)
  • 0.3.0(Oct 20, 2022)

    0.3.0 (2022-10-20)

    Features

    • AdditionalLayerInfos; LinkedLayers (#54) (bf9540b) - Thanks @scoiatael
    • expose ICC profile (aa7679e) - Thanks @scoiatael
    • Implement additional ImageResources (#55) (1d1e702) - Thanks @scoiatael
    • implement EngineData parsing (8c5f09c) - Thanks @scoiatael
    • Implement reading mask data (#56) (0f99674) - Thanks @scoiatael

    Bug Fixes

    • ALI block 'cinf' has diffent size for PSB (70ffd71)
    • Don't align ALI block size to 4 byte boundary (f4fc949)
    • handle empty channel data (9cd2d8f)
    • left sidebar width in wider screen (702eeed) - Thanks @sifatul
    • readme update. need to fileVariable name instead of module name (1b6d1ca) - Thanks @sifatul
    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Jun 20, 2022)

    0.2.0 (2022-06-20)

    ⚠ BREAKING CHANGES

    • The image decoder in @webtoon/psd now uses WebAssembly instead of asm.js. This makes the bundle significantly larger (~73 KiB), as well as requiring runtimes that support WebAssembly. On the plus side, not using asm.js enables using esbuild or Vite without hurting performance due to deoptimized code.
    • Parse layer hidden, transparency lock flags
    • Remove named export 'parse()'
    • Export Group, Layer, Slice as types only

    Features

    • export Group, Layer, Slice as types only (564b5a5)
    • implement Rust/WebAssembly-based decoder (020f7a0)
    • init WASM during decode, no top-level init (0b0bb68)
    • parse layer hidden, transparency lock flags (1caf69b)

    Code Refactoring

    • remove named export 'parse()' (86af282)
    Source code(tar.gz)
    Source code(zip)
  • 0.1.2(Jun 3, 2022)

  • 0.1.1(May 9, 2022)

    • chore: update dependencies 20220509 (0ced7d0)
    • Update README.md (98c7cd7)
    • ci: setup auto-deploy GitHub Pages (dde2e47)
    • docs: add badges w/ links (bb8e2de)
    • docs: add intro paragraph and logo (d015f92)
    • feat: check for compat with Edge 79+, Node 12+ (ff8f310)
    • feat(benchmark): provide sample PSD file w/ download link (d907f05)
    • refactor(benchmark): improve layout (b15f89e)
    • fix(benchmark): don't show indeterminate progressbar on initial load (2f28afa)
    • refactor(benchmark): use functional architecture (8425d07)
    • chore: update all dependencies (4108db5)
    • docs: put Benchmarks section after Installation (0478f55)
    • docs: link to benchmark page (38d589a)
    • fix(demo): allow opening PSD files on Windows (9234673)
    • feat: add web-based benchmark page (9bc26f9)
    • build: use dist-web/ for publishing to GitHub Pages (d16c6ab)
    • build: use default entrypoint when building browser demo (6aa9913)
    • build: refactor Webpack config for browser example (dcb72ea)
    • build: cleanup Webpack configs (cc84744)
    • chore: make Prettier ignore browser demo artifacts (a6f0441)
    • style: Allow Prettier to format HTML files (0b4b9aa)
    • chore: replace PSD.ts with @webtoon/psd (8a62ec5)
    • chore: update dev deps (eba0f0f)
    • chore: fix GitHub badge text in demo (43a7708)
    • docs: fix import stmt (9cdf3bf)
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Jan 16, 2022)

Jsonup - This is a zero dependency compile-time JSON parser written in TypeScript

jsonup This is a zero dependency compile-time JSON parser written in TypeScript.

TANIGUCHI Masaya 39 Dec 8, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
Practice Task of HTML - Mache Free Template (PSD to HTML) - Home Task (CTG)

Practice Task of HTML - Mache Free Template (PSD to HTML) - Home Task (CTG) This Assignment is mainly on PSD TO HTML along with HTML,CSS As a Basic HT

Yasir Monon 1 Jan 29, 2022
Ctg-exam-2 - Exam Task of HTML , Responsive Page - PSD to HTML - (CTG)

Exam Task of HTML - PSD to HTML - (CTG) This Exam is mainly on PSD TO HTML along

Yasir Monon 1 Feb 16, 2022
Json-parser - A parser for json-objects without dependencies

Json Parser This is a experimental tool that I create for educational purposes, it's based in the jq works With this tool you can parse json-like stri

Gabriel Guerra 1 Jan 3, 2022
Shifty is a tiny zero-dependency secrets generator, built for the web using TypeScript.

Shifty is a tiny zero-dependency secrets generator, built for the web using TypeScript. Installation yarn add @deepsource/shifty Usage Shifty is built

DeepSource 46 Nov 24, 2022
Creates Photoshop-like guides and rulers interface on a web page

RulersGuides.js This Javascript package creates Photoshop-like guides and rulers interface on a web page. DEMO Main window: Menu: Guides are created b

Mark Rolich 925 Nov 27, 2022
Implements live reload functionality to Adobe extension development.

Adobe Live Reload Adobe Live Reload implements live reload functionality to Adobe extension development. Features Reload Adobe Extensions on file save

Duncan Lutz 4 Apr 24, 2022
EggyJS is a Javascript micro Library for simple, lightweight toast popups focused on being dependency-less, lightweight, quick and efficient.

EggyJS EggyJS is a Javascript micro Library for simple, lightweight toast popups. The goal of this library was to create something that meets the foll

Sam 10 Jan 8, 2023
A zero-dependency, buildless, terse, and type-safe way to write HTML in JavaScript.

hdot A sensible way to write HTML in JavaScript. Type-safe. Helps you follow the HTML spec. Terse. Almost character for character with plain HTML. Bui

Will Martin 31 Oct 24, 2022
A zero-dependency, strongly-typed web framework for Bun, Node and Cloudflare workers

nbit A simple, declarative, type-safe way to build web services and REST APIs for Bun, Node and Cloudflare Workers. Examples See some quick examples b

Simon Sturmer 16 Sep 16, 2022
Dynamic, fast, accessible & zero dependency accordion for React

React Fast Accordion ⚡️ Dynamic, fast, accessible & zero dependency accordion for React How it's fast? Instead of adding event listener on all the ite

Shivam 59 Oct 8, 2022
Zero dependency profanity detector.

@cnakazawa/profane Zero dependency profanity detector based on Swearjar and Profane. Note: Some examples may contain offensive language for illustrati

Christoph Nakazawa 11 Dec 28, 2022
Zero Dependency, Vanilla JavaScript Tag Editor

_____ |_ _|___ ___ ___ ___ ___ | | | .'| . | . | -_| _| |_| |__,|_ |_ |___|_| |___|___| version 0.4.4 Tagger: Zero dependenc

Jakub T. Jankiewicz 155 Nov 25, 2022
A CLI tool to create a NodeJS project with TypeScript CTSP is a CLI tool to make easier to start a new NodeJS project and configure Typescript on it.

CTSP- Create TS Project A CLI tool to create a NodeJS project with TypeScript CTSP is a CLI tool to make easier to start a new NodeJS project and conf

Jean Rodríguez 7 Sep 13, 2022
Zero Two Bot,A fully Modular Whatsapp Bot to do everything possible in WhatsApp by Team Zero Two

?? ???????? ?????? ???? ?? A Moduler WhatsApp Bot designed for both PM and Groups - To take your boring WhatsApp usage into a whole different level. T

Sam Pandey 69 Dec 25, 2022
Multiplies a number by zero. Useful for when you need to multiply a number by zero

multiply-by-zero Multiplies a number by zero. Useful for when you need to multiply a number by zero Please consider checking out the links of this pro

Dheirya Tyagi 2 Jul 3, 2022