Jump To …

psd.coffee

NodeJS or browser?

if exports?
  Root = exports
  fs = require 'fs'
else
  Root = window

Create our class and add to global scope

Root.PSD = class PSD

Version number

  @VERSION = "0.4.4"

Enable/disable debugging console logs

  @DEBUG = false

Loads a PSD from a file. If we're in node, then this loads the file from the filesystem. If we're in the browser, then this assumes it has been passed a File object (either from a file input element, or from HTML5 drag & drop).

  @fromFile: (file, cb = ->) ->
    if exports?

We're in node. Load via fs module. Callback function isn't needed.

      data = fs.readFileSync file
      new PSD data
    else

We're in the browser. Assume we have a File object.

      reader = new FileReader()
      reader.onload = (f) ->

In order to convert the file data to a useful format, we need to conver the buffer into a byte array.

        bytes = new Uint8Array(f.target.result)

        psd = new PSD(bytes)
        cb(psd)

      reader.readAsArrayBuffer(file)

Load a PSD from a URL via ajax

  @fromURL: (url, cb = ->) ->
    xhr = new XMLHttpRequest
    xhr.open "GET", url, true
    xhr.responseType = "arraybuffer"
    xhr.onload = ->
      data = new Uint8Array(xhr.response or xhr.mozResponseArrayBuffer)
      psd = new PSD(data)
      cb(psd)

    xhr.send null

  options:
    layerImages: false # Should we parse layer image data?
    onlyVisibleLayers: false # Should we skip invisible layer image parsing?
  
  constructor: (data) ->

Store the main reference to our PSD file

    @file = new PSDFile data

    @header = null
    @resources = null
    @layerMask = null
    @layers = null
    @images = null
    @image = null

  setOptions: (options) ->
    @options[key] = val for own key, val of options

Attempt to parse all sections of the PSD file

  parse: ->
    Log.debug "Beginning parsing"
    @startTime = (new Date()).getTime()

It's important to parse all of the file sections in the correct order, which is used here.

    @parseHeader()
    @parseImageResources()
    @parseLayersMasks()
    @parseImageData()

    @endTime = (new Date()).getTime()
    Log.debug "Parsing finished in #{@endTime - @startTime}ms"

Parse the first section: the header. This section cannot be skipped, since it contains important parsing information for the rest of the PSD file (and is relatively small anyways).

  parseHeader: ->
    Log.debug "\n### Header ###"

Store a reference to the file header

    @header = new PSDHeader @file
    @header.parse()

    Log.debug @header

  parseImageResources: (skip = false) ->
    Log.debug "\n### Resources ###"

Every PSD file has a number of resources, so we simply store them in an array for now. In the future, it might make more sense to store resources in an object indexed by the resource ID.

    @resources = []

Find the size of the resources section

    n = @file.readInt()
    length = n

    if skip
      Log.debug "Skipped!"
      return @file.seek n

    start = @file.tell()

Continue parsing resources until we've reached the end of the section.

    while n > 0
      pos = @file.tell()

      resource = new PSDResource @file
      resource.parse()

      n -= @file.tell() - pos
      @resources.push resource

      Log.debug "Resource: ", resource

This shouldn't happen. If it does, then likely something is being parsed incorrectly in one of the resources, or the file is corrupt.

    if n isnt 0
      Log.debug "Image resources overran expected size by #{-n} bytes"
      @file.seek start + length

  parseLayersMasks: (skip = false) ->
    @parseHeader() unless @header
    @parseImageResources(true) unless @resources

    Log.debug "\n### Layers & Masks ###"

    @layerMask = new PSDLayerMask @file, @header, @options
    @layers = @layerMask.layers

    if skip
      Log.debug "Skipped!"
      @layerMask.skip()
    else
      @layerMask.parse()

  parseImageData: ->
    @parseHeader() unless @header
    @parseImageResources(true) unless @resources
    @parseLayersMasks(true) unless @layerMask

    @image = new PSDImage @file, @header
    @image.parse()

Folder layers are denoted by a flag, isFolder. This marks the beginning of the folder. The end of the folder is marked by the isHidden flag.

  getLayerStructure: ->
    @parseLayersMasks() unless @layerMask

    result = {layers: []}
    parseStack = []
    for layer in @layers
      if layer.isFolder
        parseStack.push result
        result = {name: layer.name, layers: []}
      else if layer.isHidden
        temp = result
        result = parseStack.pop()
        result.layers.push temp
      else
        result.layers.push layer

    result

Exports a flattened version to a file. For use in NodeJS.

  toFile: (filename, cb = ->) -> 
    @parseImageData() unless @image
    @image.toFile filename, cb

  toFileSync: (filename) ->
    @parseImageData() unless @image
    @image.toFileSync filename

Given a canvas element

  toCanvas: (canvas, width = null, height = null) ->
    @parseImageData() unless @image
    @image.toCanvas canvas, width, height

  toImage: ->
    @parseImageData() unless @image
    @image.toImage()

Extracts all parsed data from this PSD in a clean JSON format excluding file and image data.

  toJSON: ->
    @parseLayersMasks() unless @layerMask

    sections = [
      'header'
      'layerMask'
    ]

    data = resources: []

    for resource in @resources
      data.resources.push resource.toJSON()

    for section in sections
      data[section] = @[section].toJSON()

    data