classpad_image/
lib.rs

1//! Image converter for the Casio fx-CP400 calculator.
2//!
3//! Provides functions to convert an image with a common format (PNG, JPG, WEBP, BMP) into a C2P file, or multiple image files into a C2B animation file.
4//! Also provides functions to convert C2P and C2B files back into more common formats.
5
6use image::{GenericImageView, Pixel};
7use std::error::Error;
8
9/// The largest possible image width on the fx-CP400.
10/// MAX_IMAGE_WIDTH is equal to 310 (0x136).
11pub const MAX_IMAGE_WIDTH: u32 = 0x136; // 310
12
13/// The largest possible image height on the fx-CP400.
14/// MAX_IMAGE_HEIGHT is equal to 401 (0x191).
15pub const MAX_IMAGE_HEIGHT: u32 = 0x191; // 401
16
17/// Function to convert an image to C2B.
18///
19/// Takes two arguments: a vector of image paths, which must point to valid image files with PNG, JPG, WEBP or BMP format.
20/// At least two images must be supplied.
21/// The second argument is the destination of the C2B file.
22///
23/// Example:
24/// ```
25/// let paths: Vec<String> = vec![String::from("path/to/file1.png"), String::from("path/to/file2.png")];
26/// convert_img_to_c2b(paths, String::from("path/to/destination.c2b")).unwrap();
27/// ```
28pub fn convert_img_to_c2b(
29    image_paths: Vec<String>,
30    destination: String,
31) -> Result<(), Box<dyn Error>> {
32    if image_paths.len() < 2 {
33        eprintln!("At least two images have to be supplied!");
34        return Ok(());
35    }
36
37    let image = image::ImageReader::open(image_paths[0].clone())
38        .expect("Failed to open image!")
39        .decode()
40        .expect("Failed to decode image!");
41    let (mut width, mut height): (u32, u32) = (image.width(), image.height());
42    (width, height) = fit_image_dimensions(width, height);
43    assert!(width > 0 && height > 0);
44
45    let mut images_data: Vec<u8> = Vec::new();
46    for path in &image_paths {
47        let image = image::ImageReader::open(path)
48            .expect("Failed to open image!")
49            .decode()
50            .expect("Failed to decode image!");
51        let (i_width, i_height): (u32, u32) = (image.width(), image.height());
52        assert!(i_width > 0 && i_height > 0);
53
54        let image = if i_width > MAX_IMAGE_WIDTH || i_height > MAX_IMAGE_HEIGHT {
55            eprintln!("Image too big! scaling down...");
56            image.resize(width, height, image::imageops::FilterType::Lanczos3)
57        } else {
58            image
59        };
60
61        let img_data = bitmap_to_rgb565_data(image.clone());
62        let compressed_img_data = compress(img_data);
63
64        // Segment length
65        let f = compressed_img_data.len();
66        let f4 = f >> 24 & 0xFF;
67        let f3 = f >> 16 & 0xFF;
68        let f2 = f >> 8 & 0xFF;
69        let f1 = f & 0xFF;
70
71        images_data.extend_from_slice(&[f4 as u8, f3 as u8, f2 as u8, f1 as u8]);
72        images_data.extend_from_slice(&compressed_img_data);
73    }
74    let header = &get_c2b_header(
75        images_data.len(),
76        image_paths.len(),
77        width as usize,
78        height as usize,
79    );
80    let footer = &get_c2b_footer();
81    let mut new_file: Vec<u8> = Vec::new();
82
83    new_file.extend_from_slice(header);
84    new_file.extend_from_slice(&images_data);
85    new_file.extend_from_slice(footer);
86    std::fs::write(destination, new_file)?;
87
88    Ok(())
89}
90
91/// Function to convert an image to C2P.
92///
93/// Takes two arguments: an image path, which must point to a valid image file with PNG, JPG, WEBP or BMP format.
94/// The second argument is the destination of the C2P file.
95///
96/// Example:
97/// ```
98/// let path: &str = "path/to/file.png";
99/// convert_img_to_c2p(path, String::from("path/to/destination.c2p")).unwrap();
100/// ```
101pub fn convert_img_to_c2p(image_path: &str, destination: String) -> Result<(), Box<dyn Error>> {
102    let image = image::ImageReader::open(image_path)
103        .expect("Failed to open image!")
104        .decode()
105        .expect("Failed to decode image!");
106    let (width, height): (u32, u32) = fit_image_dimensions(image.width(), image.height());
107    assert!(width > 0 && height > 0);
108
109    let image = if width > MAX_IMAGE_WIDTH || height > MAX_IMAGE_HEIGHT {
110        eprintln!("Image too big! scaling down...");
111        let (new_width, new_height) = (
112            (width / (width / MAX_IMAGE_WIDTH)).clamp(1, MAX_IMAGE_WIDTH),
113            (height / (height / MAX_IMAGE_HEIGHT)).clamp(1, MAX_IMAGE_HEIGHT),
114        );
115        image.resize(new_width, new_height, image::imageops::FilterType::Lanczos3)
116    } else {
117        image
118    };
119
120    let img_data = bitmap_to_rgb565_data(image.clone());
121    let compressed_img_data = compress(img_data);
122
123    let header = &get_c2p_header(
124        compressed_img_data.len(),
125        image.width() as usize,
126        image.height() as usize,
127    );
128
129    let footer = &get_c2p_footer();
130
131    let mut new_file = Vec::new();
132    new_file.extend_from_slice(header);
133    new_file.extend_from_slice(&compressed_img_data);
134    new_file.extend_from_slice(footer);
135
136    std::fs::write(destination, new_file)?;
137
138    Ok(())
139}
140
141/// Function to convert a C2P image into a normal image file.
142///
143/// Takes two arguments: an image path, which must point to a valid C2P image.
144/// The second argument is the destination of the converted image file.
145///
146/// Example:
147/// ```
148/// let path: &str = "path/to/file.c2p";
149/// convert_c2p_to_img(path, String::from("path/to/destination.png")).unwrap();
150/// ```
151pub fn convert_c2p_to_img(c2p_path: &str, destination: String) -> Result<(), Box<dyn Error>> {
152    let c2p_data: Vec<u8> = std::fs::read(c2p_path)?;
153    let (header, slice) = c2p_data.split_at(0xDC);
154    let (compressed_data, _footer) = slice.split_at(slice.len() - 0x17C);
155
156    let (width, height): (u32, u32) = (
157        ((header[0xC2] as u32) << 8) + header[0xC3] as u32,
158        ((header[0xC4] as u32) << 8) + header[0xC5] as u32,
159    );
160
161    let bytes = miniz_oxide::inflate::decompress_to_vec_zlib(compressed_data)
162        .expect("Failed to decompress compressed c2p image data!");
163
164    let mut img_buffer = image::RgbImage::new(width, height);
165
166    let (mut x, mut y) = (0, 0);
167    for i in (0..bytes.len()).step_by(2) {
168        let pixel = img_buffer.get_pixel_mut(x, y);
169
170        let rgb565: u32 = ((bytes[i] as u32) << 8) + bytes[i + 1] as u32;
171
172        let r5 = rgb565 >> 11;
173        let g6 = (rgb565 >> 5) & 0b111111;
174        let b5 = rgb565 & 0b11111;
175
176        let r = (r5 as f32 / 31.0 * 255.0) as u8;
177        let g = (g6 as f32 / 63.0 * 255.0) as u8;
178        let b = (b5 as f32 / 31.0 * 255.0) as u8;
179
180        *pixel = image::Rgb::from([r, g, b]);
181
182        x += 1;
183        if x == img_buffer.width() {
184            x = 0;
185            y += 1
186        }
187    }
188
189    img_buffer.save(destination)?;
190    Ok(())
191}
192
193/// Function to convert a C2B image into a group of normal image file.
194///
195/// Takes two arguments: an image path, which must point to a valid C2B image.
196/// The second argument is the destination of the converted image files.
197/// All images following the first will be named after the first with their respective index.
198///
199/// Example:
200/// ```
201/// let path: &str = "path/to/file.c2b";
202/// convert_c2p_to_img(path, String::from("path/to/destination.png")).unwrap();
203/// ```
204pub fn convert_c2b_to_imgs(c2b_path: &str, destination: String) -> Result<(), Box<dyn Error>> {
205    let c2b_data: Vec<u8> = std::fs::read(c2b_path)?;
206    let (header, data) = c2b_data.split_at(0xD8);
207
208    let (width, height): (u32, u32) = (
209        ((header[0xC2] as u32) << 8) + header[0xC3] as u32,
210        ((header[0xC4] as u32) << 8) + header[0xC5] as u32,
211    );
212
213    let mut image_block_len = (((data[0x00] as u32) << 24)
214        + ((data[0x01] as u32) << 16)
215        + ((data[0x02] as u32) << 8)
216        + (data[0x03] as u32)) as usize;
217    let mut offset: usize = 0x04;
218    let mut index: usize = 0;
219
220    while data[offset] == 0x78 && data[offset + 1] == 0x9C {
221        let (mut x, mut y) = (0, 0);
222        let mut img_buffer = image::RgbImage::new(width, height);
223        let data_buffer = &data[offset..offset + image_block_len];
224        let bytes = miniz_oxide::inflate::decompress_to_vec_zlib(data_buffer)
225            .expect("Failed to decompress compressed c2p image data!");
226
227        for i in (0..bytes.len()).step_by(2) {
228            let pixel = img_buffer.get_pixel_mut(x, y);
229
230            let rgb565: u32 = ((bytes[i] as u32) << 8) + bytes[i + 1] as u32;
231
232            let r5 = rgb565 >> 11;
233            let g6 = (rgb565 >> 5) & 0b111111;
234            let b5 = rgb565 & 0b11111;
235
236            let r = (r5 as f32 / 31.0 * 255.0) as u8;
237            let g = (g6 as f32 / 63.0 * 255.0) as u8;
238            let b = (b5 as f32 / 31.0 * 255.0) as u8;
239
240            *pixel = image::Rgb::from([r, g, b]);
241
242            x += 1;
243            if x == img_buffer.width() {
244                x = 0;
245                y += 1
246            }
247        }
248
249        let tmp = image_block_len;
250        image_block_len = (((data[offset + image_block_len + 0x00] as u32) << 24)
251            + ((data[offset + image_block_len + 0x01] as u32) << 16)
252            + ((data[offset + image_block_len + 0x02] as u32) << 8)
253            + (data[offset + image_block_len + 0x03] as u32)) as usize;
254        offset += tmp + 4;
255
256        let (file_dest, ext) = destination.rsplit_once('.').unwrap();
257        let destination = format!("{file_dest}{index}.{ext}");
258        index += 1;
259
260        img_buffer.save(&destination)?;
261    }
262
263    Ok(())
264}
265
266/// Function to scale the initial image dimensions to fit into the maximum image scale.
267fn fit_image_dimensions(width: u32, height: u32) -> (u32, u32) {
268    let (mut new_width, mut new_height) = (width, height);
269
270    if width > MAX_IMAGE_WIDTH || height > MAX_IMAGE_HEIGHT {
271        eprintln!("Image too big! scaling down...");
272        if width < height {
273            let aspect_ratio: f32 = width as f32 / height as f32;
274            new_height = MAX_IMAGE_HEIGHT;
275            new_width = (aspect_ratio * new_height as f32) as u32;
276        } else {
277            let aspect_ratio: f32 = height as f32 / width as f32;
278            new_width = MAX_IMAGE_WIDTH;
279            new_height = (aspect_ratio * new_width as f32) as u32;
280        }
281
282        eprintln!("New size: {} x {}", new_width, new_height);
283    }
284
285    (new_width, new_height)
286}
287
288/// Function to convert the color data of the image into the RGB565 color format.
289fn bitmap_to_rgb565_data(image: image::DynamicImage) -> Vec<u8> {
290    let mut image_data = Vec::new();
291
292    for y in 0..image.height() {
293        for x in 0..image.width() {
294            let c = image.get_pixel(x, y).to_rgb().0;
295
296            let (r, g, b) = (
297                convert_range(0, 255, 0, 0x1F, c[0] as usize),
298                convert_range(0, 255, 0, 0x3F, c[1] as usize),
299                convert_range(0, 255, 0, 0x1F, c[2] as usize),
300            );
301
302            let rgb565: usize = (r << 11) + (g << 5) + b;
303            let rgb565_2: u8 = (rgb565 >> 8 & 0xFF) as u8;
304            let rgb565_1: u8 = (rgb565 & 0xFF) as u8;
305
306            image_data.push(rgb565_2);
307            image_data.push(rgb565_1);
308        }
309    }
310    image_data
311}
312
313/// Function that converts the given range to correctly display the bytes.
314fn convert_range(
315    original_start: usize,
316    original_end: usize,
317    new_start: usize,
318    new_end: usize,
319    value: usize,
320) -> usize {
321    let scale: f64 = (new_end - new_start) as f64 / (original_end - original_start) as f64;
322    (new_start as f64 + ((value - original_start) as f64 * scale)) as usize
323}
324
325/// Compresses the given image data with the default zlib compression.
326fn compress(input: Vec<u8>) -> Vec<u8> {
327    miniz_oxide::deflate::compress_to_vec_zlib(&input, 6)
328}
329
330/// Function that returns the C2P file header, with certain bytes changed depending on the file size.
331fn get_c2p_header(image_data_size: usize, width: usize, height: usize) -> Vec<u8> {
332    let file_size: usize = image_data_size + 0xDC + 0x17C; // image size + header size + footer size
333    let a = !(file_size & 0xFFFFFF) & 0xFFFFFF;
334    let a3 = (a >> 16 & 0xFF) as u8;
335    let a2 = (a >> 8 & 0xFF) as u8;
336    let a1 = (a & 0xFF) as u8;
337
338    let b1 = ((0x1D1 - (file_size & 0xFF)) & 0xFF) as u8;
339
340    let c = file_size - 0x20;
341    let c4 = (c >> 24 & 0xFF) as u8;
342    let c3 = (c >> 16 & 0xFF) as u8;
343    let c2 = (c >> 8 & 0xFF) as u8;
344    let c1 = (c & 0xFF) as u8;
345
346    let d = file_size - 0x234;
347    let d4 = (d >> 24 & 0xFF) as u8;
348    let d3 = (d >> 16 & 0xFF) as u8;
349    let d2 = (d >> 8 & 0xFF) as u8;
350    let d1 = (d & 0xFF) as u8;
351
352    let e = file_size - 0x254;
353    let e4 = (e >> 24 & 0xFF) as u8;
354    let e3 = (e >> 16 & 0xFF) as u8;
355    let e2 = (e >> 8 & 0xFF) as u8;
356    let e1 = (e & 0xFF) as u8;
357
358    let w = width & 0xFFFF;
359    let h = height & 0xFFFF;
360    let w2 = (w >> 8 & 0xFF) as u8;
361    let w1 = (w & 0xFF) as u8;
362    let h2 = (h >> 8 & 0xFF) as u8;
363    let h1 = (h & 0xFF) as u8;
364
365    let f = file_size - 0x258;
366    let f4 = (f >> 24 & 0xFF) as u8;
367    let f3 = (f >> 16 & 0xFF) as u8;
368    let f2 = (f >> 8 & 0xFF) as u8;
369    let f1 = (f & 0xFF) as u8;
370
371    vec![
372        0xBC, 0xBE, 0xAC, 0xB6, 0xB0, 0xFF, 0xFF, 0xFF, 0x9C, 0xCD, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF,
373        0xFF, 0xFF, 0xFE, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, a3, a2, a1, b1, 0x00, 0x00, 0x00, 0x00,
374        0x00, 0x43, 0x43, 0x30, 0x31, 0x30, 0x30, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x43, 0x50, 0x00,
375        0x00, 0x00, c4, c3, c2, c1, 0x00, 0x00, 0x00, 0x09, d4, d3, d2, d1, 0x00, 0x00, 0x00, 0x00,
376        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
377        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
378        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
379        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
380        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
381        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
382        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
383        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
384        0x30, 0x31, 0x30, 0x30, e4, e3, e2, e1, 0x00, 0x00, w2, w1, h2, h1, 0x00, 0x10, 0x00, 0xFF,
385        0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, f4, f3,
386        f2, f1,
387    ]
388}
389
390/// Function that returns the C2P footer.
391/// This is the same for all C2P files.
392fn get_c2p_footer() -> Vec<u8> {
393    vec![
394        0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x8C, 0x07, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
395        0x00, 0x00, 0x00, 0x60, 0x00, 0x07, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
396        0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x05,
397        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x98, 0x04, 0x60, 0x00, 0x00,
398        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
399        0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
400        0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x98, 0x00,
401        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x28, 0x31, 0x85,
402        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x28, 0x32, 0x00, 0x00, 0x00, 0x00,
403        0x00, 0x00, 0x00, 0x09, 0x98, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x31,
404        0x30, 0x30, 0xE0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
405        0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
406        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x02, 0x50, 0x00,
407        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
408        0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
409        0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
410        0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x00, 0x00,
411        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
412        0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
413        0x00, 0x10, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01,
414        0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x07, 0x00, 0x00,
415        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x14, 0x15, 0x93, 0x00, 0x00,
416        0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x14, 0x15, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00,
417        0x00, 0x10, 0x00, 0x03, 0x14, 0x15, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00,
418        0x03, 0x14, 0x15, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x01, 0x01,
419        0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
420    ]
421}
422
423/// Function that returns the C2B file header, with certain bytes changed depending on the file size.
424fn get_c2b_header(
425    image_data_size: usize,
426    total_image_count: usize,
427    width: usize,
428    height: usize,
429) -> Vec<u8> {
430    let file_size: usize = image_data_size + 0xD8 + 0x1A8; // image size + header size + footer size
431    let a = !(file_size & 0xFFFFFF) & 0xFFFFFF;
432    let a3 = (a >> 16 & 0xFF) as u8;
433    let a2 = (a >> 8 & 0xFF) as u8;
434    let a1 = (a & 0xFF) as u8;
435
436    let b1 = ((0x1D0 - (file_size & 0xFF)) & 0xFF) as u8;
437
438    let c = file_size - 0x20;
439    let c4 = (c >> 24 & 0xFF) as u8;
440    let c3 = (c >> 16 & 0xFF) as u8;
441    let c2 = (c >> 8 & 0xFF) as u8;
442    let c1 = (c & 0xFF) as u8;
443
444    let d = file_size - 0x260;
445    let d4 = (d >> 24 & 0xFF) as u8;
446    let d3 = (d >> 16 & 0xFF) as u8;
447    let d2 = (d >> 8 & 0xFF) as u8;
448    let d1 = (d & 0xFF) as u8;
449
450    let e = file_size - 0x280;
451    let e4 = (e >> 24 & 0xFF) as u8;
452    let e3 = (e >> 16 & 0xFF) as u8;
453    let e2 = (e >> 8 & 0xFF) as u8;
454    let e1 = (e & 0xFF) as u8;
455
456    let w = width & 0xFFFF;
457    let h = height & 0xFFFF;
458    let w2 = (w >> 8 & 0xFF) as u8;
459    let w1 = (w & 0xFF) as u8;
460    let h2 = (h >> 8 & 0xFF) as u8;
461    let h1 = (h & 0xFF) as u8;
462
463    let i = total_image_count as u8;
464
465    vec![
466        0xBC, 0xBE, 0xAC, 0xB6, 0xB0, 0xFF, 0xFF, 0xFF, 0x9C, 0xCD, 0x9D, 0xFF, 0xFF, 0xFF, 0xFF,
467        0xFF, 0xFF, 0xFE, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, a3, a2, a1, b1, 0x00, 0x00, 0x00, 0x00,
468        0x00, 0x43, 0x43, 0x30, 0x31, 0x30, 0x30, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x43, 0x50, 0x00,
469        0x00, 0x00, c4, c3, c2, c1, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, d4, d3, d2, d1,
470        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7C, 0x00, 0x00, 0x00,
471        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
472        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
473        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
474        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
475        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
476        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
477        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
478        0x30, 0x31, 0x30, 0x30, e4, e3, e2, e1, 0x00, 0x00, w2, w1, h2, h1, 0x00, 0x10, 0x00, 0xFF,
479        0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x01, 0x00, i, 0xFF, 0xFF, 0xFF, 0xFF,
480    ]
481}
482
483/// Function that returns the C2B footer.
484/// Note: this is the same for all files in this program. There are certain bytes that change the size of the window in the 'Picture Plot' program, which are ignored here.
485fn get_c2b_footer() -> Vec<u8> {
486    vec![
487        0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
488        0x00, 0x00, 0x00, 0x10, 0x01, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
489        0x10, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03,
490        0x24, 0x67, 0x53, 0x24, 0x67, 0x53, 0x25, 0x00, 0x00, 0x09, 0x97, 0x01, 0x02, 0x54, 0x71,
491        0x69, 0x81, 0x13, 0x21, 0x00, 0x00, 0x10, 0x01, 0x01, 0x07, 0x45, 0x28, 0x30, 0x18, 0x86,
492        0x79, 0x00, 0x00, 0x10, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
493        0x10, 0x00, 0x02, 0x35, 0x84, 0x90, 0x56, 0x60, 0x37, 0x74, 0x00, 0x00, 0x09, 0x97, 0x00,
494        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x28, 0x31, 0x85,
495        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x28, 0x32, 0x00, 0x00, 0x00, 0x00,
496        0x00, 0x00, 0x00, 0x09, 0x98, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x31,
497        0x30, 0x30, 0xE0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
498        0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
499        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x02, 0x50, 0x00,
500        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
501        0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
502        0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
503        0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x00, 0x00,
504        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
505        0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
506        0x00, 0x10, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01,
507        0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x07, 0x00, 0x00,
508        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x14, 0x15, 0x93, 0x00, 0x00,
509        0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x14, 0x15, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00,
510        0x00, 0x10, 0x00, 0x03, 0x14, 0x15, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00,
511        0x03, 0x14, 0x15, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x01, 0x01,
512        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
513        0x50, 0x47, 0x54, 0x69, 0x6D, 0x65, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
514        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
515        0x00, 0x00, 0x09, 0x99,
516    ]
517}