<p>Have a look at <em>Improved Alpha-Tested Magnification for Vector Textures and Special Effects</em> [Gre07] (<a href="http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf" rel="nofollow noreferrer">pdf</a>). The gist of their method is to store distance field information in the font texture instead of bitmap glyphs. This information is then used to build much higher quality up-scaled versions of the glyphs. They even describe a rendering path without programmable shading that gives ok results.</p>

CC BY-SA 3.0
2017-12-01T00:37:00.790Z
Thanks for the link. Since I'm new to this having something like this to read is very helpful.
uhoh
CC BY-SA 3.0
2017-11-30T16:27:08.630Z
2017-11-30T16:27:08.630Z
bernie
<p>This is just a supplementary answer based on the <a href="https://computergraphics.stackexchange.com/a/5940/5846">accepted answer</a>, using the original 64x128 pixel images instead of the zoomed-in interpolated screen shot.</p>

<p><code>imtype = "RGB":</code> <a href="https://i.sstatic.net/x5BOT.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/x5BOT.png" alt="enter image description here"></a> (true RGB)</p>

<p><code>imtype  =  "L":</code> <a href="https://i.sstatic.net/IT7Fd.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/IT7Fd.png" alt="enter image description here"></a> (grayscale 8-bit)</p>

<p><code>imtype  =  "1":</code> <a href="https://i.sstatic.net/BYTgZ.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/BYTgZ.png" alt="enter image description here"></a> (binary 1-bit, threshold = 128)</p>

<p><code>imtype  =  "1":</code> <a href="https://i.sstatic.net/IBTd7.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/IBTd7.png" alt="enter image description here"></a> (binary 1-bit, threshold = 80)</p>

<hr>

<p>Here are screenshots zoomed-in for those without their glasses:</p>

<p><a href="https://i.sstatic.net/gdNwt.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/gdNwt.png" alt="enter image description here"></a> (screenshot of 1-bit, threshold = 128)</p>

<p><a href="https://i.sstatic.net/Ki3eh.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/Ki3eh.png" alt="enter image description here"></a> (screenshot of 1-bit, threshold = 80)</p>

<pre><code>from PIL import Image, ImageDraw, ImageFont
w_disp   = 128
h_disp   =  64
fontsize =  32
text     =  u"你好!"

imageRGB  = Image.new("RGB", (w_disp, h_disp))
draw      = ImageDraw.Draw(imageRGB)
font      = ImageFont.truetype("/Library/Fonts/Arial Unicode.ttf", fontsize)
w, h      = draw.textsize(text, font=font)

draw.text(((w_disp - w)/2, (h_disp - h)/2), text, font=font)

imageRGB.save("NiHao! " + "RGB" + ".bmp")

image8bit = imageRGB.convert("L")

image8bit.save("NiHao! " + "L" + ".bmp")

threshold = 80
image1bit = image8bit.point(lambda x: 0 if x &lt; threshold else 1, mode='1')
image1bit.save("NiHao! " + "1" + " threshold = " + str(threshold) + ".bmp")

threshold = 128
image1bit = image8bit.point(lambda x: 0 if x &lt; threshold else 1, mode='1')
image1bit.save("NiHao! " + "1" + " threshold = " + str(threshold) + ".bmp")
</code></pre>

CC BY-SA 3.0
2017-12-01T04:28:05.280Z
2017-12-01T04:28:05.280Z
uhoh
<p>In the olden days, for things like fonts, they didn't do antialiasing. They generally hand made bitmap fonts because it generated the clearest, easiest-to-read results.</p>

<p>That said, you could look into <a href="https://en.wikipedia.org/wiki/Error_diffusion" rel="nofollow noreferrer">error diffusion</a>, <a href="https://en.wikipedia.org/wiki/Halftone" rel="nofollow noreferrer">halftoning</a> or other <a href="https://en.wikipedia.org/wiki/Dither" rel="nofollow noreferrer">dithering</a> techniques for changing continuous-tone images into 1-bit images. These are techniques for reducing the bit depth of images.</p>

