Repository navigation
Expand file tree
/
Copy pathPixelFormat.h
More file actions
204 lines (189 loc) · 6.53 KB
/
Copy pathPixelFormat.h
File metadata and controls
204 lines (189 loc) · 6.53 KB
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
// Pixel-format arithmetic shared by the rasterizer and the packages that
// prepare data for it: RGB565 pack, unpack and quantisation, exact /255, and
// the ordered-dither threshold matrix.
//
// RGB565 expands to 8 bits by shift, without bit replication: 0x1F -> 0xF8.
// The engine does this everywhere a 565 value meets 8-bit maths (blending,
// tinting, chroma keys), so a key quantised with QuantizeRGB565 compares
// equal to a pixel unpacked with UnpackRGB565.
#pragma once
#include <cstdint>
#include <cstring>
#include <deki/Engine.h> // Deki::ColorFormat
namespace DekiPixel
{
/// x / 255 for x in [0, 65535], exact. Do not replace it with
/// (x + 128) >> 8: that gives 254 for 255 * 255, so repeated tints and blends
/// drift darker.
inline constexpr uint8_t Div255(uint32_t x)
{
return static_cast<uint8_t>((x + 1 + (x >> 8)) >> 8);
}
inline constexpr uint16_t PackRGB565(uint8_t r, uint8_t g, uint8_t b)
{
return static_cast<uint16_t>(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
}
inline void UnpackRGB565(uint16_t v, uint8_t& r, uint8_t& g, uint8_t& b)
{
r = static_cast<uint8_t>((v >> 11) << 3);
g = static_cast<uint8_t>(((v >> 5) & 0x3F) << 2);
b = static_cast<uint8_t>((v & 0x1F) << 3);
}
/// Drops the bits RGB565 cannot hold, so an 8-bit colour compares equal to the
/// same colour after a round trip through PackRGB565/UnpackRGB565.
inline void QuantizeRGB565(uint8_t& r, uint8_t& g, uint8_t& b)
{
r = static_cast<uint8_t>((r >> 3) << 3);
g = static_cast<uint8_t>((g >> 2) << 2);
b = static_cast<uint8_t>((b >> 3) << 3);
}
/// Standard src-over alpha union for coverage targets (RGB565A8):
/// out.a = src.a + dst.a * (255 - src.a) / 255. On a freshly cleared target
/// (dst.a == 0) this is src.a, as code that checks coverage expects.
inline uint8_t AlphaUnion(uint8_t srcA, uint8_t dstA)
{
if (srcA == 255)
{
return 255;
}
if (dstA == 0)
{
return srcA;
}
return static_cast<uint8_t>(srcA + Div255(static_cast<uint32_t>(dstA) * (255u - srcA)));
}
/// 8x8 Bayer threshold matrix scaled to 0..255 (the recursive definition,
/// mapped to (m + 1) * 256 / 64 - 1). Ordered dithering writes a pixel opaque
/// when its alpha exceeds the threshold at (x & 7, y & 7).
/// https://en.wikipedia.org/wiki/Ordered_dithering
inline constexpr uint8_t kBayer8x8[64] = {
0, 128, 32, 160, 8, 136, 40, 168, 192, 64, 224, 96, 200, 72, 232, 104, 48, 176, 16, 144, 56, 184,
24, 152, 240, 112, 208, 80, 248, 120, 216, 88, 12, 140, 44, 172, 4, 132, 36, 164, 204, 76, 236, 108,
196, 68, 228, 100, 60, 188, 28, 156, 52, 180, 20, 148, 252, 124, 220, 92, 244, 116, 212, 84,
};
inline uint8_t BayerThreshold(int32_t px, int32_t py)
{
return kBayer8x8[((py & 7) << 3) | (px & 7)];
}
// ---------------------------------------------------------------------------
// Per-format pixel access, shared by the 2D blitter (QuadBlit) and deki-3d's
// rasteriser so both read and write every format the same way. Templates, so
// each format compiles to its own tight loop.
// ---------------------------------------------------------------------------
// Source layouts. RGB565A8 is any isRGB565 source with 3 or more bytes per
// pixel, whether or not it declares alpha (hasAlpha decides whether byte 2 is
// read). RGBA8888 is the 4-byte non-565 layout, RGB888 3 bytes, and ALPHA8 a
// coverage-only byte (a font or icon atlas drawn as a sprite: its colour is
// the tint, white when untinted).
enum class SrcKind
{
RGB565,
RGB565A8,
RGBA8888,
RGB888,
ALPHA8
};
template <SrcKind SK>
inline void ReadSrcPixel(const uint8_t* p, bool hasAlpha, uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& a)
{
if constexpr (SK == SrcKind::RGB565 || SK == SrcKind::RGB565A8)
{
// Byte by byte, little-endian, rather than memcpy: the pixel is often
// unaligned (RGB565A8 is 3 bytes), and on the ESP32's Xtensa, which
// has no unaligned loads, GCC makes such a memcpy a function call:
// one per pixel, which made blits six times slower.
const uint16_t v = static_cast<uint16_t>(p[0] | (p[1] << 8));
UnpackRGB565(v, r, g, b);
if constexpr (SK == SrcKind::RGB565A8)
{
a = hasAlpha ? p[2] : 255;
}
else
{
a = 255;
}
}
else if constexpr (SK == SrcKind::RGBA8888)
{
r = p[0];
g = p[1];
b = p[2];
a = p[3];
(void)hasAlpha;
}
else if constexpr (SK == SrcKind::RGB888)
{
r = p[0];
g = p[1];
b = p[2];
a = 255;
(void)hasAlpha;
}
else
{
r = g = b = 255;
a = p[0];
(void)hasAlpha;
}
}
// Destination read: colour plus coverage alpha (255 for formats without one).
template <Deki::ColorFormat F>
inline void ReadDstPixel(const uint8_t* target, size_t idx, uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& a)
{
if constexpr (F == Deki::ColorFormat::RGB565)
{
UnpackRGB565(((const uint16_t*)target)[idx], r, g, b);
a = 255;
}
else if constexpr (F == Deki::ColorFormat::RGB888)
{
r = target[idx * 3];
g = target[idx * 3 + 1];
b = target[idx * 3 + 2];
a = 255;
}
else if constexpr (F == Deki::ColorFormat::ARGB8888)
{
const uint32_t v = ((const uint32_t*)target)[idx];
r = (v >> 16) & 0xFF;
g = (v >> 8) & 0xFF;
b = v & 0xFF;
a = 255;
}
else // RGB565A8: [lo, hi, alpha]
{
const uint16_t v = (uint16_t)target[idx * 3] | ((uint16_t)target[idx * 3 + 1] << 8);
UnpackRGB565(v, r, g, b);
a = target[idx * 3 + 2];
}
}
// Destination write, with the coverage alpha for formats that keep one.
template <Deki::ColorFormat F>
inline void WriteDstPixel(uint8_t* target, size_t idx, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if constexpr (F == Deki::ColorFormat::RGB565)
{
((uint16_t*)target)[idx] = PackRGB565(r, g, b);
(void)a;
}
else if constexpr (F == Deki::ColorFormat::RGB888)
{
target[idx * 3] = r;
target[idx * 3 + 1] = g;
target[idx * 3 + 2] = b;
(void)a;
}
else if constexpr (F == Deki::ColorFormat::ARGB8888)
{
((uint32_t*)target)[idx] = (0xFFu << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
(void)a;
}
else
{
const uint16_t v = PackRGB565(r, g, b);
target[idx * 3] = (uint8_t)(v & 0xFF);
target[idx * 3 + 1] = (uint8_t)(v >> 8);
target[idx * 3 + 2] = a;
}
}
} // namespace DekiPixel