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))
; =>
#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>