about summary refs log tree commit diff
path: root/squash/src/image.rs
blob: 098e8a039f076b08afe42d3c557e93c0f677af58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use std::{fs::File, io::BufWriter};

use anyhow::{anyhow, bail};
use camino::{Utf8Path, Utf8PathBuf};
use colorsquash::Squasher;
use gifed::writer::{GifBuilder, ImageBuilder};
use png::{ColorType, Decoder, Encoder};
use zune_jpeg::{zune_core::colorspace::ColorSpace, JpegDecoder};

pub struct Image {
	pub width: usize,
	pub height: usize,
	pub data: Vec<u8>,
}

pub fn get_png<P: AsRef<Utf8Path>>(path: P) -> Result<Image, anyhow::Error> {
	let decoder = Decoder::new(File::open(path.as_ref())?);
	let mut reader = decoder.read_info()?;

	let mut data = vec![0; reader.output_buffer_size()];
	let info = reader.next_frame(&mut data)?;
	data.resize(info.buffer_size(), 0);

	let colors = info.color_type;
	match colors {
		ColorType::Grayscale | ColorType::GrayscaleAlpha | ColorType::Indexed => {
			bail!("colortype {colors:?} not supported")
		}
		ColorType::Rgba => {
			let pixels = info.width as usize * info.height as usize;

			// the first RGB is fine, we don't need to touch it
			for idx in 1..pixels {
				data[idx * 3] = data[idx * 4];
				data[idx * 3 + 1] = data[idx * 4 + 1];
				data[idx * 3 + 2] = data[idx * 4 + 2];
			}
			data.resize(pixels * 3, 0);

			Ok(Image {
				width: info.width as usize,
				height: info.height as usize,
				data,
			})
		}
		ColorType::Rgb => Ok(Image {
			width: info.width as usize,
			height: info.height as usize,
			data,
		}),
	}
}

pub fn get_jpg<P: AsRef<Utf8Path>>(path: P) -> Result<Image, anyhow::Error> {
	let content = std::fs::read(path.as_ref())?;
	let mut dec = JpegDecoder::new(&content);
	let pixels = dec.decode()?;
	let info = dec
		.info()
		.ok_or(anyhow!("image had no info; this should be impossible"))?;

	let colorspace = dec.get_output_colorspace();
	match colorspace {
		Some(ColorSpace::RGB) => (),
		_ => bail!("colorspace {colorspace:?} not supported"),
	}

	Ok(Image {
		width: info.width as usize,
		height: info.height as usize,
		data: pixels,
	})
}

pub fn save_png(
	image: Image,
	squasher: Squasher<u8>,
	path: Utf8PathBuf,
) -> Result<(), anyhow::Error> {
	let file = File::create(path)?;
	let bufw = BufWriter::new(file);

	let mut enc = Encoder::new(bufw, image.width as u32, image.height as u32);
	enc.set_color(ColorType::Indexed);
	enc.set_depth(png::BitDepth::Eight);
	enc.set_palette(squasher.palette_bytes());
	enc.write_header()?.write_image_data(&image.data)?;

	Ok(())
}

pub fn save_gif(
	image: Image,
	squasher: Squasher<u8>,
	path: Utf8PathBuf,
) -> Result<(), anyhow::Error> {
	GifBuilder::new(image.width as u16, image.height as u16)
		.palette(squasher.palette_bytes().as_slice().try_into().unwrap())
		.image(ImageBuilder::new(image.width as u16, image.height as u16).build(image.data)?)
		.build()?
		.save(path)?;

	Ok(())
}