embedded_sprites_proc_macro/
lib.rs1use embedded_graphics::pixelcolor::{Bgr888, RgbColor as _};
2use image::{io::Reader as ImageReader, Pixel, RgbaImage};
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6use std::path::PathBuf;
7use syn::{parse_macro_input, spanned::Spanned as _, Expr, ExprLit, ItemConst, Lit};
8
9fn expand(
10 ItemConst {
11 attrs,
12 vis,
13 const_token,
14 ident,
15 colon_token,
16 ty,
17 eq_token,
18 expr,
19 semi_token,
20 }: ItemConst,
21) -> syn::Result<TokenStream2> {
22 let path_lit = match expr.as_ref() {
23 Expr::Lit(ExprLit { lit: Lit::Str(path), .. }) => path,
24 expr => return Err(syn::Error::new(expr.span(), "Expected path to image")),
25 };
26 let path: PathBuf = path_lit
27 .value()
28 .parse()
29 .map_err(|err| syn::Error::new(path_lit.span(), format!("Invalid path: {err}")))?;
30 let image = ImageReader::open(&path)
31 .map_err(|err| syn::Error::new(path_lit.span(), format!("Failed to open image {path:?}: {err}")))?
32 .decode()
33 .map_err(|err| syn::Error::new(path_lit.span(), format!("Failed to decode image {path:?}: {err}")))?;
34 let path = path
35 .canonicalize()
36 .ok()
37 .and_then(|path| path.to_str().map(String::from))
38 .unwrap_or_else(|| path_lit.value());
39
40 let image: RgbaImage = image.into_rgba8();
42 let pixels = image.pixels();
43 let mut colors: Vec<Bgr888> = Vec::new();
44 let mut transparency = Vec::new();
45 for pixel in pixels.into_iter() {
46 let mut chanel = pixel.channels().iter();
47 let color = Bgr888::new(
50 chanel.next().unwrap_or(&0).to_owned(),
51 chanel.next().unwrap_or(&0).to_owned(),
52 chanel.next().unwrap_or(&0).to_owned(),
53 );
54 let a = chanel.next().map(|value| value == &0).unwrap_or(false);
55 transparency.push(a as u8);
56 colors.push(color);
57 }
58
59 let tmap_array = quote!(embedded_sprites::transparency![#(#transparency),*]);
61 let color_ty = quote!(<#ty as ::embedded_sprites::private::Image>::Color);
62 let colors = colors.into_iter().map(|color| {
63 let r = color.r();
64 let g = color.g();
65 let b = color.b();
66 quote!({
67 let (r, g, b) = ::embedded_sprites::private::convert_from_bgr888::
68 <#color_ty>(#r, #g, #b);
69 #color_ty::new(r, g, b)
70 })
71 });
72 let color_array = quote!([#(#colors),*]);
73 let width = image.width() as u16;
74 let height = image.height() as u16;
75 let output = quote! {
76 #(#attrs)* #vis #const_token #ident #colon_token #ty #eq_token {
77 const _: &[u8] = ::core::include_bytes!(#path);
80
81 const COLOR_ARRAY: &[#color_ty] = &#color_array;
82 const TRANSPARENTY_MAP: &[u8] = &#tmap_array;
83 match ::embedded_sprites::image::Image::<'static, #color_ty>::new(
84 COLOR_ARRAY, TRANSPARENTY_MAP, #width, #height
85 ) {
86 ::core::result::Result::Ok(img) => img,
87 _ => panic!("Failed to construct image")
88 }
89 }
90 #semi_token
91 };
92 Ok(output)
94}
95
96#[proc_macro_attribute]
109pub fn include_image(_attr: TokenStream, item: TokenStream) -> TokenStream {
110 expand(parse_macro_input!(item))
111 .unwrap_or_else(|err| err.into_compile_error())
112 .into()
113}