1#![feature(iter_array_chunks)]
2
3use ab_glyph::{Font, FontRef, PxScale, ScaleFont};
4use image::{GenericImageView, GrayImage, Luma, Rgb};
5use imageproc::drawing::{draw_text_mut, text_size};
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::parse::{Parse, ParseStream, Result};
9use syn::{parse_macro_input, Ident, Lit, LitByteStr, Token};
10
11#[derive(Debug)]
12struct TextImageOptions {
13 text: String,
14 font: String,
15 font_size: f32,
16 inverse: bool,
17 line_spacing: i32,
18 gray_depth: i32,
20}
21
22impl Parse for TextImageOptions {
23 fn parse(input: ParseStream) -> Result<Self> {
24 let mut opts = TextImageOptions {
25 text: "".to_string(),
26 font: "".to_string(),
27 font_size: 16.0,
28 inverse: false,
29 line_spacing: 0,
30 gray_depth: 1,
31 };
32
33 loop {
34 let name: Ident = input.parse()?;
35
36 match &*name.to_string() {
37 "text" => {
38 input.parse::<Token![=]>()?;
39 let text: Lit = input.parse()?;
40
41 let text = if let Lit::Str(text) = &text {
42 text.value()
43 } else {
44 return Err(syn::Error::new_spanned(text, "expected a string literal"));
45 };
46
47 opts.text = text;
48 }
49 "font" => {
50 input.parse::<Token![=]>()?;
51 let font: Lit = input.parse()?;
52
53 let font = if let Lit::Str(font) = &font {
54 font.value()
55 } else {
56 return Err(syn::Error::new_spanned(font, "expected a string literal"));
57 };
58
59 opts.font = font;
60 }
61 "font_size" => {
62 input.parse::<Token![=]>()?;
63 let font_size: Lit = input.parse()?;
64
65 let font_size = if let Lit::Float(font_size) = &font_size {
66 font_size.base10_parse()?
67 } else {
68 return Err(syn::Error::new_spanned(
69 font_size,
70 "expected a float literal",
71 ));
72 };
73
74 opts.font_size = font_size;
75 }
76 "line_spacing" => {
77 input.parse::<Token![=]>()?;
78 let line_spacing: Lit = input.parse()?;
79
80 let line_spacing = if let Lit::Int(line_spacing) = &line_spacing {
81 line_spacing.base10_parse()?
82 } else {
83 return Err(syn::Error::new_spanned(
84 line_spacing,
85 "expected a integer literal",
86 ));
87 };
88
89 opts.line_spacing = line_spacing;
90 }
91 "inverse" => {
92 opts.inverse = true;
93 }
94 "Gray2" => {
95 opts.gray_depth = 2;
96 }
97 "Gray4" => {
98 opts.gray_depth = 4;
99 }
100 "Gray8" => {
101 opts.gray_depth = 8;
102 }
103 _ => {
104 return Err(syn::Error::new_spanned(
105 name,
106 "expected `text`, `font`, `font_size` or `inverse`",
107 ));
108 }
109 }
110
111 let _ = input.parse::<Token![,]>();
112 if input.is_empty() {
113 break;
114 }
115 }
116
117 if opts.text.is_empty() {
119 return Err(syn::Error::new_spanned(
120 "text",
121 "required option `text` is missing",
122 ));
123 }
124 if opts.font.is_empty() {
125 return Err(syn::Error::new_spanned(
126 "font",
127 "required option `font` is missing",
128 ));
129 }
130
131 Ok(opts)
132 }
133}
134
135#[proc_macro]
157pub fn text_image(input: TokenStream) -> TokenStream {
158 let opts = parse_macro_input!(input as TextImageOptions);
159 println!("text_image: {:#?}", opts);
160
161 let font_raw = std::fs::read(opts.font).expect("Can not read font file");
162 let font = FontRef::try_from_slice(&font_raw).expect("Can not load font");
163
164 let scale = PxScale {
165 x: opts.font_size,
166 y: opts.font_size,
167 };
168
169 let sfont = font.as_scaled(scale);
171 let line_height = (sfont.ascent() - sfont.descent() + sfont.line_gap())
172 .abs()
173 .ceil() as i32;
174
175 let mut h = 0;
176 let mut w = 0;
177 let mut lines = 0;
178
179 for line in opts.text.lines() {
180 let (lw, _lh) = text_size(scale, &font, line);
181 w = w.max(lw);
183 h += line_height;
184 lines += 1;
185 }
186 w += 1;
187 h += opts.line_spacing as i32 * (lines - 1);
188
189 if w % 8 != 0 {
191 w = (w / 8 + 1) * 8;
192 }
193 println!("text_image: result size {}x{}, {} lines", w, h, lines);
194
195 let mut image: image::ImageBuffer<Luma<u8>, Vec<u8>> = GrayImage::new(w as _, h as _);
196
197 let mut luma = 0xFF;
198 if opts.inverse {
199 image.fill(0xFF);
200 luma = 0x00;
201 }
202
203 for (i, line) in opts.text.lines().enumerate() {
204 draw_text_mut(
206 &mut image,
207 Luma([luma]),
208 1,
209 (line_height + opts.line_spacing) * (i as i32) - 1,
210 scale,
211 &font,
212 &line,
213 );
214 }
215
216 let raw = image.into_raw();
217
218 let raw: Vec<u8> = match opts.gray_depth {
220 8 => raw,
221 4 => raw
222 .chunks(2)
223 .map(|ch| (ch[1] >> 4) | (ch[0] & 0xF0))
224 .collect(),
225 2 => {
226 let mut ret = Vec::with_capacity(raw.len() / 4);
227 for ch in raw.chunks(4) {
228 ret.push(
229 (ch[3] >> 6) | ((ch[2] >> 4) & 0x0C) | ((ch[1] >> 2) & 0x30) | (ch[0] & 0xC0),
230 );
231 }
232 ret
233 }
234 1 => {
235 let mut ret = Vec::with_capacity(raw.len() / 8);
236 for ch in raw.chunks(8) {
237 ret.push(
238 (ch[7] >> 7)
239 | ((ch[6] >> 6) & 0x02)
240 | ((ch[5] >> 5) & 0x04)
241 | ((ch[4] >> 4) & 0x08)
242 | ((ch[3] >> 3) & 0x10)
243 | ((ch[2] >> 2) & 0x20)
244 | ((ch[1] >> 1) & 0x40)
245 | (ch[0] & 0x80),
246 );
247 }
248 ret
249 }
250 _ => unreachable!(),
251 };
252
253 let raw_bytes = Lit::ByteStr(LitByteStr::new(&raw, proc_macro2::Span::call_site()));
256
257 let w = w as u32;
258 let h = h as u32;
259
260 let expanded = quote! {
263 (#w, #h, #raw_bytes)
264 };
265
266 TokenStream::from(expanded)
267}
268
269#[derive(Debug)]
270struct ImageOptions {
271 image: String,
272 channel: u8,
274 gray_depth: i32,
275}
276
277impl Parse for ImageOptions {
278 fn parse(input: ParseStream) -> Result<Self> {
279 let mut opts = ImageOptions {
280 image: "".to_string(),
281 channel: 0,
282 gray_depth: 1,
283 };
284
285 let name: Lit = input.parse()?;
286
287 let image = if let Lit::Str(image) = &name {
288 image.value()
289 } else {
290 return Err(syn::Error::new_spanned(
291 "image",
292 "expected a string literal",
293 ));
294 };
295 opts.image = image;
296
297 while let Ok(_) = input.parse::<Token![,]>() {
298 if input.is_empty() {
299 break;
300 }
301
302 let name: Ident = input.parse()?;
303
304 match &*name.to_string() {
305 "channel" => {
306 input.parse::<Token![=]>()?;
307 let channel: Lit = input.parse()?;
308
309 let channel = if let Lit::Int(channel) = &channel {
310 channel.base10_parse()?
311 } else {
312 return Err(syn::Error::new_spanned(
313 channel,
314 "expected a integer literal",
315 ));
316 };
317
318 opts.channel = channel;
319 }
320 "Gray2" => {
321 opts.gray_depth = 2;
322 }
323 "Gray4" => {
324 opts.gray_depth = 4;
325 }
326 "Gray8" => {
327 opts.gray_depth = 8;
328 }
329 _ => {
330 return Err(syn::Error::new_spanned(
331 name,
332 "expected `palette` or `channel`",
333 ));
334 }
335 }
336 }
337
338 Ok(opts)
339 }
340}
341
342struct BWR;
343
344impl BWR {
345 fn map_palette(&self, c: &Rgb<u8>) -> u8 {
346 let palette = vec![0x000000, 0xFFFFFF, 0xFF0000];
347 let mut min = 0;
348 let mut min_dist = 0x7FFF_FFFF;
349 for (i, p) in palette.iter().enumerate() {
350 let dist = (c.0[0] as i32 - (p >> 16) as i32).pow(2)
351 + (c.0[1] as i32 - ((p >> 8) & 0xFF) as i32).pow(2)
352 + (c.0[2] as i32 - (p & 0xFF) as i32).pow(2);
353 if dist < min_dist {
354 min_dist = dist;
355 min = i;
356 }
357 }
358 min as u8
359 }
360}
361
362impl image::imageops::colorops::ColorMap for BWR {
363 type Color = Rgb<u8>;
364
365 fn index_of(&self, color: &Self::Color) -> usize {
366 let palette = vec![0x000000, 0xFFFFFF, 0xFF0000];
367 let mut min = 0;
368 let mut min_dist = 0x7FFF_FFFF;
369 for (i, p) in palette.iter().enumerate() {
370 let dist = (color.0[0] as i32 - (p >> 16) as i32).pow(2)
371 + (color.0[1] as i32 - ((p >> 8) & 0xFF) as i32).pow(2)
372 + (color.0[2] as i32 - (p & 0xFF) as i32).pow(2);
373 if dist < min_dist {
374 min_dist = dist;
375 min = i;
376 }
377 }
378 min
379 }
380 fn map_color(&self, color: &mut Self::Color) {
381 let idx = self.index_of(color);
382 let palette = [
383 Rgb([0x00, 0x00, 0x00]),
384 Rgb([0xFF, 0xFF, 0xFF]),
385 Rgb([0xFF, 0x00, 0x00]),
386 ];
387 *color = palette[idx];
388 }
389}
390
391#[proc_macro]
392pub fn monochrome_image(input: TokenStream) -> TokenStream {
393 let opts = parse_macro_input!(input as ImageOptions);
394 println!("text_image: {:#?}", opts);
395
396 let im = image::open(&opts.image).expect("Can not read image file");
397 let (mut w, h) = im.dimensions();
398
399 let mut im = im.to_rgb8();
400
401 image::imageops::colorops::dither(&mut im, &BWR);
403
404 let mut ret = vec![];
405
406 for (y, row) in im.enumerate_rows() {
408 let mut n = 0u8;
409 for (x, (_, _, px)) in row.enumerate() {
410 let ix = BWR.map_palette(px);
411 if ix == opts.channel {
412 n |= 1 << (7 - x % 8);
413 }
414 if x % 8 == 7 {
415 ret.push(n);
416 n = 0;
417 }
418 }
419 if w % 8 != 0 {
420 ret.push(n);
421 }
422 }
423
424 w = (w / 8 + if w % 8 != 0 { 1 } else { 0 }) * 8;
425
426 let raw_bytes = Lit::ByteStr(LitByteStr::new(&ret, proc_macro2::Span::call_site()));
427
428 let expanded = quote! {
429 (#w, #h, #raw_bytes)
430 };
431
432 TokenStream::from(expanded)
433}
434
435struct BWYR;
436
437impl BWYR {
438 fn map_palette(&self, c: &Rgb<u8>) -> u8 {
439 let palette = vec![0x000000, 0xFFFFFF, 0xFF0000, 0xFFFF00];
440 let mut min = 0;
441 let mut min_dist = 0x7FFF_FFFF;
442 for (i, p) in palette.iter().enumerate() {
443 let dist = (c.0[0] as i32 - (p >> 16) as i32).pow(2)
444 + (c.0[1] as i32 - ((p >> 8) & 0xFF) as i32).pow(2)
445 + (c.0[2] as i32 - (p & 0xFF) as i32).pow(2);
446 if dist < min_dist {
447 min_dist = dist;
448 min = i;
449 }
450 }
451 min as u8
452 }
453}
454
455impl image::imageops::colorops::ColorMap for BWYR {
456 type Color = Rgb<u8>;
457
458 fn index_of(&self, color: &Self::Color) -> usize {
459 let palette = vec![0x000000, 0xFFFFFF, 0xFFFF00, 0xFF0000];
460 let mut min = 0;
461 let mut min_dist = 0x7FFF_FFFF;
462 for (i, p) in palette.iter().enumerate() {
463 let dist = (color.0[0] as i32 - (p >> 16) as i32).abs()
464 + (color.0[1] as i32 - ((p >> 8) & 0xFF) as i32).abs()
465 + (color.0[2] as i32 - (p & 0xFF) as i32).abs();
466 if dist < min_dist {
467 min_dist = dist;
468 min = i;
469 }
470 }
471 min
472 }
473 fn map_color(&self, color: &mut Self::Color) {
474 let idx = self.index_of(color);
475 let palette = [
476 Rgb([0x00, 0x00, 0x00]),
477 Rgb([0xFF, 0xFF, 0xFF]),
478 Rgb([0xFF, 0x00, 0x00]),
479 Rgb([0xFF, 0xFF, 0x00]),
480 ];
481 *color = palette[idx];
482 }
483}
484
485#[proc_macro]
487pub fn quadcolor_image(input: TokenStream) -> TokenStream {
488 let opts = parse_macro_input!(input as ImageOptions);
489 println!("text_image: {:#?}", opts);
490
491 let im = image::open(&opts.image).expect("Can not read image file");
492 let (w, h) = im.dimensions();
493
494 let mut im = im.to_rgb8();
495
496 image::imageops::colorops::dither(&mut im, &BWYR);
498
499 let mut ret = vec![];
500
501 for pixels in im.pixels().array_chunks::<4>() {
502 let mut n = 0u8;
503 for pix in pixels {
504 let ix = BWYR.map_palette(pix);
505 if ix != 0 && ix != 1 && ix != 2 {
506 println!("ix => {}", ix);
507 }
508 n = (n << 2) | (ix & 0b11);
509 }
510 ret.push(n);
511 }
512
513 let raw_bytes = Lit::ByteStr(LitByteStr::new(&ret, proc_macro2::Span::call_site()));
514
515 let expanded = quote! {
516 (#w, #h, #raw_bytes)
517 };
518
519 TokenStream::from(expanded)
520}
521
522#[proc_macro]
530pub fn gray_image(input: TokenStream) -> TokenStream {
531 let opts = parse_macro_input!(input as ImageOptions);
532 println!("text_image: {:#?}", opts);
533
534 let im = image::open(&opts.image).expect("Can not read image file");
535 let (w, h) = im.dimensions();
536
537 let im = im.to_luma8();
538
539 let mut ret = vec![];
540
541 let steps_per_pixel = 8 / opts.gray_depth;
542 let shift_per_pixel = match opts.gray_depth {
543 8 => 0,
544 4 => 4,
545 2 => 6,
546 1 => 7,
547 _ => unreachable!(),
548 };
549
550 let mut c = 0;
551 let mut n = 0u8;
552 for pixel in im.pixels() {
553 let val = pixel.0[0];
554
555 n = (n << opts.gray_depth) | (val >> shift_per_pixel);
556 c += 1;
557
558 if c == steps_per_pixel {
559 ret.push(n);
560 n = 0;
561 c = 0;
562 }
563 }
564
565 let raw_bytes = Lit::ByteStr(LitByteStr::new(&ret, proc_macro2::Span::call_site()));
566
567 let expanded = quote! {
568 (#w, #h, #raw_bytes)
569 };
570
571 TokenStream::from(expanded)
572}