1#[cfg(not(feature = "std"))]
27#[allow(unused_imports)]
28use alloc::{
29 borrow::ToOwned,
30 format,
31 string::{String, ToString},
32 vec,
33 vec::Vec,
34};
35
36use crate::{Canvas as RenderCanvas, Pixel, StyledPixel};
37use qrcode_core::Color as ModuleColor;
38
39#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
44pub struct Color {
45 r: u8,
46 g: u8,
47 b: u8,
48}
49
50impl Color {
51 pub const fn new(r: u8, g: u8, b: u8) -> Self {
53 Self { r, g, b }
54 }
55
56 fn fg_ansi(self) -> String {
58 format!("\x1b[38;2;{};{};{}m", self.r, self.g, self.b)
59 }
60
61 fn bg_ansi(self) -> String {
63 format!("\x1b[48;2;{};{};{}m", self.r, self.g, self.b)
64 }
65}
66
67impl Pixel for Color {
68 type Image = String;
69 type Canvas = CanvasAnsi;
70
71 fn default_unit_size() -> (u32, u32) {
72 (1, 1)
73 }
74
75 fn default_color(color: ModuleColor) -> Self {
76 match color {
77 ModuleColor::Dark => Color::new(0, 0, 0),
78 ModuleColor::Light => Color::new(255, 255, 255),
79 }
80 }
81}
82
83impl StyledPixel for Color {
84 fn from_hex(hex: &str) -> Self {
85 let (r, g, b) = crate::colors::hex_to_rgb(hex).unwrap_or((0, 0, 0));
86 Color::new(r, g, b)
87 }
88}
89
90pub struct CanvasAnsi {
96 canvas: Vec<u8>,
97 width: u32,
98 dark_pixel: u8,
99 dark_color: Color,
100 light_color: Color,
101}
102
103impl RenderCanvas for CanvasAnsi {
104 type Pixel = Color;
105 type Image = String;
106
107 fn new(width: u32, height: u32, dark_pixel: Color, light_pixel: Color) -> Self {
108 CanvasAnsi {
109 canvas: vec![0u8; (width * height) as usize],
110 width,
111 dark_pixel: 1,
112 dark_color: dark_pixel,
113 light_color: light_pixel,
114 }
115 }
116
117 fn draw_dark_pixel(&mut self, x: u32, y: u32) {
118 self.canvas[(x + y * self.width) as usize] = self.dark_pixel;
119 }
120
121 fn into_image(self) -> String {
122 let w = self.width as usize;
123 let dark = 1u8;
124 let reset = "\x1b[0m";
125
126 self.canvas
127 .chunks_exact(w)
128 .collect::<Vec<&[u8]>>()
129 .chunks(2)
130 .map(|rows| {
131 let top_row = rows[0];
132 let bot_row = rows.get(1).map_or(&[][..], |r| *r);
133
134 let mut line = String::with_capacity(w * 40);
135 let mut last_fg = None;
136 let mut last_bg = None;
137
138 for col in 0..w {
139 let top = top_row.get(col).copied().unwrap_or(0);
140 let bot = bot_row.get(col).copied().unwrap_or(0);
141
142 let (fg, bg) = if top == dark && bot == dark {
143 (self.dark_color, self.dark_color)
144 } else if top == dark && bot != dark {
145 (self.dark_color, self.light_color)
146 } else if top != dark && bot == dark {
147 (self.light_color, self.dark_color)
148 } else {
149 (self.light_color, self.light_color)
150 };
151
152 if last_bg != Some(bg) {
154 line.push_str(&bg.bg_ansi());
155 last_bg = Some(bg);
156 }
157 if last_fg != Some(fg) {
158 line.push_str(&fg.fg_ansi());
159 last_fg = Some(fg);
160 }
161
162 if top == dark && bot == dark {
163 line.push('█');
164 } else if top == dark {
165 line.push('▀');
166 } else if bot == dark {
167 line.push('▄');
168 } else {
169 line.push(' ');
170 }
171 }
172
173 line.push_str(reset);
174 line
175 })
176 .collect::<Vec<String>>()
177 .join("\n")
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::Renderer;
185
186 #[test]
187 fn test_ansi_all_dark() {
188 let colors = vec![ModuleColor::Dark; 4];
189 let image: String = Renderer::<Color>::new(&colors, 2, 0).module_dimensions(1, 1).build();
190 assert!(image.contains('█'));
192 assert!(image.contains("\x1b["));
193 assert!(image.contains("\x1b[0m"));
194 }
195
196 #[test]
197 fn test_ansi_all_light() {
198 let colors = vec![ModuleColor::Light; 4];
199 let image: String = Renderer::<Color>::new(&colors, 2, 0).module_dimensions(1, 1).build();
200 assert!(image.contains(' '));
201 assert!(image.contains("\x1b[0m"));
202 }
203
204 #[test]
205 fn test_ansi_mixed() {
206 let colors = vec![ModuleColor::Dark, ModuleColor::Light, ModuleColor::Light, ModuleColor::Dark];
207 let image: String = Renderer::<Color>::new(&colors, 2, 0).module_dimensions(1, 1).build();
208 assert!(image.contains('▀'));
210 }
211
212 #[test]
213 fn test_ansi_custom_colors() {
214 let colors = vec![ModuleColor::Dark, ModuleColor::Light, ModuleColor::Light, ModuleColor::Dark];
215 let image = Renderer::<Color>::new(&colors, 2, 0)
216 .dark_color(Color::new(0, 51, 102))
217 .light_color(Color::new(224, 224, 224))
218 .module_dimensions(1, 1)
219 .build();
220 assert!(image.contains("0;51;102"));
222 assert!(image.contains("224;224;224"));
223 }
224
225 #[test]
226 fn test_ansi_color_optimization() {
227 let colors = vec![ModuleColor::Dark; 16]; let image: String = Renderer::<Color>::new(&colors, 4, 0).module_dimensions(1, 1).build();
230 let lines: Vec<&str> = image.split('\n').collect();
231 assert_eq!(lines.len(), 2);
232 for line in &lines {
234 let esc_count = line.matches("\x1b[").count();
235 assert_eq!(esc_count, 3);
236 }
237 }
238}