graphics: ssd1306: reimplement dithering using an ordered dither map

This commit is contained in:
2026-01-03 14:47:38 +01:00
parent b6029778e0
commit f4c379a3e8

View File

@@ -1,12 +1,12 @@
#![cfg(feature="oled")] #![cfg(feature="oled")]
use core::cmp::min; use core::{cmp::min, ops::{BitAnd, Shr}};
use display_interface::DisplayError; use display_interface::DisplayError;
use embedded_graphics::prelude::*; use embedded_graphics::prelude::*;
use esp_hal::{gpio::Output, i2c::master::I2c, Async}; use esp_hal::{gpio::Output, i2c::master::I2c, Async};
use figments::{liber8tion::{interpolate::Fract8, noise}, mappings::embedded_graphics::Matrix2DSpace, prelude::*}; use figments::{liber8tion::{interpolate::Fract8, noise}, mappings::embedded_graphics::Matrix2DSpace, prelude::*};
use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::pixelcolor::BinaryColor;
use figments::pixels::PixelSink; use figments::pixels::AdditivePixelSink;
use figments_render::output::OutputAsync; use figments_render::output::OutputAsync;
use ssd1306::{prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306Async}; use ssd1306::{prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306Async};
use embassy_time::Delay; use embassy_time::Delay;
@@ -38,29 +38,38 @@ impl SsdPixel {
} }
} }
impl PixelSink<BinaryColor> for SsdPixel { const DITHER_MAP: [u32;15] = [
fn set(&mut self, pixel: &BinaryColor) { 0b1000_0000_0000_0000,
self.set_pixel(*pixel); 0b1000_0000_0010_0000,
} 0b1010_0000_0010_0000,
} 0b1010_0000_1010_0000,
0b1010_0100_1010_0000,
0b1010_0100_1010_0001,
0b1010_0101_1010_0001,
0b1010_0101_1010_0101,
0b1110_0101_1010_0101,
0b1110_0101_1011_0101,
0b1111_0101_1011_0101,
0b1111_0101_1111_0101,
0b1111_1101_1111_0101,
0b1111_1101_1111_0111,
0b1111_1111_1111_0111,
];
impl PixelBlend<BinaryColor> for SsdPixel { impl AdditivePixelSink<BinaryColor> for SsdPixel {
fn blend_pixel(self, overlay: BinaryColor, opacity: Fract8) -> Self { fn add(&mut self, pixel: BinaryColor, opacity: Fract8) {
let scale = 48; match opacity {
let x = self.coords.x * scale; 0 => (),
let y = self.coords.y * scale; 255 => self.set_pixel(pixel),
let stiple_idx = noise::inoise8(x as i16, y as i16); _ => {
if opacity >= stiple_idx { let dither_value = DITHER_MAP[opacity as usize / 16];
self.set_pixel(overlay); let dither_x = self.coords.x % 4;
let dither_y = self.coords.x % 4;
if dither_value.shr(dither_x).shr(dither_y * 4).bitand(0x01) == 1 {
self.set_pixel(pixel);
}
}
} }
self
}
fn multiply(self, overlay: BinaryColor) -> Self {
if overlay != self.get_pixel() {
self.set_pixel(overlay);
}
self
} }
} }
@@ -195,9 +204,9 @@ impl DrawTarget for SsdOutput {
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
if color.is_on() { if color.is_on() {
self.pixbuf.fill(255); self.pixbuf.fill_with(|| { 255 });
} else { } else {
self.pixbuf.fill(0); self.pixbuf.fill_with(|| { 0 });
} }
Ok(()) Ok(())
} }