1#![allow(deprecated)]
42
43use fast_image_resize as fr;
44
45use png::{BitDepth, ColorType, Decoder};
46use rand::{
47 distributions::{Distribution, Uniform},
48 RngCore, SeedableRng,
49};
50
51use std::cmp;
52
53use crate::{
54 err::MoshError,
55 fx::{Mosh, MoshChunk, MoshLine},
56};
57
58pub mod err;
59pub mod fx;
60pub mod ops;
61
62#[derive(Clone)]
66pub struct MoshData {
67 pub buf: Vec<u8>,
69 pub image: Vec<u8>,
71 pub width: u32,
73 pub height: u32,
75 pub color_type: ColorType,
77 pub bit_depth: BitDepth,
79 pub line_size: usize,
81}
82
83#[derive(Clone, Debug)]
87pub struct MoshOptions {
88 pub min_rate: u16,
90 pub max_rate: u16,
92 pub pixelation: u8,
94 pub line_shift: f64,
96 pub reverse: f64,
98 pub flip: f64,
100 pub channel_swap: f64,
102 pub channel_shift: f64,
104 pub seed: u64,
106}
107
108#[derive(Clone, Default)]
112pub struct MoshCore {
113 pub data: MoshData,
114 pub options: MoshOptions,
115}
116
117impl MoshCore {
118 pub fn new() -> Self {
122 Self {
123 data: MoshData::default(),
124 options: MoshOptions::default(),
125 }
126 }
127
128 pub fn read_image(&mut self, input: &[u8]) -> Result<(), MoshError> {
134 let decoder = Decoder::new(input);
135 let mut reader = decoder.read_info()?;
136 let mut buf = vec![0_u8; reader.output_buffer_size()];
137 let info = reader.next_frame(&mut buf)?;
138
139 self.data.buf.clone_from(&buf);
140 self.data.image = buf;
141 self.data.width = info.width;
142 self.data.height = info.height;
143 self.data.color_type = info.color_type;
144 self.data.bit_depth = info.bit_depth;
145 self.data.line_size = info.line_size;
146
147 Ok(())
148 }
149
150 pub fn mosh(&mut self) -> Result<(), MoshError> {
200 self.data.mosh(&self.options)?;
201
202 Ok(())
203 }
204}
205
206impl MoshOptions {
207 fn generate_seed() -> u64 {
208 if cfg!(test) {
209 TEST_SEED
210 } else {
211 rand::thread_rng().next_u64()
212 }
213 }
214
215 pub fn new_seed(&mut self) {
217 self.seed = Self::generate_seed();
218 }
219}
220
221impl MoshData {
222 #[deprecated(since = "3.1.0", note = "Users should use MoshCore instead")]
223 pub fn new(input: &[u8]) -> Result<Self, MoshError> {
224 let decoder = Decoder::new(input);
225 let mut reader = decoder.read_info()?;
226 let mut buf = vec![0_u8; reader.output_buffer_size()];
227 let info = reader.next_frame(&mut buf)?;
228
229 Ok(Self {
230 buf: vec![0_u8],
231 image: buf,
232 width: info.width,
233 height: info.height,
234 color_type: info.color_type,
235 bit_depth: info.bit_depth,
236 line_size: info.line_size,
237 })
238 }
239
240 #[deprecated(since = "3.1.0")]
241 pub fn mosh(&mut self, options: &MoshOptions) -> Result<(), MoshError> {
242 self.buf.clone_from(&self.image);
243
244 let min_rate = options.min_rate;
245 let max_rate = cmp::max(options.min_rate, options.max_rate);
246 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(options.seed);
247 let chunk_count_distrib = Uniform::from(min_rate..=max_rate);
248 let mosh_rate = chunk_count_distrib.sample(&mut rng);
249
250 for _ in 0..mosh_rate {
251 Self::chunkmosh(self, &mut rng, options);
252 }
253
254 match self.color_type {
255 ColorType::Indexed => {
256 return Err(MoshError::UnsupportedColorType);
257 }
258 ColorType::Grayscale => {
259 Self::pixelation(self, options, fr::PixelType::U8);
260 }
261 ColorType::GrayscaleAlpha => {
262 Self::pixelation(self, options, fr::PixelType::U8x2);
263 }
264 ColorType::Rgb => {
265 Self::pixelation(self, options, fr::PixelType::U8x3);
266 }
267 ColorType::Rgba => {
268 Self::pixelation(self, options, fr::PixelType::U8x4);
269 }
270 }
271
272 Ok(())
273 }
274
275 fn pixelation(&mut self, options: &MoshOptions, pixel_type: fr::PixelType) {
276 if options.pixelation > 1 {
277 let width = self.width;
278 let height = self.height;
279 let src_image =
280 fr::images::Image::from_vec_u8(width, height, self.buf.clone(), pixel_type)
281 .unwrap();
282
283 let dest_width = self.width / u32::from(options.pixelation);
284 let dest_height = self.height / u32::from(options.pixelation);
285 let orig_width = self.width;
286 let orig_height = self.height;
287
288 let mut dest_image =
289 fr::images::Image::new(dest_width, dest_height, src_image.pixel_type());
290 let mut orig_image =
291 fr::images::Image::new(orig_width, orig_height, src_image.pixel_type());
292 let mut resizer = fr::Resizer::new();
293
294 resizer
295 .resize(
296 &src_image,
297 &mut dest_image,
298 &fr::ResizeOptions::new().resize_alg(fr::ResizeAlg::Nearest),
299 )
300 .unwrap();
301 resizer
302 .resize(
303 &dest_image,
304 &mut orig_image,
305 &fr::ResizeOptions::new().resize_alg(fr::ResizeAlg::Nearest),
306 )
307 .unwrap();
308
309 self.buf = orig_image.into_vec();
310 }
311 }
312
313 fn chunkmosh(&mut self, rng: &mut impl rand::Rng, options: &MoshOptions) {
318 let line_count = self.buf.len() / self.line_size;
319 let channel_count = match self.color_type {
320 ColorType::Grayscale | ColorType::Indexed => 1,
321 ColorType::GrayscaleAlpha => 2,
322 ColorType::Rgb => 3,
323 ColorType::Rgba => 4,
324 };
325
326 let line_shift_distrib = Uniform::from(0..self.line_size);
327 let line_number_distrib = Uniform::from(0..line_count);
328 let channel_count_distrib = Uniform::from(0..channel_count);
329
330 let first_line = line_number_distrib.sample(rng);
331 let chunk_size = line_number_distrib.sample(rng) / 2;
332 let last_line = if (first_line + chunk_size) > line_count {
333 line_count
334 } else {
335 first_line + chunk_size
336 };
337
338 let reverse = rng.gen_bool(options.reverse);
339 let flip = rng.gen_bool(options.flip);
340
341 let line_shift = rng.gen_bool(options.line_shift).then(|| {
342 let line_shift_amount = line_shift_distrib.sample(rng);
343 MoshLine::Shift(line_shift_amount)
344 });
345
346 let channel_shift = rng.gen_bool(options.channel_shift).then(|| {
347 let amount = line_shift_distrib.sample(rng) / channel_count;
348 let channel = channel_count_distrib.sample(rng);
349 MoshLine::ChannelShift(amount, channel, channel_count)
350 });
351
352 let channel_swap = rng.gen_bool(options.channel_swap).then(|| {
353 let channel_1 = channel_count_distrib.sample(rng);
354 let channel_2 = channel_count_distrib.sample(rng);
355 MoshChunk::ChannelSwap(channel_1, channel_2, channel_count)
356 });
357
358 for line_number in first_line..last_line {
359 let line_start = line_number * self.line_size;
360 let line_end = line_start + self.line_size;
361 let line = &mut self.buf[line_start..line_end];
362
363 if let Some(do_channel_shift) = &channel_shift {
364 do_channel_shift.glitch(line);
365 }
366
367 if let Some(do_line_shift) = &line_shift {
368 do_line_shift.glitch(line);
369 }
370 if reverse {
371 MoshLine::Reverse.glitch(line);
372 }
373 }
374
375 let chunk_start = first_line * self.line_size;
376 let chunk_end = last_line * self.line_size;
377 let chunk = &mut self.buf[chunk_start..chunk_end];
378
379 if let Some(do_channel_swap) = channel_swap {
380 do_channel_swap.glitch(chunk);
381 };
382
383 if flip {
384 MoshChunk::Flip.glitch(chunk);
385 };
386 }
387}
388
389impl Default for MoshData {
390 fn default() -> Self {
391 Self {
392 buf: vec![0_u8],
393 image: vec![0_u8],
394 width: 1,
395 height: 1,
396 color_type: ColorType::Rgba,
397 bit_depth: BitDepth::Eight,
398 line_size: 1,
399 }
400 }
401}
402
403impl Default for MoshOptions {
404 fn default() -> Self {
405 Self {
406 min_rate: 1,
407 max_rate: 7,
408 pixelation: 10,
409 line_shift: 0.3,
410 reverse: 0.3,
411 flip: 0.3,
412 channel_swap: 0.3,
413 channel_shift: 0.3,
414 seed: Self::generate_seed(),
415 }
416 }
417}
418
419const TEST_SEED: u64 = 901_042_006;
420
421#[cfg(test)]
422mod util;