aboutsummaryrefslogtreecommitdiffstats
path: root/src/psdlayermask.coffee
blob: 98c31fceadf8954db1601564b61604fabfec788b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class PSDLayerMask
  constructor: (@file, @header, @options) ->
    # Array to hold all of the layers
    @layers = []

    # Does the first alpha channel contain the transparency data?
    @mergedAlpha = false

    # The global layer mask
    @globalMask = {}

    # Additional layer information
    @extras = []

  # Skip over this section and don't parse it
  skip: -> @file.seek @file.readInt()

  parse: ->
    # Read the size of the entire layers and masks section
    maskSize = @file.readInt()
    endLoc = @file.tell() + maskSize

    Log.debug "Layer mask size is #{maskSize}"

    # If the mask size is > 0, then parse the section. Otherwise,
    # this section doesn't exist and the whole layers/masks data
    # is 4 bytes (the length we've already read)
    return if maskSize <= 0
    
    # Size of the layer info section. 4 bytes, rounded up by 2's.
    layerInfoSize = Util.pad2 @file.readInt()

    # Store the current position in case we need to bail
    # and skip over this section.
    pos = @file.tell()

    # If the layer info size is > 0, then we have some layers
    if layerInfoSize > 0
      # Read the number of layers, 2 bytes.
      @numLayers = @file.readShortInt()

      # If the number of layers is negative, the absolute value is
      # the actual number of layers, and the first alpha channel contains
      # the transparency data for the merged image.
      if @numLayers < 0
        Log.debug "Note: first alpha channel contains transparency data"
        @numLayers = Math.abs @numLayers
        @mergedAlpha = true

      if @numLayers * (18 + 6 * @header.channels) > layerInfoSize
        throw "Unlikely number of #{@numLayers} layers for #{@header['channels']} with #{layerInfoSize} layer info size. Giving up."

      Log.debug "Found #{@numLayers} layer(s)"

      for i in [0...@numLayers]
        layer = new PSDLayer @file
        layer.parse(i)
        @layers.push layer

      for layer in @layers
        if layer.isFolder or layer.isHidden
          # Layer contains no image data. Skip ahead.
          @file.seek 8
          continue

        layer.image = new PSDChannelImage(@file, @header, layer)

        if @options.layerImages and (
          (@options.onlyVisibleLayers and layer.visible) or
          !@options.onlyVisibleLayers
          )
          layer.image.parse()
        else
          layer.image.skip()

      # Layers are parsed in reverse order
      @layers.reverse()
      @groupLayers()

    # In case there are filler zeros
    @file.seek pos + layerInfoSize, false

    # Parse the global layer mask
    @parseGlobalMask()

    # Temporarily skip the rest of layers & masks section
    @file.seek endLoc, false
    return

    # We have more additional info to parse, especially beacuse this is PS >= 4.0
    #@parseExtraInfo(endLoc) if @file.tell() < endLoc

  parseGlobalMask: ->
    length = @file.readInt()
    return if length is 0

    start = @file.tell()
    end = @file.tell() + length

    Log.debug "Global mask length: #{length}"

    # Undocumented
    @globalMask.overlayColorSpace = @file.readShortInt()

    # TODO: parse color space components into actual color.
    @globalMask.colorComponents = []
    for i in [0...4]
      @globalMask.colorComponents.push(@file.readShortInt() >> 8)

    # 0 = transparent, 100 = opaque
    @globalMask.opacity = @file.readShortInt()

    # 0 = color selected; 1 = color protected; 128 = use value per layer
    @globalMask.kind = @file.read(1)[0]

    Log.debug "Global mask:", @globalMask

    # Filler zeros, seek to end.
    @file.seek end, false

  parseExtraInfo: (end) ->
    while @file.tell() < end
      # Temporary
      [
        sig,
        key,
        length
      ] = @file.readf ">4s4sI"

      length = Util.pad2 length

      Log.debug "Layer extra:", sig, key, length

      @file.seek length

  groupLayers: ->
    groupLayer = null
    for layer in @layers
      if layer.isFolder
        groupLayer = layer
      else if layer.isHidden
        groupLayer = null
      else
        layer.groupLayer = groupLayer

  toJSON: ->
    data =
      mergedAlpha: @mergedAlpha
      globalMask: @globalMask
      extraInfo: @extras
      numLayers: @numLayers
      layers: []

    for layer in @layers
      data.layers.push layer.toJSON()

    data