CC BY-SA 3.0
2017-11-30T07:08:20.040Z
Ah! for ellipses (and presumably arbitrary angle lines) PIL does draw fairly "OK-looking" 1-bit curved lines without standard antialiasing, https://i.sstatic.net/HGDox.png and in fact, [this answer](https://stackoverflow.com/a/2292690/3904031) shows how to do more arbitrary curves. So combining those with a search for historical bitmapped fonts, I should be good to go! If you'd like to include this info into your answer I'll delete this comment. Thanks!
uhoh
CC BY-SA 3.0
2017-11-30T10:34:04.010Z
The problem is, all those techniques trade off spatial resolution, and it's not like these displays have pixels to spare.
Dan Hulme
CC BY-SA 3.0
2017-11-30T06:34:02.233Z
2017-11-30T06:34:02.233Z
user1118321
<p>PIL's <a href="http://pillow.readthedocs.io/en/4.0.x/reference/Image.html#PIL.Image.Image.convert" rel="noreferrer"><code>Image.convert</code></a> function performs dithering by default when you convert the image to 1-bit—not simply thresholding. That's what creates the noise along the edges of the shapes; the antialiasing isn't the problem. Unfortunately, at such a low resolution, the dithering does more harm than good.</p>

<p>I grabbed your original image from your other question and did a straight threshold at 128, and it comes out looking a lot better:</p>

<p><img src="https://i.sstatic.net/WAlDF.png" alt="threshold, no dithering"></p>

<p>That's probably about as good as you'll be able to do on a low-resolution 1-bit display, absent a bitmap font that's specifically designed for those circumstances.</p>

<p>In PIL this operation can be implemented using <a href="http://pillow.readthedocs.io/en/4.0.x/reference/Image.html#PIL.Image.Image.point" rel="noreferrer"><code>Image.point</code></a>, as follows:</p>

<pre><code>image1Bit = image8Bit.point(lambda x: 0 if x &lt; 128 else 1, mode='1')
</code></pre>

CC BY-SA 3.0
2017-11-30T21:17:42.623Z
altough to be honest if you touchup a few pixels you can get a good compromize between the two.
joojaa
CC BY-SA 3.0
2017-12-01T00:33:43.687Z
Excellent! This is exactly what I needed, thank you.
uhoh
CC BY-SA 3.0
2017-11-30T21:01:13.407Z
2017-11-30T21:01:13.407Z
Nathan Reed
<p>These days anti-aliasing uses gray scale pixel values on displays with high pixel counts.</p>

<p>I'd like to take a step back in time and learn what is available for anti-aliasting or at least improving the quality of curved lines and fonts drawn on bindary (on/off) 1-bit displays, such as these low cost OLED displays shown below.</p>

<p>I don't know if the procedure would be called anti-aliasing, or something else. Ideally an explanation or a link/reference to a mathematical procedure or algorithm would be most helpful, or if something exists in Python, that would be great as well.</p>

<p>PIL, the Python Image Library is wonderful but I have a hunch it is mostly useful for gray scale or RGB continuous tone images rather than binary 1-bit images.</p>

<p>See for example the question <a href="https://stackoverflow.com/q/47545359/3904031">Drawing multilingual text using PIL and saving as 1-bit and 8-bit bitmaps</a>, where PIL turned this gray scale image:</p>

<p><a href="https://i.sstatic.net/otMYr.png" rel="noreferrer"><img src="https://i.sstatic.net/otMYr.png" alt="enter image description here"></a></p>

<p>into this 1-bit binary image:</p>

<p><a href="https://i.sstatic.net/ov0i3.png" rel="noreferrer"><img src="https://i.sstatic.net/ov0i3.png" alt="enter image description here"></a></p>

<p>and similar things would happen if I drew circles in PIL and then converted to 1-bit. The antialiasing is designed for continuous tone display, and so the subsequent thresholding just makes a mess.</p>

<hr>

<p>Example of a 1-bit binary OLED display (128x64 pixels) cropped from <a href="https://cdn-shop.adafruit.com/1200x900/326-18.jpg" rel="noreferrer">this image</a> from the AdaFruit Page <a href="https://www.adafruit.com/product/326" rel="noreferrer">Monochrome 0.96" 128x64 OLED graphic display, Product ID: 326</a>.</p>

<p><a href="https://i.sstatic.net/THcMR.jpg" rel="noreferrer"><img src="https://i.sstatic.net/THcMR.jpg" alt="enter image description here"></a></p>

CC BY-SA 3.0
2017-11-30T17:11:54.367Z
I don't know all the details of your specific usage case, but if you have a decent refresh rate, you could get anti aliasing by faking greyscale through rapidly turning on and off individual pixels.

