use crate::global_type_helper::{ColorClass, RgbPalette, RgbPaletteHelper};
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
pub fn indexed_data_to_png(
indexed_palette: RgbPalette,
indexed_bitmap: Vec<Vec<ColorClass>>,
dimension: (usize, usize),
is_compressed: bool,
) -> Vec<u8> {
const PNG_HEADER: &[u8] = b"\x89PNG\r\n\x1a\n";
const BIT_DEPTH: u8 = 2;
let mut ret_data: Vec<u8> = Vec::from(PNG_HEADER);
ret_data.append(&mut ihdr_chunk(dimension, BIT_DEPTH));
ret_data.append(&mut plte_chunk(indexed_palette));
ret_data.append(&mut idat_chunk(
indexed_bitmap,
dimension,
BIT_DEPTH,
is_compressed,
));
ret_data.append(&mut iend_chunk());
ret_data
}
fn ihdr_chunk(dimension: (usize, usize), bit_depth: u8) -> Vec<u8> {
const CHUNK_TYPE: &[u8] = b"IHDR";
const COLOR_TYPE: u8 = 3;
const COMPRESSION_METHOD: u8 = 0;
const FILTER_METHOD: u8 = 0;
const INTERLACE_METHOD: u8 = 0;
let mut chunk_data: Vec<u8> = CHUNK_TYPE.to_vec();
chunk_data.extend_from_slice(&(dimension.0 as u32).to_be_bytes());
chunk_data.extend_from_slice(&(dimension.1 as u32).to_be_bytes());
chunk_data.extend_from_slice(&bit_depth.to_be_bytes());
chunk_data.extend_from_slice(&[
COLOR_TYPE,
COMPRESSION_METHOD,
FILTER_METHOD,
INTERLACE_METHOD,
]);
pack_png_chunk(&mut chunk_data)
}
fn plte_chunk(palette: RgbPalette) -> Vec<u8> {
const CHUNK_TYPE: &[u8] = b"PLTE";
let mut chunk_data: Vec<u8> = CHUNK_TYPE.to_vec();
chunk_data.extend_from_slice(&palette.serialize());
pack_png_chunk(&mut chunk_data)
}
fn idat_chunk(
data: Vec<Vec<ColorClass>>,
dimension: (usize, usize),
bit_depth: u8,
is_compressed: bool,
) -> Vec<u8> {
const CHUNK_TYPE: &[u8] = b"IDAT";
const FILTER_TYPE: &[u8] = &[0];
let mut chunk_data: Vec<u8> = CHUNK_TYPE.to_vec();
let classes_per_byte: usize = (u8::BITS as u8 / bit_depth) as usize;
let bytes_per_scanline =
FILTER_TYPE.len() + ((dimension.0 + classes_per_byte - 1) / classes_per_byte);
let mut img_data = vec![0_u8; bytes_per_scanline * dimension.1];
img_data
.chunks_mut(bytes_per_scanline)
.zip(data.iter())
.for_each(|(scanline_dest, scanline_src)| {
let (scanline_dest_filter, scanline_dest_data) =
scanline_dest.split_at_mut(FILTER_TYPE.len());
scanline_dest_filter.copy_from_slice(FILTER_TYPE);
scanline_dest_data
.iter_mut()
.zip(scanline_src.chunks(classes_per_byte))
.for_each(|(scanline_dest_elem, scanline_src_chunk)| {
*scanline_dest_elem = scanline_src_chunk
.iter()
.map(|colorclass| *colorclass as u8)
.enumerate()
.fold(0_u8, |byte, (idx, class): (_, u8)| {
(class << (u8::BITS as u8 - ((idx + 1) as u8 * bit_depth))) | byte
});
});
});
match is_compressed {
true => {
#[cfg(feature = "compressed_png")]
fn compress_png(input: &[u8]) -> Vec<u8> {
deflate::deflate_bytes_zlib(&input)
}
#[cfg(not(feature = "compressed_png"))]
fn compress_png(_input: &[u8]) -> Vec<u8> {
panic!("eth_blockies: Unexpected error: compressed_png is called when its feature is not enabled!");
}
img_data = compress_png(&img_data);
}
false => {
const CM: u8 = 8;
const CINFO: u8 = 7;
const COMPRESSION_METHOD: &[u8] = &[CINFO << 4 | CM];
const FCHECK: u8 = 1;
const FDICT: u8 = 0;
const FLEVEL: u8 = 0;
const FLG: &[u8] = &[FLEVEL << 6 | FDICT << 5 | FCHECK];
const IS_LAST_BLOCK: &[u8] = &[1];
chunk_data.extend_from_slice(COMPRESSION_METHOD);
chunk_data.extend_from_slice(FLG);
chunk_data.extend_from_slice(IS_LAST_BLOCK);
let img_data_len: u16 = (img_data.len()) as u16;
chunk_data.extend_from_slice(&img_data_len.to_le_bytes());
chunk_data.extend_from_slice(&(!img_data_len).to_le_bytes());
img_data.extend_from_slice(&adler32(&img_data).to_be_bytes());
}
}
chunk_data.append(&mut img_data);
pack_png_chunk(&mut chunk_data)
}
fn iend_chunk() -> Vec<u8> {
const CHUNK_TYPE: &[u8] = b"IEND";
let mut chunk_data: Vec<u8> = CHUNK_TYPE.to_vec();
pack_png_chunk(&mut chunk_data)
}
fn pack_png_chunk(chunk_data: &mut Vec<u8>) -> Vec<u8> {
let crc_val = crc32(&chunk_data).to_be_bytes();
let mut chunk: Vec<u8> = Vec::new();
chunk.extend_from_slice(&(chunk_data.len() as u32 - 4).to_be_bytes());
chunk.append(chunk_data);
chunk.extend_from_slice(&crc_val);
chunk
}
fn crc32(buf: &[u8]) -> u32 {
const CRC32_TABLE: &[u32; 256] = &[
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
];
buf.iter().fold(0xffffffff_u32, |c, b| {
CRC32_TABLE[((c ^ (*b as u32)) & 0xff) as usize] ^ (c >> 8)
}) ^ 0xffffffff_u32
}
fn adler32(buf: &[u8]) -> u32 {
const MOD_ADLER: u32 = 65521;
const MAX_CHUNK: usize = 5552;
let (a, b) = buf
.chunks(MAX_CHUNK)
.fold((1_u32, 0_u32), |(a, b), idx_data_tuple| {
let (a_new, b_new) = idx_data_tuple
.iter()
.fold((a, b), |(a_inner, b_inner), elem| {
(a_inner + *elem as u32, a_inner + b_inner + *elem as u32)
});
(a_new % MOD_ADLER, b_new % MOD_ADLER)
});
b << 16 | a
}
fn base64(buf: &[u8]) -> Vec<u8> {
const BASE64_TABLE: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const BASE64_EMPTY: u8 = b'=';
const SRC_BYTES_PER_UNIT: usize = 3;
const DEST_BYTES_PER_UNIT: usize = 4;
let ret_size =
((buf.len().overflowing_sub(1).0 / SRC_BYTES_PER_UNIT) + 1) * DEST_BYTES_PER_UNIT;
let mut base64_vec = vec![0_u8; ret_size];
base64_vec
.chunks_mut(DEST_BYTES_PER_UNIT)
.zip(buf.chunks(SRC_BYTES_PER_UNIT))
.for_each(|(bytes_dest, bytes_src)| {
let bytes_src_opt = [bytes_src.get(0), bytes_src.get(1), bytes_src.get(2)];
let bits = u32::from_be_bytes([
0_u8,
*bytes_src_opt[0].expect("base64"),
*bytes_src_opt[1].unwrap_or(&0_u8),
*bytes_src_opt[2].unwrap_or(&0_u8),
]);
bytes_dest[0] = BASE64_TABLE[(bits >> 18 & 0b111111_u32) as usize];
bytes_dest[1] = BASE64_TABLE[(bits >> 12 & 0b111111_u32) as usize];
bytes_dest[2] = match bytes_src_opt[1].is_some() {
true => BASE64_TABLE[(bits >> 6 & 0b111111_u32) as usize],
false => BASE64_EMPTY,
};
bytes_dest[3] = match bytes_src_opt[2].is_some() {
true => BASE64_TABLE[(bits & 0b111111_u32) as usize],
false => BASE64_EMPTY,
};
});
base64_vec
}
pub fn base64_wrapper(buf: &[u8], data_uri_output: bool) -> String {
String::from_utf8(
[
Vec::<u8>::from(match data_uri_output {
true => b"data:image/png;base64,".as_ref(),
false => b"".as_ref(),
}),
base64(buf),
]
.concat(),
)
.expect("unexpected internal error") }