# HG changeset patch # User Steve Losh # Date 1550949808 18000 # Node ID 70f64dff49b5236c50cb30fc9955fd83c78f7ed6 # Parent 8a6db152fb11f96d4d4b94ff3312c585f684cbd2 Add actual documentation diff -r 8a6db152fb11 -r 70f64dff49b5 LICENSE.markdown --- a/LICENSE.markdown Sat Feb 02 14:30:59 2019 -0500 +++ b/LICENSE.markdown Sat Feb 23 14:23:28 2019 -0500 @@ -1,4 +1,4 @@ -Copyright (c) 2017 Steve Losh and contributors +Copyright (c) 2019 Steve Losh and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff -r 8a6db152fb11 -r 70f64dff49b5 Makefile --- a/Makefile Sat Feb 02 14:30:59 2019 -0500 +++ b/Makefile Sat Feb 23 14:23:28 2019 -0500 @@ -36,6 +36,8 @@ docs/build/index.html: $(docfiles) $(apidocs) docs/title cd docs && ~/.virtualenvs/d/bin/d + cd docs && rm -rf build/assets + cd docs && cp -R ./assets build/assets docs: docs/build/index.html diff -r 8a6db152fb11 -r 70f64dff49b5 README.markdown --- a/README.markdown Sat Feb 02 14:30:59 2019 -0500 +++ b/README.markdown Sat Feb 23 14:23:28 2019 -0500 @@ -1,5 +1,8 @@ -cl-netpbm is a Common Lisp library for reading and writing the [netpbm image -formats (PPM, PGM, and PBM)](https://en.wikipedia.org/wiki/Netpbm_format). +cl-netpbm is a pure Common Lisp library for reading and writing the [netpbm +image formats (PPM, PGM, and PBM)](https://en.wikipedia.org/wiki/Netpbm_format). + +These image formats are very simple, but not efficient. If you need extreme +performance you should not use these formats (or this library). * **License:** MIT/X11 * **Documentation:** diff -r 8a6db152fb11 -r 70f64dff49b5 cl-netpbm.asd --- a/cl-netpbm.asd Sat Feb 02 14:30:59 2019 -0500 +++ b/cl-netpbm.asd Sat Feb 23 14:23:28 2019 -0500 @@ -9,7 +9,7 @@ :depends-on () - :in-order-to ((asdf:test-op (asdf:test-op :netpbm/test))) + :in-order-to ((asdf:test-op (asdf:test-op :cl-netpbm/test))) :serial t :components ((:module "vendor" :serial t @@ -25,7 +25,7 @@ :author "Steve Losh " :license "MIT/X11" - :depends-on (:netpbm :1am) + :depends-on (:cl-netpbm :1am) :serial t :components ((:file "package.test") diff -r 8a6db152fb11 -r 70f64dff49b5 docs/01-installation.markdown --- a/docs/01-installation.markdown Sat Feb 02 14:30:59 2019 -0500 +++ b/docs/01-installation.markdown Sat Feb 23 14:23:28 2019 -0500 @@ -5,4 +5,11 @@ can clone the repository into your [Quicklisp local-projects][local] directory for now. +The `cl-netpbm` system provides all the functionality of the library. It has no +dependencies. + +The `cl-netpbm/test` system provides the unit tests. It depends on `1am` and +`uiop`, and also requires [ImageMagick][im] for its fuzz testing. + [local]: https://www.quicklisp.org/beta/faq.html#local-project +[im]: https://www.imagemagick.org/ diff -r 8a6db152fb11 -r 70f64dff49b5 docs/02-usage.markdown --- a/docs/02-usage.markdown Sat Feb 02 14:30:59 2019 -0500 +++ b/docs/02-usage.markdown Sat Feb 23 14:23:28 2019 -0500 @@ -1,10 +1,164 @@ Usage ===== +The [netpbm image formats (PPM, PGM, and PBM)][netpbm] are a family of very +simple image formats. You can convert to/from these formats with third-party +tools like [ImageMagick][im]. + +Instead of trying to link `libjpeg` into your Lisp program with CFFI, you can +use this library to read/write images in the simple netpbm format and then use +a third-party tool to convert to whatever other format(s) you need. + +cl-netpbm provides functions for both reading and writing images, as well as +a little bit of sugar for working with OpenGL textures. + +[netpbm]: https://en.wikipedia.org/wiki/Netpbm_format +[im]: https://www.imagemagick.org/ [TOC] -Docs ----- +Reading Images +-------------- + +The `netpbm:read-from-stream` function can be used to read a netpbm file from +stream. The stream *must* be a binary input stream with `element-type` of +`(unsigned-byte 8)`. + +Three values are returned: a 2D array of pixels, the format of the image +(`:pbm`, `:pgm`, or `:ppm`), and the bit depth of the image: + + (with-open-file (f "foo.ppm" :element-type '(unsigned-byte 8)) + (netpbm:read-from-stream f)) + ; => + #2A((#(255 0 0) #(0 255 0) #(0 0 255)) + (#(255 0 0) #(0 255 0) #(0 0 255)) + (#(255 0 0) #(0 255 0) #(0 0 255)) + (#(255 0 0) #(0 255 0) #(0 0 255))) + :PPM + 255 + +A `netpbm:read-from-file` function is also provided to save you some +boilerplate: + + (netpbm:read-from-file "foo.ppm") + +See the [API reference](../reference) for these functions for more information. + +Image Arrays +------------ + +When an image is read a 2D array of pixels is returned. + +The first array dimension is the columns of the image and the second array +dimension is the rows. This means to access pixel `(x, y)` of the image you use +`(aref image x y)`. The `y` dimension starts at the top of the image and grows +downwards, so: + +* `(aref image 0 0)` returns the top-left pixel. +* `(aref image width height)` returns the bottom-right pixel. + +The `element-type` of the array (i.e. the type of the pixels) depends on the +format of the image that was read: + +* Pixels of PBM images are of type `bit`. +* Pixels of PGM images are of type `(integer 0 ,bit-depth)`. +* Pixels of PPM images are of type `(simple-array (integer 0 ,bit-depth) (3))`. + +Lower values represent darker colors, e.g. for PBM images `0` is black and `1` +is white, for PGM `0` is black, `1` is very dark gray, etc. + +(Note that the actual PBM image format on disk is backwards from all the other +netpbm formats — in PBM a `1` bit represents black. cl-netpbm flips the bits +when reading/writing PBM files for consistency on the Lisp side of things.) + +Writing Images +-------------- + +An image array can be written to a stream with `netpbm:write-to-stream`. The +stream *must* be a binary output stream with `element-type` of `(unsigned-byte +8)`. The input image array must have the appropriate contents for the desired +output format (e.g. integers for `:pbm` and `:pgm`, 3-element vectors for +`:pgm`): -foo + (with-open-file (f "foo.pbm" + :direction :output + :element-type '(unsigned-byte 8)) + (netpbm:write-to-stream + f #2A((0 0 0 0 0 0 0 0 0 0) + (0 1 1 1 1 1 1 1 1 0) + (0 1 0 0 1 1 0 0 0 0) + (0 1 0 0 1 0 1 0 0 0) + (0 0 1 1 0 0 0 1 1 0) + (0 0 0 0 0 0 0 0 0 0)) + :format :pbm + :encoding :binary)) + ; => Write an "R" character into "foo.pbm" + +`netpbm:write-to-file` is provided for convenience: + + (netpbm:write-to-file "foo.pbm" image :format :pbm) + +See the [API reference](../reference) for these functions for more information. + +Example: Inverting an Image +--------------------------- + +For a concrete example, let's invert an image. + +First we'll get a kitten photo to work with and convert it to PPM with +ImageMagick: + + wget 'https://upload.wikimedia.org/wikipedia/commons/7/75/Cute_grey_kitten.jpg' -O kitten.jpg + convert -resize x600 kitten.jpg kitten.ppm + +The initial kitten ([source](https://en.m.wikipedia.org/wiki/File:Cute_grey_kitten.jpg)): + +![kitten photo](../assets/kitten.jpg) + +Now we can write our Lisp code: + + (defun invert-value (value) + (- 255 value)) + + (defun invert-pixel (pixel) + (map-into pixel #'invert-value pixel)) + + (defun invert-image (image) + (destructuring-bind (width height) (array-dimensions image) + (dotimes (y height) + (dotimes (x width) + (invert-pixel (aref image x y)))))) + + (let ((image (netpbm:read-from-file "kitten.ppm"))) + (invert-image image) + (netpbm:write-to-file "kitten-inverted.ppm" image)) + +And convert it back into JPG: + + convert kitten-inverted.ppm kitten-inverted.jpg + +And now we have an inverted kitten: + +![kitten photo](../assets/kitten-inverted.jpg) + +OpenGL Textures +--------------- + +cl-netpbm's image array layout (column major, 3-element vectors for RGB pixels, +y-axis from top to bottom) is meant to be simple for humans to code against. +If you're working with OpenGL and were hoping to use cl-netpbm to easily load +textures (instead of fiddling around with FFI'ing out to something like +[STB](https://github.com/nothings/stb)) this won't work, because OpenGL expects +a different format (a flat array of `single-float`s, y-axis from bottom to top). + +For your convenience, cl-netpbm provides two additional functions that will +return an array in the format OpenGL expects: `netpbm:read-texture-from-stream` +and `netpbm:read-texture-from-file`. + +Remember, though, that the netpbm formats are designed for simplicity, not +efficiency. If you're just going through [an OpenGL +tutorial](https://learnopengl.com/) and want to load a texture without screwing +around with CFFI, cl-netpbm can help you out. But if you're creating an actual +game where performance matters, you'll likely want to replace it with something +much more efficient. + diff -r 8a6db152fb11 -r 70f64dff49b5 docs/03-reference.markdown --- a/docs/03-reference.markdown Sat Feb 02 14:30:59 2019 -0500 +++ b/docs/03-reference.markdown Sat Feb 23 14:23:28 2019 -0500 @@ -57,6 +57,42 @@ +### `READ-TEXTURE-FROM-FILE` (function) + + (READ-TEXTURE-FROM-FILE PATH) + +Read a PPM image file from `path`, returning an OpenGL-style array and more. + + The primary return value will be an OpenGL-style array of type: + + (simple-array (single-float 0.0 1.0) (* width height 3)) + + The vertical axis of the image will be flipped, which is what OpenGL expects. + + Three values are returned: the array, the width, and the height. + + + +### `READ-TEXTURE-FROM-STREAM` (function) + + (READ-TEXTURE-FROM-STREAM STREAM) + +Read a PPM image file from `stream`, returning an OpenGL-style array and more. + + `stream` must be a binary input stream, specifically of `(unsigned-byte 8)`s + unless you *really* know what you're doing. The stream must contain a PPM + formatted image — PBM and PGM images are not supported. + + The primary return value will be an OpenGL-style array of type: + + (simple-array (single-float 0.0 1.0) (* width height 3)) + + The vertical axis of the image will be flipped, which is what OpenGL expects. + + Three values are returned: the array, the width, and the height. + + + ### `WRITE-TO-FILE` (function) (WRITE-TO-FILE PATH DATA &KEY (IF-EXISTS NIL IF-EXISTS-GIVEN) (FORMAT :PPM) (ENCODING :BINARY) diff -r 8a6db152fb11 -r 70f64dff49b5 docs/assets/kitten-inverted.jpg Binary file docs/assets/kitten-inverted.jpg has changed diff -r 8a6db152fb11 -r 70f64dff49b5 docs/assets/kitten.jpg Binary file docs/assets/kitten.jpg has changed diff -r 8a6db152fb11 -r 70f64dff49b5 docs/index.markdown --- a/docs/index.markdown Sat Feb 02 14:30:59 2019 -0500 +++ b/docs/index.markdown Sat Feb 23 14:23:28 2019 -0500 @@ -1,7 +1,1 @@ -cl-netpbm is a Common Lisp library for reading and writing the [netpbm image -formats (PPM, PGM, and PBM)](https://en.wikipedia.org/wiki/Netpbm_format). - -* **License:** MIT/X11 -* **Documentation:** -* **Mercurial:** -* **Git:** +../README.markdown \ No newline at end of file diff -r 8a6db152fb11 -r 70f64dff49b5 src/main.lisp --- a/src/main.lisp Sat Feb 02 14:30:59 2019 -0500 +++ b/src/main.lisp Sat Feb 23 14:23:28 2019 -0500 @@ -363,28 +363,25 @@ (file-format (read-magic-byte stream)) (read-netpbm (make-peekable-stream stream) format binary? nil))) -(defun read-texture-from-stream (stream) - "Read a PPM image file from `stream`, returning an OpenGL-style array and more. +(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: - `stream` must be a binary input stream, specifically of `(unsigned-byte 8)`s - unless you *really* know what you're doing. The stream must contain a PPM - formatted image — PBM and PGM images are not supported. - - The primary return value will be an OpenGL-style array of type: + * PBM: `bit` + * PGM: `(integer 0 maximum-value)` + * PPM: `(simple-array (integer 0 maximum-value) (3))` - (simple-array (single-float 0.0 1.0) (* width height 3)) + Two other values are returned: - The vertical axis of the image will be flipped, which is what OpenGL expects. - - Three values are returned: the array, the width, and the height. + * The format of the image that was read (one of `:pbm`, `:pgm`, `:ppm`). + * The bit depth of the image. " - (check-type stream stream) - (assert (input-stream-p stream) (stream) - "Stream ~S is not an input stream." stream) - (multiple-value-bind (format binary?) - (file-format (read-magic-byte stream)) - (read-netpbm (make-peekable-stream stream) format binary? t))) + (with-open-file (s path :direction :input :element-type '(unsigned-byte 8)) + (read-from-stream s))) (defun write-to-stream (stream data &key @@ -424,42 +421,6 @@ (write-netpbm data stream format (eql :binary encoding) maximum-value) (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 maximum-value)` - * PPM: `(simple-array (integer 0 maximum-value) (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 read-texture-from-file (path) - "Read a PPM image file from `path`, returning an OpenGL-style array and more. - - The primary return value will be an OpenGL-style array of type: - - (simple-array (single-float 0.0 1.0) (* width height 3)) - - The vertical axis of the image will be flipped, which is what OpenGL expects. - - Three values are returned: the array, the width, and the height. - - " - (with-open-file (s path :direction :input :element-type '(unsigned-byte 8)) - (read-texture-from-stream s))) - (defun write-to-file (path data &key (if-exists nil if-exists-given) (format :ppm) @@ -500,3 +461,43 @@ (with-open-file (s path :direction :output :element-type '(unsigned-byte 8)) (write-it s)))) (values)) + + +(defun read-texture-from-file (path) + "Read a PPM image file from `path`, returning an OpenGL-style array and more. + + The primary return value will be an OpenGL-style array of type: + + (simple-array (single-float 0.0 1.0) (* width height 3)) + + The vertical axis of the image will be flipped, which is what OpenGL expects. + + Three values are returned: the array, the width, and the height. + + " + (with-open-file (s path :direction :input :element-type '(unsigned-byte 8)) + (read-texture-from-stream s))) + +(defun read-texture-from-stream (stream) + "Read a PPM image file from `stream`, returning an OpenGL-style array and more. + + `stream` must be a binary input stream, specifically of `(unsigned-byte 8)`s + unless you *really* know what you're doing. The stream must contain a PPM + formatted image — PBM and PGM images are not supported. + + The primary return value will be an OpenGL-style array of type: + + (simple-array (single-float 0.0 1.0) (* width height 3)) + + The vertical axis of the image will be flipped, which is what OpenGL expects. + + Three values are returned: the array, the width, and the height. + + " + (check-type stream stream) + (assert (input-stream-p stream) (stream) + "Stream ~S is not an input stream." stream) + (multiple-value-bind (format binary?) + (file-format (read-magic-byte stream)) + (read-netpbm (make-peekable-stream stream) format binary? t))) + diff -r 8a6db152fb11 -r 70f64dff49b5 test/run.lisp --- a/test/run.lisp Sat Feb 02 14:30:59 2019 -0500 +++ b/test/run.lisp Sat Feb 23 14:23:28 2019 -0500 @@ -1,5 +1,5 @@ #+ecl (setf compiler:*user-cc-flags* "-Wno-shift-negative-value") -(ql:quickload :netpbm) -(time (asdf:test-system :netpbm)) +(ql:quickload :cl-netpbm) +(time (asdf:test-system :cl-netpbm)) (quit) diff -r 8a6db152fb11 -r 70f64dff49b5 vendor/quickutils.lisp --- a/vendor/quickutils.lisp Sat Feb 02 14:30:59 2019 -0500 +++ b/vendor/quickutils.lisp Sat Feb 23 14:23:28 2019 -0500 @@ -2,20 +2,19 @@ ;;;; See http://quickutil.org for details. ;;;; To regenerate: -;;;; (qtlc:save-utils-as "quickutils.lisp" :utilities '(:CURRY :SYMB :WITH-GENSYMS :TRANSPOSE) :ensure-package T :package "TRIVIAL-PPM.QUICKUTILS") +;;;; (qtlc:save-utils-as "quickutils.lisp" :utilities '(:CURRY :SYMB :WITH-GENSYMS :TRANSPOSE) :ensure-package T :package "NETPBM.QUICKUTILS") (eval-when (:compile-toplevel :load-toplevel :execute) - (unless (find-package "TRIVIAL-PPM.QUICKUTILS") - (defpackage "TRIVIAL-PPM.QUICKUTILS" + (unless (find-package "NETPBM.QUICKUTILS") + (defpackage "NETPBM.QUICKUTILS" (:documentation "Package that contains Quickutil utility functions.") (:use #:cl)))) -(in-package "TRIVIAL-PPM.QUICKUTILS") +(in-package "NETPBM.QUICKUTILS") (when (boundp '*utilities*) - (setf *utilities* (union *utilities* '(:MAKE-GENSYM-LIST :ENSURE-FUNCTION - :CURRY :MKSTR :SYMB :STRING-DESIGNATOR - :WITH-GENSYMS :TRANSPOSE)))) + (setf *utilities* (union *utilities* '(:MAKE-GENSYM-LIST :ENSURE-FUNCTION :CURRY :MKSTR :SYMB + :STRING-DESIGNATOR :WITH-GENSYMS :TRANSPOSE)))) (eval-when (:compile-toplevel :load-toplevel :execute) (defun make-gensym-list (length &optional (x "G")) "Returns a list of `length` gensyms, each generated as if with a call to `make-gensym`,