;;;; -*- Mode: Lisp; Package: User; -*-
;;;; --------------------------------------------------------------------------
;;;; File:          png.cl
;;;; Description:   A program to decode and encode PNG files
;;;; Author:        Harald Musum <musum@pvv.org>
;;;; Created:       2001-01-17
;;;; Distribution:  See the accompanying file COPYING.
;;;; --------------------------------------------------------------------------
;;;;  (c) copyright 2001 by Harald Musum
;;;;
;;;; $Id: png.cl,v 1.6 2001/05/21 20:43:32 musum Exp $
;;;;
;;;; DOCUMENTATION
;;;;
;;;; See the file README.
;;;; 



(in-package "PNG")


(eval-when (compile)
  (declaim (optimize (speed 0) (safety 3) (space 1) (debug 3) #+cmu (ext:inhibit-warnings 3))))
;  (declaim (optimize (speed 3) (safety 0) (space 1) (debug 1) #+cmu (ext:inhibit-warnings 3))))



(defparameter *debug-level* 0)
(defparameter *debug-stream* t)


(defmacro debug-format-1 (&body body)
  `(case *debug-level*
     (0 nil)
     (1 (format *debug-stream* ,@body))
     (2 (format *debug-stream* ,@body))))


(defmacro debug-format-2 (&body body)
  `(case *debug-level*
     (0 nil)
     (1 nil)
     (2 (format *debug-stream*,@body))))



(defconstant +png-major-version+ 0)
(defconstant +png-minor-version+ 1)

(defconstant +png-signature+ #(137 80 78 71 13 10 26 10))


(defconstant +crc-table+
  (loop with array = (make-array 256 :element-type '(unsigned-byte 32))
	for i from 0 below 256
	for c = i
	do
	(loop for k from 0 below 8
	      do
	      (if (= (logand c 1) 1)
		  (setq c (logxor (ash c -1) #xedb88320))
		(setq c (ash c -1))))
	(setf (aref array i) c)
	finally (return array)))



(deftype bit-depth () '(member 1 2 4 8 16))
(deftype color-type () '(member 0 2 3 4 6))
(defconstant +bit-depth-and-color-type-combination+ '((0 1) (0 2) (0 4) (0 8) (0 16)
						      (2 8) (2 16)
						      (3 1) (3 2) (3 4) (3 8)
						      (4 8) (4 16)
						      (6 8) (6 16)))


(defstruct png-image
  ihdr
  idat
  plte
  text)


(defstruct ihdr
  width
  height
  bit-depth
  color-type
  compression-method
  filter-method
  interlace-method)

(defstruct text
  keywords
  strings)




(defun read-32-bits (stream)
  "Read a 32-bit word from STREAM, MSB first"
  (loop with length = 0
        for i from 0 below 4 do
        (incf length (ash (read-byte stream nil nil) (* 8 (- 3 i))))
        finally (return length)))



(defun read-32-bits-from-array (array)
  "Read a 32-bit word from ARRAY, MSB first"
  (loop with length = 0
        for i from 0 below 4 do
        (incf length (ash (aref array i) (* 8 (- 3 i))))
        finally (return length)))


(defun read-n-bytes-from-array (array n)
  "Read N octets from ARRAY, MSB first"
  (loop with length = 0
        for i from 0 below n do
        (incf length (ash (aref array i) (* 8 (- 1 i))))
        finally (return length)))



;;; CRC-32 functions

(defun update-crc (crc buffer)
  (declare (type (unsigned-byte 32) crc)
           (type (simple-array (unsigned-byte 8)) buffer)
           (optimize speed))
  (setq crc (logxor crc #xffffffff))
  (loop for n from 0 below (length buffer)
	for i = (logand #xff (logxor crc (aref buffer n)))
	do	
	(setq crc (logxor (aref +crc-table+ i) (ash crc -8)))
	finally (return (logxor crc #xffffffff))))

(defun crc (buffer)
  (update-crc 0 buffer))




;;; Functions that operate on PNG chunks

(defun read-chunk-length (stream)
  "Read 4 bytes from STREAM and return length, else return nil"
  (loop with length = 0
        for i from 0 below 4
        for byte = (read-byte stream nil nil)
        do
        (if byte
            (incf length (ash byte (* 8 (- 3 i))))
          (return nil))
        finally (return length)))



(defun read-chunk-type (stream)
  (let ((type (make-array 4 :element-type '(unsigned-byte 8))))
    (read-sequence type stream)
    (print type)
    (map 'string #'code-char type)))



(defun read-chunk (stream length)
  "Read a chunk from STREAM, calculate CRC and compare with supplied CRC.  Return
data if the CRCs are equal, else return an error"
  ;; We have to calculate the CRC of both chunk data and chunk type
  ;; (which is 4 bytes long, so we add 4 bytes to the data array)
  (let ((data (make-array (+ length 4) :element-type '(unsigned-byte 8)))
        data-crc crc chunk-type)
;    (format t "~&chunk-length is ~D~%" length)
    (read-sequence data stream)
    (setq chunk-type (map 'string #'code-char (subseq data 0 4)))
    (debug-format-2 "~&chunk-type is ~a.~%" chunk-type)
    (setq data-crc (crc data))
    (setq crc (read-32-bits stream))
    (if (equal crc data-crc)
        (values chunk-type (subseq data 4))
      (error "CRC error"))))



;;; Chunk decoding functions


(defun decode-idat (idat size)
  "Decode IDAT chunk and return a vector with the result"
  (debug-format-2 "~&IDAT processing. Length of data: ~D.~%" (length idat))
  (decode-buffer idat size))



(defun decode-plte (plte)
  "Decode PLTE chunk and return a vector palette"
  (let* ((length (length plte))
	 (palette (make-array length)))
    (assert (zerop (mod length 3)))
    (debug-format-2 "~&PLTE processing. Length of plte: ~D.~%" length)
    (loop for i from 0 below (floor length 3)
	  do
	  (setf (aref palette i) (vector (aref plte (* i 3))
					 (aref plte (+ (* i 3) 1))
					 (aref plte (+ (* i 3) 2))))
	  finally (return palette))))


(defun decode-ihdr (ihdr)
  "Decode IHDR chunk and return an ihdr struct"
  (debug-format-2 "~&IHDR processing.~%")
  (make-ihdr :width (read-32-bits-from-array (subseq ihdr 0 4))
             :height (read-32-bits-from-array (subseq ihdr 4 8))
             :bit-depth (aref ihdr 8)
             :color-type (aref ihdr 9)
             :compression-method (aref ihdr 10)
             :filter-method (aref ihdr 11)
             :interlace-method (aref ihdr 12)))


(defun decode-gama (gama)
  "Decode gAMA chunk.  Not implemented"
  (debug-format-1 "~&gAMA processing not implemented. Gamma: ~D.~%"
		  (read-32-bits-from-array gama)))



(defun decode-text (text)
  "Decode TEXT chunk and return keyword and string"
  (debug-format-2 "~&tEXt processing")
  (let* ((position (position (code-char 0)
			     (map 'string #'code-char text) :test #'equal))
	 (keyword (map 'string #'code-char (subseq text 0 position)))
	 (string (map 'string #'code-char (subseq text (1+ position)))))
    (values keyword string)))




;;; Filtering functions

    
(defun paeth-predictor (a b c)
  (declare (type (unsigned-byte 8) a b c)
	   (optimize speed))
  (let* ((p (+ a (- b c )))
         (pa (abs (- p a)))
         (pb (abs (- p b)))
         (pc (abs (- p c))))
    (cond ((and (<= pa pb) (<= pa pc)) a)
	  ((<= pb pc) b)
	  (t c))))
    

;; Remove filter line for line, since filter method can be changed for
;; each scanline and we may want to display line by line anyway
(defun remove-filter (data filter-type index previous-line-index
		 length bytes-per-pixel)
  "Remove filtering of FILTER-TYPE for a scanline"
  (declare (type fixnum index length filter-type bytes-per-pixel)
	   (type (vector (unsigned-byte 8)) data)
;	   (type (unsigned-byte 8) filter-type bytes-per-pixel)
	   (optimize speed))
  (loop for i fixnum from 0 below length
	for raw fixnum = (aref data (+ index i))
	for above fixnum = (if previous-line-index
			       (aref data (+ previous-line-index i))
			     0)
	for left fixnum = (if (>= (- i bytes-per-pixel) 1)
			      (aref data (+ index (- i bytes-per-pixel)))
			    0)
	for upper-left fixnum = (if (and previous-line-index
					 (>= (- i bytes-per-pixel) 1))
				    (aref data (+ previous-line-index
						  (- i bytes-per-pixel)))
				  0)
	do
;      (format t "~&index: ~D~%" index)
;      (format t "~&previous-line-index: ~D~%" previous-line-index)
;      (format t "~&i: ~D~%" i)
;      (format t "~&raw: ~D~%" raw)
;      (format t "~&above: ~D~%" above)
;      (format t "~&left: ~D~%" left)
;      (format t "~&upper-left: ~D~%" upper-left)
	(setf (aref data (+ index i))
	      (ecase filter-type
		(0 raw)
		(1 (logand 255 (+ raw left)))
		(2 (logand 255 (+ raw above)))
		(3 (logand 255 (+ raw (floor (+ left above) 2))))
		(4 (logand 255 (+ raw (paeth-predictor left above upper-left))))))))


(defun apply-filter (data filtered-line filter-type index previous-line-index
		     length bytes-per-pixel)
  "Apply FILTER-TYPE filter to a scanline"
  (declare (type (vector (unsigned-byte 8)) data filtered-line)
	   (type fixnum index length)
	   (type (unsigned-byte 8) filter-type bytes-per-pixel)
	   (optimize speed))
  (loop for i fixnum from 1 below length
	;; left-index is the index of the pixel to the left of the
	;; current one
	for left-index fixnum = (- i bytes-per-pixel)
	for raw of-type (unsigned-byte 8) = (aref data (+ index i))
	for left of-type (unsigned-byte 8) = (if (>= left-index 1)
						 (aref data (+ index left-index))
					       0)
	for above of-type (unsigned-byte 8) = (if previous-line-index
						  (aref data (+ previous-line-index i))
						0)
	for upper-left of-type (unsigned-byte 8) = (if (and previous-line-index
							    (>= left-index 1))
						       (aref data
							     (+ previous-line-index left-index))
						     0)
	do
;      (format t "~&index: ~D~%" index)
;      (format t "~&previous-line-index: ~D~%" previous-line-index)
;      (format t "~&i: ~D~%" i)
;      (format t "~&raw: ~D~%" raw)
;      (format t "~&above: ~D~%" above)
;      (format t "~&left: ~D~%" left)
;      (format t "~&upper-left: ~D~%" upper-left)
	(setf (aref filtered-line i)
	      (ecase filter-type
		(0 raw)
		(1 (mod (- raw left) 256))
		(2 (mod (- raw above) 256))
		(3 (mod (- raw (floor (+ left above) 2)) 256))
		(4 (mod (- raw (paeth-predictor left above upper-left)) 256))))))



;;; Output to file (PGM and PNM pictures)

(defun write-pgm (image filename width height comment &optional (16-bit t))
  (debug-format-1 "~&Writing PNG image to PGM file: ~A.~%" filename)
  (with-open-file (stream filename :direction :output :if-exists :supersede)
    (format stream "P2~%#~A~%~D ~D~%" comment width height)
    (if 16-bit
	(progn (write-string "65536" stream)
	       (dotimes (i height)
		 (dotimes (j width)
		   (when (zerop (mod j 4))
		     (terpri stream))
		   (write (ldb (byte 16 0) (aref image i j)) :stream stream)
		   (write-string " " stream))))
      (progn (write-string "255" stream)
	     (dotimes (i height)
	       (dotimes (j width)
		 (when (zerop (mod j 4))
		   (terpri stream))
		 (write (ldb (byte 8 0) (aref image i j)) :stream stream)
		 (write-string " " stream)))))))


(defun write-raw-pgm (image filename width height comment)
  (debug-format-1 "~&Writing PNG image to raw PGM file: ~A.~%" filename)
  (with-open-file (stream filename
			  :direction :output
			  :if-exists :supersede
			  :element-type '(unsigned-byte 8))
    (format stream "P5~%#~A~%~D ~D~%~d~%" comment width height 255)
    (dotimes (i height)
      (dotimes (j width)
	(write-byte (ldb (byte 8 0) (aref image i j)) stream)))))



(defun write-ppm (image filename width height comment &optional (16-bit t))
  (debug-format-1 "~&Writing PNG image to PPM file: ~A.~%" filename)
  (with-open-file (stream filename :direction :output :if-exists :supersede)
    (format stream "P3~%#~A~%~D ~D~%" comment width height)
    (if 16-bit
	(progn (write-string "65536" stream)
      	       (dotimes (i height)
		 (dotimes (j width)
		   (when (zerop (mod j 4))
		     (terpri stream))
		   (write (ldb (byte 16 0) (aref image i j)) :stream stream)
		   (write-string " " stream)
		   (write (ldb (byte 16 16) (aref image i j)) :stream stream)
		   (write-string " " stream)
		   (write (ldb (byte 16 32) (aref image i j)) :stream stream)
		   (write-string " " stream))))
      (progn (write-string "255" stream)
	     (dotimes (i height)
	       (dotimes (j width)
		 (when (zerop (mod j 4))
		   (terpri stream))
		 (write (ldb (byte 8 0) (aref image i j)) :stream stream)
		 (write-string " " stream)
		 (write (ldb (byte 8 8) (aref image i j)) :stream stream)
		 (write-string " " stream)
		 (write (ldb (byte 8 16) (aref image i j)) :stream stream)
		 (write-string " " stream)))))))


(defun write-raw-ppm (image filename width height comment)
  (debug-format-1 "~&Writing PNG image to raw PPM file: ~A.~%" filename)
  (with-open-file (stream filename
			  :direction :output
			  :if-exists :supersede
			  :element-type '(unsigned-byte 8))
    (format stream "P6~%#~A~%~D ~D~%~d~%" comment width height 255)
    (dotimes (i height)
      (dotimes (j width)
	(write-byte (ldb (byte 8 0) (aref image i j)) stream)
	(write-byte (ldb (byte 8 8) (aref image i j)) stream)
	(write-byte (ldb (byte 8 16) (aref image i j)) stream)))))


(defun write-pnm (image output-file width height comment color-type bit-depth)
  (let ((comment (concatenate 'string "Source file: " (file-namestring comment)))
	(16-bit (= bit-depth 16)))
    (if (or (= color-type 0) (= color-type 4))
        (write-pgm image (if (stringp output-file) output-file "pgm") width
                   height comment 16-bit)
;        (write-raw-pgm image (if (stringp output-file) output-file "pgm") width
;                       height comment)
      (write-ppm image (if (stringp output-file) output-file "ppm") width
                 height comment 16-bit))))
;        (write-raw-ppm image (if (stringp output-file) output-file "ppm") width
;                       height comment))))





(defun read-signature (stream)
  "Read PNG signature and return t if correct, else return nil"
  (dotimes (i (length +png-signature+) t)
    (unless (eql (read-byte stream nil nil) (aref +png-signature+ i))
      (return nil))))



(defun result-array-size (ihdr)
  "Compute the size of the array to store the IDAT chunks in."
  (if (= (ihdr-interlace-method ihdr) 0)
      (* (ihdr-height ihdr) (line-length ihdr))
      ;; FIXME.  Is this correct for 16 bit images too?
    (+ (* (ihdr-height ihdr) (line-length ihdr)) (* (ceiling (ihdr-height ihdr) 8) 15))))


(defun line-length (ihdr)
  "Return the length of a scanline"
  ;; Add one because the first byte in each line represents the filter type
  (+ 1
     (ceiling
      (* (ihdr-width ihdr)
         (bits-per-pixel ihdr))
      8)))


(defun bits-per-pixel (ihdr)
  (ecase (ihdr-color-type ihdr)
    (0 (ihdr-bit-depth ihdr))
    (2 (* (ihdr-bit-depth ihdr) 3))
    (3 (ihdr-bit-depth ihdr))
    (4 (* (ihdr-bit-depth ihdr) 2))
    (6 (* (ihdr-bit-depth ihdr) 4))))


(defun bytes-per-pixel (ihdr)
  (ceiling (bits-per-pixel ihdr) 8))



(defun write-pixel-16-bits (target x y red green blue alpha)
  (setf (aref target y x)
	(dpb red (byte 16 0)
	     (dpb green (byte 16 16)
		  (dpb blue (byte 16 32)
		       (dpb (- 255 alpha) (byte 16 48) 0))))))



;; FIXME: Should be able to write pixels that have greater width and
;; height than 1 (when the image is interlaced)
(defun write-pixel (target x y red green blue alpha)
  "Write each pixel to target array as a 32-bit value"
  (declare (type fixnum x y red green blue alpha)
	   (optimize speed))
  (setf (aref target y x)
	(dpb red (byte 8 0)
	     (dpb green (byte 8 8)
		  (dpb blue (byte 8 16)
		       (dpb (- 255 alpha) (byte 8 24) 0))))))



(defun raw-component-value (source byte-index bit-index bit-depth)
  (declare (type (unsigned-byte 32) byte-index bit-index)
	   (type (unsigned-byte 8) bit-depth)
	   (type (vector (unsigned-byte 8)) source)
	   (optimize speed))
  (ecase bit-depth
    (1 (ldb (byte 1 (- 7 (mod bit-index 8)))
	    (aref source (+ byte-index (ash bit-index -3)))))
    (2 (ldb (byte 2 (* 2 (- 3 (mod bit-index 4))))
	    (aref source (+ byte-index (ash bit-index -2)))))
    (4 (ldb (byte 4 (* 4 (- 1 (mod bit-index 2))))
	    (aref source (+ byte-index (ash bit-index -1)))))
    (8 (aref source (+ byte-index bit-index)))
    (16 (logior (ash (aref source byte-index) 8)
		(aref source (+ byte-index 1))))))


(defun component-value (source byte-index bit-index bit-depth)
  (ecase bit-depth
    (1 (* 255 (raw-component-value source byte-index bit-index bit-depth)))
    (2 (* 85 (raw-component-value source byte-index bit-index bit-depth)))
    (4 (* 17 (raw-component-value source byte-index bit-index bit-depth)))
    (8 (raw-component-value source byte-index bit-index bit-depth))
    (16 (raw-component-value source byte-index bit-index bit-depth))))



(defun write-scanline (image image-data target x0 dx y index
			     width pixel-width pixel-height bit-depth color-type)
  ;; pixel-width and pixel-height might be used later if we implement
  ;; rendering of interlaced picture. Ignored for now.
  (declare (ignore pixel-width pixel-height)
	   (type (unsigned-byte 32) x0 dx y index width)
	   (type (unsigned-byte 8) bit-depth color-type)
	   (optimize speed))
  (loop for x of-type (unsigned-byte 32) from x0 by dx
	;; i is the index sample, which means that if bit-depth is 1
	;; it counts bits, if bit-depth is 8 it counts octets
	for i of-type (unsigned-byte 32) from 0
	until (>= x width)
	do
	(ecase color-type
	  (0
	   (let ((value (component-value image-data
					 (if (= bit-depth 16)
					     (+ index (* 2 i))
					   index)
					 i
					 bit-depth)))
	     (if (= bit-depth 16)
		 (write-pixel-16-bits target x y value value value 255)
	       (write-pixel target x y value value value 255))))
	  (2
	   (let ((red (component-value image-data
				       (+ index (* 3 i (ash bit-depth -3)))
				       0
				       bit-depth))
		 (green (component-value image-data
					 (+ index (* 3 i (ash bit-depth -3))
					    (ash bit-depth -3))
					 0
					 bit-depth))
		 (blue (component-value image-data
					(+ index (* 3 i (ash bit-depth -3))
					   (* 2 (ash bit-depth -3)))
					0
					bit-depth)))
	     (if (= bit-depth 16)
		 (write-pixel-16-bits target x y red green blue 255)
               (write-pixel target x y red green blue 255))))
	  (3
	   (let* ((palette-index (raw-component-value image-data index i bit-depth))
		  (plte (png-image-plte image))
		  (red (aref (aref plte palette-index) 0))
		  (green (aref (aref plte palette-index) 1))
		  (blue (aref (aref plte palette-index) 2)))
	     (write-pixel target x y red green blue 255)))
	  (4
	   (let ((value (component-value image-data
					 (+ index (* 2 i (ash bit-depth -3)))
					 0
					 bit-depth))
		 (alpha (component-value image-data
					 (+ index (* 2 i (ash bit-depth -3))
					    (ash bit-depth -3))
					 0
					 bit-depth)))
	     (if (= bit-depth 16)
		 (write-pixel-16-bits target x y value value value alpha)
	       (write-pixel target x y value value value alpha))))
	  (6
	   (let ((red (component-value image-data
				       (+ index (* 4 i (ash bit-depth -3)))
				       0
				       bit-depth))
		 (green (component-value image-data
					 (+ index (* 4 i (ash bit-depth -3))
					    (ash bit-depth -3))
					 0
					 bit-depth))
		 (blue (component-value image-data
					(+ index (* 4 i (ash bit-depth -3))
					   (* (ash bit-depth -3)) 2)
					0
					bit-depth))
		 (alpha (component-value image-data
					 (+ index (* 4 i (ash bit-depth -3))
					    (* (ash bit-depth -3) 3))
					 0
					 bit-depth)))
	     (if (= bit-depth 16)
		 (write-pixel-16-bits target x y red green blue alpha)
               (write-pixel target x y red green blue alpha)))))))


(defun decode-image (stream)
  (declare (optimize speed))
  (if (read-signature stream)
      (debug-format-2 "~&PNG signature OK.~%")
    (error "~A is not a PNG file." stream))
  (loop for chunk-length  = (read-chunk-length stream)
	with idat = (make-array 0 :element-type '(unsigned-byte 8))
	with ihdr
	with plte
	with text
	with width
	with height
	when chunk-length
	do
	(multiple-value-bind (chunk-type data)
	    (read-chunk stream chunk-length)
	  (cond ((string= chunk-type "IHDR")
		 (debug-format-1 "~&Processing IHDR chunk.~%")
		 (setq ihdr (decode-ihdr data))
		 ;; FIXME. Should also check that the combinations of
		 ;; the different parameters are valid.
		 (check-type (ihdr-bit-depth ihdr) bit-depth)
		 (check-type (ihdr-color-type ihdr) color-type)
		 (check-type (ihdr-compression-method ihdr) (integer 0))
		 (check-type (ihdr-compression-method ihdr) (integer 0))
		 (check-type (ihdr-filter-method ihdr) (integer 0))
		 (check-type (ihdr-interlace-method ihdr) (integer 0 1))
		 (setq width (ihdr-width ihdr))
		 (setq height (ihdr-height ihdr))
		 (assert (> width 0))
		 (assert (> height 0))
		 (when (>= *debug-level* 1) (print ihdr)))
		((string= chunk-type "gAMA")
		 (debug-format-1 "~&Processing gAMA chunk.~%")
		 (decode-gama data))
		((string= chunk-type "PLTE")
		 (debug-format-1 "~&Processing PLTE chunk.~%")
		 (setq plte (decode-plte data)))
		 ;;(print plte))
		((string= chunk-type "IDAT")
		 (debug-format-1 "~&Processing IDAT chunk.~%")
		 (setq idat (concatenate '(vector (unsigned-byte 8) *) idat data)))
		((string= chunk-type "tEXt")
                 (debug-format-1 "~&Processing tEXt chunk.~%")
                 (multiple-value-bind (keyword string) (decode-text data)
                   (if text
                       (progn
                         (push keyword (text-keywords text))
                         (push string (text-strings text)))
                     (setq text (make-text :keywords (list keyword) :strings (list string)))))
		 (when (>= *debug-level* 1) (print text)))
		((string= chunk-type "IEND")
		 (debug-format-1 "~&IEND chunk found.  End of processing.~%")
		 (return (make-png-image
			  :ihdr ihdr
			  :idat (decode-idat idat
					     (result-array-size ihdr))
			  :plte plte
			  :text text)))
		(t (debug-format-1 "~&Unsupported chunk type found.  Skipping chunk.~%"))))))




;; FIXME.  Much common code with decode-image
(defun image-size (file)
  (with-open-file (stream file :direction :input :element-type '(unsigned-byte 8))
    (if (read-signature stream)
	(debug-format-2 "~&PNG signature OK.~%")
      (error "~A is not a PNG file." stream))
    (let ((chunk-length (read-chunk-length stream))
	  ihdr width height)
      (when chunk-length
	(multiple-value-bind (chunk-type data)
	    (read-chunk stream chunk-length)
	  (cond ((string= chunk-type "IHDR")
		 (debug-format-1 "~&Processing IHDR chunk.~%")
		 (setq ihdr (decode-ihdr data))
		 ;; FIXME. Should also check that the combinations of
		 ;; the different parameters are valid.
		 (check-type (ihdr-bit-depth ihdr) bit-depth)
		 (check-type (ihdr-color-type ihdr) color-type)
		 (check-type (ihdr-compression-method ihdr) (integer 0))
		 (check-type (ihdr-compression-method ihdr) (integer 0))
		 (check-type (ihdr-filter-method ihdr) (integer 0))
		 (check-type (ihdr-interlace-method ihdr) (integer 0 1))
		 (setq width (ihdr-width ihdr))
		 (setq height (ihdr-height ihdr))
		 (assert (> width 0))
		 (assert (> height 0))
		 (when (>= *debug-level* 1) (print ihdr)))))
	 (return-from image-size (values width height))))))



(defun decode-stream (stream &key output-file)
  (let* ((image (decode-image stream))
	   (image-data (png-image-idat image))
	   (line-length (line-length (png-image-ihdr image)))
	   (previous-line-index nil)
	   (width (ihdr-width (png-image-ihdr image)))
	   (height (ihdr-height (png-image-ihdr image)))
	   (bit-depth (ihdr-bit-depth (png-image-ihdr image)))
	   (color-type (ihdr-color-type (png-image-ihdr image)))
	   (target (make-array (list height width))))
      (declare (type (vector (unsigned-byte 8)) image-data)
	       (optimize speed))
      (debug-format-1 "~&Decoding stream: ~A.~%" stream)
      (case (ihdr-interlace-method (png-image-ihdr image))
	(0
	 (debug-format-2 "~&Interlace method 0.~%")
	 (loop for y from 0 below height
	       with index = 0
	       with dx = 1
	       do
;               (format t "~&Line ~D~%" y)
;               (format t "~&index ~D~%" index)
;               (format t "~&line-length ~D~%" line-length)
;               (format t "~&Filter type ~D~%" (aref image-data index))
	       (remove-filter image-data (aref image-data index) index previous-line-index
			      line-length (bytes-per-pixel (png-image-ihdr image)))
	       (setq previous-line-index index)
;               (format t "~&Line filtered: ~A~%"
;                       (subseq image-data index (+ index line-length)))
	       (write-scanline image image-data target 0 dx y (1+ index)
			       width 1 1 bit-depth color-type)
	       (incf index line-length)))
	(1
	 (debug-format-2 "~&Interlace method 1.~%")
	 (loop for j fixnum from 1 to 7
	       for x0 fixnum in '(0 4 0 2 0 1 0)
	       for dx fixnum in '(8 8 4 4 2 2 1)
	       for y0 fixnum in '(0 0 4 0 2 0 1)
	       for dy fixnum in '(8 8 8 4 4 2 2)
	       for pixel-width fixnum in '(8 4 4 2 2 1 1)
	       for pixel-height fixnum in '(8 4 4 2 2 1 1)
	       ;; Add one because the first byte represents filter method
	       for line-length = (+ 1 (ceiling
				       (* (bits-per-pixel (png-image-ihdr image))
					  (ceiling (- width x0) dx)) 8))
	       for previous-line-index = nil
	       with index fixnum = y0
	       do
	       (when (= j 1)
		 (setq index y0))
;               (format t "~&Pass no. ~D~%" j)
;               (format t "~&x0: ~D~%" x0)
;               (format t "~&dx: ~D~%" dx)
;               (format t "~&y0: ~D~%" y0)
;               (format t "~&dy: ~D~%" dy)
;               (format t "~&pixel-width: ~D~%" pixel-width)
;               (format t "~&pixel-height: ~D~%" pixel-height)
	       (loop for y from y0 by dy
		     until (>= y height)
		     when (> line-length 1)
		     do
;                     (format t "~&Line ~D~%" y)
;                     (format t "~&index ~D~%" index)
;                     (format t "~&line-length ~D~%" line-length)
;                     (format t "~&Filter type ~D~%" (aref image-data index))
		     (remove-filter image-data (aref image-data index) index
				    previous-line-index line-length
				    (bytes-per-pixel (png-image-ihdr image)))
		     (setq previous-line-index index)
;                     (format t "~&Line filtered: ~A~%" (subseq image-data index
;                                                             (+ index line-length)))
		     (write-scanline image image-data target x0 dx
				     y (1+ index) width pixel-width
				     pixel-height bit-depth color-type)
		     (incf index line-length))))
	(t (error "Unknown interlace method")))
;      (format t "~&idat array length ~D~%" (length image-data))
;      (format t "~&idat array written to target arrray")
      (when output-file
	(write-pnm target output-file width height (pathname stream) color-type bit-depth))
      target))


(defun decode-file (file &key (output-file nil))
  (with-open-file (stream file :direction :input :element-type '(unsigned-byte 8))
    (debug-format-1 "~&Decoding file: ~A.~%" file)
    (decode-stream stream :output-file output-file)))




;;; Writing PNG files


(defun encode-stream (source stream &key (filter-type 4) (btype 1)
		      (bit-depth 8) (color-type 2) (source-bit-depth 8))
  (let* ((compression-method 0)
	   (filter-method 0)
	   (interlace-method 0)
	   (height (array-dimension source 0))
	   (width (array-dimension source 1))
	   (ihdr (make-ihdr :width width
			    :height height
			    :bit-depth bit-depth
			    :color-type color-type
			    :compression-method compression-method
			    :filter-method filter-method
			    :interlace-method interlace-method))
	   (text (list "Software"
		       (format nil "CL PNG library, version ~D.~D."
			       +png-major-version+
			       +png-minor-version+)))
	   (plte-hash-table nil)
	   (line-length (line-length ihdr))
	   ;; Add one byte per line to for the filter type
	   (idat (make-array (* height line-length)
			     :initial-element 0
			     :element-type '(unsigned-byte 8)))
	   (line (make-array line-length :element-type '(unsigned-byte 8))))
      (check-type (ihdr-bit-depth ihdr) bit-depth)
      (check-type (ihdr-color-type ihdr) color-type)
      (check-type (ihdr-compression-method ihdr) (integer 0))
      (check-type (ihdr-compression-method ihdr) (integer 0))
      (check-type (ihdr-filter-method ihdr) (integer 0))
      (check-type (ihdr-interlace-method ihdr) (integer 0 1))

      (when (= source-bit-depth 16)
	(loop for i from 0 below (* width height)
	      for red = (ldb (byte 16 0) (row-major-aref source i))
	      for green = (ldb (byte 16 16) (row-major-aref source i))
	      for blue = (ldb (byte 16 32) (row-major-aref source i))
	      for alpha = (ldb (byte 16 48) (row-major-aref source i))
	      for result = 0
	      do
;              (debug-format-1 "~&red ~D.~%" (ash red -8))
;              (debug-format-1 "~&green ~D.~%" (ash green -8))
;              (debug-format-1 "~&blue ~D.~%" (ash blue -8))
;              (debug-format-1 "~&alpha ~D.~%" (ash alpha -8))
              (setq result (dpb (ash red -8) (byte 8 0)
				(dpb (ash green -8) (byte 8 8)
				     (dpb (ash blue -8) (byte 8 16)
					  (dpb (ash alpha -8) (byte 8 24) result)))))
;	      (debug-format-1 "~&result ~D.~%" result)
	      (setf (row-major-aref source i) result)))

      
      (unless (member (list color-type bit-depth)
		      +bit-depth-and-color-type-combination+ :test #'equal)
	(error "~&This combination of color-type (~A) and bit-depth (~A) is illegal."
	       color-type bit-depth))
      (debug-format-1 "~&Encoding image.~%")
      (when (>= *debug-level* 1) (print ihdr))
;      (format t "~&line-length: ~A~%" line-length)
;      (print (type-of idat))
      (when (= color-type 3)
	(setq plte-hash-table (make-plte-hash-table source)))
;	(print plte-hash-table))
      (loop for i from 0 below height
	    do
;            (format t "~&Indices: ~D,~D,~D~%"
;                      (1+ (* j 3)) (+ (* j 3) 2) (+ (* j 3) 3))
	    (setf (subseq idat (* i line-length) (* (1+ i) line-length))
		  (if plte-hash-table
		      (read-pixel source line i width filter-type
				  color-type bit-depth plte-hash-table)
		    (read-pixel source line i width filter-type
				color-type bit-depth))))
;	    (format t "~&Line: ~8b" (subseq idat (* i line-length) (* (1+ i) line-length))))
;      (print idat)
      (loop for i from 0 below height
	    with previous-line-index = nil
	    with index = 0
	    with filtered-line1 = (make-array line-length
					      :element-type '(unsigned-byte 8)
					      :initial-element filter-type)
	    with filtered-line2 = (make-array line-length
					      :element-type '(unsigned-byte 8)
					      :initial-element filter-type)
	    do
;            (format t "~&Line ~D before filtering: ~A~%"
;                    i (subseq idat index (+ index line-length)))
	    (apply-filter idat filtered-line1 filter-type index previous-line-index
			  line-length (bytes-per-pixel ihdr))
;	    (format t "~&Line ~D after filtering: ~A~%" i filtered-line1)
	    (when (> i 0)
	      (replace idat filtered-line2 :start1 (- index  (* 1 line-length))
		       :end1 (+ line-length (- index (* 1 line-length))))
	      ;; Special case for the last line
	      (when (= i (1- height))
		(replace idat filtered-line1 :start1 index
			 :end1 (+ line-length index))))
	    (replace filtered-line2 filtered-line1)
	    (setq previous-line-index index)
      	    (incf index line-length))
      (debug-format-1 "~&Writing PNG signature.~%")
      (write-sequence +png-signature+ stream)
      (debug-format-1 "~&Writing IHDR chunk.~%")
      (write-ihdr stream ihdr)
      (when (= color-type 3)
	(debug-format-1 "~&Writing PLTE chunk.~%")
	(write-plte stream plte-hash-table))
      (debug-format-1 "~&Writing IDAT chunk.~%")
      (encode-idat idat btype #'write-idat stream)
      (debug-format-1 "~&Writing tEXt chunk.~%")
      (write-text stream text)
      (debug-format-1 "~&Writing IEND chunk.~%")
      (write-iend stream)
      (debug-format-1 "~&Image written to file ~A.~%" (pathname stream))))


(defun encode-file (source output-file &key (filter-type 4) (btype 1)
			   (bit-depth 8) (color-type 2) (source-bit-depth 8))
  (with-open-file (stream output-file
			  :direction :output
			  :if-exists :supersede
			  :element-type '(unsigned-byte 8))
    (encode-stream source stream :filter-type filter-type :btype btype
		   :bit-depth bit-depth :color-type color-type
		   :source-bit-depth source-bit-depth)))



(defun write-32-bits-value (stream value)
  (write-byte (ldb (byte 8 24) value) stream)
  (write-byte (ldb (byte 8 16) value) stream)
  (write-byte (ldb (byte 8 8) value) stream)
  (write-byte (ldb (byte 8 0) value) stream))


(defun write-32-bits-to-array (array value &optional (start 0))
  (loop for i from 0 to 3
	do
	(setf (aref array (+ start i)) (ldb (byte 8 (* 8 (- 3 i))) value))))


(defun write-chunk (stream data)
  (write-32-bits-value stream (- (length data) 4))
  (write-sequence data stream)
  (write-32-bits-value stream (crc data)))



(defun write-idat (stream idat)
  (let ((array (concatenate '(vector (unsigned-byte 8) *)
			    (map 'vector #'char-code "IDAT")
			    idat)))
    (write-chunk stream array)))


(defun write-ihdr (stream ihdr)
  ;; Make an array consisting of the values in ihdr plus (13 bytes)
  ;; the chunk type (4 bytes)a
  (let ((ihdr-array (make-array (+ 13 4) :element-type '(unsigned-byte 8))))
    (setf (subseq ihdr-array 0 4) (map 'vector #'char-code "IHDR"))
    (write-32-bits-to-array ihdr-array (ihdr-width ihdr) 4)
    (write-32-bits-to-array ihdr-array (ihdr-height ihdr) 8)
    (setf (aref ihdr-array 12) (ihdr-bit-depth ihdr))
    (setf (aref ihdr-array 13) (ihdr-color-type ihdr))
    (setf (aref ihdr-array 14) (ihdr-compression-method ihdr))
    (setf (aref ihdr-array 15) (ihdr-filter-method ihdr))
    (setf (aref ihdr-array 16) (ihdr-interlace-method ihdr))
    (write-chunk stream ihdr-array)))


(defun write-iend (stream)
  (write-chunk stream (make-array 4
				  :element-type '(unsigned-byte 8)
				  :initial-contents (map 'vector #'char-code "IEND"))))


(defun write-plte (stream plte-hash-table)
  (let* ((entries (hash-table-count plte-hash-table))
	 (plte (make-array (+ (* 3 entries) 4) :element-type '(unsigned-byte 8))))
    (setf (subseq plte 0 4) (map 'vector #'char-code "PLTE"))
    (loop for value being the hash-values of plte-hash-table
	  for index = (first value)
	  for pixel-value = (second value)
	  do
;          (format t "~&index, pixel-value: ~D, ~D~%" index pixel-value)
;          (format t "~&R,G,B: ~D, ~D, ~D~%" (ldb (byte 8 0) pixel-value)
;                  (ldb (byte 8 8) pixel-value) (ldb (byte 8 16) pixel-value))
	  (setf (aref plte (+ 4 (* index 3))) (ldb (byte 8 0) pixel-value))
    	  (setf (aref plte (+ 5 (* index 3))) (ldb (byte 8 8) pixel-value))
       	  (setf (aref plte (+ 6 (* index 3))) (ldb (byte 8 16) pixel-value)))
    (write-chunk stream plte)))


(defun write-text (stream text)
  (let* ((keyword (first text))
	 (string (second text))
	 (length (+ (length keyword) 1 (length string)))
	 (text (make-array (+ length 4) :element-type '(unsigned-byte 8)
			   :initial-contents
    			   (concatenate '(vector (unsigned-byte 8) *)
					(map 'vector #'char-code "tEXt")
					(map 'vector #'char-code keyword)
                                        (make-array 1 :initial-element 0)
					(map 'vector #'char-code string)))))
    ;; FIXME. Check that keyword and string does not contain the null
    ;; character and that the length of them are not too large.
    (write-chunk stream text)))




(defun encode-idat (idat btype writer-function stream)
  (encode-buffer idat btype writer-function stream))




(defun read-pixel (source line i width filter-type color-type
		   bit-depth &optional plte-hash-table)
  "Read each pixel from source array and write it to a scanline,
applying a byte for the filter-type as the first byte in the line"
;  (declare (type fixnum x y red green blue alpha)
;           (optimize speed))
;  (format t "~&width in read-pixel: ~D~%" width)
  (setf (aref line 0) filter-type)
  (ecase color-type
    (0 (loop for j from 0 below (floor width (floor 8 bit-depth))
	     do
	     (setf (aref line (1+ j)) (pixel-value source i j bit-depth 0))))
    (2 (loop for j from 0 below (floor width (floor 8 bit-depth))
	     do
    	     (setf (aref line (1+ (* j 3))) (pixel-value source i j bit-depth 0))
	     (setf (aref line (+ (* j 3) 2)) (pixel-value source i j bit-depth 8))
	     (setf (aref line (+ (* j 3) 3)) (pixel-value source i j bit-depth 16))))
    (3 (loop for j from 0 below (floor width (floor 8 bit-depth))
	     do
	     (setf (aref line (1+ j))
		   (first (gethash (ldb (byte 24 0) (aref source i j)) plte-hash-table)))))
    (4 (loop for j from 0 below (floor width (floor 8 bit-depth))
	     do
    	     (setf (aref line (1+ (* j 2))) (pixel-value source i j bit-depth 0))
    	     (setf (aref line (+ (* j 2) 2)) (pixel-value source i j bit-depth 24))))
    (6 (loop for j from 0 below (floor width (floor 8 bit-depth))
	     do
  	     (setf (aref line (1+ (* j 4))) (pixel-value source i j bit-depth 0))
	     (setf (aref line (+ (* j 4) 2)) (pixel-value source i j bit-depth 8))
	     (setf (aref line (+ (* j 4) 3)) (pixel-value source i j bit-depth 16))
	     (setf (aref line (+ (* j 4) 4)) (pixel-value source i j bit-depth 24)))))
  line)
    


(defun pixel-value (source i j bit-depth weight)
;  (format t "~&items-per-byte: ~D~%" (floor 8 bit-depth))
;  (format t "~&i,j: ~D,~D~%" i j)
  (declare (type fixnum i j)
           (type (unsigned-byte 8) bit-depth weight)
           (optimize speed))
  (loop with result of-type (unsigned-byte 8) = 0
        with items-per-byte of-type (unsigned-byte 8) = (floor 8 bit-depth)
	for l of-type (unsigned-byte 32) from (* j items-per-byte) below (* (1+ j) items-per-byte)
	do
;        (format t "~&l: ~D~%" l)
;        (format t "~&byte-weight: ~D~%" (* bit-depth (mod l items-per-byte)))
;        (format t "~&source-value: ~D~%" (ldb (byte 8 weight) (aref source i l)))
	(setq result (dpb (ldb (byte 8 weight) (aref source i l))
			  (byte bit-depth
				(- 8 (* (mod l items-per-byte) bit-depth) bit-depth))
			  result))
	finally (return result)))



(defun make-plte-hash-table (source)
;  (debug-format-2 "~&height: ~D~%" (array-dimension source 0))
;  (debug-format-2 "~&widht: ~D~%" (array-dimension source 1))
  (loop with hash-table = (make-hash-table :test #'equal)
	with index = 0
	for i from 0 below (* (array-dimension source 0) (array-dimension source 1))
	for key = (ldb (byte 24 0) (row-major-aref source i))
	do
	(unless (gethash key hash-table)
	  (setf (gethash key hash-table) (list index key))
	  (incf index))
	finally (progn
		  (debug-format-1 "~&Number of entries in PLTE: ~D~%" index)
		  (when (> index 255)
		    (error "Sorry. Too many entries in PLTE palette. You cannot use a PLTE chunk for this picture."))
		  (return
		    hash-table))))
	


(defun decode-test-png (input-file &optional (output-file t))
  (format t "~&")
  (format t "~&---------------------------------------------------------")
  (format t "~&Testing PNG file decoding with file: ~A.~%" input-file)
  (decode-file input-file :output-file output-file))
    


(defun decode-test-all ()
  (let ((output-file-number1 97)
	(output-file-number2 97))
    (dolist (k (directory "pictures/*.png"))
      (unless (char= (char (pathname-name k) 0) #\0)
	(format t "~&Now processing ~A.~%" k)
	(decode-test-png k
		  (concatenate
		   'string "test"
		   (make-string 1 :initial-element (code-char output-file-number1))
		   (make-string 1 :initial-element (code-char output-file-number2))
		   ".pnm"))
;	(sleep 10)
	(if (= output-file-number2 122)	    
	    (progn
	      (incf output-file-number1)
	      (setq output-file-number2 97))
	  (incf output-file-number2))))))



(defun encode-test-png (input-file &optional (output-file t))
  (format t "~&")
  (format t "~&---------------------------------------------------------")
  (format t "~&Testing PNG file encoding with input file: ~A.~%" input-file)
  (format t "~&Output to file: ~A.~%" output-file)
  (encode-file (decode-file input-file) output-file))


(defun encode-test-all ()
  (let ((output-file-number1 97)
	(output-file-number2 97))
    ;; Lispworks and Clisp lists the file in opposite order and when
    ;; testing I have files with errors in them as the last files ...
    (dolist (k #-(or lispworks clisp) (directory "pictures/*.png")
	       #+(or lispworks clisp) (reverse(directory "pictures/*.png")))
      (unless (char= (char (pathname-name k) 0) #\0)
	(format t "~&Now processing ~A.~%" k)
	(encode-test-png k
			 (concatenate
			  'string "test"
			  (make-string 1 :initial-element (code-char output-file-number1))
			  (make-string 1 :initial-element (code-char output-file-number2))
			  ".png"))
	(if (= output-file-number2 122)
	    (progn
	      (incf output-file-number1)
	      (setq output-file-number2 97))
	  (incf output-file-number2))))))

