From 46c7e9aefa2a922b55f524f1672756d6b66c3f94 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sat, 8 Feb 2025 00:32:48 +0100 Subject: [PATCH] Cleanup QOI --- lib/std/compression/qoi.c3 | 172 ++++++++++++++++------------ test/unit/stdlib/compression/qoi.c3 | 6 +- 2 files changed, 102 insertions(+), 76 deletions(-) diff --git a/lib/std/compression/qoi.c3 b/lib/std/compression/qoi.c3 index 7738639a4..431441701 100644 --- a/lib/std/compression/qoi.c3 +++ b/lib/std/compression/qoi.c3 @@ -73,18 +73,19 @@ import std::io; *> fn usz! write(String filename, char[] input, QOIDesc* desc) { - @pool() { + @pool() + { // encode data - char[] output = encode(input, desc)!; + char[] output = new_encode(input, desc)!; // open file File! f = file::open(filename, "wb"); - if (catch f) { return QOIError.FILE_OPEN_FAILED?; } + if (catch f) return QOIError.FILE_OPEN_FAILED?; // write data to file and close it usz! written = f.write(output); - if (catch written) { return QOIError.FILE_WRITE_FAILED?; } - if (catch f.close()) { return QOIError.FILE_WRITE_FAILED?; } + if (catch written) return QOIError.FILE_WRITE_FAILED?; + if (catch f.close()) return QOIError.FILE_WRITE_FAILED?; return written; }; @@ -112,15 +113,21 @@ fn usz! write(String filename, char[] input, QOIDesc* desc) @param [&out] desc `The descriptor to fill with the image's info` @param channels `The channels to be used` *> -fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) +fn char[]! new_read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) { - // read file - char[]! data = file::load_new(filename); - if (catch data) return QOIError.FILE_OPEN_FAILED?; - defer mem::free(data); + @pool(allocator) + { + // read file + char[] data = file::load_temp(filename) ?? QOIError.FILE_OPEN_FAILED?!; - // pass data to decode function - return decode(data, desc, channels, allocator); + // pass data to decode function + return new_decode(data, desc, channels, allocator); + }; +} + +fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) @deprecated("Use new_read") +{ + return new_read(filename, desc, channels, allocator); } @@ -128,6 +135,11 @@ fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, All module std::compression::qoi; import std::bits; +fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap()) @deprecated("use encode_new") +{ + return new_encode(input, desc, allocator); +} + <* Encode raw RGB or RGBA pixels into a QOI image in memory. @@ -141,7 +153,7 @@ import std::bits; @param [in] input `The raw RGB or RGBA pixels to encode` @param [&in] desc `The descriptor of the image` *> -fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap()) +fn char[]! new_encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap()) { // check info in desc if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_PARAMETERS?; @@ -191,70 +203,77 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator:: if (desc.channels == RGBA) p.a = input[loc + 3]; // check if we can run the previous pixel - if (prev == p) { + if (prev == p) + { run_length++; - if (run_length == 62 || loc == loc_end) { - *@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 }; - run_length = 0; - } - } else { - // end last run if there was one - if (run_length > 0) { + if (run_length == 62 || loc == loc_end) + { *@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 }; run_length = 0; } + continue; + } + // end last run if there was one + if (run_length > 0) + { + *@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 }; + run_length = 0; + } - switch { - // check if we can index the palette - case (palette[p.hash()] == p): - *@extract(OpIndex, output, &pos) = { - OP_INDEX, - p.hash() + switch + { + // check if we can index the palette + case (palette[p.hash()] == p): + *@extract(OpIndex, output, &pos) = { + OP_INDEX, + p.hash() + }; + + // check if we can use diff or luma + case (prev != p && prev.a == p.a): + // diff the pixels + diff = p.rgb - prev.rgb; + if (diff.r > -3 && diff.r < 2 + && diff.g > -3 && diff.g < 2 + && diff.b > -3 && diff.b < 2) + { + *@extract(OpDiff, output, &pos) = { + OP_DIFF, + (char)diff.r + 2, + (char)diff.g + 2, + (char)diff.b + 2 }; - - // check if we can use diff or luma - case (prev != p && prev.a == p.a): - // diff the pixels - diff = p.rgb - prev.rgb; - if ( - diff.r > -3 && diff.r < 2 && - diff.g > -3 && diff.g < 2 && - diff.b > -3 && diff.b < 2 - ) { - *@extract(OpDiff, output, &pos) = { - OP_DIFF, - (char)diff.r + 2, - (char)diff.g + 2, - (char)diff.b + 2 - }; - palette[p.hash()] = p; - } else { - // check luma eligibility - luma = { diff.r - diff.g, diff.g, diff.b - diff.g }; - if ( - luma.r >= -8 && luma.r <= 7 && - luma.g >= -32 && luma.g <= 31 && - luma.b >= -8 && luma.b <= 7 - ) { - *@extract(OpLuma, output, &pos) = { - OP_LUMA, - (char)luma.g + 32, - (char)luma.r + 8, - (char)luma.b + 8 - }; - palette[p.hash()] = p; - } else { nextcase; } - } - - // worst case scenario: just encode the raw pixel - default: - if (prev.a != p.a) { - *@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a }; - } else { - *@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b }; - } palette[p.hash()] = p; - } + break; + } + // check luma eligibility + luma = { diff.r - diff.g, diff.g, diff.b - diff.g }; + if (luma.r >= -8 && luma.r <= 7 + && luma.g >= -32 && luma.g <= 31 + && luma.b >= -8 && luma.b <= 7) + { + *@extract(OpLuma, output, &pos) = { + OP_LUMA, + (char)luma.g + 32, + (char)luma.r + 8, + (char)luma.b + 8 + }; + palette[p.hash()] = p; + break; + } + nextcase; + + // worst case scenario: just encode the raw pixel + default: + if (prev.a != p.a) + { + *@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a }; + } + else + { + *@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b }; + } + palette[p.hash()] = p; } } @@ -266,6 +285,11 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator:: } +fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) +{ + return new_decode(data, desc, channels, allocator); +} + <* Decode a QOI image from memory. @@ -287,7 +311,7 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator:: @param [&out] desc `The descriptor to fill with the image's info` @param channels `The channels to be used` *> -fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) +fn char[]! new_decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) { // check input data if (data.len < Header.sizeof + END_OF_STREAM.len) return QOIError.INVALID_DATA?; @@ -414,14 +438,16 @@ const char[?] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1}; // inefficient, but it's only run once at a time macro @enumcast($Type, raw) { - foreach (value : $Type.values) { + foreach (value : $Type.values) + { if (value.id == raw) return value; } return QOIError.INVALID_DATA?; } distinct Pixel = inline char[<4>]; -macro char Pixel.hash(Pixel p) { +macro char Pixel.hash(Pixel p) +{ return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64; } diff --git a/test/unit/stdlib/compression/qoi.c3 b/test/unit/stdlib/compression/qoi.c3 index efca1715d..bf027e5bd 100644 --- a/test/unit/stdlib/compression/qoi.c3 +++ b/test/unit/stdlib/compression/qoi.c3 @@ -14,18 +14,18 @@ fn void test_qoi_all() QOIDesc test_desc; // decode the test data - char[] decoded = qoi::decode(TEST_QOI_DATA[..], &test_desc)!!; + char[] decoded = qoi::new_decode(TEST_QOI_DATA[..], &test_desc)!!; assert(test_desc.width == 340 && test_desc.height == 169, "Expected resolution of 340x169"); // encode the decoded data - char[] encoded = qoi::encode(decoded, &test_desc)!!; + char[] encoded = qoi::new_encode(decoded, &test_desc)!!; assert(encoded == TEST_QOI_DATA[..], "Encoder output should match the test data"); // encode and write the decoded data to a file usz written = qoi::write("unittest.qoi", decoded, &test_desc)!!; // read and decode the written data - char[] read = qoi::read("unittest.qoi", &test_desc)!!; + char[] read = qoi::new_read("unittest.qoi", &test_desc)!!; assert(read == decoded, "Read data should match the decoded data"); // cleanup