1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
|
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="/syntax.css">
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1">
<meta content="#ffffff" name="theme-color">
<meta name="HandheldFriendly" content="true">
<meta property="og:title" content="SDL2 Devlog">
<meta property="og:type" content="website">
<meta property="og:description" content="a static site {for, by, about} me ">
<meta property="og:url" content="https://peppe.rs">
<link rel="icon" type="image/x-icon" href="/favicon.png">
<title>SDL2 Devlog · peppe.rs</title>
<body>
<div class="posts">
<div class="post">
<a href="/" class="post-end-link">Home</a>
<span>/</span>
<a href="/posts" class="post-end-link">Posts</a>
<span>/</span>
<a class="post-end-link">SDL2 Devlog</a>
<a class="stats post-end-link" href="https://git.peppe.rs/web/site/plain/posts/SDL2_devlog.md
">View Raw</a>
<div class="separator"></div>
<div class="date">
11/04 — 2021
<div class="stats">
<span class="stats-number">
124.28
</span>
<span class="stats-unit">cm</span>
 
<span class="stats-number">
10.0
</span>
<span class="stats-unit">min</span>
</div>
</div>
<h1>
SDL2 Devlog
</h1>
<div class="post-text">
<p>I have been working on an editor for the <a
href="https://git.peppe.rs/graphics/obi/about">One Bit Image</a> file
format in Rust and SDL2. This entry in my blog follows my progress on
the editor. The days are listed in reverse chronological order, begin
from the bottom, if this is your first time on this page.</p>
<h3 id="day-20">Day 20</h3>
<p>More <code>lisp</code> stuff! I added a new brush, for rectangular
selections. While selection doesn’t do much on its own, the selected
area can be passed onto a <code>lisp</code> procedure, for example, a
procedure to draw horizontal black and white lines:</p>
<figure>
<video src="https://u.peppe.rs/frU.mp4" controls=""><a
href="https://u.peppe.rs/frU.mp4">Day 20</a></video>
<figcaption aria-hidden="true">Day 20</figcaption>
</figure>
<h3 id="day-19">Day 19</h3>
<p>Attempted <a href="https://peppe.rs/art/conduit.png">some isometric
art</a> within the editor. The angles displayed alongside the line brush
are handly, however, having only a rectangular grid did not help. I
implemented an isometric grid today. Isometric grids in pixel art differ
in that the tangent of the isometric angle is exactly 0.5! For every
pixel down, you go exactly two pixels sideways. The math works out
really well in the drawing procedures too, dealing with floating points
is a pain.</p>
<figure>
<img src="https://u.peppe.rs/1Kb.png" alt="Day 19" />
<figcaption aria-hidden="true">Day 19</figcaption>
</figure>
<h3 id="day-18">Day 18</h3>
<p>I added basic support for guides, they can be added and activated
from the <code>lisp</code> REPL. Another long standing improvement I
wanted to make was reworking the pixmap drawing procedure. The old
procedure draws a square for each pixel in the pixmap, coloured
according to its value in the pixmap. Naturally, this means, for an
<strong>NxN</strong> pixmap, there are <strong>N²</strong> calls to SDL!
I reworked this procedure to compress each line of the pixmap using RLE
(run length encoding), and call out to SDL for each run in the line.
This drastically improved drawing speeds on larger grids. The following
is a comparison between the two procedures, the leftmost picture is the
rendered image, the middle picture is the optimized drawing procedure
(draws each run instead of pixel), and the right most picture is the
primitive drawing procedure (draws each pixel):</p>
<figure>
<img src="https://u.peppe.rs/U4B.png" alt="Day 18" />
<figcaption aria-hidden="true">Day 18</figcaption>
</figure>
<h3 id="day-17">Day 17</h3>
<p>I decided to give the text-only statusline a touch up, by adding a
active color and dither level preview. Aligning the “widget” to the
right of statusline involved a lot more than I thought, so I created a
ghetto CSS-like rectangle placement system to position containers inside
containers:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">// roughly something like this</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> statusline <span class="op">=</span> </span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="pp">Container::</span>new(<span class="pp">Offset::</span>Left(<span class="dv">0</span>)<span class="op">,</span> <span class="pp">Offset::</span>Bottom(<span class="dv">40</span>))</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>width(<span class="pp">Size::</span>Max)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>height(<span class="pp">Size::</span>Absolute(<span class="dv">20</span>))<span class="op">;</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="kw">mut</span> primary <span class="op">=</span> <span class="pp">Container::</span>uninit()</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>width(<span class="pp">Size::</span>Absolute(<span class="dv">16</span>))</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>height(<span class="pp">Size::</span>Absolute(<span class="dv">16</span>))<span class="op">;</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>container<span class="op">.</span>place(</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a> <span class="op">&</span><span class="kw">mut</span> padding_box<span class="op">,</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="pp">HorAlign::</span>Right<span class="op">,</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="pp">VertAlign::</span>Center</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>)<span class="op">;</span></span></code></pre></div>
<p>The result (brush preview on the bottom right):</p>
<figure>
<video src="https://u.peppe.rs/OtU.mp4" controls=""><a
href="https://u.peppe.rs/OtU.mp4">Day 17</a></video>
<figcaption aria-hidden="true">Day 17</figcaption>
</figure>
<h3 id="day-16">Day 16</h3>
<p>The embedded lisp is coming along nicely, users can load a custom
<code>rc.lisp</code>, which is evaluated on startup. To disable to grid
on start, for example:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">;;; rc.lisp</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>(toggle-grid)</span></code></pre></div>
<p>Some aliases to switch between brushes:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">;;; rc.lisp</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>(<span class="ex">define</span><span class="fu"> </span>(brush kind)</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> (<span class="kw">cond</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind 'f) (brush-fill))</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind 'c) (brush-circle))</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind 'l) (brush-line))</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind 'l+) (brush-line-extend))</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> (<span class="kw">else</span> (brush-circle))))</span></code></pre></div>
<p>The following script draws a straight line along a given axis, at a
given distance from the canvas boundary:</p>
<figure>
<video src="https://u.peppe.rs/b3i.mp4" controls=""><a
href="https://u.peppe.rs/b3i.mp4">Day 16</a></video>
<figcaption aria-hidden="true">Day 16</figcaption>
</figure>
<h3 id="day-15">Day 15</h3>
<p>I began writing a standard library for the lisp, in lisp. It includes
basic list operations: <code>car</code>, <code>cdr</code>,
<code>null?</code>, <code>list</code>, higher order functions:
<code>map</code>, <code>filter</code>, <code>fold</code>:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode lisp"><code class="sourceCode commonlisp"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>(define (member? item ls)</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> (fold <span class="dv">#f</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> (<span class="kw">lambda</span> (acc x) (<span class="kw">or</span> acc (eq? item x)))</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> ls))</span></code></pre></div>
<h3 id="day-14">Day 14</h3>
<p>I attempted a <a href="https://peppe.rs/art/ramen_noodles.png">small
art piece</a> using the editor, while it was largely usable, I felt a
certain lack of feedback. The brushes just didn’t relay as much info as
I’d have liked, for example, the approximate points of the line or the
angle made by the line against the x-axis. Unfortunately, the existing
infrastructure around brushes and line drawing didn’t easily allow for
this either. I went ahead and reimplemented brushes, and added a new
flood fill brush too:</p>
<figure>
<video src="https://u.peppe.rs/8q.mp4" controls=""><a
href="https://u.peppe.rs/8q.mp4">Day 14</a></video>
<figcaption aria-hidden="true">Day 14</figcaption>
</figure>
<h3 id="day-13">Day 13</h3>
<p>I added a few more forms to the <code>lisp</code> evaluator. It
handles recursion, definitions, variable mutation and more. The prelude
contains 20 subroutines so far, including comparision and logic
operators. The REPL interface on the SDL side requires some UX tweaks;
environment based completion, readline motions sound doable.</p>
<figure>
<video src="https://u.peppe.rs/u3.mp4" controls=""><a
href="https://u.peppe.rs/u3.mp4">Day 13</a></video>
<figcaption aria-hidden="true">Day 13</figcaption>
</figure>
<h3 id="day-12">Day 12</h3>
<p>I lifted most of <a
href="https://github.com/murarth/ketos">murarth/ketos</a> into the
editor. <code>ketos</code>’s implementation of <code>lisp</code> is too
vast for my use case. For example, the editor does not need data types
to handle raw strings or byte strings. I have got a basic evaluator
running inside the SDL2 context (notice the <code>lisp</code> REPL at
the bottom of the window). Over the following days, I intend to create a
set of prelude functions to manipulate the pixmap. Users can implement
their own brushes, dithering patterns, keybinds and more
(hopefully).</p>
<figure>
<video src="https://u.peppe.rs/y0.mp4" controls=""><a
href="https://u.peppe.rs/y0.mp4">Day 12</a></video>
<figcaption aria-hidden="true">Day 12</figcaption>
</figure>
<h3 id="day-11">Day 11</h3>
<p>I intend to supplement the editor with scripting language and an
inbuilt REPL for the same. I began by implementing a text box widget
from scratch, with history and readline like editing:</p>
<figure>
<video src="https://u.peppe.rs/Mh.mp4" controls=""><a
href="https://u.peppe.rs/Mh.mp4">Day 11</a></video>
<figcaption aria-hidden="true">Day 11</figcaption>
</figure>
<h3 id="day-10">Day 10</h3>
<p>I started reading up on dithering methods and half-toning, I wanted
to create a dithering brush that would automatically produce popular
dithering patterns. The method that caught my eye (and also the one used
most often in pixel art), was Bayer’s ordered dithering. When applied to
a black and white image, each pixel, based on its intensity, is mapped
to a 4x4 grid of pixels. A completely empty (completely black) 4x4 grid
represents zero intensity, and a filled 4x4 grid represents full
intensity. Bayer’s ordered dithering can produce 15 steps of intensity
between zero and full (by switching on exactly 1 pixel more at each
level), thus, being able to draw 17 “shades” from white to black.
Creating a dithering brush from here was fairly trivial. Our pixmap is
supposed to represent the final dithered image, it must be divided into
4x4 grids. Each grid is colored based on the intensity of the brush
passing over it:</p>
<figure>
<img src="https://u.peppe.rs/Mn.png" alt="Day 10" />
<figcaption aria-hidden="true">Day 10</figcaption>
</figure>
<h3 id="day-9">Day 9</h3>
<p>I started working towards an interface. I like the idea of a largely
read-only HUD, i. e., an interface that simply describes the state of
the application. Changes to this state are initiated via keybinds or
text commands. I am proud of the symmetry indicator; <code>-</code> for
horizontal symmetry, <code>|</code> for vertical symmetry,
<code>+</code> for radial symmetry.</p>
<figure>
<img src="https://u.peppe.rs/hx.png" alt="Day 9" />
<figcaption aria-hidden="true">Day 9</figcaption>
</figure>
<h3 id="day-8">Day 8</h3>
<p>One of my favourite features of GIMP was symmetric editing. I added
some coordinate geometry primitives to my pixmap abstraction, allowing
for mirroring and reflecting figures about lines or points. The result
was an ergonomic function that applies symmetry to any painting
operation, (undo/redo works as expected):</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode rust"><code class="sourceCode rust"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> line <span class="op">=</span> <span class="kw">self</span><span class="op">.</span>pixmap<span class="op">.</span>get_line(start<span class="op">,</span> end)<span class="op">;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> sym_line <span class="op">=</span> <span class="kw">self</span><span class="op">.</span>symmetry<span class="op">.</span>apply(<span class="op">&</span>line)<span class="op">;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> point on line<span class="op">.</span>extend(sym_line) <span class="op">{</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="co">// draw to window</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<figure>
<video src="https://u.peppe.rs/B1.mp4" controls=""><a
href="https://u.peppe.rs/B1.mp4">Day 8</a></video>
<figcaption aria-hidden="true">Day 8</figcaption>
</figure>
<h3 id="day-7">Day 7</h3>
<p>Bresenham saves the day again! This time, I implemented his line
drawing algorithm, to, well, draw lines. Each point on the line is then
“buffed” based on the active brush size. Today’s changes fit in very
well with the undo system and the brush size feature. Creating the right
abstractions, one at a time :)</p>
<figure>
<video src="https://u.peppe.rs/xt.mp4" controls=""><a
href="https://u.peppe.rs/xt.mp4">Day 7</a></video>
<figcaption aria-hidden="true">Day 7</figcaption>
</figure>
<h3 id="day-6">Day 6</h3>
<p>I extended Bresenham’s algorithm to draw not just circle outlines,
but also generate their fills. Unlike Bresenham’s algorithm, this
variant generates points for two quadrants at once, these points are
mirrored over the dividing axis to generate the other two quadrants.</p>
<figure>
<img src="https://u.peppe.rs/f3.png" alt="Day 6" />
<figcaption aria-hidden="true">Day 6</figcaption>
</figure>
<h3 id="day-5">Day 5</h3>
<p>I discovered and implemented Bresenham’s algorithm for efficient
circle drawing. The algorithm allowed for sized circular brushes,
something I really liked from GIMP. Very convenient that the Wikipedia
page for Bresenham’s algorithm also includes a section about optimizing
for integer based arithmetic. I managed to abstract out another giant
component of the application, the pixmap. Any image is just a grid of
pixels (a pixmap), where the pixel’s value is decided by the application
(1-bit in my case). I could potentially extend the application to a
24-bit image editor!</p>
<figure>
<video src="https://u.peppe.rs/Kh.mp4" controls=""><a
href="https://u.peppe.rs/Kh.mp4">Day 5</a></video>
<figcaption aria-hidden="true">Day 5</figcaption>
</figure>
<h3 id="day-4">Day 4</h3>
<p>I created a generic “undo stack” data structure that allows for
infinite “undos” and “redos”. Every modification operation to the grid
is persisted to the application state. A couple of keybinds allow the
user to revert and re-apply these operations! I expect abstracting this
component will come in handy down the line.</p>
<figure>
<video src="https://u.peppe.rs/w5.mp4" controls=""><a
href="https://u.peppe.rs/w5.mp4">Day 4</a></video>
<figcaption aria-hidden="true">Day 4</figcaption>
</figure>
<h3 id="day-3">Day 3</h3>
<p>I implemented the bare minimum required to call the program an
“editor”. The application displays a grid, tracks mouse events, paints
white to the canvas on left click, and black to the canvas on right
click. I created a make-shift MVC architecture à la Elm in Rust.</p>
<figure>
<video src="https://u.peppe.rs/GF.mp4" controls=""><a
href="https://u.peppe.rs/GF.mp4">Day 3</a></video>
<figcaption aria-hidden="true">Day 3</figcaption>
</figure>
<h3 id="day-2">Day 2</h3>
<p>I started figuring out event handling today. Implemented a couple of
keybinds to zoom in/out of the drawing area. Conversions of SDL2
coordinates (measured in signed 32 bit integers) to my internal “drawing
area” coordinates (measured in unsigned 32 bit integers) is very
annoying. Hopefully the unchecked conversions won’t haunt me later.</p>
<figure>
<video src="https://u.peppe.rs/L4.mp4" controls=""><a
href="https://u.peppe.rs/L4.mp4">Day 2</a></video>
<figcaption aria-hidden="true">Day 2</figcaption>
</figure>
<h3 id="day-1">Day 1</h3>
<p>Getting started with Rust and SDL2 is very straightforward. The
<code>rust-sdl2</code> library contains some detailed examples that
allowed me to get all the way to drawing a grid from a
<code>Vec<bool></code>:</p>
<figure>
<img src="https://u.peppe.rs/Ma.png" alt="Day 1" />
<figcaption aria-hidden="true">Day 1</figcaption>
</figure>
</div>
<div class="intro">
Hi.
<div class="hot-links">
<a href="https://peppe.rs/index.xml" class="feed-button">Subscribe (RSS)</a>
</div>
<p>I'm Akshay, programmer and pixel-artist.</p>
<p>
I write <a href="https://git.peppe.rs">open-source stuff</a> to pass time.
I also design fonts:
<a href="https://git.peppe.rs/fonts/scientifica/about">scientifica</a>,
<a href="https://git.peppe.rs/fonts/curie/about">curie</a>.
</p>
<p>Send me a mail at [email protected] or a message at [email protected].</p>
</div>
<a href="/" class="post-end-link">Home</a>
<span>/</span>
<a href="/posts" class="post-end-link">Posts</a>
<span>/</span>
<a class="post-end-link">SDL2 Devlog</a>
<a class="stats post-end-link" href="https://git.peppe.rs/web/site/plain/posts/SDL2_devlog.md
">View Raw</a>
</div>
</div>
</body>
</html>
|