diff --git a/lib/image/png.coffee b/lib/image/png.coffee index ccdd8bd..abb228a 100644 --- a/lib/image/png.coffee +++ b/lib/image/png.coffee @@ -1,146 +1,75 @@ -fs = require 'fs' -Data = '../data' zlib = require 'zlib' +PNG = require 'png-js' -class PNG - constructor: (@data) -> - data.pos = 8 # Skip the default header - - @palette = [] - @imgData = [] - @transparency = {} - - loop - chunkSize = data.readUInt32() - section = data.readString(4) - - switch section - when 'IHDR' - # we can grab interesting values from here (like width, height, etc) - @width = data.readUInt32() - @height = data.readUInt32() - @bits = data.readByte() - @colorType = data.readByte() - @compressionMethod = data.readByte() - @filterMethod = data.readByte() - @interlaceMethod = data.readByte() - - when 'PLTE' - @palette = data.read(chunkSize) - - when 'IDAT' - for i in [0...chunkSize] - @imgData.push data.readByte() - - 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 = data.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 = data.read(chunkSize)[0] - when 2 - # True color with proper alpha channel. - @transparency.rgb = data.read(chunkSize) - - 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 - data.pos += chunkSize - - data.pos += 4 # Skip the CRC - - return +class PNGImage + constructor: (data) -> + @image = new PNG(data.data) + @width = @image.width + @height = @image.height + @imgData = @image.imgData object: (document, fn) -> # get the async stuff out of the way first if not @alphaChannel - if @transparency.indexed + if @image.transparency.indexed # Create a transparency SMask for the image based on the data # in the PLTE and tRNS sections. See below for details on SMasks. @loadIndexedAlphaChannel => @object document, fn return - else if @hasAlphaChannel + else if @image.hasAlphaChannel # For PNG color types 4 and 6, the transparency data is stored as a alpha # channel mixed in with the main image data. Separate this data out into an # SMask object and store it separately in the PDF. @splitAlphaChannel => @object document, fn return - + obj = document.ref Type: 'XObject' Subtype: 'Image' - BitsPerComponent: @bits + BitsPerComponent: @image.bits Width: @width Height: @height Length: @imgData.length Filter: 'FlateDecode' - - unless @hasAlphaChannel - obj.data['DecodeParms'] = + + unless @image.hasAlphaChannel + obj.data['DecodeParms'] = document.ref Predictor: 15 - Colors: @colors - BitsPerComponent: @bits + Colors: @image.colors + BitsPerComponent: @image.bits Columns: @width - if @palette.length is 0 - obj.data['ColorSpace'] = @colorSpace + if @image.palette.length is 0 + obj.data['ColorSpace'] = @image.colorSpace else # embed the color palette in the PDF as an object stream palette = document.ref - Length: @palette.length - - palette.add new Buffer(@palette) - + Length: @image.palette.length + + palette.add new Buffer(@image.palette) + # build the color space array for the image - obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', (@palette.length / 3) - 1, palette] + obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', (@image.palette.length / 3) - 1, palette] # For PNG color types 0, 2 and 3, the transparency data is stored in # a dedicated PNG chunk. - if @transparency.grayscale + if @image.transparency.grayscale # Use Color Key Masking (spec section 4.8.5) # An array with N elements, where N is two times the number of color components. - val = @transparency.greyscale + val = @image.transparency.greyscale obj.data['Mask'] = [val, val] - - else if @transparency.rgb + + else if @image.transparency.rgb # Use Color Key Masking (spec section 4.8.5) # An array with N elements, where N is two times the number of color components. - rgb = @transparency.rgb + rgb = @image.transparency.rgb mask = [] for x in rgb mask.push x, x - + obj.data['Mask'] = mask - + if @alphaChannel sMask = document.ref Type: 'XObject' @@ -152,146 +81,49 @@ class PNG Filter: 'FlateDecode' ColorSpace: 'DeviceGray' Decode: [0, 1] - + sMask.add @alphaChannel obj.data['SMask'] = sMask - + # add the actual image data obj.add @imgData fn obj - decodePixels: (fn) -> - zlib.inflate @imgData, (err, data) => - throw err if err - - pixelBytes = @pixelBitlength / 8 - scanlineLength = pixelBytes * @width - - row = 0 - pixels = [] - length = data.length - pos = 0 - - while pos < length - filter = data[pos++] - i = 0 - rowData = [] - - switch filter - when 0 # None - while i < scanlineLength - rowData[i++] = data[pos++] - - when 1 # Sub - while i < scanlineLength - byte = data[pos++] - left = if i < pixelBytes then 0 else rowData[i - pixelBytes] - rowData[i++] = (byte + left) % 256 - - when 2 # Up - while i < scanlineLength - byte = data[pos++] - col = (i - (i % pixelBytes)) / pixelBytes - upper = if row is 0 then 0 else pixels[row - 1][col][i % pixelBytes] - rowData[i++] = (upper + byte) % 256 - - when 3 # Average - while i < scanlineLength - byte = data[pos++] - col = (i - (i % pixelBytes)) / pixelBytes - left = if i < pixelBytes then 0 else rowData[i - pixelBytes] - upper = if row is 0 then 0 else pixels[row - 1][col][i % pixelBytes] - rowData[i++] = (byte + Math.floor((left + upper) / 2)) % 256 - - when 4 # Paeth - while i < scanlineLength - byte = data[pos++] - col = (i - (i % pixelBytes)) / pixelBytes - left = if i < pixelBytes then 0 else rowData[i - pixelBytes] - - if row is 0 - upper = upperLeft = 0 - else - upper = pixels[row - 1][col][i % pixelBytes] - upperLeft = if col is 0 then 0 else pixels[row - 1][col - 1][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 - - rowData[i++] = (byte + paeth) % 256 - - else - throw new Error "Invalid filter algorithm: " + filter - - s = [] - for i in [0...rowData.length] by pixelBytes - s.push rowData.slice(i, i + pixelBytes) - - pixels.push(s) - row += 1 - - fn pixels - splitAlphaChannel: (fn) -> - @decodePixels (pixels) => - colorByteSize = @colors * @bits / 8 - alphaByteSize = 1 - + @image.decodePixels (pixels) => + colorByteSize = @image.colors * @image.bits / 8 pixelCount = @width * @height imgData = new Buffer(pixelCount * colorByteSize) alphaChannel = new Buffer(pixelCount) - - p = a = 0 - for row in pixels - for pixel in row - imgData[p++] = pixel[i] for i in [0...colorByteSize] - alphaChannel[a++] = pixel[colorByteSize] - + + i = p = a = 0 + len = pixels.length + while i < len + imgData[p++] = pixels[i++] + imgData[p++] = pixels[i++] + imgData[p++] = pixels[i++] + alphaChannel[a++] = pixels[i++] + done = 0 zlib.deflate imgData, (err, @imgData) => throw err if err fn() if ++done is 2 - + zlib.deflate alphaChannel, (err, @alphaChannel) => throw err if err fn() if ++done is 2 - - decodePalette: -> - palette = @palette - transparency = @transparency.indexed ? [] - decodingMap = [] - index = 0 - - for i in [0...palette.length] by 3 - alpha = transparency[index++] ? 255 - pixel = palette.slice(i, i + 3).concat(alpha) - decodingMap.push pixel - - return decodingMap - + loadIndexedAlphaChannel: (fn) -> - palette = @decodePalette() - @decodePixels (pixels) => - pixelCount = @width * @height - alphaChannel = new Buffer(pixelCount) - + transparency = @image.transparency.indexed + @image.decodePixels (pixels) => + alphaChannel = new Buffer(@width * @height) + i = 0 - for row in pixels - for pixel in row - pixel = pixel[0] - alphaChannel[i++] = palette[pixel][3] - + for j in [0...pixels.length] by 1 + alphaChannel[i++] = transparency[pixels[j]] + zlib.deflate alphaChannel, (err, @alphaChannel) => throw err if err fn() - -module.exports = PNG \ No newline at end of file + +module.exports = PNGImage \ No newline at end of file diff --git a/package.json b/package.json index d9c8c29..f34bdbc 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,9 @@ "devDependencies": { "coffee-script": ">=1.0.1" }, + "dependencies": { + "png-js": ">=0.1.0" + }, "scripts": { "prepublish": "coffee -o js -c lib/ && cp -r lib/font/data js/font/data", "postpublish": "rm -rf ./js"