There's more to it than just that though, so you might find these two write ups interesting:

https://blog.demofox.org/2017/10/31/animating-noise-for-integration-over-time/

https://blog.demofox.org/2017/11/03/animating-noise-for-integration-over-time-2-uniform-over-time/
Alan Wolfe
CC BY-SA 3.0
2017-12-01T00:45:54.533Z
@AlanWolfe that's a great idea! For this particular class of units the data rate is slow and there are no extra data buffers to toggle between, but I will keep this in mind, and if I have a unit where this would be possible I'll give it a go. Thanks!
uhoh
<antialiasing><bitmap-graphics>
Algorithms to "anti-alias" (or somehow improve) binary 1-bit drawings and fonts
uhoh
# Algorithms to "anti-alias" (or somehow improve) binary 1-bit drawings and fonts

(Asked by: username_0 on 2017-11-30)

These days anti-aliasing uses gray scale pixel values on displays with high pixel counts.

I'd like to take a step back in time and learn what is available for anti-aliasting or at least improving the quality of curved lines and fonts drawn on bindary (on/off) 1-bit displays, such as these low cost OLED displays shown below.

I don't know if the procedure would be called anti-aliasing, or something else. Ideally an explanation or a link/reference to a mathematical procedure or algorithm would be most helpful, or if something exists in Python, that would be great as well.

PIL, the Python Image Library is wonderful but I have a hunch it is mostly useful for gray scale or RGB continuous tone images rather than binary 1-bit images.

