pdfkit/lib/image/png.coffee

129 lines
4.5 KiB
CoffeeScript

zlib = require 'zlib'
PNG = require 'png-js'
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 @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 @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: @image.bits
Width: @width
Height: @height
Length: @imgData.length
Filter: 'FlateDecode'
unless @image.hasAlphaChannel
obj.data['DecodeParms'] = document.ref
Predictor: 15
Colors: @image.colors
BitsPerComponent: @image.bits
Columns: @width
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: @image.palette.length
palette.add new Buffer(@image.palette)
# build the color space array for the image
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 @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 = @image.transparency.greyscale
obj.data['Mask'] = [val, val]
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 = @image.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
splitAlphaChannel: (fn) ->
@image.decodePixels (pixels) =>
colorByteSize = @image.colors * @image.bits / 8
pixelCount = @width * @height
imgData = new Buffer(pixelCount * colorByteSize)
alphaChannel = new Buffer(pixelCount)
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
loadIndexedAlphaChannel: (fn) ->
transparency = @image.transparency.indexed
@image.decodePixels (pixels) =>
alphaChannel = new Buffer(@width * @height)
i = 0
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 = PNGImage