mspxr

Smooth Rounded Anti-Aliasing

Micro Sub Pixel Rendering

Micro Sub Pixel Rendering (mspxr)
Copyright (C) 2024 Canmi, all rights reserved.

Introduction

mspxr is a lightweight graphic rendering library. It leverages the unique properties of LCDs to display 3x times more pixels using limited physical pixels. Additionally, it employs anti-aliasing techniques to enhance the detail and smoothness of fonts and images.


Dot

Here is a 1px dot.


Line


Rectangle

We can use these dots to construct a rectangle.


Circle?

Without anti-aliasing, it may seem like your screen resolution is too low. But do you really need such a high resolution?


Midpoint Circle Algorithm

The Midpoint Circle Algorithm is an efficient method for drawing a circle on a grid by using symmetry and a decision-making process to determine the next pixel to plot based on a midpoint criterion.

Initialization:

Update Rule:

Draw the following points:

Now, let’s implement this concept in C.

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
void mspxr_draw_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, uint16_t color)
{
int16_t x = 0;
int16_t y = radius;
int16_t decision = 3 - 2 * radius;

// Use Bresenham's algorithm to draw the circle
while (y >= x)
{
// Plot points in all octants
mspxr_draw_dot(center_x + x, center_y + y, color);
mspxr_draw_dot(center_x - x, center_y + y, color);
mspxr_draw_dot(center_x + x, center_y - y, color);
mspxr_draw_dot(center_x - x, center_y - y, color);
mspxr_draw_dot(center_x + y, center_y + x, color);
mspxr_draw_dot(center_x - y, center_y + x, color);
mspxr_draw_dot(center_x + y, center_y - x, color);
mspxr_draw_dot(center_x - y, center_y - x, color);

// Update decision parameter
if (decision <= 0)
{
decision += 4 * x + 6;
}
else
{
decision += 4 * (x - y) + 10;
y--;
}
x++;
}
}

Now we have a circle, but it’s not exactly what we want.


Rounded Rectangle

We can divide this circle into four pieces to create a corner for a rounded rectangle.

Now we can use a circle divided into 4 corners and 5 rectangles to form a rounded rectangle.

Rounded Rectangle:

To draw a rounded rectangle, the algorithm combines straight lines for the rectangle’s edges and circular arcs for its corners. The edges of the rectangle are drawn using straight lines:

Draw horizontal lines from , maintaining the y-coordinates at and , and draw vertical lines from to , maintaining the x-coordinates at and .

Top Edge:

Bottom Edge:

Left Edge:

Right Edge:

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
void mspxr_draw_rounded_rectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t corner, uint16_t color, uint8_t argument)
{
// Draw horizontal edges
mspxr_draw_straight_line(x1 + corner, y1, x2 - corner, y1, color);
mspxr_draw_straight_line(x1 + corner, y2, x2 - corner, y2, color);

// Draw vertical edges
mspxr_draw_straight_line(x1, y1 + corner - argument, x1, y2 - corner + argument, color);
mspxr_draw_straight_line(x2, y1 + corner - argument, x2, y2 - corner + argument, color);

int16_t dx, dy;

// Draw corner arcs
for (dx = 0; dx <= corner; dx++)
{
dy = (int16_t)(sqrt(corner * corner - dx * dx));

// Upper-left corner
mspxr_draw_dot(x1 + corner - dx, y1 + corner - dy, color);
// Lower-left corner
mspxr_draw_dot(x1 + corner - dx, y2 - corner + dy, color);
// Upper-right corner
mspxr_draw_dot(x2 - corner + dx, y1 + corner - dy, color);
// Lower-right corner
mspxr_draw_dot(x2 - corner + dx, y2 - corner + dy, color);
}
}

Rounded Rectangle Filled:

Top Section:
For lines where the current y-coordinate is within the top rounded corner area, the x-coordinates are calculated based on the circle equation:

Horizontal lines are drawn between:

Middle Section:
For lines in the middle rectangle (outside the corner areas), the horizontal lines span directly between:

Bottom Section:
For lines in the bottom rounded corner area, the x-coordinates are calculated similarly to the top section, but using the offset from the bottom:

