aboutsummaryrefslogtreecommitdiffstats
path: root/src/psdchannelimage.coffee
blob: 48f39f009684e25484791c4dc2160898c59cb775 (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
# PSD files also store merged image data for each individual layer.
# Unfortunately, parsing this image data is a bit different than parsing
# the overall merged image data at the end of the file. The main difference
# is that each image channel has independent compression, and the channel
# order is defined individually for each layer. Also, the layer size is often
# not the same size as the full image.
class PSDChannelImage extends PSDImage
  constructor: (file, header, @layer) ->
    @width = @layer.cols
    @height = @layer.rows
    @channelsInfo = @layer.channelsInfo

    super file, header

  skip: ->
    Log.debug "Skipping channel image data. Layer = #{@layer.name}"
    for channel in @channelsInfo
      @file.seek channel.length

  getImageWidth: -> @width
  getImageHeight: -> @height
  getImageChannels: -> @layer.channels

  # Since we're working on a per-channel basis now, we only read the byte counts
  # for the current channel only.
  getByteCounts: ->
    byteCounts = []
    for i in [0...@getImageHeight()]
      byteCounts.push @file.readShortInt()

    byteCounts

  parse: ->
    Log.debug "\nLayer: #{@layer.name}, image size: #{@length} (#{@getImageWidth()}x#{@getImageHeight()})"
    
    # We must keep track of the current channel data position global to this object
    # now, since we parse a single channel at a time.
    @chanPos = 0

    # Loop through each image channel and parse each one like a full image.
    for i in [0...@getImageChannels()]
      @chInfo = @layer.channelsInfo[i]

      if @chInfo.length <= 0
        @parseCompression()
        continue

      # If the ID of this current channel is -2, then we assume the dimensions
      # of the layer mask.
      if @chInfo.id is -2
        @width = @layer.mask.width
        @height = @layer.mask.height
      else
        @width = @layer.cols
        @height = @layer.rows

      start = @file.tell()

      Log.debug "Channel ##{@chInfo.id}: length=#{@chInfo.length}"
      @parseImageData()

      end = @file.tell()

      # Sanity check
      if end isnt start + @chInfo.length
        Log.debug "ERROR: read incorrect number of bytes for channel ##{@chInfo.id}. Layer=#{@layer.name}, Expected = #{start + @chInfo.length}, Actual: #{end}"
        @file.seek start + @chInfo.length, false

    # Futher sanity checks
    if @channelData.length isnt @length
      Log.debug "ERROR: #{@channelData.length} read; expected #{@length}"

    @processImageData()
    #@parseUserMask()

    if exports?
      memusage = process.memoryUsage()
      used = Math.round memusage.heapUsed / 1024 / 1024
      total = Math.round memusage.heapTotal / 1024 / 1024
      Log.debug "\nMemory usage: #{used}MB / #{total}MB"

  # Since we're parsing on a per-channel basis, we need to modify the behavior
  # of the RAW encoding parser a bit. This version is aware of the current
  # channel data position, since layers that have RAW encoding often use RLE
  # encoded alpha channels.
  parseRaw: ->
    Log.debug "Attempting to parse RAW encoded channel..."
    data = @file.read(@chInfo.length - 2)
    dataIndex = 0
    for i in [@chanPos...@chanPos+@chInfo.length - 2]
      @channelData[i] = data[dataIndex++]

    @chanPos += @chInfo.length - 2

  # Compression is stored on a per-channel basis, not a per-image basis for layers
  parseImageData: ->
    @compression = @parseCompression()

    switch @compression
      when 0 then @parseRaw()
      when 1 then @parseRLE()
      when 2, 3 then @parseZip()
      else
        Log.debug "Unknown image compression. Attempting to skip."
        return @file.seek @endPos, false

  # Parse a single channel instead of every image channel
  parseChannelData: ->
    lineIndex = 0
    
    Log.debug "Parsing layer channel ##{@chInfo.id}, Start = #{@file.tell()}"
    [@chanPos, lineIndex] = @decodeRLEChannel(@chanPos, lineIndex)

  #parseUserMask: ->
  #  if @getImageDepth() is 8
  #    @parseUserMask8()