psdchannelimage.coffee | |
|---|---|
| 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() |
|