| 1 |
;;; cl-pdf copyright 2002-2005 Marc Battyani see license.txt for the details |
|---|
| 2 |
;;; You can reach me at marc.battyani@fractalconcept.com or marc@battyani.net |
|---|
| 3 |
;;; The homepage of cl-pdf is here: http://www.fractalconcept.com/asp/html/cl-pdf.html |
|---|
| 4 |
|
|---|
| 5 |
(in-package #:pdf) |
|---|
| 6 |
|
|---|
| 7 |
;;; PNG Images code proposal by Dmitri Ivanov |
|---|
| 8 |
;;; Notes: |
|---|
| 9 |
;;; The /ProcSet entry of page resource dictionary should be expanded to |
|---|
| 10 |
;;; [/PDF /Text /ImageB /ImageC /ImageI] |
|---|
| 11 |
;;; for compatibility with existing viewer applications. |
|---|
| 12 |
;;; It is considered obsolete in PDF 1.4 (section 9.1) |
|---|
| 13 |
;;; |
|---|
| 14 |
;;; Acrobat error "There was an error processing a page. A drawing error occurred" |
|---|
| 15 |
;;; Reason: Acrobat 5 has a bug and is unable to display transparent monochrome images. |
|---|
| 16 |
;;; Workaround: Remove transparency or save your image in 16 colors (4 bits per pixel) |
|---|
| 17 |
;;; or more. |
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
(declaim (ftype (function (stream) (unsigned-byte 16)) read-byte16) |
|---|
| 21 |
(inline read-byte16)) |
|---|
| 22 |
|
|---|
| 23 |
(defun read-byte16 (stream) |
|---|
| 24 |
;;; Read a 2-byte integer |
|---|
| 25 |
(the (unsigned-byte 16) (+ (ash (read-byte stream) 8) (read-byte stream)))) |
|---|
| 26 |
|
|---|
| 27 |
(declaim (ftype (function (stream) (unsigned-byte 32)) read-byte32)) |
|---|
| 28 |
(defun read-byte32 (stream) |
|---|
| 29 |
;;; Read a 4-byte integer |
|---|
| 30 |
(the (unsigned-byte 32) |
|---|
| 31 |
(+ (ash (read-byte stream) 24) (ash (read-byte stream) 16) |
|---|
| 32 |
(ash (read-byte stream) 8) (read-byte stream)))) |
|---|
| 33 |
|
|---|
| 34 |
(defun read-base-string (stream byte-count) |
|---|
| 35 |
(let ((result (make-string byte-count :element-type 'base-char))) |
|---|
| 36 |
(dotimes (i byte-count result) |
|---|
| 37 |
(declare (fixnum i)) |
|---|
| 38 |
(setf (schar result i) (code-char (read-byte stream)))))) |
|---|
| 39 |
|
|---|
| 40 |
(defclass png-image (bitmap-image) |
|---|
| 41 |
((bits-per-color :accessor bits-per-color :initarg :bits-per-color) |
|---|
| 42 |
;(color-space :accessor color-space :initarg :color-space) |
|---|
| 43 |
(palette :accessor palette :initarg :palette) |
|---|
| 44 |
(mask :accessor mask :initarg :mask))) |
|---|
| 45 |
|
|---|
| 46 |
(defun read-png-file (pathname) |
|---|
| 47 |
(with-open-file (stream pathname :direction :input :element-type '(unsigned-byte 8)) |
|---|
| 48 |
(flet ((skip-octets (octet-length) |
|---|
| 49 |
(dotimes (i octet-length) (read-byte stream)) |
|---|
| 50 |
(values))) |
|---|
| 51 |
(declare (ftype (function (fixnum) (values)) skip-octets)) |
|---|
| 52 |
;; Check signature |
|---|
| 53 |
(unless (and (= (read-byte stream) 137) |
|---|
| 54 |
(= (read-byte stream) #.(char-code #\P)) |
|---|
| 55 |
(= (read-byte stream) #.(char-code #\N)) |
|---|
| 56 |
(= (read-byte stream) #.(char-code #\G)) |
|---|
| 57 |
(= (read-byte stream) 13) |
|---|
| 58 |
(= (read-byte stream) 10) |
|---|
| 59 |
(= (read-byte stream) 26) |
|---|
| 60 |
(= (read-byte stream) 10)) |
|---|
| 61 |
(error "Incorrect PNG file ~s - wrong signature." pathname)) |
|---|
| 62 |
|
|---|
| 63 |
;; Read header |
|---|
| 64 |
(skip-octets 4) |
|---|
| 65 |
(when (string/= (read-base-string stream 4) "IHDR") |
|---|
| 66 |
(error "Incorrect PNG file ~s - wrong header." pathname)) |
|---|
| 67 |
(let* ((width (read-byte32 stream)) |
|---|
| 68 |
(height (read-byte32 stream)) |
|---|
| 69 |
(bits-per-color (read-byte stream)) |
|---|
| 70 |
(cs (read-byte stream)) |
|---|
| 71 |
(color-space (case cs |
|---|
| 72 |
(0 "DeviceGray") |
|---|
| 73 |
(2 "DeviceRGB") |
|---|
| 74 |
(3 "Indexed") |
|---|
| 75 |
(otherwise (error "Alpha channel not supported ~s" pathname)))) |
|---|
| 76 |
palette mask data) |
|---|
| 77 |
(when (> bits-per-color 8) (error "16-bit depth not supported ~s" pathname)) |
|---|
| 78 |
(when (/= (read-byte stream) 0) (error "Unknown compression method ~s" pathname)) |
|---|
| 79 |
(when (/= (read-byte stream) 0) (error "Unknown filter method ~s" pathname)) |
|---|
| 80 |
(when (/= (read-byte stream) 0) (error "Interlacing not supported ~s" pathname)) |
|---|
| 81 |
(skip-octets 4) |
|---|
| 82 |
|
|---|
| 83 |
;; Extract palette, transparency and data if any |
|---|
| 84 |
(do ((octet-length (read-byte32 stream) (read-byte32 stream)) |
|---|
| 85 |
(marker (read-base-string stream 4) (read-base-string stream 4))) |
|---|
| 86 |
((= octet-length 0)) |
|---|
| 87 |
;(declare (type (unsigned-byte 32) octet-length) |
|---|
| 88 |
(cond ((string= marker "PLTE") |
|---|
| 89 |
;; Palette: octet-length should be a multiple of 3 |
|---|
| 90 |
(setq palette (make-array octet-length :element-type '(unsigned-byte 8))) |
|---|
| 91 |
(read-sequence palette stream)) |
|---|
| 92 |
((string= marker "tRNS") ; transparency info |
|---|
| 93 |
(let ((trns (make-array octet-length :element-type '(unsigned-byte 8)))) |
|---|
| 94 |
(read-sequence trns stream) |
|---|
| 95 |
(setq mask (case cs |
|---|
| 96 |
(0 ; DeviceGray |
|---|
| 97 |
(list (aref trns 1))) |
|---|
| 98 |
(2 ; DeviceRGB |
|---|
| 99 |
(list (aref trns 1) (aref trns 3) (aref trns 5))) |
|---|
| 100 |
(otherwise ; Indexed |
|---|
| 101 |
(let ((position (position 0 trns))) |
|---|
| 102 |
(when position (list position)))))))) |
|---|
| 103 |
((string= marker "IDAT") ; image data block |
|---|
| 104 |
(let ((start 0)) |
|---|
| 105 |
(if (null data) |
|---|
| 106 |
(setf data (make-array octet-length |
|---|
| 107 |
:element-type '(unsigned-byte 8) |
|---|
| 108 |
:adjustable t)) |
|---|
| 109 |
(progn |
|---|
| 110 |
(setf start (first (array-dimensions data))) |
|---|
| 111 |
(adjust-array data (+ start octet-length)))) |
|---|
| 112 |
(read-sequence data stream :start start))) |
|---|
| 113 |
((string= marker "IEND") |
|---|
| 114 |
(return)) |
|---|
| 115 |
(t ;"pHYs" |
|---|
| 116 |
(skip-octets octet-length))) |
|---|
| 117 |
(skip-octets 4)) |
|---|
| 118 |
|
|---|
| 119 |
(when (and (= cs 3) (null palette)) ; Indexed |
|---|
| 120 |
(error "Palette is missing in ~s" pathname)) |
|---|
| 121 |
(make-instance 'png-image :nb-components color-space |
|---|
| 122 |
:width width :height height :data data |
|---|
| 123 |
:bits-per-color bits-per-color :palette palette :mask mask))))) |
|---|
| 124 |
|
|---|
| 125 |
(defmethod make-image ((png png-image) &key &allow-other-keys) |
|---|
| 126 |
;; For color key masking, the Mask entry is an array of 2*N integers, |
|---|
| 127 |
;; [min1 max1 ... minN maxN], where N is the number of color components in the |
|---|
| 128 |
;; image's color space. |
|---|
| 129 |
(let* ((nb-components (nb-components png)) |
|---|
| 130 |
(palette (palette png)) |
|---|
| 131 |
(lookup |
|---|
| 132 |
(if palette ;(string= nb-components "Indexed") |
|---|
| 133 |
(make-instance 'indirect-object :content ; comperess is controlled by config |
|---|
| 134 |
(make-instance 'pdf-stream |
|---|
| 135 |
;:dict-values `(("/Filter" . ,filter)) |
|---|
| 136 |
:content palette |
|---|
| 137 |
:no-compression t)))) |
|---|
| 138 |
(mask (mask png))) |
|---|
| 139 |
(make-instance 'pdf:image |
|---|
| 140 |
:bits (data png) |
|---|
| 141 |
:width (width png) :height (height png) |
|---|
| 142 |
:color-space (if (string= nb-components "Indexed") |
|---|
| 143 |
(vector (pdf-name nb-components) |
|---|
| 144 |
"/DeviceRGB" ; base |
|---|
| 145 |
(1- (truncate (length palette) 3)) ; maximum valid index value |
|---|
| 146 |
lookup) |
|---|
| 147 |
(pdf-name nb-components)) |
|---|
| 148 |
:bits-per-color (bits-per-color png) |
|---|
| 149 |
:decode (if (string= nb-components "DeviceCMYK") #(1 0 1 0 1 0 1 0)) |
|---|
| 150 |
:mask (when mask |
|---|
| 151 |
(apply #'vector (mapcan (lambda (i) (list i i)) mask))) |
|---|
| 152 |
:filter "/FlateDecode" ; the only method we recognize |
|---|
| 153 |
:decode-parms `(("/Predictor" . 15) |
|---|
| 154 |
("/Colors" . ,(if (string= nb-components "DeviceRGB") 3 1)) |
|---|
| 155 |
("/BitsPerComponent" . ,(bits-per-color png)) |
|---|
| 156 |
("/Columns" . ,(width png))) |
|---|
| 157 |
:no-compression t))) ; data bits already come compressed |
|---|
| 158 |
|
|---|
| 159 |
|
|---|