<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>aNNi::Writes</title>
    <subtitle>aNNiMON&#x27;s development blog</subtitle>
    <link rel="self" type="application/atom+xml" href="https://blog.annimon.com/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://blog.annimon.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-03-31T12:34:00+00:00</updated>
    <id>https://blog.annimon.com/atom.xml</id>
    <entry xml:lang="en">
        <title>tiki - markdown project management</title>
        <published>2026-03-31T12:34:00+00:00</published>
        <updated>2026-03-31T12:34:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/tui-tiki/"/>
        <id>https://blog.annimon.com/tui-tiki/</id>
        
        <summary type="html">&lt;p&gt;Almost 8 months ago, I switched from &lt;a href=&quot;&#x2F;kanboard-migration&#x2F;&quot;&gt;Kanboard to self-hosted Gitea&lt;&#x2F;a&gt; for managing my personal projects. It&#x27;s not perfect, has limitations, unusable on mobile, but it fits my needs, so I keep using it.&lt;&#x2F;p&gt;
&lt;p&gt;But recently, I found a cool project called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;boolean-maybe&#x2F;tiki&quot;&gt;tiki&lt;&#x2F;a&gt;. It&#x27;s a markdown-based git-versioned documentation and issue management tool. All tasks live in your project directory. Here&#x27;s why I think you&#x27;d love it if you&#x27;re a fan of terminal user interfaces:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;tui-tiki&#x2F;logo.jpg&quot; alt=&quot;logo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>How I almost lost access to the Oracle Cloud server</title>
        <published>2025-12-07T10:34:00+00:00</published>
        <updated>2025-12-07T10:34:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/server-access/"/>
        <id>https://blog.annimon.com/server-access/</id>
        
        <summary type="html">&lt;p&gt;Recently, I tried to set up a network tunnel to access some of my services running on a free-tier Oracle Cloud ARM compute instance. Despite correctly setting the ingress rules and &lt;code&gt;iptables&lt;&#x2F;code&gt; and &lt;code&gt;ufw&lt;&#x2F;code&gt;, there was a problem with opening the UDP port. Then I made a stupid mistake: I routed all the server&#x27;s traffic through the closed port.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AllowedIPs = 0.0.0.0&#x2F;0, ::&#x2F;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;wg-quick&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; up wg0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Immediately, my SSH connection was closed. All my running services stopped responding.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Kanboard to Gitea migration</title>
        <published>2025-08-05T19:17:00+00:00</published>
        <updated>2025-08-05T19:17:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/kanboard-migration/"/>
        <id>https://blog.annimon.com/kanboard-migration/</id>
        
        <summary type="html">&lt;p&gt;Some time ago I decided to move all tasks from &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;kanboard.org&#x2F;&quot;&gt;Kanboard&lt;&#x2F;a&gt; closer to the projects in my Gitea self-hosted instance. Surprisingly, Gitea doesn&#x27;t have any importers, so I ended up preparing the data and calling the Gitea API.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;kanboard-migration&#x2F;logo.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 10. Convolution Kernel</title>
        <published>2025-06-29T23:06:00+00:00</published>
        <updated>2025-06-29T23:06:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-10/"/>
        <id>https://blog.annimon.com/image-processing-10/</id>
        
        <summary type="html">&lt;p&gt;With convolution kernel it&#x27;s possible to implement numerous interesting effects, such as motion blur, sharpening, edge detecting, emboss, etc. These effects we going to explore in this article.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-10&#x2F;logo10.jpg&quot; alt=&quot;logo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 9. Blur</title>
        <published>2025-06-24T00:38:00+00:00</published>
        <updated>2025-06-24T00:38:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-9/"/>
        <id>https://blog.annimon.com/image-processing-9/</id>
        
        <summary type="html">&lt;p&gt;Let&#x27;s return to pixel processing and this time explore image blurring algorithms.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-9&#x2F;logo9.jpg&quot; alt=&quot;logo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 8. Image scaling</title>
        <published>2025-06-23T21:17:00+00:00</published>
        <updated>2025-06-23T21:17:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-8/"/>
        <id>https://blog.annimon.com/image-processing-8/</id>
        
        <summary type="html">&lt;p&gt;Let&#x27;s explore a highly relevant topic in image processing: image scaling. You&#x27;ve likely encountered this task and may have even heard about interpolation algorithms used for scaling. This article will focus on that.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-8&#x2F;logo8.jpg&quot; alt=&quot;logo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 7. Histogram</title>
        <published>2025-06-19T23:04:00+00:00</published>
        <updated>2025-06-19T23:04:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-7/"/>
        <id>https://blog.annimon.com/image-processing-7/</id>
        
        <summary type="html">&lt;p&gt;Today&#x27;s article is about histograms. I&#x27;ll cover what histograms can tell us about a photo, how to construct histograms, and how image adjustments affect their appearance.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-7&#x2F;logo7.jpg&quot; alt=&quot;logo7&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 6. Brightness, saturation, contrast, gamma correction</title>
        <published>2025-06-15T21:35:00+00:00</published>
        <updated>2025-06-15T21:35:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-6/"/>
        <id>https://blog.annimon.com/image-processing-6/</id>
        
        <summary type="html">&lt;p&gt;Let&#x27;s examine the most fundamental image correction parameters: brightness, saturation (without switching to HSV color model), contrast, and gamma.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-6&#x2F;logo6.jpg&quot; alt=&quot;logo6&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 5. Color Models</title>
        <published>2025-06-15T08:21:00+00:00</published>
        <updated>2025-06-15T08:21:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-5/"/>
        <id>https://blog.annimon.com/image-processing-5/</id>
        
        <summary type="html">&lt;p&gt;Working with the RGB model isn&#x27;t always convenient. For example, printers don&#x27;t follow the RGB model, because they use white paper and consume less ink for light tones. This is why multiple models exist to solve various tasks. We&#x27;ll discuss the main color models here. And to keep things interesting, we&#x27;ll  play around with some sliders.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-5&#x2F;logo5.jpg&quot; alt=&quot;logo5&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 4. Desaturation</title>
        <published>2025-06-14T19:58:00+00:00</published>
        <updated>2025-06-14T19:58:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-4/"/>
        <id>https://blog.annimon.com/image-processing-4/</id>
        
        <summary type="html">&lt;p&gt;Next up is the &lt;strong&gt;Desaturation filter&lt;&#x2F;strong&gt;, which converts a color image to grayscale.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-4&#x2F;logo4.jpg&quot; alt=&quot;logo4&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 3. Negate filter, channel extraction and inversion</title>
        <published>2025-06-10T13:34:00+00:00</published>
        <updated>2025-06-10T13:34:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-3/"/>
        <id>https://blog.annimon.com/image-processing-3/</id>
        
        <summary type="html">&lt;p&gt;In this article we&#x27;ll split images into color components, create a negate filter, and invert individual channels. Finally, we&#x27;ll manipulate RGB components!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-3&#x2F;logo3.jpg&quot; alt=&quot;logo3&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 2. Simple transformation</title>
        <published>2025-06-09T17:58:00+00:00</published>
        <updated>2025-06-09T17:58:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-2/"/>
        <id>https://blog.annimon.com/image-processing-2/</id>
        
        <summary type="html">&lt;p&gt;In this article, we&#x27;ll implement flipping and 90°&#x2F;270° rotations for images. But before doing that, let&#x27;s explore image pixel data and how to access it in JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-2&#x2F;logo2.jpg&quot; alt=&quot;logo2&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Image Processing 1. Introduction</title>
        <published>2025-06-07T20:13:00+00:00</published>
        <updated>2025-06-07T20:13:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/image-processing-1/"/>
        <id>https://blog.annimon.com/image-processing-1/</id>
        
        <summary type="html">&lt;p&gt;In this introductory article I&#x27;ll explain how color is encoded in computer graphics, make you work &lt;em&gt;a bit&lt;&#x2F;em&gt; with bits, and show how to manipulate pixels a better way in JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;image-processing-1&#x2F;logo1.jpg&quot; alt=&quot;logo1&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>WezTerm projects selector</title>
        <published>2025-04-04T14:42:00+00:00</published>
        <updated>2025-04-04T14:42:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/wezterm-projects/"/>
        <id>https://blog.annimon.com/wezterm-projects/</id>
        
        <summary type="html">&lt;p&gt;Personally, I like WezTerm for using lua configs instead of boring json&#x2F;yaml&#x2F;toml files, and for the powerful and configurable multiplexer. In this post I&#x27;ll show you how to create a fuzzy project selector that launches a named workspace, splits a window and executes predefined commands.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;wezterm-projects&#x2F;wezterm.png&quot; alt=&quot;thumbnail&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Complete guide to Java Stream API in pictures and examples</title>
        <published>2025-02-16T17:19:00+00:00</published>
        <updated>2025-02-16T17:19:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/stream-api/"/>
        <id>https://blog.annimon.com/stream-api/</id>
        
        <summary type="html">&lt;p&gt;Since the release of Java 8, I almost immediately started using the Stream API, because I liked the functional approach of data processing. I wanted to use it everywhere, so I began developing my own library, which brings a similar approach to earlier versions of Java, specially for Android. I was also interested in the stream internals. Over time, I&#x27;ve accumulated enough experience, and now I&#x27;m eager to share it.&lt;&#x2F;p&gt;
