### # MIT LICENSE # Copyright (c) 2011 Devon Govett # # 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. ### fs = require 'fs' zlib = require 'zlib' module.exports = class PNG @decode: (path, fn) -> fs.readFile path, (err, file) -> png = new PNG(file) png.decode (pixels) -> fn pixels @load: (path) -> file = fs.readFileSync path return new PNG(file) constructor: (@data) -> @pos = 8 # Skip the default header @palette = [] @imgData = [] @transparency = {} @text = {} loop chunkSize = @readUInt32() section = (String.fromCharCode @data[@pos++] for i in [0...4]).join('') switch section when 'IHDR' # we can grab interesting values from here (like width, height, etc) @width = @readUInt32() @height = @readUInt32() @bits = @data[@pos++] @colorType = @data[@pos++] @compressionMethod = @data[@pos++] @filterMethod = @data[@pos++] @interlaceMethod = @data[@pos++] when 'PLTE' @palette = @read(chunkSize) when 'IDAT' for i in [0...chunkSize] by 1 @imgData.push @data[@pos++] when 'tRNS' # This chunk can only occur once and it must occur after the # PLTE chunk and before the IDAT chunk. @transparency = {} switch @colorType when 3 # Indexed color, RGB. Each byte in this chunk is an alpha for # the palette index in the PLTE ("palette") chunk up until the # last non-opaque entry. Set up an array, stretching over all # palette entries which will be 0 (opaque) or 1 (transparent). @transparency.indexed = @read(chunkSize) short = 255 - @transparency.indexed.length if short > 0 @transparency.indexed.push 255 for i in [0...short] when 0 # Greyscale. Corresponding to entries in the PLTE chunk. # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1 @transparency.grayscale = @read(chunkSize)[0] when 2 # True color with proper alpha channel. @transparency.rgb = @read(chunkSize) when 'tEXt' text = @read(chunkSize) index = text.indexOf(0) key = String.fromCharCode text.slice(0, index)... @text[key] = String.fromCharCode text.slice(index + 1)... when 'IEND' # we've got everything we need! @colors = switch @colorType when 0, 3, 4 then 1 when 2, 6 then 3 @hasAlphaChannel = @colorType in [4, 6] colors = @colors + if @hasAlphaChannel then 1 else 0 @pixelBitlength = @bits * colors @colorSpace = switch @colors when 1 then 'DeviceGray' when 3 then 'DeviceRGB' @imgData = new Buffer @imgData return else # unknown (or unimportant) section, skip it @pos += chunkSize @pos += 4 # Skip the CRC if @pos > @data.length throw new Error "Incomplete or corrupt PNG file" return read: (bytes) -> (@data[@pos++] for i in [0...bytes]) readUInt32: -> b1 = @data[@pos++] << 24 b2 = @data[@pos++] << 16 b3 = @data[@pos++] << 8 b4 = @data[@pos++] b1 | b2 | b3 | b4 readUInt16: -> b1 = @data[@pos++] << 8 b2 = @data[@pos++] b1 | b2 decodePixels: (fn) -> zlib.inflate @imgData, (err, data) => throw err if err pixelBytes = @pixelBitlength / 8 scanlineLength = pixelBytes * @width #console.log("pixelBytes="+ pixelBytes) #console.log("scanlineLength="+ scanlineLength) pixels = new Buffer(scanlineLength * @height) length = data.length row = 0 pos = 0 c = 0 while pos < length switch data[pos++] when 0 # None for i in [0...scanlineLength] by 1 pixels[c++] = data[pos++] when 1 # Sub for i in [0...scanlineLength] by 1 byte = data[pos++] left = if i < pixelBytes then 0 else pixels[c - pixelBytes] pixels[c++] = (byte + left) % 256 when 2 # Up for i in [0...scanlineLength] by 1 byte = data[pos++] col = (i - (i % pixelBytes)) / pixelBytes upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)] pixels[c++] = (upper + byte) % 256 when 3 # Average for i in [0...scanlineLength] by 1 byte = data[pos++] col = (i - (i % pixelBytes)) / pixelBytes left = if i < pixelBytes then 0 else pixels[c - pixelBytes] upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)] pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256 when 4 # Paeth for i in [0...scanlineLength] by 1 byte = data[pos++] col = (i - (i % pixelBytes)) / pixelBytes left = if i < pixelBytes then 0 else pixels[c - pixelBytes] if row is 0 upper = upperLeft = 0 else upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)] upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)] p = left + upper - upperLeft pa = Math.abs(p - left) pb = Math.abs(p - upper) pc = Math.abs(p - upperLeft) if pa <= pb and pa <= pc paeth = left else if pb <= pc paeth = upper else paeth = upperLeft pixels[c++] = (byte + paeth) % 256 else throw new Error "Invalid filter algorithm: " + data[pos - 1] row++ fn pixels decodePalette: -> palette = @palette transparency = @transparency.indexed or [] ret = new Buffer(transparency.length + palette.length) pos = 0 length = palette.length c = 0 for i in [0...palette.length] by 3 ret[pos++] = palette[i] ret[pos++] = palette[i + 1] ret[pos++] = palette[i + 2] ret[pos++] = transparency[c++] ? 255 return ret copyToImageData: (imageData, pixels) -> colors = @colors palette = null alpha = @hasAlphaChannel if @palette.length palette = @_decodedPalette ?= @decodePalette() colors = 4 alpha = true data = imageData?.data or imageData length = data.length input = palette or pixels i = j = 0 if colors is 1 while i < length k = if palette then pixels[i / 4] * 4 else j v = input[k++] data[i++] = v data[i++] = v data[i++] = v data[i++] = if alpha then input[k++] else 255 j = k else while i < length k = if palette then pixels[i / 4] * 4 else j data[i++] = input[k++] data[i++] = input[k++] data[i++] = input[k++] data[i++] = if alpha then input[k++] else 255 j = k return decode: (fn) -> ret = new Buffer(@width * @height * 4) @decodePixels (pixels) => @copyToImageData ret, pixels fn ret