Horizontal lines are drawn between:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void mspxr_draw_rounded_rectangle_filled(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t corner, uint16_t color)
{
for (uint16_t y = y1; y <= y2; y++)
{
if (y < y1 + corner)
{
int16_t dy = y1 + corner - y;
int16_t dx = (int16_t)(sqrt(corner * corner - dy * dy));
mspxr_draw_straight_line(x1 + corner - dx, y, x2 - corner + dx, y, color);
}
else if (y > y2 - corner)
{
int16_t dy = y - (y2 - corner);
int16_t dx = (int16_t)(sqrt(corner * corner - dy * dy));
mspxr_draw_straight_line(x1 + corner - dx, y, x2 - corner + dx, y, color);
}
else
{
mspxr_draw_straight_line(x1, y, x2, y, color);
}
}
}

FXAA

FXAA (Fast Approximate Anti-Aliasing) is a modern technique developed to reduce the jagged edges (aliasing) that appear in graphics, especially on diagonal or curved lines. Unlike traditional anti-aliasing methods, FXAA operates as a post-processing effect, smoothing out edges without the need for heavy computational power. It works by blending the colors of pixels along the edges, which reduces the visual impact of jaggedness. FXAA is widely used in real-time rendering applications, offering a balance between quality and performance.


Blend Color

In order to use FXAA, we must create some extra pixels for transitioning.

Blended Color =

Where:

is the foreground color (the pixel being drawn).

is the background color (the pixel already in place).

is the alpha value (ranging from 0 to 255), which controls the mix between the two colors.

This formula ensures a smooth gradient by proportionally mixing the two colors based on the alpha value, where higher alpha emphasizes the foreground color.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uint16_t mspxr_blend_color(uint16_t color, uint16_t background_color, uint16_t alpha)
{
// Foreground color
uint8_t r1 = (color >> 11) & 0x1F; // (5 bits)
uint8_t g1 = (color >> 5) & 0x3F; // (6 bits)
uint8_t b1 = color & 0x1F; // (5 bits)

// Background color
uint8_t r2 = (background_color >> 11) & 0x1F; // (5 bits)
uint8_t g2 = (background_color >> 5) & 0x3F; // (6 bits)
uint8_t b2 = background_color & 0x1F; // (5 bits)

// Apply alpha blending
uint8_t r = (r1 * alpha + r2 * (255 - alpha)) / 255;
uint8_t g = (g1 * alpha + g2 * (255 - alpha)) / 255;
uint8_t b = (b1 * alpha + b2 * (255 - alpha)) / 255;

// Combine into 16-bit color (RGB565 format)
return (r << 11) | (g << 5) | b;
}

FXAA Circle

We can renders a filled circle with a smooth, anti-aliased edge using Fast Approximate Anti-Aliasing (FXAA). The function draws the solid interior of the circle first and then gradually blends the outer edge into the background, creating a smooth transition.

Circle Interior:

Iterate through all pixels within the radius of the circle, drawing them with the specified foreground color.
For a pixel at coordinates relative to the circle’s center, it is part of the interior if:

Anti-Aliased Edge:

Pixels in the transition zone are determined based on their distance from the circle’s edge.
The transition zone spans from to , where "expansion" controls the width of the blending area.

The alpha value for blending is calculated as:

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
void mspxr_fxaa_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, uint16_t expansion, uint16_t color, uint16_t background_color)
{
int16_t dx, dy;
uint16_t alpha;
float total_radius = radius + expansion;

// Draw the solid interior of the circle
for (dx = -radius; dx <= radius; dx++)
{
for (dy = -radius; dy <= radius; dy++)
{
if (sqrt(dx * dx + dy * dy) <= radius)
{
mspxr_draw_dot(center_x + dx, center_y + dy, color);
}
}
}

// Draw the anti-aliased edge
for (dx = -total_radius; dx <= total_radius; dx++)
{
for (dy = -total_radius; dy <= total_radius; dy++)
{
float distance = sqrt(dx * dx + dy * dy);

// Skip pixels already drawn in the interior
if (distance <= radius)
continue;

// Only process pixels in the transition zone
if (distance <= total_radius)
{
alpha = (uint16_t)((1.0f - ((distance - radius) / expansion)) * 255);

// Blend the foreground and background colors
uint16_t mixed_color = mspxr_blend_color(color, background_color, alpha);

mspxr_draw_dot(center_x + dx, center_y + dy, mixed_color);
}
}
}
}


