cl-netpbm/usage/index.html @ 0f6bab39c0f4 default tip

adopt: Update site.
author Steve Losh <steve@stevelosh.com>
date Thu, 13 Jun 2024 13:05:28 -0400
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))
; =&gt;
#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))
; =&gt; 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>