@geastack/core 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +36 -0
- package/app_frame_perf.cpp +77 -0
- package/css.d.ts +1 -0
- package/events.cpp +67 -0
- package/gea_app_entry.cpp +317 -0
- package/gea_sources.mjs +126 -0
- package/gea_sources.sh +36 -0
- package/include/app.h +60 -0
- package/include/apps.h +46 -0
- package/include/audio.h +90 -0
- package/include/ble.h +212 -0
- package/include/bluetooth/hid_profile.h +92 -0
- package/include/camera.h +107 -0
- package/include/canvas.h +290 -0
- package/include/display.h +390 -0
- package/include/display_present.h +869 -0
- package/include/event.h +32 -0
- package/include/events.h +116 -0
- package/include/gea/embedded.h +13 -0
- package/include/gea/reactive_runtime.h +32 -0
- package/include/gea.h +14 -0
- package/include/gea_perf_config.h +96 -0
- package/include/geaos/abi.h +165 -0
- package/include/geolocation.h +126 -0
- package/include/gpio.h +61 -0
- package/include/graphics/font.h +122 -0
- package/include/i2c.h +20 -0
- package/include/image.h +156 -0
- package/include/imu.h +23 -0
- package/include/input.h +33 -0
- package/include/memory.h +52 -0
- package/include/pixel.h +907 -0
- package/include/platform/file_cache.h +18 -0
- package/include/power.h +11 -0
- package/include/runtime.h +83 -0
- package/include/services/app_runner.h +10 -0
- package/include/services/app_state.h +14 -0
- package/include/services/battery_service.h +12 -0
- package/include/services/bluetooth_service.h +13 -0
- package/include/services/diagnostics.h +76 -0
- package/include/services/diagnostics_platform.h +54 -0
- package/include/services/frame_scheduler.h +71 -0
- package/include/services/mirror.h +49 -0
- package/include/services/network_services.h +25 -0
- package/include/services/ota.h +32 -0
- package/include/services/runtime_log.h +11 -0
- package/include/services/storage_service.h +26 -0
- package/include/touch.h +38 -0
- package/include/ui/signal.h +149 -0
- package/include/wifi.h +67 -0
- package/index.d.ts +1940 -0
- package/jsx.d.ts +1 -0
- package/package.json +70 -0
- package/runtime/ble.ts +49 -0
- package/runtime/color.ts +21 -0
- package/runtime/compiler.ts +43 -0
- package/runtime/host.ts +1083 -0
- package/runtime/images.ts +129 -0
- package/runtime/primitives.ts +49 -0
- package/runtime-surface.ts +180 -0
- package/runtime.cpp +431 -0
- package/runtime.ts +34 -0
- package/scripts/build-gea-vite-geatsc.mjs +4514 -0
- package/scripts/dotenv-defines.mjs +98 -0
- package/scripts/gea-bundle-type-hints.mjs +546 -0
- package/scripts/gea-embedded-compat-staging.mjs +17 -0
- package/scripts/gea-embedded-compat-transform.mjs +427 -0
- package/scripts/gea-vite-module-graph-plugin.mjs +578 -0
- package/scripts/generate-gea-embedded-assets.mjs +296 -0
- package/scripts/generate-gea-embedded-fonts.mjs +1252 -0
- package/scripts/generate-installed-app-plan.mjs +55 -0
- package/scripts/native-object-cache.sh +244 -0
package/include/pixel.h
ADDED
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
|
|
8
|
+
// ESP32-S3 PIE (Processor Instruction Extensions): 128-bit vector stores for
|
|
9
|
+
// the RGB565 span-fill hot path. Guarded on the IDF target so every other
|
|
10
|
+
// target (wasm, apple, geaos, host tools) compiles the portable path.
|
|
11
|
+
#if defined(__XTENSA__) && __has_include("sdkconfig.h")
|
|
12
|
+
#include "sdkconfig.h"
|
|
13
|
+
#endif
|
|
14
|
+
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
|
15
|
+
#define GEA_PIXEL_PIE_FILL16 1
|
|
16
|
+
#else
|
|
17
|
+
#define GEA_PIXEL_PIE_FILL16 0
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
#ifndef GEA_EMBEDDED_PIXEL_PANEL_ENDIAN
|
|
21
|
+
#define GEA_EMBEDDED_PIXEL_PANEL_ENDIAN 0
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
// Per-target native framebuffer pixel format, fixed at COMPILE TIME. A target
|
|
25
|
+
// has exactly one format, so this is a build directive — never a runtime branch.
|
|
26
|
+
// esp32 / geaos panels are RGB565 (16-bit); iOS is RGBA8888 (32-bit, full
|
|
27
|
+
// colour); Android is ARGB8888 to match Bitmap.Config.ARGB_8888. Each target compiles to one native pixel type + native pixel ops via
|
|
28
|
+
// the `native_t` typedef + `#if` below — no per-pixel colour conversion.
|
|
29
|
+
#define GEA_PIXEL_RGB565 0
|
|
30
|
+
#define GEA_PIXEL_RGBA8888 1
|
|
31
|
+
#define GEA_PIXEL_ARGB8888 2
|
|
32
|
+
// GRAY4: 16-level grayscale, ONE nibble (4 bits) per pixel. For panels that
|
|
33
|
+
// display exactly 16 gray levels (e.g. the M5Paper IT8951 in GL16 mode). The
|
|
34
|
+
// pixel VALUE type is a byte holding 0..15, so every colour/blend helper is a
|
|
35
|
+
// plain scalar op; the 4-bit STORAGE packing (2 pixels per byte) lives entirely
|
|
36
|
+
// in the Canvas framebuffer accessors. This is what lets a grayscale target
|
|
37
|
+
// render straight into the panel's own format — no RGB565 buffer, no per-frame
|
|
38
|
+
// repack, quarter-size framebuffer. Additive + compile-time gated: RGB565/8888
|
|
39
|
+
// targets never compile any GRAY4 branch, so their output is unchanged.
|
|
40
|
+
#define GEA_PIXEL_GRAY4 3
|
|
41
|
+
// GRAY2: 4-level grayscale, TWO bits per pixel (4 px/byte). Same idea as GRAY4,
|
|
42
|
+
// one step further down: for panels that can only show four levels anyway (the
|
|
43
|
+
// SSD1677's dual RAM planes give exactly black / dark / light / white) on parts
|
|
44
|
+
// with no PSRAM. A 528x792 framebuffer costs 102 KB at 2bpp against 204 KB at
|
|
45
|
+
// 4bpp — the difference between fitting and not fitting in an ESP32-C3's ~380 KB
|
|
46
|
+
// of SRAM. Like GRAY4 the pixel VALUE type is a byte (0..3); only the
|
|
47
|
+
// framebuffer STORAGE is packed.
|
|
48
|
+
#define GEA_PIXEL_GRAY2 4
|
|
49
|
+
#ifndef GEA_EMBEDDED_PIXEL_FORMAT
|
|
50
|
+
#define GEA_EMBEDDED_PIXEL_FORMAT GEA_PIXEL_RGB565
|
|
51
|
+
#endif
|
|
52
|
+
#define GEA_PIXEL_FORMAT_IS_8888 \
|
|
53
|
+
(GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888 || GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888)
|
|
54
|
+
#define GEA_PIXEL_FORMAT_IS_GRAY4 (GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_GRAY4)
|
|
55
|
+
#define GEA_PIXEL_FORMAT_IS_GRAY2 (GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_GRAY2)
|
|
56
|
+
// "A grayscale-level format" — the pixel value is a small integer level in a
|
|
57
|
+
// single byte rather than packed colour channels. Everything that treats GRAY4
|
|
58
|
+
// as "not an RGB565 pack and not an 8888" applies verbatim to GRAY2.
|
|
59
|
+
#define GEA_PIXEL_FORMAT_IS_GRAY (GEA_PIXEL_FORMAT_IS_GRAY4 || GEA_PIXEL_FORMAT_IS_GRAY2)
|
|
60
|
+
// The grayscale formats store the framebuffer in the panel's own sub-byte
|
|
61
|
+
// packing, so it goes straight to the controller with no per-frame repack. The
|
|
62
|
+
// Canvas span/pixel primitives handle the read-modify-write; the pixel VALUE
|
|
63
|
+
// type stays a full byte.
|
|
64
|
+
//
|
|
65
|
+
// GEA_PIXEL_STORAGE_PACKED_BITS is the ONE knob: bits of framebuffer storage per
|
|
66
|
+
// pixel when the format is sub-byte packed (4 for GRAY4 = 2 px/byte, 2 for GRAY2
|
|
67
|
+
// = 4 px/byte), and 0 for every byte-addressed format. Packed code is written
|
|
68
|
+
// against this constant rather than against a per-depth macro, so a new depth is
|
|
69
|
+
// a value here plus the value helpers below — not a second copy of every
|
|
70
|
+
// framebuffer primitive.
|
|
71
|
+
#if GEA_PIXEL_FORMAT_IS_GRAY4
|
|
72
|
+
#define GEA_PIXEL_STORAGE_PACKED_BITS 4
|
|
73
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
74
|
+
#define GEA_PIXEL_STORAGE_PACKED_BITS 2
|
|
75
|
+
#else
|
|
76
|
+
#define GEA_PIXEL_STORAGE_PACKED_BITS 0
|
|
77
|
+
#endif
|
|
78
|
+
#define GEA_PIXEL_STORAGE_PACKED (GEA_PIXEL_STORAGE_PACKED_BITS != 0)
|
|
79
|
+
|
|
80
|
+
namespace gea::framework::graphics::pixel {
|
|
81
|
+
|
|
82
|
+
enum class Format : std::uint8_t {
|
|
83
|
+
Rgb565,
|
|
84
|
+
Rgb888,
|
|
85
|
+
Rgba8888,
|
|
86
|
+
Argb8888
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
constexpr int bitsPerPixel(Format format)
|
|
90
|
+
{
|
|
91
|
+
switch (format) {
|
|
92
|
+
case Format::Rgb565:
|
|
93
|
+
return 16;
|
|
94
|
+
case Format::Rgb888:
|
|
95
|
+
return 24;
|
|
96
|
+
case Format::Rgba8888:
|
|
97
|
+
case Format::Argb8888:
|
|
98
|
+
return 32;
|
|
99
|
+
}
|
|
100
|
+
return 16;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
constexpr int bytesPerPixel(Format format)
|
|
104
|
+
{
|
|
105
|
+
return bitsPerPixel(format) / 8;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
constexpr const char *name(Format format)
|
|
109
|
+
{
|
|
110
|
+
switch (format) {
|
|
111
|
+
case Format::Rgb565:
|
|
112
|
+
return "rgb565";
|
|
113
|
+
case Format::Rgb888:
|
|
114
|
+
return "rgb888";
|
|
115
|
+
case Format::Rgba8888:
|
|
116
|
+
return "rgb8888";
|
|
117
|
+
case Format::Argb8888:
|
|
118
|
+
return "argb8888";
|
|
119
|
+
}
|
|
120
|
+
return "rgb565";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
inline Format parseFormat(const char *value, Format fallback = Format::Rgb565)
|
|
124
|
+
{
|
|
125
|
+
if (!value) return fallback;
|
|
126
|
+
const char *expected = name(Format::Rgb565);
|
|
127
|
+
const char *cursor = value;
|
|
128
|
+
while (*cursor && *expected && *cursor == *expected) {
|
|
129
|
+
++cursor;
|
|
130
|
+
++expected;
|
|
131
|
+
}
|
|
132
|
+
if (!*cursor && !*expected) return Format::Rgb565;
|
|
133
|
+
expected = name(Format::Rgb888);
|
|
134
|
+
cursor = value;
|
|
135
|
+
while (*cursor && *expected && *cursor == *expected) {
|
|
136
|
+
++cursor;
|
|
137
|
+
++expected;
|
|
138
|
+
}
|
|
139
|
+
if (!*cursor && !*expected) return Format::Rgb888;
|
|
140
|
+
expected = name(Format::Rgba8888);
|
|
141
|
+
cursor = value;
|
|
142
|
+
while (*cursor && *expected && *cursor == *expected) {
|
|
143
|
+
++cursor;
|
|
144
|
+
++expected;
|
|
145
|
+
}
|
|
146
|
+
if (!*cursor && !*expected) return Format::Rgba8888;
|
|
147
|
+
expected = name(Format::Argb8888);
|
|
148
|
+
cursor = value;
|
|
149
|
+
while (*cursor && *expected && *cursor == *expected) {
|
|
150
|
+
++cursor;
|
|
151
|
+
++expected;
|
|
152
|
+
}
|
|
153
|
+
if (!*cursor && !*expected) return Format::Argb8888;
|
|
154
|
+
return fallback;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
constexpr std::uint16_t byteSwap16(std::uint16_t value)
|
|
158
|
+
{
|
|
159
|
+
return static_cast<std::uint16_t>((value << 8) | (value >> 8));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
constexpr std::uint16_t fromRgb565(std::uint16_t rgb565)
|
|
163
|
+
{
|
|
164
|
+
#if GEA_EMBEDDED_PIXEL_PANEL_ENDIAN
|
|
165
|
+
return byteSwap16(rgb565);
|
|
166
|
+
#else
|
|
167
|
+
return rgb565;
|
|
168
|
+
#endif
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
constexpr std::uint16_t toRgb565(std::uint16_t pixel)
|
|
172
|
+
{
|
|
173
|
+
#if GEA_EMBEDDED_PIXEL_PANEL_ENDIAN
|
|
174
|
+
return byteSwap16(pixel);
|
|
175
|
+
#else
|
|
176
|
+
return pixel;
|
|
177
|
+
#endif
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
constexpr std::uint16_t rgb565FromRgb888(int r, int g, int b)
|
|
181
|
+
{
|
|
182
|
+
return static_cast<std::uint16_t>(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
constexpr std::uint16_t fromRgb888(int r, int g, int b)
|
|
186
|
+
{
|
|
187
|
+
return fromRgb565(rgb565FromRgb888(r, g, b));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
inline void unpackRgb565(std::uint16_t pixel, int *r, int *g, int *b)
|
|
191
|
+
{
|
|
192
|
+
std::uint16_t rgb565 = toRgb565(pixel);
|
|
193
|
+
*r = (rgb565 >> 11) & 0x1F;
|
|
194
|
+
*g = (rgb565 >> 5) & 0x3F;
|
|
195
|
+
*b = rgb565 & 0x1F;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
constexpr int expand5To8(int value)
|
|
199
|
+
{
|
|
200
|
+
return (value * 255 + 15) / 31;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
constexpr int expand6To8(int value)
|
|
204
|
+
{
|
|
205
|
+
return (value * 255 + 31) / 63;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
constexpr int quantize8To5(int value)
|
|
209
|
+
{
|
|
210
|
+
return (value * 31 + 127) / 255;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
constexpr int quantize8To6(int value)
|
|
214
|
+
{
|
|
215
|
+
return (value * 63 + 127) / 255;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
constexpr std::uint16_t packRgb565Components(int r, int g, int b)
|
|
219
|
+
{
|
|
220
|
+
return fromRgb565(static_cast<std::uint16_t>((r << 11) | (g << 5) | b));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ---- Grayscale-level value helpers ------------------------------------------
|
|
224
|
+
// A grayscale pixel VALUE is a byte holding a small level — 0..15 for GRAY4,
|
|
225
|
+
// 0..3 for GRAY2, in both cases 0 = black and max = white. These convert to/from
|
|
226
|
+
// the framework's RGB565 authoring model and blend two gray values. Sub-byte
|
|
227
|
+
// PACKING (2 or 4 px/byte in the framebuffer) is separate and lives in Canvas —
|
|
228
|
+
// see GEA_PIXEL_STORAGE_PACKED_BITS.
|
|
229
|
+
//
|
|
230
|
+
// Luma is depth-independent, so both depths share it.
|
|
231
|
+
constexpr int grayLuma8(int r, int g, int b)
|
|
232
|
+
{
|
|
233
|
+
// Rec.601 luma on 8-bit channels: (77 R + 150 G + 29 B) / 256.
|
|
234
|
+
return (77 * r + 150 * g + 29 * b) >> 8;
|
|
235
|
+
}
|
|
236
|
+
constexpr std::uint8_t gray4FromRgb888(int r, int g, int b)
|
|
237
|
+
{
|
|
238
|
+
// 8-bit luma (0..255) -> 4-bit level (0..15), rounded.
|
|
239
|
+
return static_cast<std::uint8_t>((grayLuma8(r, g, b) * 15 + 127) / 255);
|
|
240
|
+
}
|
|
241
|
+
constexpr std::uint8_t gray4FromRgb565(std::uint16_t pixel)
|
|
242
|
+
{
|
|
243
|
+
const std::uint16_t rgb = fromRgb565(pixel); // undo any panel byte-swap
|
|
244
|
+
const int r5 = (rgb >> 11) & 0x1F, g6 = (rgb >> 5) & 0x3F, b5 = rgb & 0x1F;
|
|
245
|
+
return gray4FromRgb888((r5 * 255 + 15) / 31, (g6 * 255 + 31) / 63, (b5 * 255 + 15) / 31);
|
|
246
|
+
}
|
|
247
|
+
constexpr int gray4Expand8(std::uint8_t v) { return (v & 0x0F) * 255 / 15; } // 0..15 -> 0..255
|
|
248
|
+
constexpr std::uint16_t gray4ToRgb565(std::uint8_t v)
|
|
249
|
+
{
|
|
250
|
+
const int y = gray4Expand8(v);
|
|
251
|
+
return packRgb565Components(quantize8To5(y), quantize8To6(y), quantize8To5(y));
|
|
252
|
+
}
|
|
253
|
+
inline std::uint8_t gray4Blend(std::uint8_t fg, std::uint8_t bg, int alpha)
|
|
254
|
+
{
|
|
255
|
+
// Native 4-bit blend: the values ARE 4-bit levels, so expanding to 8-bit,
|
|
256
|
+
// mixing with two /255 divides and requantizing with a third bought nothing
|
|
257
|
+
// but cycles on a per-pixel path (text AA / translucent fills — Xtensa has
|
|
258
|
+
// no integer divide). alpha 0..255 → 0..16, one fused multiply-accumulate
|
|
259
|
+
// in the 4-bit domain: exact at the endpoints, within half a level in the
|
|
260
|
+
// mid-tones the atlas already quantized.
|
|
261
|
+
const int a = (alpha + 8) >> 4; // 0..16
|
|
262
|
+
return static_cast<std::uint8_t>(((fg & 0x0F) * a + (bg & 0x0F) * (16 - a) + 8) >> 4);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// GRAY2 (4-level grayscale, 2 bits/pixel). Same shape as the GRAY4 helpers, one
|
|
266
|
+
// depth down: 0 = black, 3 = white.
|
|
267
|
+
constexpr std::uint8_t gray2FromRgb888(int r, int g, int b)
|
|
268
|
+
{
|
|
269
|
+
// 8-bit luma (0..255) -> 2-bit level (0..3), rounded.
|
|
270
|
+
return static_cast<std::uint8_t>((grayLuma8(r, g, b) * 3 + 127) / 255);
|
|
271
|
+
}
|
|
272
|
+
constexpr std::uint8_t gray2FromRgb565(std::uint16_t pixel)
|
|
273
|
+
{
|
|
274
|
+
const std::uint16_t rgb = fromRgb565(pixel); // undo any panel byte-swap
|
|
275
|
+
const int r5 = (rgb >> 11) & 0x1F, g6 = (rgb >> 5) & 0x3F, b5 = rgb & 0x1F;
|
|
276
|
+
return gray2FromRgb888((r5 * 255 + 15) / 31, (g6 * 255 + 31) / 63, (b5 * 255 + 15) / 31);
|
|
277
|
+
}
|
|
278
|
+
constexpr int gray2Expand8(std::uint8_t v) { return (v & 0x03) * 255 / 3; } // 0..3 -> 0..255
|
|
279
|
+
constexpr std::uint16_t gray2ToRgb565(std::uint8_t v)
|
|
280
|
+
{
|
|
281
|
+
const int y = gray2Expand8(v);
|
|
282
|
+
return packRgb565Components(quantize8To5(y), quantize8To6(y), quantize8To5(y));
|
|
283
|
+
}
|
|
284
|
+
inline std::uint8_t gray2Blend(std::uint8_t fg, std::uint8_t bg, int alpha)
|
|
285
|
+
{
|
|
286
|
+
// Mirror of gray4Blend, and for the same reason: the RISC-V core on an
|
|
287
|
+
// ESP32-C3 has no fast divide either (RV32IMC's DIV is multi-cycle and the
|
|
288
|
+
// -march we build with may not even include M), so the blend must stay
|
|
289
|
+
// divide-free. alpha 0..255 → 0..16 and ONE fused multiply-accumulate, kept
|
|
290
|
+
// at 16 weight steps rather than 4: the extra steps cost nothing (same two
|
|
291
|
+
// multiplies, same shift) and keep the rounding of an AA edge from snapping
|
|
292
|
+
// to the endpoints, while a=0/a=16 remain exactly bg/fg. Result is 0..3
|
|
293
|
+
// because 3*16 + 8 >> 4 == 3.
|
|
294
|
+
const int a = (alpha + 8) >> 4; // 0..16
|
|
295
|
+
return static_cast<std::uint8_t>(((fg & 0x03) * a + (bg & 0x03) * (16 - a) + 8) >> 4);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
namespace detail {
|
|
299
|
+
// Compile-time lookup tables for the 5/6-bit<->8-bit conversions, exact copies of
|
|
300
|
+
// expand5To8/expand6To8/quantize8To5/quantize8To6. The Xtensa LX7 has no integer
|
|
301
|
+
// divide, so the original blend cost ~12 software __divsi3 per pixel (4x/31, 2x/63,
|
|
302
|
+
// 3x/255 mix, 3x/255 quantize) — the dominant per-pixel cost of EVERY translucent
|
|
303
|
+
// fill/gradient/text pixel. These tables move all of that to compile time.
|
|
304
|
+
struct Lut5To8 { std::uint8_t v[32]; constexpr Lut5To8() : v{} { for (int i = 0; i < 32; i++) v[i] = static_cast<std::uint8_t>((i * 255 + 15) / 31); } };
|
|
305
|
+
struct Lut6To8 { std::uint8_t v[64]; constexpr Lut6To8() : v{} { for (int i = 0; i < 64; i++) v[i] = static_cast<std::uint8_t>((i * 255 + 31) / 63); } };
|
|
306
|
+
struct Lut8To5 { std::uint8_t v[256]; constexpr Lut8To5() : v{} { for (int i = 0; i < 256; i++) v[i] = static_cast<std::uint8_t>((i * 31 + 127) / 255); } };
|
|
307
|
+
struct Lut8To6 { std::uint8_t v[256]; constexpr Lut8To6() : v{} { for (int i = 0; i < 256; i++) v[i] = static_cast<std::uint8_t>((i * 63 + 127) / 255); } };
|
|
308
|
+
inline constexpr Lut5To8 kExp5{};
|
|
309
|
+
inline constexpr Lut6To8 kExp6{};
|
|
310
|
+
inline constexpr Lut8To5 kQuant5{};
|
|
311
|
+
inline constexpr Lut8To6 kQuant6{};
|
|
312
|
+
} // namespace detail
|
|
313
|
+
|
|
314
|
+
// ---- RGBA8888 (32-bit) helpers ---------------------------------------------
|
|
315
|
+
// Used by the iOS target, which renders into a full-colour RGBA8888 framebuffer
|
|
316
|
+
// instead of RGB565. Byte layout in memory is R,G,B,A (matches a little-endian
|
|
317
|
+
// uint32 of (A<<24)|(B<<16)|(G<<8)|R and an iOS CGImage configured RGBA8 little).
|
|
318
|
+
// esp32/geaos never touch these — they stay on the RGB565 path above.
|
|
319
|
+
|
|
320
|
+
constexpr std::uint32_t packRgba8888(int r, int g, int b, int a = 255)
|
|
321
|
+
{
|
|
322
|
+
return static_cast<std::uint32_t>((r & 0xFF) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16) |
|
|
323
|
+
((a & 0xFF) << 24));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
constexpr std::uint32_t packArgb8888(int r, int g, int b, int a = 255)
|
|
327
|
+
{
|
|
328
|
+
return static_cast<std::uint32_t>(((a & 0xFF) << 24) | ((r & 0xFF) << 16) |
|
|
329
|
+
((g & 0xFF) << 8) | (b & 0xFF));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Expand an RGB565 (panel-endian-aware) pixel to full RGBA8888. The 5/6-bit
|
|
333
|
+
// channels are expanded to 8-bit; this is lossless within 565's gamut but lets
|
|
334
|
+
// subsequent 8-bit blends (gradients, alpha, AA) accumulate without re-banding.
|
|
335
|
+
inline std::uint32_t rgba8888FromRgb565(std::uint16_t pixel)
|
|
336
|
+
{
|
|
337
|
+
int r, g, b;
|
|
338
|
+
unpackRgb565(pixel, &r, &g, &b);
|
|
339
|
+
return packRgba8888(detail::kExp5.v[r], detail::kExp6.v[g], detail::kExp5.v[b], 255);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
inline std::uint32_t argb8888FromRgb565(std::uint16_t pixel)
|
|
343
|
+
{
|
|
344
|
+
int r, g, b;
|
|
345
|
+
unpackRgb565(pixel, &r, &g, &b);
|
|
346
|
+
return packArgb8888(detail::kExp5.v[r], detail::kExp6.v[g], detail::kExp5.v[b], 255);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
constexpr std::uint32_t rgba8888FromRgb888(int r, int g, int b, int a = 255)
|
|
350
|
+
{
|
|
351
|
+
return packRgba8888(r, g, b, a);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
inline void unpackRgba8888(std::uint32_t pixel, int *r, int *g, int *b, int *a)
|
|
355
|
+
{
|
|
356
|
+
*r = static_cast<int>(pixel & 0xFF);
|
|
357
|
+
*g = static_cast<int>((pixel >> 8) & 0xFF);
|
|
358
|
+
*b = static_cast<int>((pixel >> 16) & 0xFF);
|
|
359
|
+
*a = static_cast<int>((pixel >> 24) & 0xFF);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
inline void unpackArgb8888(std::uint32_t pixel, int *r, int *g, int *b, int *a)
|
|
363
|
+
{
|
|
364
|
+
*a = static_cast<int>((pixel >> 24) & 0xFF);
|
|
365
|
+
*r = static_cast<int>((pixel >> 16) & 0xFF);
|
|
366
|
+
*g = static_cast<int>((pixel >> 8) & 0xFF);
|
|
367
|
+
*b = static_cast<int>(pixel & 0xFF);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Alpha-composite fg over bg in full 8-bit per channel (no 565 re-quantization),
|
|
371
|
+
// so gradients / translucent fills / anti-aliased edges stay smooth on iOS.
|
|
372
|
+
inline std::uint32_t blend8888(std::uint32_t fg, std::uint32_t bg, int alpha)
|
|
373
|
+
{
|
|
374
|
+
if (alpha >= 255) return fg | 0xFF000000u;
|
|
375
|
+
if (alpha <= 0) return bg;
|
|
376
|
+
const int inv = 255 - alpha;
|
|
377
|
+
const int fr = static_cast<int>(fg & 0xFF);
|
|
378
|
+
const int fgc = static_cast<int>((fg >> 8) & 0xFF);
|
|
379
|
+
const int fb = static_cast<int>((fg >> 16) & 0xFF);
|
|
380
|
+
const int br = static_cast<int>(bg & 0xFF);
|
|
381
|
+
const int bgc = static_cast<int>((bg >> 8) & 0xFF);
|
|
382
|
+
const int bb = static_cast<int>((bg >> 16) & 0xFF);
|
|
383
|
+
const int r = ((fr * alpha + br * inv) + 128) * 257 >> 16;
|
|
384
|
+
const int g = ((fgc * alpha + bgc * inv) + 128) * 257 >> 16;
|
|
385
|
+
const int b = ((fb * alpha + bb * inv) + 128) * 257 >> 16;
|
|
386
|
+
return packRgba8888(r, g, b, 255);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
inline std::uint32_t blendArgb8888(std::uint32_t fg, std::uint32_t bg, int alpha)
|
|
390
|
+
{
|
|
391
|
+
if (alpha >= 255) return fg | 0xFF000000u;
|
|
392
|
+
if (alpha <= 0) return bg;
|
|
393
|
+
const int inv = 255 - alpha;
|
|
394
|
+
const int fr = static_cast<int>((fg >> 16) & 0xFF);
|
|
395
|
+
const int fgc = static_cast<int>((fg >> 8) & 0xFF);
|
|
396
|
+
const int fb = static_cast<int>(fg & 0xFF);
|
|
397
|
+
const int br = static_cast<int>((bg >> 16) & 0xFF);
|
|
398
|
+
const int bgc = static_cast<int>((bg >> 8) & 0xFF);
|
|
399
|
+
const int bb = static_cast<int>(bg & 0xFF);
|
|
400
|
+
const int r = ((fr * alpha + br * inv) + 128) * 257 >> 16;
|
|
401
|
+
const int g = ((fgc * alpha + bgc * inv) + 128) * 257 >> 16;
|
|
402
|
+
const int b = ((fb * alpha + bb * inv) + 128) * 257 >> 16;
|
|
403
|
+
return packArgb8888(r, g, b, 255);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
inline std::uint16_t blend(std::uint16_t fg, std::uint16_t bg, int alpha)
|
|
407
|
+
{
|
|
408
|
+
int fr, fg6, fb;
|
|
409
|
+
int br, bg6, bb;
|
|
410
|
+
unpackRgb565(fg, &fr, &fg6, &fb);
|
|
411
|
+
unpackRgb565(bg, &br, &bg6, &bb);
|
|
412
|
+
const int inverse = 255 - alpha;
|
|
413
|
+
// 5/6->8 expand via LUT, /255 via multiply-shift (((v+128)*257)>>16 == round(v/255)
|
|
414
|
+
// for v in [0,65025]), 8->5/6 quantize via LUT. Bit-identical to the divide-based
|
|
415
|
+
// version but with zero per-pixel software divides.
|
|
416
|
+
const int vr = detail::kExp5.v[fr] * alpha + detail::kExp5.v[br] * inverse;
|
|
417
|
+
const int vg = detail::kExp6.v[fg6] * alpha + detail::kExp6.v[bg6] * inverse;
|
|
418
|
+
const int vb = detail::kExp5.v[fb] * alpha + detail::kExp5.v[bb] * inverse;
|
|
419
|
+
const int r8 = ((vr + 128) * 257) >> 16;
|
|
420
|
+
const int g8 = ((vg + 128) * 257) >> 16;
|
|
421
|
+
const int b8 = ((vb + 128) * 257) >> 16;
|
|
422
|
+
return packRgb565Components(
|
|
423
|
+
detail::kQuant5.v[r8],
|
|
424
|
+
detail::kQuant6.v[g8],
|
|
425
|
+
detail::kQuant5.v[b8]);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// ---- Compile-time native pixel type ----------------------------------------
|
|
429
|
+
// `native_t` is THIS target's framebuffer/storage pixel — RGB565 (16-bit) or
|
|
430
|
+
// RGBA8888 (32-bit) per GEA_EMBEDDED_PIXEL_FORMAT. Canvas and the renderer
|
|
431
|
+
// operate solely on native_t; there is no runtime format branch and no
|
|
432
|
+
// per-pixel colour conversion. The framework colour model is RGB565
|
|
433
|
+
// (std::uint16_t); `toNative` converts a colour to the native pixel ONCE per
|
|
434
|
+
// draw call (identity on RGB565 targets, so it compiles away).
|
|
435
|
+
#if GEA_PIXEL_FORMAT_IS_8888
|
|
436
|
+
using native_t = std::uint32_t;
|
|
437
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY
|
|
438
|
+
// A grayscale pixel value is a single byte holding the gray level (0..15 GRAY4,
|
|
439
|
+
// 0..3 GRAY2). Storage in the framebuffer is sub-byte packed (2 or 4 px/byte),
|
|
440
|
+
// but the value carried through style, display commands and blends is this
|
|
441
|
+
// scalar byte.
|
|
442
|
+
using native_t = std::uint8_t;
|
|
443
|
+
#else
|
|
444
|
+
using native_t = std::uint16_t;
|
|
445
|
+
#endif
|
|
446
|
+
|
|
447
|
+
struct NativeColor {
|
|
448
|
+
native_t value;
|
|
449
|
+
|
|
450
|
+
constexpr NativeColor(native_t native = native_t{}) : value(native) {}
|
|
451
|
+
constexpr operator native_t() const { return value; }
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
inline native_t toNative(std::uint16_t rgb565)
|
|
455
|
+
{
|
|
456
|
+
#if GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888
|
|
457
|
+
return rgba8888FromRgb565(rgb565);
|
|
458
|
+
#elif GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888
|
|
459
|
+
return argb8888FromRgb565(rgb565);
|
|
460
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY4
|
|
461
|
+
return gray4FromRgb565(rgb565);
|
|
462
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
463
|
+
return gray2FromRgb565(rgb565);
|
|
464
|
+
#else
|
|
465
|
+
return rgb565;
|
|
466
|
+
#endif
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
constexpr std::uint32_t rgba8888ToArgb8888(std::uint32_t rgba)
|
|
470
|
+
{
|
|
471
|
+
return (rgba & 0xFF000000u) | ((rgba & 0x000000FFu) << 16) |
|
|
472
|
+
(rgba & 0x0000FF00u) | ((rgba & 0x00FF0000u) >> 16);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ---- The ONE colour conversion: authoring RGBA8888 -> this board's pixel -----
|
|
476
|
+
// gea apps author colours in RGBA8888 (bytes R,G,B,A — `packRgba8888`). This is
|
|
477
|
+
// the single place a colour becomes a pixel, and it is `constexpr`: a colour that
|
|
478
|
+
// is known at compile time (CSS literal folded by geatsc, a constant) is lowered
|
|
479
|
+
// to the board's native pixel AT BUILD TIME (esp32 -> RGB565, iOS -> RGBA8888) and
|
|
480
|
+
// costs nothing at runtime. A colour computed at runtime (the imperative
|
|
481
|
+
// `fillStyle = …` path) runs the same function once at the set-site. It never
|
|
482
|
+
// appears in the render path: style/IR/Canvas all carry `native_t`, born native.
|
|
483
|
+
constexpr native_t nativeFromRgba8888(std::uint32_t rgba)
|
|
484
|
+
{
|
|
485
|
+
#if GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888
|
|
486
|
+
return rgba;
|
|
487
|
+
#elif GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888
|
|
488
|
+
return rgba8888ToArgb8888(rgba);
|
|
489
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY4
|
|
490
|
+
return gray4FromRgb888(static_cast<int>(rgba & 0xFFu),
|
|
491
|
+
static_cast<int>((rgba >> 8) & 0xFFu),
|
|
492
|
+
static_cast<int>((rgba >> 16) & 0xFFu));
|
|
493
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
494
|
+
return gray2FromRgb888(static_cast<int>(rgba & 0xFFu),
|
|
495
|
+
static_cast<int>((rgba >> 8) & 0xFFu),
|
|
496
|
+
static_cast<int>((rgba >> 16) & 0xFFu));
|
|
497
|
+
#else
|
|
498
|
+
return fromRgb888(static_cast<int>(rgba & 0xFFu),
|
|
499
|
+
static_cast<int>((rgba >> 8) & 0xFFu),
|
|
500
|
+
static_cast<int>((rgba >> 16) & 0xFFu));
|
|
501
|
+
#endif
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Author a colour from 8-bit channels (alpha defaults opaque). Same build-time /
|
|
505
|
+
// runtime folding behaviour as nativeFromRgba8888.
|
|
506
|
+
constexpr native_t nativeColor(int r, int g, int b, int a = 255)
|
|
507
|
+
{
|
|
508
|
+
return nativeFromRgba8888(packRgba8888(r, g, b, a));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
constexpr NativeColor nativeColorValue(int r, int g, int b, int a = 255)
|
|
512
|
+
{
|
|
513
|
+
return NativeColor(nativeColor(r, g, b, a));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
constexpr NativeColor nativeColorFromRgba8888(std::uint32_t rgba)
|
|
517
|
+
{
|
|
518
|
+
return NativeColor(nativeFromRgba8888(rgba));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Author a colour from a STANDARD 0xRRGGBBAA value (red in the HIGH byte — the
|
|
522
|
+
// natural order an app writes, e.g. 0xff8800ff for orange / `rgb(r,g,b)` /
|
|
523
|
+
// `rgba(r,g,b,a)`). This is the canonical numeric colour the canvas API accepts;
|
|
524
|
+
// geatsc lowers a runtime canvas-colour number through here. (packRgba8888 /
|
|
525
|
+
// nativeFromRgba8888 use the in-memory R,G,B,A byte order, i.e. 0xAABBGGRR as a
|
|
526
|
+
// hex constant — an internal representation, not what apps type.)
|
|
527
|
+
constexpr native_t nativeFromRrggbbaa(std::uint32_t rrggbbaa)
|
|
528
|
+
{
|
|
529
|
+
return nativeColor(static_cast<int>((rrggbbaa >> 24) & 0xFFu),
|
|
530
|
+
static_cast<int>((rrggbbaa >> 16) & 0xFFu),
|
|
531
|
+
static_cast<int>((rrggbbaa >> 8) & 0xFFu),
|
|
532
|
+
static_cast<int>(rrggbbaa & 0xFFu));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
constexpr NativeColor nativeColorFromRrggbbaa(std::uint32_t rrggbbaa)
|
|
536
|
+
{
|
|
537
|
+
return NativeColor(nativeFromRrggbbaa(rrggbbaa));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// PRE-panel-swap authoring form, for the style pipeline that applies the panel
|
|
541
|
+
// byte-swap downstream (StyleValues::pixelFromStyleValue). On 16-bit panels this
|
|
542
|
+
// is the RAW (unswapped) RGB565 — the swap happens once on write; on full-colour
|
|
543
|
+
// boards there is no panel concept, so it equals the final native pixel.
|
|
544
|
+
constexpr native_t nativeStyleValueFromRgba8888(std::uint32_t rgba)
|
|
545
|
+
{
|
|
546
|
+
#if GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888
|
|
547
|
+
return rgba;
|
|
548
|
+
#elif GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888
|
|
549
|
+
return rgba8888ToArgb8888(rgba);
|
|
550
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY4
|
|
551
|
+
// GRAY4 has no panel byte-swap, so — like the full-colour boards — the style
|
|
552
|
+
// value IS the final native pixel (a 0..15 gray level). pixelFromStyleValue is
|
|
553
|
+
// therefore identity for GRAY4 (it must NOT re-run gray4FromRgb565, or the gray
|
|
554
|
+
// value gets re-read as an RGB565 and white collapses to black).
|
|
555
|
+
return gray4FromRgb888(static_cast<int>(rgba & 0xFFu),
|
|
556
|
+
static_cast<int>((rgba >> 8) & 0xFFu),
|
|
557
|
+
static_cast<int>((rgba >> 16) & 0xFFu));
|
|
558
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
559
|
+
// Same for GRAY2: no panel byte-swap, so the style value IS the final native
|
|
560
|
+
// pixel (a 0..3 gray level) and pixelFromStyleValue stays identity.
|
|
561
|
+
return gray2FromRgb888(static_cast<int>(rgba & 0xFFu),
|
|
562
|
+
static_cast<int>((rgba >> 8) & 0xFFu),
|
|
563
|
+
static_cast<int>((rgba >> 16) & 0xFFu));
|
|
564
|
+
#else
|
|
565
|
+
return rgb565FromRgb888(static_cast<int>(rgba & 0xFFu),
|
|
566
|
+
static_cast<int>((rgba >> 8) & 0xFFu),
|
|
567
|
+
static_cast<int>((rgba >> 16) & 0xFFu));
|
|
568
|
+
#endif
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
constexpr native_t nativeStyleValue(int r, int g, int b, int a = 255)
|
|
572
|
+
{
|
|
573
|
+
return nativeStyleValueFromRgba8888(packRgba8888(r, g, b, a));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
inline native_t blendNative(native_t fg, native_t bg, int alpha)
|
|
577
|
+
{
|
|
578
|
+
#if GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888
|
|
579
|
+
return blend8888(fg, bg, alpha);
|
|
580
|
+
#elif GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888
|
|
581
|
+
return blendArgb8888(fg, bg, alpha);
|
|
582
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY4
|
|
583
|
+
return gray4Blend(fg, bg, alpha);
|
|
584
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
585
|
+
return gray2Blend(fg, bg, alpha);
|
|
586
|
+
#else
|
|
587
|
+
return blend(fg, bg, alpha);
|
|
588
|
+
#endif
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Unpack a native pixel to full 8-bit channels. On RGB565 targets the 5/6-bit
|
|
592
|
+
// channels are expanded to 8-bit (alpha is always opaque). Used by paths that
|
|
593
|
+
// must do channel math on the source — e.g. bilinear image scaling.
|
|
594
|
+
inline void unpackNative8(native_t pixel, int *r, int *g, int *b, int *a)
|
|
595
|
+
{
|
|
596
|
+
#if GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888
|
|
597
|
+
unpackRgba8888(pixel, r, g, b, a);
|
|
598
|
+
#elif GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888
|
|
599
|
+
unpackArgb8888(pixel, r, g, b, a);
|
|
600
|
+
#else
|
|
601
|
+
#if GEA_PIXEL_FORMAT_IS_GRAY4
|
|
602
|
+
const int y = gray4Expand8(pixel);
|
|
603
|
+
*r = y; *g = y; *b = y; *a = 255;
|
|
604
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
605
|
+
const int y = gray2Expand8(pixel);
|
|
606
|
+
*r = y; *g = y; *b = y; *a = 255;
|
|
607
|
+
#else
|
|
608
|
+
int r5 = 0, g6 = 0, b5 = 0;
|
|
609
|
+
unpackRgb565(pixel, &r5, &g6, &b5);
|
|
610
|
+
*r = detail::kExp5.v[r5];
|
|
611
|
+
*g = detail::kExp6.v[g6];
|
|
612
|
+
*b = detail::kExp5.v[b5];
|
|
613
|
+
*a = 255;
|
|
614
|
+
#endif
|
|
615
|
+
#endif
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Pack full 8-bit channels into a native pixel (quantizes to 5/6-bit on RGB565).
|
|
619
|
+
inline native_t packNative8(int r, int g, int b, int a = 255)
|
|
620
|
+
{
|
|
621
|
+
#if GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888
|
|
622
|
+
return packRgba8888(r, g, b, a);
|
|
623
|
+
#elif GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888
|
|
624
|
+
return packArgb8888(r, g, b, a);
|
|
625
|
+
#else
|
|
626
|
+
(void)a;
|
|
627
|
+
#if GEA_PIXEL_FORMAT_IS_GRAY4
|
|
628
|
+
return gray4FromRgb888(r & 0xFF, g & 0xFF, b & 0xFF);
|
|
629
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
630
|
+
return gray2FromRgb888(r & 0xFF, g & 0xFF, b & 0xFF);
|
|
631
|
+
#else
|
|
632
|
+
return packRgb565Components(detail::kQuant5.v[r & 0xFF], detail::kQuant6.v[g & 0xFF], detail::kQuant5.v[b & 0xFF]);
|
|
633
|
+
#endif
|
|
634
|
+
#endif
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Downconvert a native framebuffer pixel back to an RGB565 colour. Identity on
|
|
638
|
+
// RGB565 targets (compiles away). Used by the few RGB565-internal scratch
|
|
639
|
+
// subsystems (filter-blur layers, recolor) that read framebuffer pixels into a
|
|
640
|
+
// 16-bit working buffer; on RGBA8888 targets those subsystems are inactive but
|
|
641
|
+
// must still type-check.
|
|
642
|
+
inline std::uint16_t fromNative(native_t pixel)
|
|
643
|
+
{
|
|
644
|
+
#if GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_RGBA8888
|
|
645
|
+
int r = 0, g = 0, b = 0, a = 0;
|
|
646
|
+
unpackRgba8888(pixel, &r, &g, &b, &a);
|
|
647
|
+
return packRgb565Components(detail::kQuant5.v[r & 0xFF], detail::kQuant6.v[g & 0xFF], detail::kQuant5.v[b & 0xFF]);
|
|
648
|
+
#elif GEA_EMBEDDED_PIXEL_FORMAT == GEA_PIXEL_ARGB8888
|
|
649
|
+
int r = 0, g = 0, b = 0, a = 0;
|
|
650
|
+
unpackArgb8888(pixel, &r, &g, &b, &a);
|
|
651
|
+
return packRgb565Components(detail::kQuant5.v[r & 0xFF], detail::kQuant6.v[g & 0xFF], detail::kQuant5.v[b & 0xFF]);
|
|
652
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY4
|
|
653
|
+
return gray4ToRgb565(pixel);
|
|
654
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY2
|
|
655
|
+
return gray2ToRgb565(pixel);
|
|
656
|
+
#else
|
|
657
|
+
return pixel;
|
|
658
|
+
#endif
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Row copies across the RGB565-scratch <-> native-framebuffer boundary. Both
|
|
662
|
+
// compile to a plain std::memcpy on RGB565 targets (native_t == std::uint16_t),
|
|
663
|
+
// byte-identical to the original framebuffer code; on RGBA8888 they convert
|
|
664
|
+
// per pixel (inactive path).
|
|
665
|
+
inline void copyNativeToRgb565(std::uint16_t *dst, const native_t *src, int count)
|
|
666
|
+
{
|
|
667
|
+
if (count <= 0) return;
|
|
668
|
+
#if GEA_PIXEL_FORMAT_IS_8888 || GEA_PIXEL_FORMAT_IS_GRAY
|
|
669
|
+
for (int i = 0; i < count; i++) dst[i] = fromNative(src[i]);
|
|
670
|
+
#else
|
|
671
|
+
std::memcpy(dst, src, static_cast<std::size_t>(count) * sizeof(std::uint16_t));
|
|
672
|
+
#endif
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
inline void copyRgb565ToNative(native_t *dst, const std::uint16_t *src, int count)
|
|
676
|
+
{
|
|
677
|
+
if (count <= 0) return;
|
|
678
|
+
#if GEA_PIXEL_FORMAT_IS_8888 || GEA_PIXEL_FORMAT_IS_GRAY
|
|
679
|
+
for (int i = 0; i < count; i++) dst[i] = toNative(src[i]);
|
|
680
|
+
#else
|
|
681
|
+
std::memcpy(dst, src, static_cast<std::size_t>(count) * sizeof(std::uint16_t));
|
|
682
|
+
#endif
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Fill `count` native pixels with a native colour. Keeps the RGB565 fast path
|
|
686
|
+
// (memset for black, uint32-pair fill) on 16-bit targets.
|
|
687
|
+
#if GEA_PIXEL_PIE_FILL16 && !GEA_PIXEL_FORMAT_IS_8888
|
|
688
|
+
// PIE span fill: 8 RGB565 pixels per EE.VST.128.IP through a zero-overhead
|
|
689
|
+
// loop, vs 2 per store on the portable 32-bit-pair path. The q registers are
|
|
690
|
+
// not compiler-allocated (q0 is free to clobber), and the IDF FreeRTOS
|
|
691
|
+
// xtensa port lazily context-switches PIE state like the FPU, so this is
|
|
692
|
+
// task-safe. Scalar head to reach 16-byte alignment (EE.VST.128 requires
|
|
693
|
+
// it), vector body, scalar tail.
|
|
694
|
+
inline void fillNativePie(native_t *dst, int count, native_t color)
|
|
695
|
+
{
|
|
696
|
+
while (count > 0 && (reinterpret_cast<std::uintptr_t>(dst) & 0xFu) != 0) {
|
|
697
|
+
*dst++ = color;
|
|
698
|
+
count--;
|
|
699
|
+
}
|
|
700
|
+
const int vec = count >> 3;
|
|
701
|
+
if (vec > 0) {
|
|
702
|
+
alignas(16) native_t pattern[8] = {color, color, color, color, color, color, color, color};
|
|
703
|
+
const native_t *pat = pattern;
|
|
704
|
+
asm volatile(
|
|
705
|
+
"ee.vld.128.ip q0, %[pat], 0\n"
|
|
706
|
+
"loopnez %[vec], 1f\n"
|
|
707
|
+
"ee.vst.128.ip q0, %[dst], 16\n"
|
|
708
|
+
"1:\n"
|
|
709
|
+
: [dst] "+r"(dst), [pat] "+r"(pat)
|
|
710
|
+
: [vec] "r"(vec)
|
|
711
|
+
: "memory");
|
|
712
|
+
count -= vec << 3;
|
|
713
|
+
}
|
|
714
|
+
while (count-- > 0) *dst++ = color;
|
|
715
|
+
}
|
|
716
|
+
#endif
|
|
717
|
+
|
|
718
|
+
inline void fillNative(native_t *dst, int count, native_t color)
|
|
719
|
+
{
|
|
720
|
+
if (count <= 0) return;
|
|
721
|
+
#if GEA_PIXEL_FORMAT_IS_8888
|
|
722
|
+
std::fill_n(dst, count, color);
|
|
723
|
+
#elif GEA_PIXEL_FORMAT_IS_GRAY
|
|
724
|
+
// native_t is one byte, so an UNPACKED gray-value run is a plain memset.
|
|
725
|
+
// (The sub-byte-PACKED framebuffer is filled by Canvas, not here.)
|
|
726
|
+
std::memset(dst, color, static_cast<std::size_t>(count));
|
|
727
|
+
#else
|
|
728
|
+
#if GEA_PIXEL_PIE_FILL16
|
|
729
|
+
// Worth the head/tail bookkeeping from two vector stores up.
|
|
730
|
+
if (count >= 16) {
|
|
731
|
+
fillNativePie(dst, count, color);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
#endif
|
|
735
|
+
if (color == 0) {
|
|
736
|
+
std::memset(dst, 0, static_cast<std::size_t>(count) * sizeof(native_t));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if ((reinterpret_cast<std::uintptr_t>(dst) & 0x2u) != 0) {
|
|
740
|
+
*dst++ = color;
|
|
741
|
+
count--;
|
|
742
|
+
}
|
|
743
|
+
const std::uint32_t pair = static_cast<std::uint32_t>(color) | (static_cast<std::uint32_t>(color) << 16);
|
|
744
|
+
const int pairCount = count / 2;
|
|
745
|
+
std::fill_n(reinterpret_cast<std::uint32_t *>(dst), pairCount, pair);
|
|
746
|
+
if ((count & 1) != 0) dst[pairCount * 2] = color;
|
|
747
|
+
#endif
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ---- Sub-byte packed STORAGE primitives -------------------------------------
|
|
751
|
+
// The framebuffer packs 8/Bits pixels per byte, MSB first — the order both the
|
|
752
|
+
// IT8951 (4bpp) and the SSD1677 (2bpp) read their RAM in. For Bits = 4 that is
|
|
753
|
+
// byte[b] = (px[2b] << 4) | px[2b+1]; for Bits = 2 it is
|
|
754
|
+
// byte[b] = (px[4b] << 6) | (px[4b+1] << 4) | (px[4b+2] << 2) | px[4b+3].
|
|
755
|
+
// `row` points at the first packed byte of a physical row; `x` is a pixel index
|
|
756
|
+
// within that row — always >= 0, since every Canvas caller has already clipped to
|
|
757
|
+
// the framebuffer (the / and % below truncate toward zero, so a negative index
|
|
758
|
+
// would not address the byte a floor-division would).
|
|
759
|
+
//
|
|
760
|
+
// These are pure byte-buffer ops (no format guard) parameterised on the bit
|
|
761
|
+
// depth, so a host test can exercise every depth in one binary; the Canvas
|
|
762
|
+
// framebuffer accessors instantiate exactly one of them, via the `packed` alias
|
|
763
|
+
// below, under GEA_PIXEL_STORAGE_PACKED. Bits is a compile-time constant, so
|
|
764
|
+
// every / and % here folds to a shift or a mask — the 4bpp code generated is the
|
|
765
|
+
// same as the hand-written nibble version it replaces.
|
|
766
|
+
template <int Bits>
|
|
767
|
+
struct PackedPixels {
|
|
768
|
+
static_assert(Bits == 2 || Bits == 4, "packed pixel storage supports 2 or 4 bits per pixel");
|
|
769
|
+
|
|
770
|
+
static constexpr int kBits = Bits;
|
|
771
|
+
static constexpr int kPxPerByte = 8 / Bits;
|
|
772
|
+
static constexpr std::uint8_t kMask = static_cast<std::uint8_t>((1u << Bits) - 1u);
|
|
773
|
+
|
|
774
|
+
static constexpr int byteIndex(int x) { return x / kPxPerByte; }
|
|
775
|
+
// MSB-first: pixel 0 of a byte occupies the TOP `Bits` bits.
|
|
776
|
+
static constexpr int shiftFor(int x) { return 8 - Bits - (x % kPxPerByte) * Bits; }
|
|
777
|
+
// Bytes needed to hold a row of `widthPx` pixels (partial trailing byte counts).
|
|
778
|
+
static constexpr int rowBytes(int widthPx) { return (widthPx + kPxPerByte - 1) / kPxPerByte; }
|
|
779
|
+
// A byte holding kPxPerByte copies of `v` — the value a solid span memsets with.
|
|
780
|
+
static constexpr std::uint8_t replicate(std::uint8_t v)
|
|
781
|
+
{
|
|
782
|
+
std::uint8_t out = 0;
|
|
783
|
+
for (int i = 0; i < kPxPerByte; i++) out = static_cast<std::uint8_t>((out << Bits) | (v & kMask));
|
|
784
|
+
return out;
|
|
785
|
+
}
|
|
786
|
+
// Blend in the native value domain — see gray4Blend / gray2Blend.
|
|
787
|
+
static std::uint8_t blendValue(std::uint8_t fg, std::uint8_t bg, int alpha)
|
|
788
|
+
{
|
|
789
|
+
if constexpr (Bits == 4) return gray4Blend(fg, bg, alpha);
|
|
790
|
+
else return gray2Blend(fg, bg, alpha);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
static std::uint8_t get(const std::uint8_t *row, int x)
|
|
794
|
+
{
|
|
795
|
+
return static_cast<std::uint8_t>((row[byteIndex(x)] >> shiftFor(x)) & kMask);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
static void set(std::uint8_t *row, int x, std::uint8_t v)
|
|
799
|
+
{
|
|
800
|
+
std::uint8_t &byte = row[byteIndex(x)];
|
|
801
|
+
const int shift = shiftFor(x);
|
|
802
|
+
byte = static_cast<std::uint8_t>((byte & ~(kMask << shift)) | ((v & kMask) << shift));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Fill a run of `count` pixels starting at pixel x0 with gray value v.
|
|
806
|
+
static void fillSpan(std::uint8_t *row, int x0, int count, std::uint8_t v)
|
|
807
|
+
{
|
|
808
|
+
if (count <= 0) return;
|
|
809
|
+
v &= kMask;
|
|
810
|
+
int x = x0;
|
|
811
|
+
int n = count;
|
|
812
|
+
// Leading pixels sharing a byte with pixels BEFORE the span: RMW so their
|
|
813
|
+
// neighbours already in that byte survive.
|
|
814
|
+
while (n > 0 && (x % kPxPerByte) != 0) { set(row, x, v); x++; n--; }
|
|
815
|
+
// Aligned middle: whole bytes hold kPxPerByte pixels of the same value.
|
|
816
|
+
const std::uint8_t rep = replicate(v);
|
|
817
|
+
const int bytes = n / kPxPerByte;
|
|
818
|
+
if (bytes > 0) std::memset(row + byteIndex(x), rep, static_cast<std::size_t>(bytes));
|
|
819
|
+
x += bytes * kPxPerByte;
|
|
820
|
+
n -= bytes * kPxPerByte;
|
|
821
|
+
// Trailing partial byte.
|
|
822
|
+
while (n > 0) { set(row, x, v); x++; n--; }
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Write `count` UNPACKED gray values (one level per byte) into packed pixels.
|
|
826
|
+
static void writeUnpacked(std::uint8_t *row, int x0, const std::uint8_t *src, int count)
|
|
827
|
+
{
|
|
828
|
+
for (int i = 0; i < count; i++) set(row, x0 + i, src[i]);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Read `count` packed pixels into an UNPACKED buffer (one value per byte).
|
|
832
|
+
static void readUnpacked(const std::uint8_t *row, int x0, std::uint8_t *dst, int count)
|
|
833
|
+
{
|
|
834
|
+
for (int i = 0; i < count; i++) dst[i] = get(row, x0 + i);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Blend one pixel: dst = blendValue(fg, dst, alpha).
|
|
838
|
+
static void blendPixel(std::uint8_t *row, int x, std::uint8_t fg, int alpha)
|
|
839
|
+
{
|
|
840
|
+
if (alpha <= 0) return;
|
|
841
|
+
if (alpha >= 255) { set(row, x, fg); return; }
|
|
842
|
+
set(row, x, blendValue(fg, get(row, x), alpha));
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Copy `count` pixels between two packed rows (e.g. scrollRect). Falls back to
|
|
846
|
+
// per-pixel; both endpoints may be unaligned. Overlap-safe, including when
|
|
847
|
+
// dstRow and srcRow are the SAME row.
|
|
848
|
+
static void copyPacked(std::uint8_t *dstRow, int dstX, const std::uint8_t *srcRow, int srcX, int count)
|
|
849
|
+
{
|
|
850
|
+
if (count <= 0) return;
|
|
851
|
+
|
|
852
|
+
// Same phase within the byte ⇒ the aligned middle is a byte-wise memmove.
|
|
853
|
+
if ((dstX % kPxPerByte) == (srcX % kPxPerByte)) {
|
|
854
|
+
// Split into `lead` pixels up to the first dst byte boundary, `mid`
|
|
855
|
+
// whole bytes, then the trailing remainder.
|
|
856
|
+
int lead = 0;
|
|
857
|
+
while (lead < count && ((dstX + lead) % kPxPerByte) != 0) lead++;
|
|
858
|
+
const int mid = (count - lead) / kPxPerByte;
|
|
859
|
+
const int tail = lead + mid * kPxPerByte;
|
|
860
|
+
|
|
861
|
+
// ORDER MATTERS when the rows alias and the ranges overlap. That case
|
|
862
|
+
// is live, not theoretical: Canvas::scrollRect passes dstRow ==
|
|
863
|
+
// srcRow for a horizontal pan (dy == 0, dx != 0), reached from
|
|
864
|
+
// tree_render.cpp's Display::scrollRect(vx, vy, vw, vh, panDx, 0).
|
|
865
|
+
//
|
|
866
|
+
// memmove is itself overlap-safe, but the partial-byte fixups around
|
|
867
|
+
// it are not — they read whole pixels out of bytes the memmove may
|
|
868
|
+
// already have rewritten, and they write bytes the memmove may still
|
|
869
|
+
// need to read. Doing lead-then-mid-then-tail unconditionally (as
|
|
870
|
+
// this did before) corrupts a rightward overlapping move on both
|
|
871
|
+
// counts.
|
|
872
|
+
//
|
|
873
|
+
// The fix is just to work from the far end of the move direction
|
|
874
|
+
// inward. It is sound because same-phase forces |dstX - srcX| to be a
|
|
875
|
+
// whole number of bytes, so the partial work on the far side is
|
|
876
|
+
// always at least one byte clear of the memmove's range. Keeping the
|
|
877
|
+
// memmove means scrolling does not lose its fast path.
|
|
878
|
+
if (dstX > srcX) {
|
|
879
|
+
for (int i = count - 1; i >= tail; i--) set(dstRow, dstX + i, get(srcRow, srcX + i));
|
|
880
|
+
if (mid > 0)
|
|
881
|
+
std::memmove(dstRow + byteIndex(dstX + lead), srcRow + byteIndex(srcX + lead),
|
|
882
|
+
static_cast<std::size_t>(mid));
|
|
883
|
+
for (int i = lead - 1; i >= 0; i--) set(dstRow, dstX + i, get(srcRow, srcX + i));
|
|
884
|
+
} else {
|
|
885
|
+
for (int i = 0; i < lead; i++) set(dstRow, dstX + i, get(srcRow, srcX + i));
|
|
886
|
+
if (mid > 0)
|
|
887
|
+
std::memmove(dstRow + byteIndex(dstX + lead), srcRow + byteIndex(srcX + lead),
|
|
888
|
+
static_cast<std::size_t>(mid));
|
|
889
|
+
for (int i = tail; i < count; i++) set(dstRow, dstX + i, get(srcRow, srcX + i));
|
|
890
|
+
}
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
// Mismatched phase: shift pixel by pixel. Iterate in a safe direction.
|
|
894
|
+
if (dstX <= srcX)
|
|
895
|
+
for (int i = 0; i < count; i++) set(dstRow, dstX + i, get(srcRow, srcX + i));
|
|
896
|
+
else
|
|
897
|
+
for (int i = count - 1; i >= 0; i--) set(dstRow, dstX + i, get(srcRow, srcX + i));
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
#if GEA_PIXEL_STORAGE_PACKED
|
|
902
|
+
// THIS target's packed-storage op set. Canvas writes `pixel::packed::set(...)`
|
|
903
|
+
// and gets the right depth for the board it is being compiled for.
|
|
904
|
+
using packed = PackedPixels<GEA_PIXEL_STORAGE_PACKED_BITS>;
|
|
905
|
+
#endif
|
|
906
|
+
|
|
907
|
+
} // namespace gea::framework::graphics::pixel
|