FXAA Quarter Circle

In embedded applications with limited resources, optimizing for bufferless scenarios is essential. Therefore, rather than using four full circles to create a rounded rectangle, we generate four quarter-circle segments.

A quarter-circle is restricted to the first quadrant, expanding to the right and downward from the center point. The pixel coordinates

must satisfy:

To check if a pixel (dx, dy) falls within the extended circular area, compute its Euclidean distance from the circle’s center:

A pixel is part of the extended circle if:

Drawing Logic:
For pixels inside the original circle:

Draw with the solid foreground color.

For pixels within the transition (anti-aliased) zone:

Compute the alpha value for blending based on the distance:

Use this alpha value to blend the foreground and background colors for smooth edges.


FXAA Rounded Rectangle

Building on the previous method for drawing a quarter-circle, we can break down a rounded rectangle with anti-aliasing into four quarter-circles for the corners and three rectangles to complete the shape.

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
void mspxr_fxaa_rounded_rectangle_filled(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t corner, uint16_t expansion, uint16_t color, uint16_t background_color) 
{
x2 -= 1;
y2 -= 1;
int16_t dx, dy;
float total_radius = corner + expansion;
uint16_t alpha;
uint16_t vertical_fill_start = y1 + corner;
uint16_t vertical_fill_end = y2 - corner;

for (dx = -corner; dx <= 0; dx++)
{
for (dy = -corner; dy <= 0; dy++)
{
float distance = sqrt(dx * dx + dy * dy);
if (distance <= corner + expansion && x1 + corner + dx >= x1 && y1 + corner + dy >= y1)
{
if (distance <= corner)
{
mspxr_draw_dot(x1 + corner + dx, y1 + corner + dy, color);
}
else
{
alpha = (uint16_t)((1.0f - ((distance - corner) / expansion)) * 255);
uint16_t blended_color = mspxr_blend_color(color, background_color, alpha);
mspxr_draw_dot(x1 + corner + dx, y1 + corner + dy, blended_color);
}
}
}
}

mspxr_draw_rectangle_filled(x1, vertical_fill_start, x1 + corner, vertical_fill_end, color);

for (dx = -corner; dx <= 0; dx++)
{
for (dy = 0; dy <= corner; dy++)
{
float distance = sqrt(dx * dx + dy * dy);
if (distance <= corner + expansion && x1 + corner + dx >= x1 && y2 - corner + dy <= y2)
{
if (distance <= corner)
{
mspxr_draw_dot(x1 + corner + dx, y2 - corner + dy, color);
}
else
{
alpha = (uint16_t)((1.0f - ((distance - corner) / expansion)) * 255);
uint16_t blended_color = mspxr_blend_color(color, background_color, alpha);
mspxr_draw_dot(x1 + corner + dx, y2 - corner + dy, blended_color);
}
}
}
}

mspxr_draw_rectangle_filled(x1 + corner, y1, x2 - corner, y2, color);

for (dx = 0; dx <= corner; dx++)
{
for (dy = -corner; dy <= 0; dy++)
{
float distance = sqrt(dx * dx + dy * dy);
if (distance <= corner + expansion && x2 - corner + dx <= x2 && y1 + corner + dy >= y1)
{
if (distance <= corner)
{
mspxr_draw_dot(x2 - corner + dx, y1 + corner + dy, color);
}
else
{
alpha = (uint16_t)((1.0f - ((distance - corner) / expansion)) * 255);
uint16_t blended_color = mspxr_blend_color(color, background_color, alpha);
mspxr_draw_dot(x2 - corner + dx, y1 + corner + dy, blended_color);
}
}
}
}

mspxr_draw_rectangle_filled(x2 - corner, vertical_fill_start, x2, vertical_fill_end, color);

for (dx = 0; dx <= corner; dx++)
{
for (dy = 0; dy <= corner; dy++)
{
float distance = sqrt(dx * dx + dy * dy);
if (distance <= corner + expansion && x2 - corner + dx <= x2 && y2 - corner + dy <= y2)
{
if (distance <= corner)
{
mspxr_draw_dot(x2 - corner + dx, y2 - corner + dy, color);
}
else
{
alpha = (uint16_t)((1.0f - ((distance - corner) / expansion)) * 255);
uint16_t blended_color = mspxr_blend_color(color, background_color, alpha);
mspxr_draw_dot(x2 - corner + dx, y2 - corner + dy, blended_color);
}
}
}
}
}


