docs/02-usage.markdown @ db8f927391c9
Quicklisp
| author | Steve Losh <steve@stevelosh.com> |
|---|---|
| date | Thu, 07 Mar 2019 12:48:03 -0500 |
| parents | 70f64dff49b5 |
| children | (none) |
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] 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`): (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)):  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:  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.