Создание формата .mamamia
बनाया गया: 18 जनवरी 2025
बनाया गया: 18 जनवरी 2025
Crafting compression code
I’m coding a new image format, .mamamia, in Rust. This involves creating structs, compressing methods, and testing different compression techniques.
Crafting image reassembly
I’m working on a reassembler in Rust for .mamamia format. It reads images, uses LZW or DEFLATE via flate2 crate, stores in .mamamia, and handles memory and file functions.
Piecing together file handling
I’m outlining Rust functions for file and memory management, including file opening, reading, writing, and closing, as well as memory allocation. This leads me to integrate the flate2 crate for compression.
Mapping out the structure
I’m defining the MamamiaHeader struct with fields like signature, compression method, image dimensions, and color type, aiming to store compressed data efficiently in a single file.
Crafting the reassembler
I'm working on a Rust reassembler with a memory allocation function using Vec. It will support various image compression methods, organized in a single file for ease.
Crafting the code
Creating a Rust code snippet for memory allocation and file handling, considering vectors and raw pointers. Shifting focus to simplify usage and meet the user's needs.
Thought about image format and compression for 13 seconds
Ниже приведён пример (демонстрационный) кода на Rust, который:
file_open
, file_read
, file_write
, file_close
.memory_allocate
.image
).Обратите внимание, что этот код носит учебно-демонстрационный характер и не претендует на полноценную реализацию промышленного качества. Но он даёт общее представление о том, как можно создать свой формат и «поиграться» с разными методами сжатия, сохранив логику в одном месте.
Для использования некоторых библиотек (например, flate2
для DEFLATE и image
для чтения/записи популярных форматов) необходимо добавить зависимости. Пример минимального Cargo.toml
:
[package] name = "mamamia_compressor" version = "0.1.0" edition = "2021" [dependencies] image = "0.24.6" flate2 = "1.0" # В случае, если захотите реализовывать LZW вручную, зависимости могут не потребоваться, # но при использовании готовых реализаций может понадобиться другая библиотека.
Ниже — пример содержимого main.rs
. Для упрощения в одном файле собраны все основные моменты:
use std::fs::{File, OpenOptions}; use std::io::{BufReader, BufWriter, Read, Write, Seek, SeekFrom}; use std::path::Path; /// Простой enum для режима открытия файла #[derive(Debug)] pub enum FileMode { Read, Write, } /// Структура заголовка нового формата .mamamia. /// Очень упрощённая, для демонстрации. #[repr(C)] #[derive(Debug)] pub struct MamamiaHeader { /// Подпись/«магическое число» для идентификации формата pub signature: [u8; 7], // 'MAMAMIA' /// Версия формата (демонстрационно) pub version: u8, /// Ширина изображения pub width: u32, /// Высота изображения pub height: u32, /// Количество каналов (например, 3 для RGB, 4 для RGBA) pub channels: u8, /// Метод сжатия (0 = без сжатия, 1 = LZW, 2 = DEFLATE, 3 = LOSSY_JPEG) pub compression_method: u8, } /// Демонстрационная структура для «пересборки»/упаковки/распаковки pub struct Reassembler; impl Reassembler { /// Сжать картинку и сохранить в формате .mamamia /// compression_method: /// 0 - без сжатия /// 1 - LZW (демонстрационная заглушка или ваша реализация) /// 2 - DEFLATE (через crate flate2) /// 3 - JPEG (потеря качества, но сильнее сжатие) pub fn compress_image( input_image_path: &str, output_mamamia_path: &str, compression_method: u8, ) -> std::io::Result<()> { // Откроем исходное изображение (любого формата, поддерживаемого crate `image`) let img = image::open(input_image_path) .expect("Не удалось открыть входное изображение"); let width = img.width(); let height = img.height(); let color = img.color(); // Определяем количество каналов let channels = match color { image::ColorType::Rgb8 => 3, image::ColorType::Rgba8 => 4, // Для упрощения рассмотрим только эти варианты: _ => 3, }; // Собираем пиксели в вектор let raw_data = img.to_bytes(); // Создадим заголовок let header = MamamiaHeader { signature: *b"MAMAMIA", // 7 байт version: 1, width, height, channels, compression_method, }; // Подготовим сжатые данные let compressed_data = match compression_method { 1 => Self::compress_lzw(&raw_data)?, 2 => Self::compress_deflate(&raw_data)?, 3 => Self::compress_lossy_jpeg(&img)?, _ => raw_data.clone(), // без сжатия }; // Откроем выходной файл .mamamia let mut out_file = file_open(output_mamamia_path, FileMode::Write)?; // Запишем заголовок file_write_header(&mut out_file, &header)?; // Запишем сжатые байты file_write(&mut out_file, &compressed_data)?; file_close(out_file)?; Ok(()) } /// Прочитать .mamamia-файл и «распаковать» его в обычный PNG (для примера). pub fn decompress_image( input_mamamia_path: &str, output_image_path: &str, ) -> std::io::Result<()> { // Откроем входной файл let mut in_file = file_open(input_mamamia_path, FileMode::Read)?; // Считаем заголовок let header = file_read_header(&mut in_file)?; // Проверяем «подпись» if &header.signature != b"MAMAMIA" { panic!("Неверный формат файла, подпись не совпадает!"); } let width = header.width; let height = header.height; let channels = header.channels; let compression_method = header.compression_method; // Узнаем размер оставшейся части файла (сжатых данных) let current_pos = in_file.stream_position()?; let total_size = in_file.seek(SeekFrom::End(0))?; let compressed_size = (total_size - current_pos) as usize; // Считаем сжатые байты in_file.seek(SeekFrom::Start(current_pos))?; let mut compressed_data = memory_allocate(compressed_size); let _ = file_read(&mut in_file, &mut compressed_data)?; file_close(in_file)?; // Распакуем let decompressed_data = match compression_method { 1 => Self::decompress_lzw(&compressed_data)?, 2 => Self::decompress_deflate(&compressed_data)?, 3 => { // Для JPEG-метода в демонстрационном сценарии // могли бы хранить исходный JPEG (без перекодирования) // Но для наглядности здесь будем считать, // что мы просто вернули готовые пиксели. // Реализация на практике сложнее. panic!("Демонстрационная заглушка для JPEG: нет реального декодирования!"); }, _ => compressed_data, }; // Создадим картинку из байтов // Предполагаем 8-битные каналы let img_buffer = match channels { 3 => image::ImageBuffer::<image::Rgb<u8>, _>::from_raw( width, height, decompressed_data ).expect("Не удалось собрать Rgb картинку"), 4 => image::ImageBuffer::<image::Rgba<u8>, _>::from_raw( width, height, decompressed_data ).expect("Не удалось собрать Rgba картинку"), _ => panic!("Неизвестное количество каналов: {}", channels), }; // Сохраним как PNG (для примера) let dyn_img = image::DynamicImage::ImageRgb8(img_buffer.into_rgb8()); dyn_img.save(output_image_path) .expect("Не удалось сохранить результат в PNG"); Ok(()) } /// Пример «сжатия» методом LZW (настоящего LZW здесь нет, можно поставить свою реализацию). pub fn compress_lzw(data: &[u8]) -> std::io::Result<Vec<u8>> { // Здесь может быть реальная реализация LZW или // использование готовой библиотеки. // Мы же для примера просто делаем «заглушку». println!("[LZW] Демонстрационная заглушка: не реальное сжатие!"); Ok(data.to_vec()) // не сжимаем по-настоящему } /// Пример «распаковки» методом LZW pub fn decompress_lzw(data: &[u8]) -> std::io::Result<Vec<u8>> { println!("[LZW] Демонстрационная заглушка: не реальная распаковка!"); Ok(data.to_vec()) // не распаковываем по-настоящему } /// Сжатие методом DEFLATE через flate2 pub fn compress_deflate(data: &[u8]) -> std::io::Result<Vec<u8>> { use flate2::write::ZlibEncoder; use flate2::Compression; let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); encoder.write_all(data)?; let compressed_data = encoder.finish()?; Ok(compressed_data) } /// Распаковка методом DEFLATE pub fn decompress_deflate(data: &[u8]) -> std::io::Result<Vec<u8>> { use flate2::read::ZlibDecoder; let mut decoder = ZlibDecoder::new(data); let mut buf = Vec::new(); decoder.read_to_end(&mut buf)?; Ok(buf) } /// Пример «сжатия» с потерями (через JPEG). /// Для демонстрации пользуемся тем, что crate `image` умеет сохранять JPEG, /// а затем полученный JPEG-байтовый поток записать в файл. /// Однако для реальной схемы нужно хранить и заголовок, /// и другие метаданные, либо использовать свою библиотеку. pub fn compress_lossy_jpeg(img: &image::DynamicImage) -> std::io::Result<Vec<u8>> { let mut jpeg_data = Vec::new(); let mut jpeg_encoder = image::codecs::jpeg::JpegEncoder::new_with_quality( &mut jpeg_data, 70, // Качество (0-100) ); jpeg_encoder.encode_image(img).expect("Ошибка при JPEG-сжатии"); Ok(jpeg_data) } } /// Открыть файл на чтение/запись pub fn file_open(path: &str, mode: FileMode) -> std::io::Result<File> { match mode { FileMode::Read => { OpenOptions::new() .read(true) .open(path) }, FileMode::Write => { OpenOptions::new() .write(true) .create(true) .truncate(true) .open(path) } } } /// Чтение из файла в буфер pub fn file_read(file: &mut File, buffer: &mut [u8]) -> std::io::Result<usize> { file.read(buffer) } /// Запись буфера в файл pub fn file_write(file: &mut File, buffer: &[u8]) -> std::io::Result<usize> { file.write(buffer) } /// Закрыть файл pub fn file_close(mut file: File) -> std::io::Result<()> { file.flush()?; // В Rust просто даём объекту `file` выйти из области видимости. // Явно возвращаем Ok(()) для демонстрации. Ok(()) } /// Функция для записи заголовка MamamiaHeader в файл fn file_write_header(file: &mut File, header: &MamamiaHeader) -> std::io::Result<()> { // Запишем структуру «как есть». Но в реальном коде нужно заботиться о выравнивании, // порядке байт (Little/Big Endian) и т.д. // Для простоты — запишем поле за полем. file_write(file, &header.signature)?; file_write(file, &[header.version])?; // width, height (4 байта each, little-endian) file_write(file, &header.width.to_le_bytes())?; file_write(file, &header.height.to_le_bytes())?; file_write(file, &[header.channels])?; file_write(file, &[header.compression_method])?; Ok(()) } /// Читаем заголовок MamamiaHeader из файла fn file_read_header(file: &mut File) -> std::io::Result<MamamiaHeader> { let mut signature = [0u8; 7]; let mut version = [0u8; 1]; let mut width = [0u8; 4]; let mut height = [0u8; 4]; let mut channels = [0u8; 1]; let mut compression_method = [0u8; 1]; file_read(file, &mut signature)?; file_read(file, &mut version)?; file_read(file, &mut width)?; file_read(file, &mut height)?; file_read(file, &mut channels)?; file_read(file, &mut compression_method)?; Ok(MamamiaHeader { signature, version: version[0], width: u32::from_le_bytes(width), height: u32::from_le_bytes(height), channels: channels[0], compression_method: compression_method[0], }) } /// Пример функции выделения памяти (демонстрация). /// В реальном Rust-коде обычно используют векторы `Vec<u8>` или другие безопасные конструкции. /// Здесь же — формально возвращаем вектор (хотя можно было бы вернуть и сырые указатели). pub fn memory_allocate(size: usize) -> Vec<u8> { vec![0u8; size] } /// Пример использования: fn main() -> std::io::Result<()> { // 1) Сожмём разными методами // Проверяйте на разных типах изображений (jpg, png, bmp и т.д.) // Без сжатия (compression_method = 0) Reassembler::compress_image("example.png", "example_nocompress.mamamia", 0)?; // LZW (compression_method = 1) - заглушка Reassembler::compress_image("example.png", "example_lzw.mamamia", 1)?; // DEFLATE (compression_method = 2) Reassembler::compress_image("example.png", "example_deflate.mamamia", 2)?; // JPEG (compression_method = 3) Reassembler::compress_image("example.png", "example_jpeg.mamamia", 3)?; // 2) Распакуем (для LZW, DEFLATE, без потерь). // JPEG демонстрационно не поддерживается в распаковке (см. panic в коде). Reassembler::decompress_image("example_nocompress.mamamia", "decoded_nocompress.png")?; Reassembler::decompress_image("example_lzw.mamamia", "decoded_lzw.png")?; Reassembler::decompress_image("example_deflate.mamamia", "decoded_deflate.png")?; // При попытке распаковать example_jpeg.mamamia увидим panic! // (нужно реализовать отдельную логику декодирования jpeg). // Reassembler::decompress_image("example_jpeg.mamamia", "decoded_jpeg.png")?; println!("Все операции завершены успешно!"); Ok(()) }
Создание собственного заголовка:
MamamiaHeader
хранится «магическая» подпись MAMAMIA
, версия, размеры и метод сжатия.Методы сжатия:
flate2
с ZlibEncoder
/ZlibDecoder
.Разделение данных на блоки:
Функции работы с файлами (file_open
, file_read
, file_write
, file_close
):
Функция memory_allocate
:
Vec<u8>
, поэтому для демонстрации мы просто возвращаем вектор.Box::into_raw
, но в реальных приложениях это чаще всего не нужно.Таким образом, вы можете поэкспериментировать с кодом, попробовать сжимать разные изображения (PNG, JPEG, BMP и т.д.), а также доработать (или заменить заглушки) для LZW/JP(E)G-сжатия. При желании вы можете добавить разбивку на строки (или тайлы) и по очереди сжимать и записывать каждую строку — это может дать более эффективное сжатие на больших изображениях.
Удачи в экспериментах с собственным форматом .mamamia!