Summary

Building on the approach described above, we can add support for subpixel rendering by performing triple sampling of horizontal pixels, calculating weights, and compressing the subpixels back into place. While the specifics of the subpixel algorithm are not detailed here, the image below illustrates a comparison between a rounded rectangle calculated with subpixel and anti-aliasing techniques and a simple rounded rectangle.


Canmi
MIT & Apache-2.0 license 2024
Waline need JavaScript!

Moment

WCH-Link CH549F
一堆?
MI A->C 10G
WCH-LinkE CH32V305
ST-Link V3 STM32F723
EV2400
USB Gigabit Ethernet Adapter
USB3.0 TF-Card Reader
PD SWAP
PCB RF 433Mhz
RF 433Mhz
LiThermal T113-S3
PW-Link GD32F303
DC24S1215
PD3.1 SRC IP2366
ESP32-PICO-D4
硅胶杜邦线(x)
Hello Hexo
吃灰灰
USBC24P 测线仪
彩色丝印的正确通途(x)
浅浅测试一下
ADUM3165
你们 Arch User 都是这样的嘛
Aurora
ArchLinux&Hyprland

WCH

WCH-Link CH549F
WCH-LinkE CH32V305
PD SWAP
WCH-LinkE

HRS

WCH-Link CH549F
WCH-LinkE CH32V305
ST-Link V3 STM32F723
PW-Link GD32F303
WCH-LinkE
PW-Link (DAP-CMSIS)

MI

MI A->C 10G

TI

MI A->C 10G
EV2400
USB Gigabit Ethernet Adapter
DC24S1215

JEA

MI A->C 10G
USBC24P 测线仪

mspxr

Micro Sub Pixel Rendering
Smooth Rounded Anti-Aliasing
mspxr V1.3

LCD

Micro Sub Pixel Rendering
Smooth Rounded Anti-Aliasing
吃灰灰
USBC24P 测线仪

ST

ST-Link V3 STM32F723
DC24S1215
USBC24P 测线仪
彩色丝印的正确通途(x)
Aurora

Realtek

USB Gigabit Ethernet Adapter

Molex

USB Gigabit Ethernet Adapter
ESP32-PICO-D4

PY

USB Gigabit Ethernet Adapter
PD SWAP

luxshare

USB3.0 TF-Card Reader
PD SWAP

TF

USB3.0 TF-Card Reader

LaTeX

LaTeX
Smooth Rounded Anti-Aliasing

C

Smooth Rounded Anti-Aliasing

RF

PCB RF 433Mhz
RF 433Mhz

LiThermal

LiThermal T113-S3

allwinner

LiThermal T113-S3

GD

PW-Link GD32F303
PW-Link (DAP-CMSIS)

PW

PW-Link GD32F303
PW-Link (DAP-CMSIS)

delta

DC24S1215
浅浅测试一下

isolated

DC24S1215

injoinic

PD3.1 SRC IP2366

Amass

PD3.1 SRC IP2366

ESP

ESP32-PICO-D4
吃灰灰

krconn

ESP32-PICO-D4
彩色丝印的正确通途(x)

Hexo

Hello Hexo

OpenSource

WCH-LinkE
PW-Link (DAP-CMSIS)

sky

USBC

USBC24P 测线仪

ADI

ADUM3165

ArchLinux

你们 Arch User 都是这样的嘛
ArchLinux&Hyprland

Aurora

Aurora
mspxr V1.3

Hyprland

ArchLinux&Hyprland

Wayland

ArchLinux&Hyprland

Nvidia

ArchLinux&Hyprland

Web

Support DarkMode

DarkReader

Support DarkMode

JS

Support DarkMode

CSS

Support DarkMode
1%

AltStyle によって変換されたページ (->オリジナル) /