cl-netpbm/usage/index.html @ 15eb4bbf6d54
chancery: Update site.
| author | Steve Losh <steve@stevelosh.com> |
|---|---|
| date | Mon, 23 Dec 2019 16:18:40 -0500 |
| parents | 9e0acccd9256 |
| children | (none) |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Usage / cl-netpbm</title> <link rel="stylesheet" href="../_dmedia/tango.css"/> <link rel="stylesheet/less" type="text/css" href="../_dmedia/style.less"/> <script src="../_dmedia/less.js" type="text/javascript"> </script> </head> <body class="content"> <div class="wrap"> <header><h1><a href="..">cl-netpbm</a></h1></header> <div class="markdown"> <h1 id="usage"><a href="">Usage</a></h1><p>The <a href="https://en.wikipedia.org/wiki/Netpbm_format">netpbm image formats (PPM, PGM, and PBM)</a> are a family of very simple image formats. You can convert to/from these formats with third-party tools like <a href="https://www.imagemagick.org/">ImageMagick</a>.</p> <p>Instead of trying to link <code>libjpeg</code> 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.</p> <p>cl-netpbm provides functions for both reading and writing images, as well as a little bit of sugar for working with OpenGL textures.</p> <div class="toc"> <ul> <li><a href="#reading-images">Reading Images</a></li> <li><a href="#image-arrays">Image Arrays</a></li> <li><a href="#writing-images">Writing Images</a></li> <li><a href="#example-inverting-an-image">Example: Inverting an Image</a></li> <li><a href="#opengl-textures">OpenGL Textures</a></li> </ul></div> <h2 id="reading-images">Reading Images</h2> <p>The <code>netpbm:read-from-stream</code> function can be used to read a netpbm file from stream. The stream <em>must</em> be a binary input stream with <code>element-type</code> of <code>(unsigned-byte 8)</code>.</p> <p>Three values are returned: a 2D array of pixels, the format of the image (<code>:pbm</code>, <code>:pgm</code>, or <code>:ppm</code>), and the bit depth of the image:</p> <div class="codehilite"><pre><span/>(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 </pre></div> <p>A <code>netpbm:read-from-file</code> function is also provided to save you some boilerplate:</p> <div class="codehilite"><pre><span/>(netpbm:read-from-file "foo.ppm") </pre></div> <p>See the <a href="../reference">API reference</a> for these functions for more information.</p> <h2 id="image-arrays">Image Arrays</h2> <p>When an image is read a 2D array of pixels is returned.</p> <p>The first array dimension is the columns of the image and the second array dimension is the rows. This means to access pixel <code>(x, y)</code> of the image you use <code>(aref image x y)</code>. The <code>y</code> dimension starts at the top of the image and grows downwards, so:</p> <ul> <li><code>(aref image 0 0)</code> returns the top-left pixel.</li> <li><code>(aref image width height)</code> returns the bottom-right pixel.</li> </ul> <p>The <code>element-type</code> of the array (i.e. the type of the pixels) depends on the format of the image that was read:</p> <ul> <li>Pixels of PBM images are of type <code>bit</code>.</li> <li>Pixels of PGM images are of type <code>(integer 0 ,bit-depth)</code>.</li> <li>Pixels of PPM images are of type <code>(simple-array (integer 0 ,bit-depth) (3))</code>.</li> </ul> <p>Lower values represent darker colors, e.g. for PBM images <code>0</code> is black and <code>1</code> is white, for PGM <code>0</code> is black, <code>1</code> is very dark gray, etc.</p> <p>(Note that the actual PBM image format on disk is backwards from all the other netpbm formats — in PBM a <code>1</code> bit represents black. cl-netpbm flips the bits when reading/writing PBM files for consistency on the Lisp side of things.)</p> <h2 id="writing-images">Writing Images</h2> <p>An image array can be written to a stream with <code>netpbm:write-to-stream</code>. The stream <em>must</em> be a binary output stream with <code>element-type</code> of <code>(unsigned-byte 8)</code>. The input image array must have the appropriate contents for the desired output format (e.g. integers for <code>:pbm</code> and <code>:pgm</code>, 3-element vectors for <code>:pgm</code>):</p> <div class="codehilite"><pre><span/>(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" </pre></div> <p><code>netpbm:write-to-file</code> is provided for convenience:</p> <div class="codehilite"><pre><span/>(netpbm:write-to-file "foo.pbm" image :format :pbm) </pre></div> <p>See the <a href="../reference">API reference</a> for these functions for more information.</p> <h2 id="example-inverting-an-image">Example: Inverting an Image</h2> <p>For a concrete example, let's invert an image.</p> <p>First we'll get a kitten photo to work with and convert it to PPM with ImageMagick:</p> <div class="codehilite"><pre><span/>wget 'https://upload.wikimedia.org/wikipedia/commons/7/75/Cute_grey_kitten.jpg' -O kitten.jpg convert -resize x600 kitten.jpg kitten.ppm </pre></div> <p>The initial kitten (<a href="https://en.m.wikipedia.org/wiki/File:Cute_grey_kitten.jpg">source</a>):</p> <p><img alt="kitten photo" src="../assets/kitten.jpg"/></p> <p>Now we can write our Lisp code:</p> <div class="codehilite"><pre><span/>(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)) </pre></div> <p>And convert it back into JPG:</p> <div class="codehilite"><pre><span/>convert kitten-inverted.ppm kitten-inverted.jpg </pre></div> <p>And now we have an inverted kitten:</p> <p><img alt="kitten photo" src="../assets/kitten-inverted.jpg"/></p> <h2 id="opengl-textures">OpenGL Textures</h2> <p>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 <a href="https://github.com/nothings/stb">STB</a>) this won't work, because OpenGL expects a different format (a flat array of <code>single-float</code>s, y-axis from bottom to top).</p> <p>For your convenience, cl-netpbm provides two additional functions that will return an array in the format OpenGL expects: <code>netpbm:read-texture-from-stream</code> and <code>netpbm:read-texture-from-file</code>.</p> <p>Remember, though, that the netpbm formats are designed for simplicity, not efficiency. If you're just going through <a href="https://learnopengl.com/">an OpenGL tutorial</a> 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.</p> </div> <footer><p>Created by <a href="http://stevelosh.com">Steve Losh</a>.</p> <p><br/><a id="rochester-made" href="https://rochestermade.com" title="Rochester Made"><img src="http://rochestermade.com/media/images/rochester-made-dark-on-light.png" alt="Rochester Made" title="Rochester Made"/></a></p></footer> </div> </body> </html>