--- a/src/main.lisp Tue Jan 16 19:56:02 2018 -0500
+++ b/src/main.lisp Tue Jan 16 23:12:25 2018 -0500
@@ -9,14 +9,12 @@
(loop :while (eql #\# (peek-char t stream nil nil))
:do (skip-comment stream)))
-(defun peek (stream)
- (peek-char nil stream nil nil))
-
(defun read-number (stream)
+ "Read the next ASCII-encoded number from `stream`."
(skip-whitespace stream)
(loop :with i = 0
- :for ch = (peek stream)
+ :for ch = (peek-char nil stream nil nil)
:while ch
:for digit = (digit-char-p ch)
:while digit
@@ -25,16 +23,18 @@
:finally (return i)))
(defun write-number (value stream)
+ "Write `value` to stream as an ASCII-encoded number."
(format stream "~D " value))
(defun read-magic-byte (stream)
+ "Read the initial `P#` from `stream`, returning the magic `#` character."
(assert (eql (read-byte stream) (char-code #\P)))
(code-char (read-byte stream)))
(defun file-format (magic-byte)
- "Return `(values format binary?)` for the given magic byte."
+ "Return `(values format binary?)` for the given magic byte character."
(ecase magic-byte
(#\1 (values :pbm nil))
(#\2 (values :pgm nil))
@@ -44,6 +44,7 @@
(#\6 (values :ppm t))))
(defun magic-byte (file-format binary?)
+ "Return the magic byte character to use for the given format/encoding combination."
(if binary?
(ecase file-format
(:pbm #\4)
@@ -56,6 +57,7 @@
(defun pixel-type (format bit-depth)
+ "Return the type specifier for a pixel of an image with the given `format` and `bit-depth`."
(ecase format
(:pbm 'bit)
(:pgm `(integer 0 ,bit-depth))
@@ -83,7 +85,7 @@
(funcall reader stream)
(funcall reader stream))
:element-type 'fixnum))))))
- data))
+ (values data format bit-depth)))
(defun write% (data stream format binary? maximum-value)
@@ -106,6 +108,24 @@
;;;; API ----------------------------------------------------------------------
(defun read-from-stream (stream)
+ "Read a PPM image file from `stream`, returning an array of pixels and more.
+
+ `stream` must be a binary input stream.
+
+ The primary return value will be a 2D array with dimensions `(width height)`.
+ Each element of the array will be a single pixel whose type depends on the
+ image file format:
+
+ * PBM: `bit`
+ * PGM: `(integer 0 bit-depth)`
+ * PPM: `(simple-array (integer 0 bit-depth) (3))`
+
+ Two other values are returned:
+
+ * The format of the image that was read (one of `:pbm`, `:pgm`, `:ppm`).
+ * The bit depth of the image.
+
+ "
(multiple-value-bind (format binary?)
(file-format (read-magic-byte stream))
(read% (flexi-streams:make-flexi-stream stream :external-format :ascii)
@@ -115,18 +135,86 @@
(format :ppm)
(encoding :binary)
(bit-depth (ecase format (:pbm 1) ((:pgm :ppm) 255))))
+ "Write a PPM image array `data` to `stream`.
+
+ Nothing is returned.
+
+ `format` must be one of `:pbm`, `:pgm`, `:ppm`.
+
+ `encoding` must be one of `:binary`, `:ascii`.
+
+ `bit-depth` must be the desired bit depth of the image (the maximum value any
+ particular pixel can have). For PBM images it must be `1`.
+
+ For PBM and PGM images, `data` must be a two dimensional array of integers
+ between `0` and `bit-depth` inclusive.
+
+ For PPM images, `data` must be a two dimensional array of pixels, each of
+ which must be a 3 element vector of integers between `0` and `bit-depth`
+ inclusive.
+
+ "
(check-type format (member :ppm :pgm :pbm))
(check-type encoding (member :binary :ascii))
- (write% data stream format (eql :binary encoding) bit-depth))
+ (if (eql format :pbm)
+ (check-type bit-depth (eql 1))
+ (check-type bit-depth (integer 1 *)))
+ (write% data stream format (eql :binary encoding) bit-depth)
+ (values))
(defun read-from-file (path)
+ "Read a PPM image file from `path`, returning an array of pixels and more.
+
+ The primary return value will be a 2D array with dimensions `(width height)`.
+ Each element of the array will be a single pixel whose type depends on the
+ image file format:
+
+ * PBM: `bit`
+ * PGM: `(integer 0 bit-depth)`
+ * PPM: `(simple-array (integer 0 bit-depth) (3))`
+
+ Two other values are returned:
+
+ * The format of the image that was read (one of `:pbm`, `:pgm`, `:ppm`).
+ * The bit depth of the image.
+
+ "
(with-open-file (s path :direction :input :element-type '(unsigned-byte 8))
(read-from-stream s)))
(defun write-to-file (path data &key
+ (if-exists nil if-exists-given)
(format :ppm)
(encoding :binary)
(bit-depth (ecase format (:pbm 1) ((:pgm :ppm) 255))))
- (with-open-file (s path :direction :output :if-exists :supersede :element-type '(unsigned-byte 8))
- (write-to-stream s data :format format :encoding encoding :bit-depth bit-depth)))
+ "Write a PPM image array `data` to a file at `path`.
+
+ Nothing is returned.
+
+ `format` must be one of `:pbm`, `:pgm`, `:ppm`.
+
+ `encoding` must be one of `:binary`, `:ascii`.
+
+ `bit-depth` must be the desired bit depth of the image (the maximum value any
+ particular pixel can have). For PBM images it must be `1`.
+
+ For PBM and PGM images, `data` must be a two dimensional array of integers
+ between `0` and `bit-depth` inclusive.
+
+ For PPM images, `data` must be a two dimensional array of pixels, each of
+ which must be a 3 element vector of integers between `0` and `bit-depth`
+ inclusive.
+
+ "
+ (flet ((write-it (stream)
+ (write-to-stream stream data
+ :format format
+ :encoding encoding
+ :bit-depth bit-depth)))
+ (if if-exists-given
+ (with-open-file (s path :direction :output :if-exists if-exists :element-type '(unsigned-byte 8))
+ (write-it s))
+ (with-open-file (s path :direction :output :element-type '(unsigned-byte 8))
+ (write-it s))))
+ (values))