Skip to content
Browse files

Separate png decoding into a separate library. See http://github.com/…

  • Loading branch information...
1 parent 12a9a85 commit 39f4e9ea6aae7af154cd690ed44a515ab966a9c6 @devongovett committed
Showing with 57 additions and 222 deletions.
  1. +54 −222 lib/image/png.coffee
  2. +3 −0 package.json
View
276 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
+
+module.exports = PNGImage
View
3 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"

0 comments on commit 39f4e9e

Please sign in to comment.
Something went wrong with that request. Please try again.