working on png with alpha

main
Matthew Butterick 7 years ago
parent 71dd3b0b23
commit 1cbfb6d591

@ -145,11 +145,12 @@ class PDFDocument extends stream.Readable
return this
_refEnd: (ref) ->
console.log(ref.id)
console.log(@_offsets)
#console.log(ref.id)
#console.log(@_offsets)
@_offsets[ref.id - 1] = ref.offset
if --@_waiting is 0 and @_ended
console.log("finalize") ; @_finalize()
#console.log("finalize") ;
@_finalize()
@_ended = false
write: (filename, fn) ->
@ -173,8 +174,8 @@ class PDFDocument extends stream.Readable
'
end: ->
console.log("start document end")
console.log(@_offsets)
#console.log("start document end")
#console.log(@_offsets)
@flushPages()
@_info = @ref()
for key, val of @info
@ -182,25 +183,26 @@ class PDFDocument extends stream.Readable
val = new String val
@_info.data[key] = val
console.log(@_offsets)
#console.log(@_offsets)
@_info.end()
for name, font of @_fontFamilies
font.finalize()
console.log(@_offsets)
#console.log(@_offsets)
@_root.end()
console.log(@_offsets)
#console.log(@_offsets)
@_root.data.Pages.end()
console.log(@_offsets)
#console.log(@_offsets)
if @_waiting is 0
console.log(@_offsets.length)
console.log("finalize2") ; @_finalize()
#console.log(@_offsets.length)
#console.log("finalize2") ; @_finalize()
else
console.log("ended is true") ; @_ended = true
#console.log("ended is true") ;
@_ended = true
_finalize: (fn) ->
# generate xref

