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