See for example the question [Drawing multilingual text using PIL and saving as 1-bit and 8-bit bitmaps](https://stackoverflow.com/q/47545359/3904031), where PIL turned this gray scale image:

[![enter image description here](https://i.sstatic.net/otMYr.png)](https://i.sstatic.net/otMYr.png)

into this 1-bit binary image:

[![enter image description here](https://i.sstatic.net/ov0i3.png)](https://i.sstatic.net/ov0i3.png)

and similar things would happen if I drew circles in PIL and then converted to 1-bit. The antialiasing is designed for continuous tone display, and so the subsequent thresholding just makes a mess.

---

Example of a 1-bit binary OLED display (128x64 pixels) cropped from [this image](https://cdn-shop.adafruit.com/1200x900/326-18.jpg) from the AdaFruit Page [Monochrome 0.96" 128x64 OLED graphic display, Product ID: 326](https://www.adafruit.com/product/326).

[![enter image description here](https://i.sstatic.net/THcMR.jpg)](https://i.sstatic.net/THcMR.jpg)

**Tags:** antialiasing, bitmap-graphics

> **Comments:**
> **username_1**: I don't know all the details of your specific usage case, but if you have a decent refresh rate, you could get anti aliasing by faking greyscale through rapidly turning on and off individual pixels.  There's more to it than just that though, so you might find these two write ups interesting:  https://blog.demofox.org/2017/10/31/animating-noise-for-integration-over-time/  https://blog.demofox.org/2017/11/03/animating-noise-for-integration-over-time-2-uniform-over-time/  
> **username_0**: @username_1 that's a great idea! For this particular class of units the data rate is slow and there are no extra data buffers to toggle between, but I will keep this in mind, and if I have a unit where this would be possible I'll give it a go. Thanks!  

---

## 4 answers

### Answer by username_5 on 2017-11-30. Score: 5 | Accepted answer

PIL's [`Image.convert`](http://pillow.readthedocs.io/en/4.0.x/reference/Image.html#PIL.Image.Image.convert) function performs dithering by default when you convert the image to 1-bit—not simply thresholding. That's what creates the noise along the edges of the shapes; the antialiasing isn't the problem. Unfortunately, at such a low resolution, the dithering does more harm than good.

I grabbed your original image from your other question and did a straight threshold at 128, and it comes out looking a lot better:

![threshold, no dithering](https://i.sstatic.net/WAlDF.png)

That's probably about as good as you'll be able to do on a low-resolution 1-bit display, absent a bitmap font that's specifically designed for those circumstances.

In PIL this operation can be implemented using [`Image.point`](http://pillow.readthedocs.io/en/4.0.x/reference/Image.html#PIL.Image.Image.point), as follows:

```
image1Bit = image8Bit.point(lambda x: 0 if x < 128 else 1, mode='1')

```

> **Comments:**
> **username_6**: altough to be honest if you touchup a few pixels you can get a good compromize between the two.  
> **username_0**: Excellent! This is exactly what I needed, thank you.  

---

### Answer by username_2 on 2017-11-30. Score: 3

Have a look at *Improved Alpha-Tested Magnification for Vector Textures and Special Effects* [Gre07] ([pdf](http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf)). The gist of their method is to store distance field information in the font texture instead of bitmap glyphs. This information is then used to build much higher quality up-scaled versions of the glyphs. They even describe a rendering path without programmable shading that gives ok results.

> **Comments:**
> **username_0**: Thanks for the link. Since I'm new to this having something like this to read is very helpful.  

---

### Answer by username_0 on 2017-12-01. Score: 3

This is just a supplementary answer based on the [accepted answer](https://computergraphics.stackexchange.com/a/5940/5846), using the original 64x128 pixel images instead of the zoomed-in interpolated screen shot.

`imtype = "RGB":` [![enter image description here](https://i.sstatic.net/x5BOT.png)](https://i.sstatic.net/x5BOT.png) (true RGB)

`imtype = "L":` [![enter image description here](https://i.sstatic.net/IT7Fd.png)](https://i.sstatic.net/IT7Fd.png) (grayscale 8-bit)

`imtype = "1":` [![enter image description here](https://i.sstatic.net/BYTgZ.png)](https://i.sstatic.net/BYTgZ.png) (binary 1-bit, threshold = 128)

`imtype = "1":` [![enter image description here](https://i.sstatic.net/IBTd7.png)](https://i.sstatic.net/IBTd7.png) (binary 1-bit, threshold = 80)

---

Here are screenshots zoomed-in for those without their glasses:

[![enter image description here](https://i.sstatic.net/gdNwt.png)](https://i.sstatic.net/gdNwt.png) (screenshot of 1-bit, threshold = 128)

[![enter image description here](https://i.sstatic.net/Ki3eh.png)](https://i.sstatic.net/Ki3eh.png) (screenshot of 1-bit, threshold = 80)

```
from PIL import Image, ImageDraw, ImageFont
w_disp   = 128
h_disp   =  64
fontsize =  32
text     =  u"你好!"

imageRGB  = Image.new("RGB", (w_disp, h_disp))
draw      = ImageDraw.Draw(imageRGB)
font      = ImageFont.truetype("/Library/Fonts/Arial Unicode.ttf", fontsize)
w, h      = draw.textsize(text, font=font)

draw.text(((w_disp - w)/2, (h_disp - h)/2), text, font=font)

imageRGB.save("NiHao! " + "RGB" + ".bmp")

image8bit = imageRGB.convert("L")

image8bit.save("NiHao! " + "L" + ".bmp")

threshold = 80
image1bit = image8bit.point(lambda x: 0 if x < threshold else 1, mode='1')
image1bit.save("NiHao! " + "1" + " threshold = " + str(threshold) + ".bmp")

threshold = 128
image1bit = image8bit.point(lambda x: 0 if x < threshold else 1, mode='1')
image1bit.save("NiHao! " + "1" + " threshold = " + str(threshold) + ".bmp")

```


---

### Answer by username_3 on 2017-11-30. Score: 2

In the olden days, for things like fonts, they didn't do antialiasing. They generally hand made bitmap fonts because it generated the clearest, easiest-to-read results.

That said, you could look into [error diffusion](https://en.wikipedia.org/wiki/Error_diffusion), [halftoning](https://en.wikipedia.org/wiki/Halftone) or other [dithering](https://en.wikipedia.org/wiki/Dither) techniques for changing continuous-tone images into 1-bit images. These are techniques for reducing the bit depth of images.

> **Comments:**
> **username_0**: Ah! for ellipses (and presumably arbitrary angle lines) PIL does draw fairly "OK-looking" 1-bit curved lines without standard antialiasing, https://i.sstatic.net/HGDox.png and in fact, [this answer](https://stackoverflow.com/a/2292690/3904031) shows how to do more arbitrary curves. So combining those with a search for historical bitmapped fonts, I should be good to go! If you'd like to include this info into your answer I'll delete this comment. Thanks!  
> **username_4**: The problem is, all those techniques trade off spatial resolution, and it's not like these displays have pixels to spare.  

---