&lt;p&gt;In this article, along with a description of streams, I provide visual demonstrations of how operators work, as well as examples and self-checking tasks. It also covers new features related to streams in Java 9+.&lt;&#x2F;p&gt;
&lt;video controls autoplay loop&gt;
  &lt;source src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;stream-api&#x2F;example_1.mp4&quot; type=&quot;video&#x2F;mp4&quot; &#x2F;&gt;
  Your browser doesn&#x27;t support the video tag and&#x2F;or the video formats in use here – sorry!
&lt;&#x2F;video&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Geotagging is easy. Rust CLI app</title>
        <published>2025-01-26T17:55:00+00:00</published>
        <updated>2025-01-26T17:55:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/geotagging-is-easy-3/"/>
        <id>https://blog.annimon.com/geotagging-is-easy-3/</id>
        
        <summary type="html">&lt;p&gt;In this section, we will create a CLI application in Rust that collects all the necessary data to display photos on the map: coordinates, some camera information, a thumbnail, and will also generate the map.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;geotagging-is-easy-3&#x2F;ologo3.jpg&quot; alt=&quot;logo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Geotagging is easy. Clustering</title>
        <published>2025-01-18T18:12:00+00:00</published>
        <updated>2025-01-18T18:12:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/geotagging-is-easy-2/"/>
        <id>https://blog.annimon.com/geotagging-is-easy-2/</id>
        
        <summary type="html">&lt;p&gt;If there are a lot of photos, displaying them all at once on the map is not the right choice for UX and performance. So let&#x27;s group the photos and display their count.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;geotagging-is-easy-2&#x2F;ologo2.jpg&quot; alt=&quot;preview&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Geotagging is easy. OpenLayers</title>
        <published>2025-01-14T17:19:00+00:00</published>
        <updated>2025-01-14T17:19:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/geotagging-is-easy-1/"/>
        <id>https://blog.annimon.com/geotagging-is-easy-1/</id>
        
        <summary type="html">&lt;p&gt;I&#x27;ve always found it tedious and difficult to display stuff on maps. To understand the API, you need examples that aren&#x27;t always accessible because they require a token to display and interact with the map. The token has to be obtained and hidden from other people&#x27;s eyes.&lt;&#x2F;p&gt;
