Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
297 lines (241 sloc) 11.2 KB
fs = require 'fs'
Data = '../data'
zlib = require 'zlib'
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
object: (document, fn) ->
# get the async stuff out of the way first
if not @alphaChannel
if @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
# 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
Width: @width
Height: @height
Length: @imgData.length
Filter: 'FlateDecode'
unless @hasAlphaChannel
obj.data['DecodeParms'] =
Predictor: 15
Colors: @colors
BitsPerComponent: @bits
Columns: @width
if @palette.length is 0
obj.data['ColorSpace'] = @colorSpace
else
# embed the color palette in the PDF as an object stream
palette = document.ref
Length: @palette.length
palette.add new Buffer(@palette)
# build the color space array for the image
obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', (@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
# 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
obj.data['Mask'] = [val, val]
else if @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
mask = []
for x in rgb
mask.push x, x
obj.data['Mask'] = mask
if @alphaChannel
sMask = document.ref
Type: 'XObject'
Subtype: 'Image'
Height: @height
Width: @width
BitsPerComponent: 8
Length: @alphaChannel.length
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
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]
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)
i = 0
for row in pixels
for pixel in row
pixel = pixel[0]
alphaChannel[i++] = palette[pixel][3]
zlib.deflate alphaChannel, (err, @alphaChannel) =>
throw err if err
fn()
module.exports = PNG
Something went wrong with that request. Please try again.