dib.lisp Unix DownloadWindows Download
;; -*- mode: common-lisp; package: user -*-

;; This file implements writing of Device Independent Bitmap (dib, bmp)
;; file format that is a native format on Windows.

(in-package :user)

(defmacro lobyte (byte) `(logand #xff ,byte))
(defmacro hibyte (byte) `(logand #xff (ash ,byte -8)))
(defmacro loword (word) `(logand #xffff ,word))
(defmacro hiword (word) `(logand #xffff (ash ,word -16)))

(defun write-word (stream word)
  (write-byte (lobyte word) stream)
  (write-byte (hibyte word) stream))

(defun write-long (stream long)
   (write-word stream (loword long))
   (write-word stream (hiword long)))

(defun read-word (stream)
  (logior (read-byte stream)
	  (ash (read-byte stream) 8)))

(defun read-long (stream)
  (logior (read-word stream)
	  (ash (read-word stream) 16)))

(defmacro with-dib-writing ((stream writer padder
			     &key width height bits-per-pixel (colors #()))
			    &body body)
  (let ((gstream (gensym)))
    `(let ((,gstream ,stream))
       (multiple-value-bind (,writer ,padder)
	   (write-dib-header ,gstream ,width ,height ,bits-per-pixel ,colors)
	 ,@body))))

(defun write-dib-header (stream width height bits-per-pixel &optional (colors #()))
  (let* ((row-pad (if* (eq bits-per-pixel 24)
                     then (mod (- 4 (mod (* 3 width) 4)) 4)
                     else 0))
         (num-colors (length colors))
         (image-offset
          (+ 54 (* 4 num-colors))))	; 14 for file-header
					; 40 for bitmap-info-header
    (write-byte (char-int #\B) stream)
    (write-byte (char-int #\M) stream)
    (write-long stream
                   (+ image-offset
                      (* row-pad height)
                      (floor (* width height bits-per-pixel) ; pixels
                             8)))	; file size
    (write-long stream 0)		; reserved1 and 2
    (write-long stream image-offset)
    ;; end of file-header; start of bitmap-info-header
    (write-long stream 40)		; info-header-size [use the maximum]
    (write-long stream width)
    (write-long stream height)
    (write-word stream 1)		; bit-planes
    (write-word stream bits-per-pixel)
    (write-long stream 0)		; compression = no
    (write-long stream 0)		; size of all bits (needed if compression)
    (write-long stream 0)		; x-pixels-per-meter
    (write-long stream 0)		; y-pixels-per-meter
    (write-long stream num-colors)	; number of colors used
    (write-long stream num-colors)	; number of "important" colors
    (loop for color across colors
	do (write-byte (rgbi-blue color) stream)
	   (write-byte (rgbi-green color) stream)
	   (write-byte (rgbi-red color) stream)
	   (write-byte 0 stream))	; reserved (pad color to longword)
    (make-pixel-writers bits-per-pixel)))

;; This generic function is called with an number of bits-per-pixel and it
;; returns two functions.  The first is a function that writes a pixel value
;; to a stream.  It should be called with successive pixels for each row.
;; The second function should to be called to close out a row, doing any
;; necessary flushing and padding.

(defgeneric make-pixel-writers (bits-per-pixel))

(defmethod make-pixel-writers ((bits-per-pixel (eql 1)))
  (let ((bit 1)
	(byte 0))
    (values (lambda (stream pixel)
	      (when (eql pixel 1) (incf byte bit))
	      (when (eql (setf bit (ash bit 1)) #x100)
		(write-byte byte stream)
		(setf bit 1 byte 0)))
	    (lambda (stream)
	      ;; Pad the final byte if necessary.
	      (unless (eql bit 1)
		(write-byte byte stream))))))

(defmethod make-pixel-writers ((bits-per-pixel (eql 4)))
  (let ((hilo nil)
	(save 0))
    (values (lambda (stream pixel)
	      (if (null hilo)
		  (setq save (logand #x0f pixel)
			hilo t)
		(progn
		  (write-byte (logior save (ash (logand pixel #x0f) 4)) stream)
		  (setf hilo nil))))
	    (lambda (stream)
	      ;; Pad the final byte if necessary.
	      (when hilo
		(write-byte save stream))))))

(defmethod make-pixel-writers ((bits-per-pixel (eql 8)))
  (values (lambda (stream pixel)
	    (write-byte pixel stream))
	  (lambda (stream)
	    (declare (ignore stream))
	    nil)))

(defmethod make-pixel-writers ((bits-per-pixel (eql 16)))
  (values (lambda (stream pixel)
	    (write-byte (lobyte pixel) stream)
	    (write-byte (hibyte pixel) stream))
	  (lambda (stream)
	    (declare (ignore stream))
	    nil)))

(defmethod make-pixel-writers ((bits-per-pixel (eql 24)))
  (let ((width 0))
    (values (lambda (stream pixel)
	      (incf width)		; Count pixels in row.
	      (write-byte (rgbi-blue  pixel) stream)
	      (write-byte (rgbi-green pixel) stream)
	      (write-byte (rgbi-red   pixel) stream)
	      pixel)
	    (lambda (stream)
	      (dotimes (n (mod (- 4 (mod (* 3 width) 4)) 4))
		(write-byte 0 stream))
	      (setf width 0)))))

(defmethod make-pixel-writers ((bits-per-pixel (eql 32)))
  (values (lambda (stream pixel)
	    (write-byte (rgbi-blue  pixel) stream)
	    (write-byte (rgbi-green pixel) stream)
	    (write-byte (rgbi-red   pixel) stream)
	    (write-byte (rgbi-inten pixel) stream)
	    pixel)
	  (lambda (stream)
	    (declare (ignore stream))
	    nil)))