&lt;p&gt;But then I thought, why do I need proprietary Google Maps when there is OpenStreetMaps? I searched for APIs, came across &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;openlayers.org&#x2F;&quot;&gt;OpenLayers&lt;&#x2F;a&gt;, opened examples and was surprised by their number and simplicity.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;geotagging-is-easy-1&#x2F;ologo1.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this series of articles, we will explore OpenLayers, display photo thumbnails on a map, add clustering, and finally create a Rust application that collects geodata and thumbnails from photos and generates a map.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>How to write code so that it grows in height, not in width</title>
        <published>2020-11-10T16:10:00+00:00</published>
        <updated>2020-11-10T16:10:00+00:00</updated>
        <author><name>aNNiMON</name></author>
        <link rel="alternate" type="text/html" href="https://blog.annimon.com/how-to-write-code/"/>
        <id>https://blog.annimon.com/how-to-write-code/</id>
        
        <summary type="html">&lt;p&gt;Every time we add a new if or for to code, we have to increase the indent of the condition body or loop. The more nested loops and conditions, the broader your code becomes, and the more difficult it becomes to read. In this short article, I&#x27;ll show a few techniques by which you can avoid increasing indentation. Your code will be clearer, and you&#x27;ll never see horizontal scrolling.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.annimon.com&#x2F;how-to-write-code&#x2F;2018-09-11_12-29-14.png&quot; alt=&quot;preview&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
</feed>