@ -3,15 +3,15 @@ PNG = require 'png-js'
class PNGImage
constructor: (data, @label) ->
console.log("raw data")
console.log(data.slice(0, 20))
#console.log("raw data")
#console.log(data.slice(0, 20))
@image = new PNG(data)
@width = @image.width
@height = @image.height
@imgData = @image.imgData
console.log("result from png-js")
console.log(@imgData.slice(0, 20))
console.log(@image)
#console.log("result from png-js")
#console.log(@imgData.slice(0, 20))
#console.log(@image)
@obj = null
embed: (@document) ->
@ -47,7 +47,7 @@ class PNGImage
# For PNG color types 0, 2 and 3, the transparency data is stored in
# a dedicated PNG chunk.
if @image.transparency.grayscale
if @image.transparency.grayscale
console.log("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.
@ -72,7 +72,7 @@ class PNGImage
@loadIndexedAlphaChannel()
else if @image.hasAlphaChannel
console.log("alphachannel")
console.log("got alphachannel " + @image.colorType + " in png.coffee")
# 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.
@ -97,7 +97,7 @@ class PNGImage
@obj.data['SMask'] = sMask
# add the actual image data
console.log(@imgData)
#console.log(@imgData)
@obj.end @imgData
# free memory
@ -105,6 +105,7 @@ class PNGImage
@imgData = null
splitAlphaChannel: ->
console.log("start splitAlphaChannel in png.coffee")
@image.decodePixels (pixels) =>
colorByteSize = @image.colors * @image.bits / 8
pixelCount = @width * @height

@ -48,7 +48,7 @@ class PDFReference extends stream.Writable
end: (chunk) ->
super
console.log("end! " + @id)
#console.log("end! " + @id)
# console.log(@chunks)
if @deflate
@deflate.end()
@ -75,7 +75,7 @@ class PDFReference extends stream.Writable
@document._write '\nendstream'
@document._write 'endobj'
console.log(@id)
#console.log(@id)
@document._refEnd(this)
toString: ->

@ -376,6 +376,7 @@ Inflate.prototype.onEnd = function (status) {
* ```
**/
function inflate(input, options) {
console.log("inflating");
var inflator = new Inflate(options);
inflator.push(input, true);

@ -361,6 +361,8 @@ function updatewindow(strm, src, end, copy) {
}
function inflate(strm, flush) {
console.log("inflating big time");
var state;
var input, output; // input/output buffers
var next; /* next input INDEX */

@ -140,6 +140,9 @@ module.exports = class PNG
pixelBytes = @pixelBitlength / 8
scanlineLength = pixelBytes * @width
console.log("pixelBytes="+ pixelBytes)
console.log("scanlineLength="+ scanlineLength)
pixels = new Buffer(scanlineLength * @height)
length = data.length
row = 0

@ -56,8 +56,8 @@
this.text = {};
while (true) {
chunkSize = this.readUInt32();
console.log("chunkSize")
console.log(chunkSize)
//console.log("chunkSize")
//console.log(chunkSize)
section = ((function() {
var _i, _results;
_results = [];
@ -66,8 +66,8 @@
}
return _results;
}).call(this)).join('');
console.log("section")
console.log(section)
//console.log("section")
//console.log(section)
switch (section) {
case 'IHDR':
this.width = this.readUInt32();
@ -173,6 +173,7 @@
};
PNG.prototype.decodePixels = function(fn) {
console.log("start decodePixels in png-node.js")
var _this = this;
return zlib.inflate(this.imgData, function(err, data) {
var byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m;
@ -181,6 +182,9 @@
}
pixelBytes = _this.pixelBitlength / 8;
scanlineLength = pixelBytes * _this.width;
console.log("pixelBytes="+ pixelBytes);
console.log("scanlineLength="+ scanlineLength);
pixels = new Buffer(scanlineLength * _this.height);
length = data.length;
row = 0;

@ -145,8 +145,8 @@ class PNG
when 1 then 'DeviceGray'
when 3 then 'DeviceRGB'
console.log("imgdata")
console.log(@imgData)
#console.log("imgdata")
#console.log(@imgData)
@imgData = new Uint8Array @imgData
return
@ -159,7 +159,7 @@ class PNG
if @pos > @data.length
throw new Error "Incomplete or corrupt PNG file"
console.log("done parsing PNG")
#console.log("done parsing PNG")
return

@ -74,6 +74,11 @@
[(_ field o expr)
#'(begin (set-field! field o (+ (get-field field o) expr)) (get-field field o))]))
(define-syntax (increment! stx)
(syntax-case stx ()
[(_ id) #'(increment! id 1)]
[(_ id expr)
#'(begin (set! id (+ id expr)) id)]))
(module+ test
@ -172,7 +177,7 @@
(define (bytes->hex bstr)
(map (λ (b) (string->symbol (string-append (if (< b 16)
"x0" "x") (~r b #:base 16)))) (bytes->list bstr)))
"x0" "x") (~r b #:base 16)))) (bytes->list bstr)))
(module+ test
(check-equal? (bytes->hex #"PNG") '(x50 x4e x47)))

@ -1,5 +1,6 @@
#lang pitfall/racket
(provide read-png)
(require "zlib.rkt")
(provide read-png decodePixels)
#|
Grab key chunks from PNG. Doesn't require heavy lifting from libpng.
@ -29,12 +30,40 @@ Grab key chunks from PNG. Doesn't require heavy lifting from libpng.
'interlaceMethod (read-byte))]
[(#"PLTE") (hash-set*! png 'palette (read-bytes chunk-size))]
[(#"IDAT") (hash-set*! png 'imgData (read-bytes chunk-size))]
[(#"tRNS") (read-bytes chunk-size)] ; todo implement this section
[(#"tEXt") (read-bytes chunk-size)] ; todo implement this section
[(#"tRNS")
;; This chunk can only occur once and it must occur after the
;; PLTE chunk and before the IDAT chunk.
(define transparency (mhash))
(case (hash-ref png 'colorType (λ () (error 'read-png "PNG file is loco")))
[(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).
(hash-set! transparency 'indexed
(append (read-bytes chunk-size)
(make-list (min 0 (- 255 chunk-size)) 255)))]
[(0)
;; Greyscale. Corresponding to entries in the PLTE chunk.
;; Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1]
(hash-set! transparency 'grayscale (bytes-ref (read-bytes chunk-size) 0))]
[(2)
;; True color with proper alpha channel.
(hash-set! transparency 'rgb (read-bytes chunk-size))])
(report (hash-set! png 'transparency transparency))]
[(#"tEXt")
(define text (read-bytes chunk-size))
#|
text = @read(chunkSize)
index = text.indexOf(0)
key = String.fromCharCode text.slice(0, index)...
@text[key] = String.fromCharCode text.slice(index + 1)...
|#
42]
[(#"IEND") (define color-value (case (hash-ref png 'colorType)
[(0 3 4) 1]
[(2 6) 3]))
(define alpha-value (member (hash-ref png 'colorType) '(4 6)))
(define alpha-value (and (member (hash-ref png 'colorType) '(4 6)) (hash-ref png 'colorType)))
(hash-set*! png
'colors color-value
'hasAlphaChannel alpha-value
@ -46,6 +75,100 @@ Grab key chunks from PNG. Doesn't require heavy lifting from libpng.
(read-bytes 4) ; skip crc
(loop)]))))
(define/contract (decodePixels imgData pixelBitLength width height fn)
(bytes? number? number? number? procedure? . -> . any/c)
(define data (inflate imgData))
(define pixelBytes (/ pixelBitLength 8))
(define scanlineLength (* pixelBytes width))
(define pixels (make-bytes (* scanlineLength height)))
(define length (bytes-length data))
(define row 0)
(define pos 0)
(define c 0)
(report* width height)
(parameterize ([current-input-port (open-input-bytes data)])
(for/fold ([_ #f]) ([row (in-naturals)]
#:break (eof-object? (peek-byte)))
(report row)
(define b (read-byte))
(case b
[(0) ; none
(for ([i (in-range scanlineLength)])
(define b (read-byte))
(bytes-set! pixels c b)
(increment! c))]
[(1) ; sub
(for ([i (in-range scanlineLength)])
(define byte (read-byte))
(define left (if (< i pixelBytes)
0
(bytes-ref pixels (- c pixelBytes))))
(bytes-set! pixels c (modulo (+ byte left) 256))
(increment! c))]
[(2) ; up
(for ([i (in-range scanlineLength)])
(define byte (read-byte))
(define col ((i . - . (modulo i pixelBytes)) . / . pixelBytes))
(define upper (if (zero? row)
row
(+ (* (bytes-ref pixels (sub1 row)) scanlineLength)
(* col pixelBytes)
(modulo i pixelBytes))))
(bytes-set! pixels c (modulo (+ upper byte) 256))
(increment! c))]
[(3) ; average
(for ([i (in-range scanlineLength)])
(define byte (read-byte))
(define col ((i . - . (modulo i pixelBytes)) . / . pixelBytes))
(define left (if (< i pixelBytes)
0
(bytes-ref pixels (- c pixelBytes))))
(define upper (if (zero? row)
row
(+ (* (bytes-ref pixels (sub1 row)) scanlineLength)
(* col pixelBytes)
(modulo i pixelBytes))))
(bytes-set! pixels c (modulo (+ byte (floor (/ (+ left upper) 2))) 256))
(increment! c))]
[(4) ; paeth
(for ([i (in-range scanlineLength)])
(define byte (read-byte))
(define col ((i . - . (modulo i pixelBytes)) . / . pixelBytes))
(define left (if (< i pixelBytes)
0
(bytes-ref pixels (- c pixelBytes))))
(match-define (list upper upperLeft)
(cond
[(zero? row) (list 0 0)]
[else (define upper (+ (* (bytes-ref pixels (sub1 row)) scanlineLength)
(* col pixelBytes)
(modulo i pixelBytes)))
(define upperLeft (+ (* (bytes-ref pixels (sub1 row)) scanlineLength)
(* (sub1 col) pixelBytes)
(modulo i pixelBytes)))
(list upper upperLeft)]))
(define p (+ left upper (- upperLeft)))
(define pa (abs (- p left)))
(define pb (abs (- p upper)))
(define pc (abs (- p upperLeft)))
(define paeth (cond
[((pa . <= . pb) . and . (pa . <= . pc)) left]
[(pb . <= . pc) upper]
[else upperLeft]))
(bytes-set! pixels c (modulo (+ byte paeth) 256))
(increment! c)
)]
[else (error 'invalid-filter-algorithm (format "~a" b))])))
(report (bytes-length pixels))
(fn pixels))
(define (read-32bit-integer)
(define signed #f) (define big-endian #t)
(integer-bytes->integer (read-bytes 4) signed big-endian))

@ -1,21 +1,27 @@
#lang pitfall/racket
(require "png-reader.rkt")
(require "png-reader.rkt" "zlib.rkt")
(provide PNG)
(define-subclass object% (PNG data label)
(define-subclass object% (PNG data [label #f])
(super-new)
(field [image (read-png data)]
[pixelBitlength (· image pixelBitlength)]
[width (· image width)]
[height (· image height)]
[imgData (· image imgData)]
[document #f]
[alphaChannel #f]
[obj #f])
(as-methods
embed))
embed
finalize
splitAlphaChannel))
(define/contract (embed this doc-in)
(object? . ->m . void?)
(set-field! document this doc-in)
(unless (· this obj)
(set-field! obj this
@ -29,9 +35,9 @@
(unless (· this image hasAlphaChannel)
(define params (send doc-in ref (mhash 'Predictor 15
'Colors (· this image colors)
'BitsPerComponent (· this image bits)
'Columns (· this width))))
'Colors (· this image colors)
'BitsPerComponent (· this image bits)
'Columns (· this width))))
(hash-set! (· this obj payload) 'DecodeParms params)
(send params end))
@ -46,10 +52,80 @@
(list "Indexed" "DeviceRGB" (sub1 (bytes-length (· this image palette))) palette-ref))]
[else (hash-set! (· this obj payload) 'ColorSpace "DeviceRGB")])
(cond
[(hash-ref (· this image) 'transparency #f)
(cond
[(hash-ref (hash-ref (· this image) 'transparency) 'grayscale #f)
]
[(hash-ref (hash-ref (· this image) 'transparency) 'rgb #f)
]
[(hash-ref (hash-ref (· this image) 'transparency) 'indexed #f)
])]
[(hash-ref (· this image) 'hasAlphaChannel #f)
;; 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.]
(· this splitAlphaChannel)]
[else (· this finalize)])))
(define/contract (finalize this)
(->m void?)
(when (· this alphaChannel)
(define sMask
(send (· this document) ref
(mhash 'Type "XObject"
'Subtype "Image"
'Height (· this height)
'With (· this width)
'BitsPerComponent 8
'Filter "FlateDecode"
'ColorSpace "DeviceGray"
'Decode '(0 1))))
(send sMask end (· this alphaChannel))
(hash-set! (· this obj payload) 'SMask sMask))
;; embed the actual image data
(send (· this obj) end (· this imgData)))
(define/contract (splitAlphaChannel this)
(->m void?)
(define (pixel-proc pixels)
(define colorByteSize (* (· this image colors) (/ (· this image bits) 8)))
(define pixelCount (* (· this width) (· this height)))
(define imgData (make-bytes (* pixelCount colorByteSize)))
(define alphaChannel (make-bytes pixelCount))
(define i 0)
(define p 0)
(define a 0)
(define len (bytes-length pixels))
(for ([idx (in-naturals)]
#:when (< i len))
(bytes-set! imgData p (bytes-ref pixels i))
(increment! p) (increment! i)
(bytes-set! imgData p (bytes-ref pixels i))
(increment! p) (increment! i)
(bytes-set! imgData p (bytes-ref pixels i))
(increment! p) (increment! i)
(bytes-set! alphaChannel a (bytes-ref pixels i))
(increment! a) (increment! i))
(define done 0)
(set-field! imgData this (deflate imgData))
(increment! done)
(when (= done 2) (· this finalize))
(set-field! alphaChannel this (deflate alphaChannel))
(increment! done)
(when (= done 2) (· this finalize))
;; todo: transparency & alpha channel shit
;; embed the actual image data
(send (· this obj) end (· this imgData))))
)
(decodePixels (· this imgData) (· this pixelBitlength) (· this width) (· this height) pixel-proc))
(module+ test
(define pic (make-object PNG (file->bytes "test/assets/test.png")))
(splitAlphaChannel pic))

Binary file not shown.

@ -3,18 +3,18 @@ fs = require 'fs'
make = (doc) ->
# Set the font, draw some text, and embed an image
doc.font('Times-Italic')
doc.font('Helvetica-Bold')
.fontSize(25)
.text('Here comes a JPEG!', 100, 100, lineBreak: no)
.image('assets/test.jpeg', 100, 160, width: 412)
.text('Another fantastic pic', 100, 100, lineBreak: no)
.image('assets/test.png', 100, 160, width: 412)
doc.end()
doc = new PDFDocument({compress: no})
doc.pipe(fs.createWriteStream('test5.pdf'))
doc.pipe(fs.createWriteStream('test8.pdf'))
make doc
doc = new PDFDocument({compress: yes})
doc.pipe(fs.createWriteStream('test5c.pdf'))
doc.pipe(fs.createWriteStream('test8c.pdf'))
make doc

Binary file not shown.

@ -0,0 +1,16 @@
#lang pitfall/pdftest
(define-runtime-path pic "assets/test.png")
(define (proc doc)
(send* doc
[font "Helvetica-Bold"]
[fontSize 25]
[text "Another fantastic pic" 100 100 (hash 'lineBreak #f)]
[image pic 100 160 (hash 'width 412)]))
(define-runtime-path this "test8rkt.pdf")
(make-doc this #f proc #:test #f)
#;(define-runtime-path that "test8crkt.pdf")
#;(make-doc that #t proc)

Binary file not shown.
Loading…
Cancel
Save