16#include <goodisplay/gdey029T94.h>
28constexpr int16_t HEADER_LINE_OFF = 6;
30constexpr int16_t TRI_OX = 10;
31constexpr int16_t TRI_OY = 20;
32constexpr int16_t R_X = 50, R_Y = 0;
33constexpr int16_t G_X = 0, G_Y = 60;
34constexpr int16_t B_X = 100, B_Y = 60;
36constexpr int16_t TEXT_X = 160;
37constexpr int16_t
TEXT_Y = 22;
39constexpr int16_t SLIDER_X = 160;
40constexpr int16_t SLIDER_Y = 80;
41constexpr int16_t SLIDER_W = 125;
42constexpr int16_t SLIDER_H = 8;
44constexpr int16_t
NAME_Y = 98;
46constexpr uint8_t kBayer4[4][4] = {
48 { 207, 79, 239, 111 },
53inline int32_t sign(int16_t x1, int16_t y1,
54 int16_t x2, int16_t y2,
55 int16_t x3, int16_t y3) {
56 return (x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3);
59inline uint8_t lum(uint8_t r, uint8_t g, uint8_t b) {
60 return static_cast<uint8_t
>((77u * r + 150u * g + 29u * b) >> 8);
63const char* colorName(uint8_t r, uint8_t g, uint8_t b) {
64 uint8_t mx = std::max({r, g, b});
65 uint8_t mn = std::min({r, g, b});
66 if (mx < 24)
return "Black";
68 if (mx < 90)
return "Dark gray";
69 if (mx < 200)
return "Gray";
72 int16_t delta = mx - mn;
74 if (mx == r) hue = ((int16_t)(g - b) * 60) / delta;
75 else if (mx == g) hue = 120 + ((int16_t)(b - r) * 60) / delta;
76 else hue = 240 + ((int16_t)(r - g) * 60) / delta;
77 if (hue < 0) hue += 360;
79 if (hue < 15)
return dark ?
"Brown" :
"Red";
80 if (hue < 35)
return dark ?
"Brown" :
"Orange";
81 if (hue < 55)
return dark ?
"Olive" :
"Yellow";
82 if (hue < 80)
return dark ?
"Olive" :
"Lime";
83 if (hue < 150)
return dark ?
"Dark green" :
"Green";
84 if (hue < 180)
return dark ?
"Teal" :
"Cyan";
85 if (hue < 220)
return dark ?
"Teal" :
"Sky blue";
86 if (hue < 260)
return dark ?
"Navy" :
"Blue";
87 if (hue < 290)
return dark ?
"Purple" :
"Violet";
88 if (hue < 330)
return dark ?
"Magenta" :
"Pink";
89 return dark ?
"Brown" :
"Red";
95 value_ = std::max({r, g, b,
static_cast<uint8_t
>(1)});
96 cursorX_ = (R_X + G_X + B_X) / 3;
97 cursorY_ = (R_Y + G_Y + B_Y) / 3;
106void ColorPickerView::barycentric(int16_t px, int16_t py,
107 int32_t& aR, int32_t& aG, int32_t& aB)
const {
108 int32_t d = sign(R_X, R_Y, G_X, G_Y, B_X, B_Y);
109 if (d == 0) { aR = aG = aB = 0;
return; }
110 aR = sign(px, py, G_X, G_Y, B_X, B_Y) * 255 / d;
111 aG = sign(R_X, R_Y, px, py, B_X, B_Y) * 255 / d;
112 aB = sign(R_X, R_Y, G_X, G_Y, px, py) * 255 / d;
115void ColorPickerView::clampInsideTriangle() {
117 int16_t bestX = cursorX_;
118 int16_t bestY = cursorY_;
119 barycentric(cursorX_, cursorY_, aR, aG, aB);
120 if (aR < 0 || aG < 0 || aB < 0) {
121 int32_t total = aR + aG + aB;
123 int32_t cx = (R_X * std::max<int32_t>(aR, 0) +
124 G_X * std::max<int32_t>(aG, 0) +
125 B_X * std::max<int32_t>(aB, 0)) /
126 std::max<int32_t>(1, std::max<int32_t>(aR, 0) +
127 std::max<int32_t>(aG, 0) +
128 std::max<int32_t>(aB, 0));
129 int32_t cy = (R_Y * std::max<int32_t>(aR, 0) +
130 G_Y * std::max<int32_t>(aG, 0) +
131 B_Y * std::max<int32_t>(aB, 0)) /
132 std::max<int32_t>(1, std::max<int32_t>(aR, 0) +
133 std::max<int32_t>(aG, 0) +
134 std::max<int32_t>(aB, 0));
135 bestX =
static_cast<int16_t
>(cx);
136 bestY =
static_cast<int16_t
>(cy);
143void ColorPickerView::setFromRGB(uint8_t r, uint8_t g, uint8_t b) {
144 int32_t sum =
static_cast<int32_t
>(r) + g + b;
146 cursorX_ = (R_X + G_X + B_X) / 3;
147 cursorY_ = (R_Y + G_Y + B_Y) / 3;
150 int32_t cx = (r * R_X + g * G_X + b * B_X) / sum;
151 int32_t cy = (r * R_Y + g * G_Y + b * B_Y) / sum;
152 cursorX_ =
static_cast<int16_t
>(cx);
153 cursorY_ =
static_cast<int16_t
>(cy);
156void ColorPickerView::moveCursor(int16_t dx, int16_t dy) {
157 int16_t nx = cursorX_ + dx;
158 int16_t ny = cursorY_ + dy;
160 int16_t saved_x = cursorX_;
161 int16_t saved_y = cursorY_;
164 barycentric(cursorX_, cursorY_, aR, aG, aB);
165 if (aR < 0 || aG < 0 || aB < 0) {
173void ColorPickerView::adjustValue(int8_t delta) {
174 int16_t nv = value_ + delta;
175 nv = std::clamp<int16_t>(nv, 1, 255);
177 value_ =
static_cast<uint8_t
>(nv);
182void ColorPickerView::rawColor(uint8_t& r, uint8_t& g, uint8_t& b)
const {
184 barycentric(cursorX_, cursorY_, aR, aG, aB);
185 aR = std::max<int32_t>(0, aR);
186 aG = std::max<int32_t>(0, aG);
187 aB = std::max<int32_t>(0, aB);
188 int32_t sum = aR + aG + aB;
189 if (sum == 0) { r = g = b = 0;
return; }
190 r =
static_cast<uint8_t
>(aR * 255 / sum);
191 g =
static_cast<uint8_t
>(aG * 255 / sum);
192 b =
static_cast<uint8_t
>(aB * 255 / sum);
197 r =
static_cast<uint8_t
>((
static_cast<uint16_t
>(r) * value_) / 255);
198 g =
static_cast<uint8_t
>((
static_cast<uint16_t
>(g) * value_) / 255);
199 b =
static_cast<uint8_t
>((
static_cast<uint16_t
>(b) * value_) / 255);
202void ColorPickerView::drawDitheredBox(Gdey029T94* gfx, int16_t x, int16_t y,
203 int16_t w, int16_t h, uint8_t luminance)
const {
205 for (int16_t row = 0; row < h; ++row) {
206 for (int16_t col = 0; col < w; ++col) {
207 uint8_t threshold = kBayer4[row & 3][col & 3];
208 if (luminance < threshold) {
209 gfx->drawPixel(x + col, y + row, EPD_BLACK);
215void ColorPickerView::drawTriangle(Gdey029T94* gfx, int16_t ox, int16_t oy)
const {
216 gfx->drawLine(ox + R_X, oy + R_Y, ox + G_X, oy + G_Y, EPD_BLACK);
217 gfx->drawLine(ox + G_X, oy + G_Y, ox + B_X, oy + B_Y, EPD_BLACK);
218 gfx->drawLine(ox + B_X, oy + B_Y, ox + R_X, oy + R_Y, EPD_BLACK);
253 int16_t dx = 0, dy = 0;
256 if (kp->isKeyPressed(Key::KEY_2)) { dy -= 3; any =
true; }
257 if (kp->isKeyPressed(Key::KEY_8)) { dy += 3; any =
true; }
258 if (kp->isKeyPressed(Key::KEY_4)) { dx -= 3; any =
true; }
259 if (kp->isKeyPressed(Key::KEY_6)) { dx += 3; any =
true; }
260 if (kp->isKeyPressed(Key::KEY_7)) { dv -= 10; any =
true; }
261 if (kp->isKeyPressed(Key::KEY_9)) { dv += 10; any =
true; }
262 if (!any) { repeatStartMs_ = 0;
return; }
263 if (repeatStartMs_ == 0) {
264 repeatStartMs_ = nowMs;
265 lastRepeatMs_ = nowMs;
270 lastRepeatMs_ = nowMs;
271 if (dx || dy) moveCursor(dx, dy);
272 if (dv) adjustValue(dv);
277 return "[2/4/6/8] mix [7/9] intensity [Y] OK";
283 auto* gfx =
static_cast<Gdey029T94*
>(
display->getNativeHandle());
285 const uint16_t width =
display->getWidth();
286 const uint16_t height =
display->getHeight();
289 gfx->fillScreen(EPD_WHITE);
294 gfx->setTextColor(EPD_BLACK);
296 gfx->setTextWrap(
false);
302 int32_t det = sign(R_X, R_Y, G_X, G_Y, B_X, B_Y);
303 int16_t bx0 = std::min({R_X, G_X, B_X});
304 int16_t bx1 = std::max({R_X, G_X, B_X});
305 int16_t by0 = std::min({R_Y, G_Y, B_Y});
306 int16_t by1 = std::max({R_Y, G_Y, B_Y});
307 for (int16_t py = by0; py <= by1; ++py) {
308 for (int16_t px = bx0; px <= bx1; ++px) {
309 int32_t s1 = sign(px, py, G_X, G_Y, B_X, B_Y);
310 int32_t s2 = sign(R_X, R_Y, px, py, B_X, B_Y);
311 int32_t s3 = sign(R_X, R_Y, G_X, G_Y, px, py);
312 bool inside = (s1 >= 0 && s2 >= 0 && s3 >= 0) ||
313 (s1 <= 0 && s2 <= 0 && s3 <= 0);
314 if (!inside)
continue;
315 if (kBayer4[py & 3][px & 3] < 16) {
316 gfx->drawPixel(ox + px, oy + py, EPD_BLACK);
322 drawTriangle(gfx, ox, oy);
324 gfx->setCursor(ox + R_X - 3, oy + R_Y - 9);
326 gfx->setCursor(ox + G_X - 6, oy + G_Y + 2);
328 gfx->setCursor(ox + B_X + 2, oy + B_Y + 2);
331 int16_t cx = ox + cursorX_;
332 int16_t cy = oy + cursorY_;
333 gfx->fillRect(cx - 2, cy - 2, 5, 5, EPD_BLACK);
334 gfx->drawPixel(cx, cy, EPD_WHITE);
341 std::snprintf(buf,
sizeof(buf),
"R: %3u", r);
342 gfx->setCursor(TEXT_X,
TEXT_Y);
344 std::snprintf(buf,
sizeof(buf),
"G: %3u", g);
345 gfx->setCursor(TEXT_X,
TEXT_Y + 14);
347 std::snprintf(buf,
sizeof(buf),
"B: %3u", b);
348 gfx->setCursor(TEXT_X,
TEXT_Y + 28);
351 int16_t sy = SLIDER_Y;
352 gfx->setCursor(SLIDER_X, sy - 10);
353 std::snprintf(buf,
sizeof(buf),
"Intensity: %3u", value_);
355 gfx->drawRect(SLIDER_X, sy, SLIDER_W, SLIDER_H, EPD_BLACK);
356 int16_t fill =
static_cast<int16_t
>((
static_cast<uint32_t
>(value_) * (SLIDER_W - 4)) / 255);
357 gfx->fillRect(SLIDER_X + 2, sy + 2, fill, SLIDER_H - 4, EPD_BLACK);
359 const char*
name = colorName(r, g, b);
363 gfx->getTextBounds(
name, 0, 0, &bx, &by, &bw, &bh);
364 gfx->setCursor((width - bw) / 2,
NAME_Y);
static constexpr uint32_t REPEAT_PERIOD_MS
static constexpr uint32_t REPEAT_INITIAL_MS
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
static constexpr int NAME_Y
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
CDC Log: logging over TinyUSB CDC and UART.
void onTick(uint32_t nowMs) override
void currentColor(uint8_t &r, uint8_t &g, uint8_t &b) const
Read the currently chosen color (post brightness scaling).
void init(uint8_t r, uint8_t g, uint8_t b)
Initialize the picker with a starting color.
void render(bool partial) override
const char * getFooterHint() const override
InputResult onKey(char key) override
const char * customFooter_
static ViewStack & instance()
Returns singleton view-stack instance.
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
IKeypad * getKeypadInstance()
Returns the singleton keypad service instance.
constexpr int FOOTER_HEIGHT
Footer bar height in pixels (used by drawFooterBar).
void drawFooterBar(Gdey029T94 *gfx, uint16_t width, uint16_t height, const char *prefix, const char *hint, bool force=false)
Draws footer bar with optional prefix and hint text.
void printText(Gdey029T94 *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
Centralized key-code constants for cdc_views.
static constexpr int TEXT_Y
static constexpr char KEY_NO
Cancel / Back / Backspace.
static constexpr char KEY_YES
Confirm / OK / Save.