png_glitch/
lib.rs

1pub use crate::operation::Transpose;
2use crate::operation::{Encode, Scan};
3use crate::png::Png;
4pub use crate::png::{FilterType, ScanLine};
5use std::fs::File;
6use std::io::Read;
7use std::path::Path;
8
9mod operation;
10mod png;
11
12/// PngGlitch is a crate to create a glitched PNG image.
13/// Please refer to ["The Art of PNG glitch"](https://ucnv.github.io/pnglitch/) for the description about what glitched PNG is.
14///
15/// # Examples
16///
17/// The following snippet shows how you can glitch "./etc/sample00.png" and save the generated image as "./glitched.png".
18///
19/// ```
20/// # use std::env;
21/// # env::set_current_dir(env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())).expect("");
22///
23/// use png_glitch::{FilterType, PngGlitch};
24///
25/// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
26/// png_glitch.foreach_scanline(|scan_line|{
27///   scan_line.set_filter_type(FilterType::None);
28///   let pixel = scan_line.index(4).unwrap_or(0);
29///   scan_line.update(4, pixel / 2);
30/// });
31/// png_glitch.save("./glitched.png").expect("The glitched file should be saved as a PNG file");
32/// ```
33///
34pub struct PngGlitch {
35    png: Png,
36}
37
38impl PngGlitch {
39    /// The method creates a PngGlitch object to glitch the PNG image loaded from the given file path.
40    ///
41    /// # Example
42    ///
43    /// ```
44    /// # use std::env;
45    /// # env::set_current_dir(env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())).expect("");
46    ///
47    /// use png_glitch::PngGlitch;
48    ///
49    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
50    /// ```
51    pub fn open(path: impl AsRef<Path>) -> anyhow::Result<PngGlitch> {
52        let mut file = File::open(path)?;
53        let mut buf = vec![];
54        file.read_to_end(&mut buf)?;
55        PngGlitch::new(buf)
56    }
57
58    /// The method creates a PngGlitch object to glitch the PNG image stored in a given `Vec<u8>`.
59    ///
60    /// # Example
61    ///
62    /// A PngGlitch object is created from a `Vec<u8>` object containing PNG image data in the following snippet.
63    ///
64    /// ```
65    /// use std::fs::File;
66    /// use std::io::Read;
67    /// use png_glitch::PngGlitch;
68    ///
69    /// let mut buffer = vec![];
70    /// let mut file = File::open("./etc/sample00.png").expect("The file should be opened");
71    /// file.read_to_end(&mut buffer).expect("The bytes in the file should be written into the buffer");
72    /// let mut png_glitch = PngGlitch::new(buffer).expect("The data in the buffer should be successfully parsed as PNG");
73    /// ```
74    pub fn new(buffer: Vec<u8>) -> anyhow::Result<PngGlitch> {
75        let png = Png::try_from(&buffer as &[u8])?;
76        Ok(PngGlitch { png })
77    }
78
79    /// The method returns a list of [scan line](https://www.w3.org/TR/2003/REC-PNG-20031110/#4Concepts.EncodingScanlineAbs%22). in the given PNG file.
80    ///
81    /// # Example
82    ///
83    /// The following example changes the filter type of each scan line according its position
84    ///
85    /// ```
86    /// # use std::env;
87    /// # env::set_current_dir(env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())).expect("");
88    /// use png_glitch::{FilterType, PngGlitch};
89    ///
90    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
91    /// for (index, scan_line) in png_glitch.scan_lines().iter_mut().enumerate() {
92    ///    let filter_type = if index % 2 == 0 {
93    ///        FilterType::None
94    ///    } else {
95    ///        FilterType::Average
96    ///    };
97    ///    scan_line.set_filter_type(filter_type);
98    /// }
99    /// ```
100    pub fn scan_lines(&self) -> Vec<ScanLine> {
101        self.png.scan_lines()
102    }
103
104    /// The method takes the specified number of ScanLine objects at most.
105    /// The maximum number of ScanLines is specified as `lines` parameter.
106    /// The `from` parameter specifies the index of first ScanLine.
107    ///
108    /// # Example
109    /// ```
110    /// use png_glitch::{FilterType, PngGlitch};
111    ///
112    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
113    /// let scan_liens = png_glitch.scan_lines_from(5, 10);
114    /// ```
115    pub fn scan_lines_from(&self, from: u32, lines: u32) -> Vec<ScanLine> {
116        self.png.scan_lines_from(from as usize, lines as usize)
117    }
118
119    /// The method allows you to manipulate for each [scan line](https://www.w3.org/TR/2003/REC-PNG-20031110/#4Concepts.EncodingScanlineAbs%22).
120    /// The modifier function is called with a `ScanLine` object which represents a scan line.
121    ///
122    /// # Example
123    ///
124    /// The following example changes the filter method of all scan line to None.
125    ///
126    /// ```
127    /// # use std::env;
128    /// # env::set_current_dir(env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())).expect("");
129    ///
130    /// use png_glitch::{FilterType, PngGlitch};
131    ///
132    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
133    /// png_glitch.foreach_scanline(|scan_line|{
134    ///    scan_line.set_filter_type(FilterType::None);
135    /// });
136    /// ```
137    pub fn foreach_scanline<F>(&self, modifier: F)
138    where
139        F: FnMut(&mut ScanLine),
140    {
141        self.png.foreach_scanline(modifier)
142    }
143
144    /// The method saves the glitched image as a PNG file to the given path.
145    ///
146    /// # Example
147    ///
148    /// The following example copies `./etc/sample00.png` as `./glitched.png`.
149    /// ```
150    /// use png_glitch::{FilterType, PngGlitch};
151    ///
152    /// let png_glitch = PngGlitch::open("etc/sample00.png").expect("The PNG file should be successfully parsed");
153    /// png_glitch.save("./glitched.png").expect("The glitched PNG data should be saved to the given path");
154    /// ```
155    pub fn save(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
156        self.png.save(path)
157    }
158
159    /// The method encodes the glitched image as a PNG data and write the encoded data to the given buffer.
160    ///
161    /// # Example
162    ///
163    /// The following example writes a PNG format data into the `encoded_data`.
164    ///
165    /// ```
166    /// # use std::env;
167    /// # env::set_current_dir(env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())).expect("");
168    /// use png_glitch::PngGlitch;
169    ///
170    /// let png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
171    /// let mut encoded_data:Vec<u8> = vec![];
172    /// png_glitch.encode(&mut encoded_data).expect("The glitched PNG data should be written into the encoded_data in PNG format");
173    /// ```
174    pub fn encode(&self, buffer: &mut Vec<u8>) -> anyhow::Result<()> {
175        self.png.encode(buffer)?;
176        Ok(())
177    }
178
179    /// The method returns the width of the loaded PNG file
180    ///
181    /// # Example
182    ///
183    /// The following example retrieves width of ./etc/sample00.png
184    ///
185    /// ```
186    /// # use std::env;
187    /// # env::set_current_dir(env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())).expect("");
188    /// use png_glitch::PngGlitch;
189    ///
190    /// let png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
191    /// let width = png_glitch.width();
192    /// ```
193    pub fn width(&self) -> u32 {
194        self.png.width()
195    }
196
197    /// The method returns the height of the loaded PNG file
198    ///
199    /// # Example
200    ///
201    /// The following example retrieves height of ./etc/sample00.png
202    ///
203    /// ```
204    /// # use std::env;
205    /// # env::set_current_dir(env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())).expect("");
206    /// use png_glitch::PngGlitch;
207    ///
208    /// let png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
209    /// let width = png_glitch.width();
210    /// ```
211    pub fn height(&self) -> u32 {
212        self.png.height()
213    }
214
215    /// The method copies the lines starting from src to dest
216    ///
217    /// # Example
218    ///
219    /// ```
220    /// use png_glitch::PngGlitch;
221    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
222    /// let width = png_glitch.transpose(2, 5, 10);
223    /// ```
224    pub fn transpose(&mut self, src: u32, dst: u32, lines: u32) {
225        self.png.transpose(src as usize, dst as usize, lines)
226    }
227
228    /// The method removes filter from all scan lines.
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// use png_glitch::PngGlitch;
234    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
235    /// png_glitch.remove_filter();
236    /// png_glitch.save("./etc/removed-all.png").expect("The PNG file should be successfully saved")
237    /// ```
238    pub fn remove_filter(&mut self) {
239        self.png.remove_filter();
240    }
241
242    /// The method removes filter from the scan lines in specified region
243    ///
244    /// # Example
245    ///
246    /// ```
247    /// use png_glitch::PngGlitch;
248    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
249    /// png_glitch.remove_filter_from(5, 10); // Remove filter from the scan line #5 - # 14
250    /// png_glitch.save("./etc/removed-partial.png").expect("The PNG file should be successfully saved")
251    /// ```
252    pub fn remove_filter_from(&mut self, from: u32, lines: u32) {
253        self.png.remove_filter_from(from, lines);
254    }
255
256    /// The method removes filter from all scan lines.
257    ///
258    /// # Example
259    ///
260    /// ```
261    /// use png_glitch::{FilterType, PngGlitch};
262    /// let mut png_glitch = PngGlitch::open("./etc/none.png").expect("The PNG file should be successfully parsed");
263    /// png_glitch.apply_filter(FilterType::Sub);
264    /// png_glitch.save("./etc/filter-all.png").expect("The PNG file should be successfully saved")
265    /// ```
266    pub fn apply_filter(&mut self, filter: FilterType) {
267        self.png.apply_filter(filter);
268    }
269
270    /// The method removes filter from scan lines in specified region
271    ///
272    /// # Example
273    ///
274    /// ```
275    /// use png_glitch::{FilterType, PngGlitch};
276    /// let mut png_glitch = PngGlitch::open("./etc/none.png").expect("The PNG file should be successfully parsed");
277    /// png_glitch.apply_filter_from(FilterType::Sub, 5, 3); // Apply sub filter to the scan line #5, #6, and #7.
278    /// png_glitch.save("./etc/filter-partial.png").expect("The PNG file should be successfully saved")
279    /// ```
280    pub fn apply_filter_from(&mut self, filter_type: FilterType, from: u32, lines: u32) {
281        self.png.apply_filter_from(filter_type, from, lines);
282    }
283
284    /// The method changes the filter type of all scan lines.
285    /// It first calculates the original pixel value (decodes it),
286    /// and then calculates the scan line data based on the new filter type.
287    ///
288    /// # Example
289    ///
290    /// ```
291    /// use png_glitch::{FilterType, PngGlitch};
292    /// let mut png_glitch = PngGlitch::open("./etc/sample00.png").expect("The PNG file should be successfully parsed");
293    /// png_glitch.change_filter_type(FilterType::Sub);
294    /// png_glitch.save("./etc/changed-all.png").expect("The PNG file should be successfully saved")
295    /// ```
296    pub fn change_filter_type(&mut self, filter_type: FilterType) {
297        self.png.change_filter_type(filter_type);
298    }
299}
300
301#[cfg(test)]
302mod test {
303    use super::*;
304
305    #[test]
306    fn test_change_filter_type() -> anyhow::Result<()> {
307        let bytes = include_bytes!("../etc/sample00.png");
308
309        // 1. Baseline: Raw (removed filter)
310        let mut png_raw = PngGlitch::new(bytes.to_vec())?;
311        png_raw.remove_filter();
312
313        // 2. Test Subject
314        let mut png_glitch = PngGlitch::new(bytes.to_vec())?;
315
316        // Change to Sub
317        png_glitch.change_filter_type(FilterType::Sub);
318
319        for scan_line in png_glitch.scan_lines() {
320            assert_eq!(FilterType::Sub, scan_line.filter_type());
321        }
322
323        // Change back to None
324        png_glitch.change_filter_type(FilterType::None);
325        for scan_line in png_glitch.scan_lines() {
326            assert_eq!(FilterType::None, scan_line.filter_type());
327        }
328
329        // Compare with Baseline (png_raw)
330        let raw_lines = png_raw.scan_lines();
331        let glitched_lines = png_glitch.scan_lines();
332
333        assert_eq!(glitched_lines.len(), raw_lines.len());
334
335        for (g_line, r_line) in glitched_lines.iter().zip(raw_lines.iter()) {
336            let g_size = g_line.size();
337            let r_size = r_line.size();
338            assert_eq!(g_size, r_size);
339
340            for i in 0..g_size {
341                assert_eq!(
342                    g_line.index(i),
343                    r_line.index(i),
344                    "Pixel mismatch at index {}",
345                    i
346                );
347            }
348        }
349
350        Ok(())
351    }
352}