1use std::slice::ChunksExact;
2
3#[cfg(feature = "fir")]
31use fast_image_resize::{
32 FilterType as FIR_FilterType, PixelType as FIR_PixelType, ResizeAlg,
33 Resizer,
34};
35
36#[cfg(feature = "image-rs")]
37use image::{
38 imageops::FilterType as I_FilterType, DynamicImage, RgbImage,
39 RgbaImage,
40};
41
42pub struct ImageBlock {
46 pub x: u32,
47 pub y: u32,
48 pub block: PixlzrBlock,
49}
50
51#[derive(Clone, Debug)]
52pub struct RawImage {
53 pub alpha: bool,
54 pub width: u32,
55 pub height: u32,
56 pub data: Vec<u8>,
57}
58
59#[derive(Clone, Debug)]
60pub struct PixlzrBlockRaw {
61 pub width: u32,
62 pub height: u32,
63 pub block_value: Option<f32>,
64 pub data: RawImage,
65}
66
67#[cfg(feature = "image-rs")]
68#[derive(Clone, Debug)]
69pub struct PixlzrBlockImage {
70 pub width: u32,
71 pub height: u32,
72 pub block_value: Option<f32>,
73 pub data: DynamicImage,
74}
75
76#[derive(Clone, Debug)]
77pub enum PixlzrBlock {
78 Raw(PixlzrBlockRaw),
79 #[cfg(feature = "image-rs")]
80 Image(PixlzrBlockImage),
81}
82
83impl From<PixlzrBlockRaw> for PixlzrBlock {
84 fn from(value: PixlzrBlockRaw) -> Self {
85 PixlzrBlock::Raw(value)
86 }
87}
88#[cfg(feature = "image-rs")]
89impl From<PixlzrBlockImage> for PixlzrBlock {
90 fn from(value: PixlzrBlockImage) -> Self {
91 PixlzrBlock::Image(value)
92 }
93}
94
95#[cfg(feature = "image-rs")]
96impl From<PixlzrBlock> for PixlzrBlockImage {
97 fn from(value: PixlzrBlock) -> Self {
98 match value {
99 PixlzrBlock::Image(image) => image,
100 PixlzrBlock::Raw(raw) => {
101 let (width, height, img) =
102 (raw.width, raw.height, raw.data);
103 let buf = img.data;
104 let data: DynamicImage = if img.alpha {
105 RgbaImage::from_raw(width, height, buf).unwrap().into()
106 } else {
107 RgbImage::from_raw(width, height, buf).unwrap().into()
108 };
109 Self {
110 width,
111 height,
112 data,
113 block_value: raw.block_value,
114 }
115 }
116 }
117 }
118}
119
120impl From<PixlzrBlock> for PixlzrBlockRaw {
121 fn from(value: PixlzrBlock) -> Self {
122 match value {
123 PixlzrBlock::Raw(raw) => raw,
124 #[cfg(feature = "image-rs")]
125 PixlzrBlock::Image(image) => {
126 let (width, height, img) =
127 (image.width, image.height, image.data);
128 let data = RawImage {
129 width,
130 height,
131 alpha: img.as_rgba8().is_some(),
132 data: img.into_bytes(),
133 };
134 Self {
135 width,
136 height,
137 block_value: image.block_value,
138 data,
139 }
140 }
141 }
142 }
143}
144
145impl PixlzrBlock {
146 pub fn width(&self) -> u32 {
147 match self {
148 #[cfg(feature = "image-rs")]
149 PixlzrBlock::Image(block) => block.width,
150 PixlzrBlock::Raw(block) => block.width,
151 }
152 }
153 pub fn height(&self) -> u32 {
154 match self {
155 #[cfg(feature = "image-rs")]
156 PixlzrBlock::Image(block) => block.height,
157 PixlzrBlock::Raw(block) => block.height,
158 }
159 }
160 pub fn dimensions(&self) -> (u32, u32) {
161 (self.width(), self.height())
162 }
163 pub fn block_value(&self) -> Option<f32> {
164 match self {
165 #[cfg(feature = "image-rs")]
166 PixlzrBlock::Image(block) => block.block_value,
167 PixlzrBlock::Raw(block) => block.block_value,
168 }
169 }
170 pub fn has_alpha(&self) -> bool {
171 match self {
172 PixlzrBlock::Raw(raw) => raw.data.alpha,
173 #[cfg(feature = "image-rs")]
174 PixlzrBlock::Image(img) => img.data.color().has_alpha(),
175 }
176 }
177 pub fn block_value_was_calculated(&self) -> bool {
178 self.block_value().is_some()
179 }
180 pub fn as_slice(&self) -> &[u8] {
181 match self {
182 #[cfg(feature = "image-rs")]
183 PixlzrBlock::Image(image) => image.data.as_bytes(),
184 PixlzrBlock::Raw(raw) => raw.data.data.as_slice(),
185 }
186 }
187 pub fn set_block_value(&mut self, value: f32) {
188 match self {
189 #[cfg(feature = "image-rs")]
190 PixlzrBlock::Image(image) => image.block_value = Some(value),
191 PixlzrBlock::Raw(raw) => raw.block_value = Some(value),
192 }
193 }
194}
195
196#[allow(clippy::match_wildcard_for_single_variants)]
197impl PixlzrBlock {
198 pub fn as_image(&self) -> Option<&PixlzrBlockImage> {
199 match self {
200 #[cfg(feature = "image-rs")]
201 PixlzrBlock::Image(image) => Some(image),
202 _ => None,
203 }
204 }
205 pub fn as_raw(&self) -> Option<&PixlzrBlockRaw> {
206 match self {
207 PixlzrBlock::Raw(raw) => Some(raw),
208 _ => None,
209 }
210 }
211 pub fn is_image(&self) -> bool {
212 match self {
213 #[cfg(feature = "image-rs")]
214 PixlzrBlock::Image(_) => true,
215 _ => false,
216 }
217 }
218 pub fn is_raw(&self) -> bool {
219 matches!(self, PixlzrBlock::Raw(_))
220 }
221}
222
223impl PixlzrBlock {
224 pub fn pixels(&self) -> ChunksExact<u8> {
225 let chunk_size = 3 + self.has_alpha() as usize;
226 match self {
227 #[cfg(feature = "image-rs")]
228 PixlzrBlock::Image(image) => {
229 image.data.as_bytes().chunks_exact(chunk_size)
230 }
231 PixlzrBlock::Raw(raw) => {
232 raw.data.data.chunks_exact(chunk_size)
233 }
234 }
235 }
236
237 pub fn resize(
238 &self,
239 width: u32,
240 height: u32,
241 filter: I_FilterType,
242 ) -> Self {
243 if self.dimensions() == (width, height) {
244 return self.clone();
245 }
246 #[cfg(feature = "image-rs")]
247 #[cfg(not(feature = "fir"))]
248 {
249 let mut img = PixlzrBlockImage::from(self);
250 img.width = width;
251 img.height = height;
252 img.data = img.data.resize_exact(width, height, filter);
253 return img.into();
254 }
255
256 use fast_image_resize::{images::Image, ResizeOptions};
257
258 let alpha = self.has_alpha();
259 let pixel_type = if alpha {
260 FIR_PixelType::U8x4
261 } else {
262 FIR_PixelType::U8x3
263 };
264
265 let mut dst_image = Image::new(width, height, pixel_type);
266
267 let resize_alg = filter_type_to_fir_resizing_alg(
268 filter,
269 width > self.width() || height > self.height(),
270 2,
271 );
272
273 let mut resizer = Resizer::new();
274 let mut bytes = self.as_slice().to_owned();
275 resizer
276 .resize(
277 &Image::from_slice_u8(
278 self.width(),
279 self.height(),
280 &mut bytes,
281 pixel_type,
282 )
283 .unwrap(),
284 &mut dst_image,
285 &ResizeOptions::new().resize_alg(resize_alg),
286 )
287 .unwrap();
288
289 PixlzrBlockRaw {
290 width,
291 height,
292 block_value: None,
293 data: RawImage {
294 alpha,
295 width,
296 height,
297 data: dst_image.into_vec(),
298 },
299 }
300 .into()
301 }
302}
303
304#[cfg(feature = "fir")]
305fn filter_type_to_fir_resizing_alg(
306 filter: I_FilterType,
307 upscale: bool,
308 multiplicity: u8,
309) -> ResizeAlg {
310 match filter {
311 I_FilterType::Nearest => ResizeAlg::Nearest,
312 f if upscale => match f {
313 I_FilterType::Triangle => ResizeAlg::SuperSampling(
314 FIR_FilterType::Bilinear,
315 multiplicity,
316 ),
317 I_FilterType::Lanczos3 => ResizeAlg::SuperSampling(
318 FIR_FilterType::Lanczos3,
319 multiplicity,
320 ),
321 I_FilterType::Gaussian => ResizeAlg::SuperSampling(
322 FIR_FilterType::Gaussian,
323 multiplicity,
324 ),
325 I_FilterType::CatmullRom => ResizeAlg::SuperSampling(
326 FIR_FilterType::CatmullRom,
327 multiplicity,
328 ),
329 _ => unreachable!(),
330 },
331 f => match f {
332 I_FilterType::Triangle => {
333 ResizeAlg::Convolution(FIR_FilterType::Hamming)
334 }
335 I_FilterType::Lanczos3 => {
336 ResizeAlg::Convolution(FIR_FilterType::Lanczos3)
337 }
338 I_FilterType::Gaussian => {
339 ResizeAlg::Convolution(FIR_FilterType::Gaussian)
340 }
341 I_FilterType::CatmullRom => {
342 ResizeAlg::Convolution(FIR_FilterType::CatmullRom)
343 }
344 _ => unreachable!(),
345 },
346 }
347}