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}