libepaper 2.0.0
A C++23 library for controlling Waveshare e-paper displays on Raspberry Pi, featuring transparent sleep/wake management and a fluent builder API.
Loading...
Searching...
No Matches
pixel_codec.hpp
Go to the documentation of this file.
1#pragma once
2
49#include "epaper/color/color.hpp" // For RGB struct? If RGB is used.
50#include "epaper/core/types.hpp"
52#include <array>
53#include <cstddef>
54#include <cstdint>
55#include <span>
56
57namespace epaper {
58
59// ============================================================================
60// Constants for bit manipulation
61// ============================================================================
62
63namespace pixel_constants {
64
67constexpr std::uint8_t BW_PIXELS_PER_BYTE = 8;
68constexpr std::uint8_t BW_MSB_MASK = 0x80;
70
73constexpr std::uint8_t GRAY_PIXELS_PER_BYTE = 4;
74constexpr std::uint8_t GRAY_BITS_PER_PIXEL = 2;
75constexpr std::uint8_t GRAY_PIXEL_MASK = 0xC0;
77
80constexpr std::uint8_t SPECTRA6_BITS_PER_PIXEL = 3;
81constexpr std::uint8_t SPECTRA6_COLOR_MASK = 0x07;
83
86constexpr std::uint8_t GRAY_THRESHOLD_WHITE = 192;
87constexpr std::uint8_t GRAY_THRESHOLD_LIGHT = 128;
88constexpr std::uint8_t GRAY_THRESHOLD_DARK = 64;
90
91} // namespace pixel_constants
92
93// ============================================================================
94// Spectra6 Color Mapping
95// ============================================================================
96
98constexpr std::array<Color, 8> SPECTRA6_VALUE_TO_COLOR = {{
99 Color::Black, // 0
100 Color::White, // 1
101 Color::Red, // 2
102 Color::Yellow, // 3
103 Color::Blue, // 4
104 Color::Green, // 5
105 Color::Black, // 6 (undefined, fallback)
106 Color::Black // 7 (undefined, fallback)
107}};
108
115[[nodiscard]] constexpr auto spectra6_color_to_value(Color color) noexcept -> std::uint8_t {
116 switch (color) {
117 case Color::Black:
118 return 0;
119 case Color::White:
120 return 1;
121 case Color::Red:
122 return 2;
123 case Color::Yellow:
124 return 3;
125 case Color::Blue:
126 return 4;
127 case Color::Green:
128 return 5;
129 default:
130 return 0; // Fallback to black
131 }
132}
133
140[[nodiscard]] constexpr auto spectra6_value_to_color(std::uint8_t value) noexcept -> Color {
142}
143
144// ============================================================================
145// Color <-> RGB Conversion
146// ============================================================================
147
154[[nodiscard]] constexpr auto color_to_rgb(Color color) noexcept -> RGB {
155 switch (color) {
156 case Color::White:
157 return {255, 255, 255};
158 case Color::Gray1:
159 return {170, 170, 170};
160 case Color::Gray2:
161 return {85, 85, 85};
162 case Color::Black:
163 return {0, 0, 0};
164 case Color::Red:
165 return {255, 0, 0};
166 case Color::Yellow:
167 return {255, 255, 0};
168 case Color::Blue:
169 return {0, 0, 255};
170 case Color::Green:
171 return {0, 255, 0};
172 }
173 return {0, 0, 0}; // Fallback to black
174}
175
186[[nodiscard]] constexpr auto rgb_to_grayscale(std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept -> std::uint8_t {
187 // Use integer arithmetic to avoid floating point
188 // Formula: (299*R + 587*G + 114*B) / 1000
189 return static_cast<std::uint8_t>((299U * r + 587U * g + 114U * b) / 1000U);
190}
191
202[[nodiscard]] constexpr auto rgb_to_color_bw(std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept -> Color {
203 const auto gray = rgb_to_grayscale(r, g, b);
205}
206
217[[nodiscard]] constexpr auto rgb_to_color_grayscale4(std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept -> Color {
218 const auto gray = rgb_to_grayscale(r, g, b);
220 return Color::White;
221 }
223 return Color::Gray1;
224 }
226 return Color::Gray2;
227 }
228 return Color::Black;
229}
230
240[[nodiscard]] constexpr auto rgb_to_color(DisplayMode mode, std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept
241 -> Color {
242 switch (mode) {
244 return rgb_to_color_bw(r, g, b);
246 return rgb_to_color_grayscale4(r, g, b);
247 default:
248 // For color modes, use grayscale conversion as fallback
249 return rgb_to_color_grayscale4(r, g, b);
250 }
251}
252
253// ============================================================================
254// Framebuffer Position Calculations
255// ============================================================================
256
276[[nodiscard]] constexpr auto calculate_bw_position(std::size_t width, std::size_t x, std::size_t y) noexcept
277 -> std::pair<std::size_t, std::uint8_t> {
278 const auto width_bytes = (width + 7) / 8;
279 const auto byte_index = (x / 8) + (y * width_bytes);
280 const auto bit_offset = static_cast<std::uint8_t>(x % 8);
281 const auto bit_mask = static_cast<std::uint8_t>(pixel_constants::BW_MSB_MASK >> bit_offset);
282 return {byte_index, bit_mask};
283}
284
310[[nodiscard]] constexpr auto calculate_gray_position(std::size_t width, std::size_t x, std::size_t y) noexcept
311 -> std::pair<std::size_t, std::uint8_t> {
312 const auto width_bytes = (width + 3) / 4;
313 const auto byte_index = (x / 4) + (y * width_bytes);
314 const auto pixel_offset = static_cast<std::uint8_t>((x % 4) * 2);
315 return {byte_index, pixel_offset};
316}
317
343[[nodiscard]] constexpr auto calculate_spectra6_position(std::size_t width, std::size_t x, std::size_t y) noexcept
344 -> std::pair<std::size_t, std::size_t> {
345 const std::size_t pixel_index = (y * width) + x;
346 const std::size_t byte_index = (pixel_index * 3) / 8;
347 const std::size_t bit_offset = (pixel_index * 3) % 8;
348 return {byte_index, bit_offset};
349}
350
351// ============================================================================
352// Pixel Getters (Read from buffer)
353// ============================================================================
354
374[[nodiscard]] inline auto get_pixel_bw(std::span<const std::byte> buffer, std::size_t width, std::size_t x,
375 std::size_t y) noexcept -> Color {
376 const auto [byte_index, bit_mask] = calculate_bw_position(width, x, y);
377 if (byte_index >= buffer.size()) {
378 return Color::White;
379 }
380 const auto byte_val = static_cast<std::uint8_t>(buffer[byte_index]);
381 return ((byte_val & bit_mask) != 0) ? Color::White : Color::Black;
382}
383
407[[nodiscard]] inline auto get_pixel_grayscale4(std::span<const std::byte> buffer, std::size_t width, std::size_t x,
408 std::size_t y) noexcept -> Color {
409 const auto [byte_index, pixel_shift] = calculate_gray_position(width, x, y);
410 if (byte_index >= buffer.size()) {
411 return Color::White;
412 }
413 const auto byte_val = static_cast<std::uint8_t>(buffer[byte_index]);
414 const auto gray_level = (byte_val >> (6 - pixel_shift)) & 0x03;
415
416 switch (gray_level) {
417 case 0:
418 return Color::Black;
419 case 1:
420 return Color::Gray2;
421 case 2:
422 return Color::Gray1;
423 case 3:
424 return Color::White;
425 default:
426 return Color::White;
427 }
428}
429
461[[nodiscard]] inline auto get_pixel_bwr_bwy(std::span<const std::byte> buffer, std::size_t width, std::size_t height,
462 std::size_t x, std::size_t y, bool is_bwr) noexcept -> Color {
463 const std::size_t plane_size = (width * height + 7) / 8;
464 const auto [byte_index, bit_mask] = calculate_bw_position(width, x, y);
465
466 if (byte_index >= buffer.size()) {
467 return Color::White;
468 }
469
470 // Check B/W plane (first plane)
471 const auto bw_byte = static_cast<std::uint8_t>(buffer[byte_index]);
472 const bool is_white = (bw_byte & bit_mask) != 0;
473
474 // Check color plane (second plane) if available
475 if (byte_index + plane_size < buffer.size()) {
476 const auto color_byte = static_cast<std::uint8_t>(buffer[byte_index + plane_size]);
477
478 // In e-paper color planes, 0 usually means "Color" (Ink) and 1 means "No Color" (Transparent/White)
479 // This is active-low logic.
480 const bool is_color = (color_byte & bit_mask) == 0;
481
482 if (is_color) {
483 return is_bwr ? Color::Red : Color::Yellow;
484 }
485 }
486
487 return is_white ? Color::White : Color::Black;
488}
489
524[[nodiscard]] inline auto get_pixel_spectra6(std::span<const std::byte> buffer, std::size_t width, std::size_t x,
525 std::size_t y) noexcept -> Color {
526 const auto [byte_index, bit_offset] = calculate_spectra6_position(width, x, y);
527
528 if (byte_index >= buffer.size()) {
529 return Color::White;
530 }
531
532 std::uint8_t color_value = 0;
533
534 if (bit_offset <= 5) {
535 // 3-bit value fits within single byte
536 const auto shift = static_cast<std::uint8_t>(5 - bit_offset);
537 const auto byte_val = static_cast<std::uint8_t>(buffer[byte_index]);
538 color_value = (byte_val >> shift) & pixel_constants::SPECTRA6_COLOR_MASK;
539 } else {
540 // 3-bit value spans two bytes
541 const auto high_bits = static_cast<std::uint8_t>(8 - bit_offset);
542 const auto low_bits = static_cast<std::uint8_t>(3 - high_bits);
543
544 const auto high_byte = static_cast<std::uint8_t>(buffer[byte_index]);
545 color_value = static_cast<std::uint8_t>((high_byte & ((1U << high_bits) - 1)) << low_bits);
546
547 if (byte_index + 1 < buffer.size()) {
548 const auto low_byte = static_cast<std::uint8_t>(buffer[byte_index + 1]);
549 color_value = static_cast<std::uint8_t>(color_value | ((low_byte >> (8 - low_bits)) & ((1U << low_bits) - 1)));
550 }
551 }
552
553 return spectra6_value_to_color(color_value);
554}
555
569[[nodiscard]] inline auto get_pixel_from_buffer(DisplayMode mode, std::span<const std::byte> buffer, std::size_t width,
570 std::size_t height, std::size_t x, std::size_t y) noexcept -> Color {
571 switch (mode) {
573 return get_pixel_bw(buffer, width, x, y);
575 return get_pixel_grayscale4(buffer, width, x, y);
576 case DisplayMode::BWR:
577 return get_pixel_bwr_bwy(buffer, width, height, x, y, true);
578 case DisplayMode::BWY:
579 return get_pixel_bwr_bwy(buffer, width, height, x, y, false);
581 return get_pixel_spectra6(buffer, width, x, y);
582 }
583 return Color::White;
584}
585
586// ============================================================================
587// Pixel Setters (Write to buffer)
588// ============================================================================
589
614inline auto set_pixel_bw(std::span<std::byte> buffer, std::size_t width, std::size_t x, std::size_t y,
615 Color color) noexcept -> void {
616 const auto [byte_index, bit_mask] = calculate_bw_position(width, x, y);
617 if (byte_index >= buffer.size()) {
618 return;
619 }
620
621 auto &byte_val = buffer[byte_index];
622 if (color == Color::White) {
623 byte_val = static_cast<std::byte>(static_cast<std::uint8_t>(byte_val) | bit_mask);
624 } else {
625 byte_val = static_cast<std::byte>(static_cast<std::uint8_t>(byte_val) & ~bit_mask);
626 }
627}
628
657inline auto set_pixel_grayscale4(std::span<std::byte> buffer, std::size_t width, std::size_t x, std::size_t y,
658 Color color) noexcept -> void {
659 const auto [byte_index, pixel_shift] = calculate_gray_position(width, x, y);
660 if (byte_index >= buffer.size()) {
661 return;
662 }
663
664 auto &byte_val = buffer[byte_index];
665 const auto mask = static_cast<std::uint8_t>(pixel_constants::GRAY_PIXEL_MASK >> pixel_shift);
666 const std::uint8_t color_bits = (static_cast<std::uint8_t>(color) & pixel_constants::GRAY_PIXEL_MASK) >> pixel_shift;
667
668 // Clear the 2 bits and set new color
669 byte_val = static_cast<std::byte>((static_cast<std::uint8_t>(byte_val) & ~mask) | (color_bits & mask));
670}
671
706inline auto set_pixel_bwr_bwy(std::span<std::byte> buffer, std::size_t width, std::size_t height, std::size_t x,
707 std::size_t y, Color color, bool is_bwr) noexcept -> void {
708 const std::size_t plane_size = (width * height + 7) / 8;
709 const auto [byte_index, bit_mask] = calculate_bw_position(width, x, y);
710
711 if (byte_index >= buffer.size() || byte_index + plane_size >= buffer.size()) {
712 return;
713 }
714
715 // Determine plane values based on color
716 bool is_bw_white = false;
717 bool is_color = false;
718
719 const auto target_color = is_bwr ? Color::Red : Color::Yellow;
720 if (color == Color::White) {
721 is_bw_white = true;
722 is_color = false;
723 } else if (color == target_color) {
724 is_bw_white = false;
725 is_color = true;
726 } else {
727 // Black or other colors -> black
728 is_bw_white = false;
729 is_color = false;
730 }
731
732 // Set B/W plane (first plane)
733 auto &bw_byte = buffer[byte_index];
734 if (is_bw_white) {
735 bw_byte = static_cast<std::byte>(static_cast<std::uint8_t>(bw_byte) | bit_mask);
736 } else {
737 bw_byte = static_cast<std::byte>(static_cast<std::uint8_t>(bw_byte) & ~bit_mask);
738 }
739
740 // Set color plane (second plane)
741 auto &color_byte = buffer[byte_index + plane_size];
742 if (is_color) {
743 color_byte = static_cast<std::byte>(static_cast<std::uint8_t>(color_byte) | bit_mask);
744 } else {
745 color_byte = static_cast<std::byte>(static_cast<std::uint8_t>(color_byte) & ~bit_mask);
746 }
747}
748
758inline auto set_pixel_spectra6(std::span<std::byte> buffer, std::size_t width, std::size_t x, std::size_t y,
759 Color color) noexcept -> void {
760 const auto [byte_index, bit_offset] = calculate_spectra6_position(width, x, y);
761
762 if (byte_index >= buffer.size()) {
763 return;
764 }
765
766 const std::uint8_t color_value = spectra6_color_to_value(color);
767
768 if (bit_offset <= 5) {
769 // 3-bit value fits within single byte
770 const auto shift = static_cast<std::uint8_t>(5 - bit_offset);
771 const auto mask = static_cast<std::uint8_t>(pixel_constants::SPECTRA6_COLOR_MASK << shift);
772 auto &byte_val = buffer[byte_index];
773 byte_val = static_cast<std::byte>((static_cast<std::uint8_t>(byte_val) & ~mask) | ((color_value & 0x07) << shift));
774 } else {
775 // 3-bit value spans two bytes
776 const auto high_bits = static_cast<std::uint8_t>(8 - bit_offset);
777 const auto low_bits = static_cast<std::uint8_t>(3 - high_bits);
778
779 // First byte: set the high bits
780 const auto high_mask = static_cast<std::uint8_t>((1U << high_bits) - 1);
781 auto &high_byte = buffer[byte_index];
782 high_byte = static_cast<std::byte>((static_cast<std::uint8_t>(high_byte) & ~high_mask) |
783 ((color_value >> low_bits) & high_mask));
784
785 // Second byte: set the low bits
786 if (byte_index + 1 < buffer.size()) {
787 const auto low_mask = static_cast<std::uint8_t>(((1U << low_bits) - 1) << (8 - low_bits));
788 auto &low_byte = buffer[byte_index + 1];
789 low_byte = static_cast<std::byte>((static_cast<std::uint8_t>(low_byte) & ~low_mask) |
790 ((color_value & ((1U << low_bits) - 1)) << (8 - low_bits)));
791 }
792 }
793}
794
808inline auto set_pixel_in_buffer(DisplayMode mode, std::span<std::byte> buffer, std::size_t width, std::size_t height,
809 std::size_t x, std::size_t y, Color color) noexcept -> void {
810 switch (mode) {
812 set_pixel_bw(buffer, width, x, y, color);
813 break;
815 set_pixel_grayscale4(buffer, width, x, y, color);
816 break;
817 case DisplayMode::BWR:
818 set_pixel_bwr_bwy(buffer, width, height, x, y, color, true);
819 break;
820 case DisplayMode::BWY:
821 set_pixel_bwr_bwy(buffer, width, height, x, y, color, false);
822 break;
824 set_pixel_spectra6(buffer, width, x, y, color);
825 break;
826 }
827}
828
829} // namespace epaper
constexpr std::uint8_t GRAY_THRESHOLD_DARK
Definition pixel_codec.hpp:88
constexpr std::uint8_t GRAY_PIXEL_MASK
Definition pixel_codec.hpp:75
constexpr std::uint8_t GRAY_THRESHOLD_LIGHT
Definition pixel_codec.hpp:87
constexpr std::uint8_t GRAY_PIXELS_PER_BYTE
Definition pixel_codec.hpp:73
constexpr std::uint8_t BW_PIXELS_PER_BYTE
Definition pixel_codec.hpp:67
constexpr std::uint8_t GRAY_BITS_PER_PIXEL
Definition pixel_codec.hpp:74
constexpr std::uint8_t BW_MSB_MASK
Definition pixel_codec.hpp:68
constexpr std::uint8_t SPECTRA6_BITS_PER_PIXEL
Definition pixel_codec.hpp:80
constexpr std::uint8_t SPECTRA6_COLOR_MASK
Definition pixel_codec.hpp:81
constexpr std::uint8_t GRAY_THRESHOLD_WHITE
Definition pixel_codec.hpp:86
Definition color.hpp:5
auto get_pixel_bw(std::span< const std::byte > buffer, std::size_t width, std::size_t x, std::size_t y) noexcept -> Color
Read a pixel from a B/W mode buffer.
Definition pixel_codec.hpp:374
auto set_pixel_grayscale4(std::span< std::byte > buffer, std::size_t width, std::size_t x, std::size_t y, Color color) noexcept -> void
Write a pixel to a Grayscale4 mode buffer.
Definition pixel_codec.hpp:657
constexpr auto spectra6_value_to_color(std::uint8_t value) noexcept -> Color
Convert a 3-bit Spectra6 value to Color.
Definition pixel_codec.hpp:140
constexpr auto rgb_to_color(DisplayMode mode, std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept -> Color
Convert RGB to Color based on display mode.
Definition pixel_codec.hpp:240
auto set_pixel_bw(std::span< std::byte > buffer, std::size_t width, std::size_t x, std::size_t y, Color color) noexcept -> void
Write a pixel to a B/W mode buffer.
Definition pixel_codec.hpp:614
auto set_pixel_spectra6(std::span< std::byte > buffer, std::size_t width, std::size_t x, std::size_t y, Color color) noexcept -> void
Write a pixel to a Spectra6 mode buffer.
Definition pixel_codec.hpp:758
constexpr std::array< Color, 8 > SPECTRA6_VALUE_TO_COLOR
Mapping from 3-bit Spectra6 value to Color enum.
Definition pixel_codec.hpp:98
constexpr auto calculate_bw_position(std::size_t width, std::size_t x, std::size_t y) noexcept -> std::pair< std::size_t, std::uint8_t >
Calculate byte index and bit mask for B/W mode pixel.
Definition pixel_codec.hpp:276
auto set_pixel_in_buffer(DisplayMode mode, std::span< std::byte > buffer, std::size_t width, std::size_t height, std::size_t x, std::size_t y, Color color) noexcept -> void
Write a pixel to a buffer based on display mode.
Definition pixel_codec.hpp:808
constexpr auto rgb_to_color_grayscale4(std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept -> Color
Convert RGB to Color for 4-level grayscale mode.
Definition pixel_codec.hpp:217
constexpr auto color_to_rgb(Color color) noexcept -> RGB
Convert a Color enum to RGB values.
Definition pixel_codec.hpp:154
constexpr auto spectra6_color_to_value(Color color) noexcept -> std::uint8_t
Convert a Color to its 3-bit Spectra6 value.
Definition pixel_codec.hpp:115
constexpr auto rgb_to_color_bw(std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept -> Color
Convert RGB to Color for black/white mode.
Definition pixel_codec.hpp:202
constexpr auto rgb_to_grayscale(std::uint8_t r, std::uint8_t g, std::uint8_t b) noexcept -> std::uint8_t
Convert RGB values to grayscale using standard luminance formula.
Definition pixel_codec.hpp:186
auto get_pixel_spectra6(std::span< const std::byte > buffer, std::size_t width, std::size_t x, std::size_t y) noexcept -> Color
Read a pixel from a Spectra6 mode buffer.
Definition pixel_codec.hpp:524
auto get_pixel_grayscale4(std::span< const std::byte > buffer, std::size_t width, std::size_t x, std::size_t y) noexcept -> Color
Read a pixel from a Grayscale4 mode buffer.
Definition pixel_codec.hpp:407
constexpr auto calculate_gray_position(std::size_t width, std::size_t x, std::size_t y) noexcept -> std::pair< std::size_t, std::uint8_t >
Calculate byte index and pixel shift for Grayscale4 mode pixel.
Definition pixel_codec.hpp:310
Color
Definition types.hpp:32
@ Gray1
First gray level - lighter (Grayscale4 mode only)
@ White
White (or lightest gray in grayscale modes)
@ Yellow
Yellow (BWY and Spectra6 modes)
@ Blue
Blue (Spectra6 mode only)
@ Green
Green (Spectra6 mode only)
@ Gray2
Second gray level - darker (Grayscale4 mode only)
@ Black
Black (or darkest gray in grayscale modes)
@ Red
Red (BWR and Spectra6 modes)
constexpr auto calculate_spectra6_position(std::size_t width, std::size_t x, std::size_t y) noexcept -> std::pair< std::size_t, std::size_t >
Calculate byte index and bit offset for Spectra6 (3-bit) mode pixel.
Definition pixel_codec.hpp:343
DisplayMode
Definition driver.hpp:46
@ BWY
Black, White, Yellow (3 colors, typically 2-bit)
@ Spectra6
6-color: Black, White, Red, Yellow, Blue, Green (3-bit)
@ BlackWhite
1-bit black and white (2 colors)
@ Grayscale4
2-bit 4-level grayscale
@ BWR
Black, White, Red (3 colors, typically 2-bit)
auto set_pixel_bwr_bwy(std::span< std::byte > buffer, std::size_t width, std::size_t height, std::size_t x, std::size_t y, Color color, bool is_bwr) noexcept -> void
Write a pixel to a BWR or BWY mode buffer.
Definition pixel_codec.hpp:706
auto get_pixel_from_buffer(DisplayMode mode, std::span< const std::byte > buffer, std::size_t width, std::size_t height, std::size_t x, std::size_t y) noexcept -> Color
Read a pixel from a buffer based on display mode.
Definition pixel_codec.hpp:569
auto get_pixel_bwr_bwy(std::span< const std::byte > buffer, std::size_t width, std::size_t height, std::size_t x, std::size_t y, bool is_bwr) noexcept -> Color
Read a pixel from a BWR or BWY mode buffer.
Definition pixel_codec.hpp:461
Definition color.hpp:50