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

This commit is contained in:
Devon Govett 2012-04-07 01:38:03 -07:00
parent 12a9a85a22
commit 39f4e9ea6a
2 changed files with 57 additions and 222 deletions

View File

@ -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 File

@ -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"