diff --git a/.gitignore b/.gitignore index 10a51ce..99e5ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ akoencx cdf53-test dd137-test elias-test +rle-test /build/ /build-debug/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9580aae..a9f92fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,22 +3,25 @@ cmake_minimum_required(VERSION 3.12) project("Ako" C CXX) -# option(AKO_EXPORT_SYMBOLS "Export symbols" ON) option(AKO_SHARED "Build shared library" ON) option(AKO_STATIC "Build static library" ON) -option(AKO_DEC "Build decoding tool" ON) -option(AKO_ENC "Build encoding tool" ON) -option(AKO_TESTS "Build tests" ON) +option(AKO_DEC "Build decoding tool" ON) +option(AKO_ENC "Build encoding tool" ON) +option(AKO_TESTS "Build tests" ON) + +set(CMAKE_EXPORT_COMPILE_COMMANDS True) # For Clangd if (MSVC) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) else () - add_compile_options(-g -Werror -Wall -Wextra -pedantic -Wno-unused-function -Wno-unused-parameter -Wno-unused-variable) + add_compile_options(-Werror -Wall -Wextra -pedantic + -Wno-unused-function -Wno-unused-parameter -Wno-unused-variable -Wno-empty-translation-unit) endif () set(AKO_SOURCES + "./library/compression-rle.c" "./library/compression.c" "./library/decode.c" "./library/developer.c" @@ -97,7 +100,7 @@ if (AKO_TESTS) target_include_directories("cdf53-test" PRIVATE "./library/") target_link_libraries("cdf53-test" PRIVATE "ako-static") - enable_testing() - add_test(NAME "elias-test" COMMAND "elias-test") - add_test(NAME "cdf53-test" COMMAND "cdf53-test") + add_executable("rle-test" "./tests/rle-test.c") + target_include_directories("rle-test" PRIVATE "./library/") + target_link_libraries("rle-test" PRIVATE "ako-static") endif () diff --git a/library/ako-private.h b/library/ako-private.h index b678dc2..2f33515 100644 --- a/library/ako-private.h +++ b/library/ako-private.h @@ -23,7 +23,8 @@ #define AKO_EXPORT __attribute__((visibility("default"))) -typedef int16_t coeff_t; // For future monomorphization... +typedef int16_t coeff_t; // For future monomorphization... +typedef uint16_t ucoeff_t; // Ditto struct akoLiftHead @@ -31,6 +32,18 @@ struct akoLiftHead int16_t quantization; }; +// compression-rle.c: + +#define AKO_RLE_BLOCKS_LEN_MAX 65535 +#define AKO_RLE_DEFAULT_TRIGGER_DELAY 0 + +size_t akoRleEncode(size_t blocks_len, size_t trigger_delay, size_t input_size, size_t output_size, const void* input, + void* output); +size_t akoRleDecode(size_t blocks_len, size_t input_size, size_t output_size, const void* input, void* output); + +size_t akoOldRleEncode(size_t blocks_len, size_t trigger_delay, size_t input_size, size_t output_size, + const void* input, void* output); +size_t akoOldRleDecode(size_t blocks_len, size_t input_size, size_t output_size, const void* input, void* output); // compression.c: diff --git a/library/compression-rle.c b/library/compression-rle.c new file mode 100644 index 0000000..0563090 --- /dev/null +++ b/library/compression-rle.c @@ -0,0 +1,415 @@ +/* + +MIT License + +Copyright (c) 2021-2022 Alexander Brandt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + + +#include "ako-private.h" +#define DEV_MAGIC_NUMBER 0 + + +enum RleEncoderMode +{ + MODE_LITERAL, + MODE_RLE +}; + +struct RleEncoderState +{ + ucoeff_t* out; + ucoeff_t* out_end; + + const ucoeff_t* in_cursor1; // Always behind cursor 2 + const ucoeff_t* in_cursor2; + + enum RleEncoderMode mode; +}; + + +static inline int sSwitchToRle(size_t blocks_len, struct RleEncoderState* s) +{ + ucoeff_t last_literal_len; + + // Emit literal + const ucoeff_t* temp = s->in_cursor1; + do + { + // Process blocks of 'blocks_len' or less + // aka: Overflow mechanism + temp += blocks_len; + if (temp > s->in_cursor2) + temp = s->in_cursor2; + + // Emit + const ucoeff_t length = (ucoeff_t)(temp - s->in_cursor1); // TODO: check + last_literal_len = length; + + if (s->out + length >= s->out_end) + return 1; + + *(s->out++) = (ucoeff_t)(length) + DEV_MAGIC_NUMBER; // Length + + do + { + *(s->out++) = *(s->in_cursor1++); // Data + } while (s->in_cursor1 != temp); + + } while (temp != s->in_cursor2); + + // Emit zero, if needed + if (last_literal_len == blocks_len) + { + // In normal operation the decoder continuously switch between literal and rle modes. + // Only exception being when a literal block of length 'blocks_len' appears, if so, + // it will assume that the following block is also a literal. + + // To break this behaviour a literal of length zero do the work. + + if (s->out == s->out_end) + return 1; + + *(s->out++) = 0 + DEV_MAGIC_NUMBER; // Zero + } + + // Set rle mode + s->mode = MODE_RLE; + + return 0; +} + + +static inline int sSwitchToLiteral(size_t blocks_len, struct RleEncoderState* s) +{ + // Emit rle, with same caveats as before, and solution too + const ucoeff_t* temp; + do + { + temp = s->in_cursor1 + blocks_len + 1; // Note this +1. We do not use rle sequences + if (temp > s->in_cursor2) // of length zero, so we start from one + temp = s->in_cursor2; + + if (s->out == s->out_end) + return 1; + + *(s->out++) = (ucoeff_t)(temp - s->in_cursor1 - 1) + DEV_MAGIC_NUMBER; // Length. Note the -1, TODO: check + + s->in_cursor1 += blocks_len + 1; // :) + if (s->in_cursor1 < s->in_cursor2) + { + if (s->out == s->out_end) + return 1; + + *(s->out++) = 0 + DEV_MAGIC_NUMBER; // Zero + } + + } while (temp != s->in_cursor2); + + // Set literal mode + s->in_cursor1 = s->in_cursor2; + s->mode = MODE_LITERAL; + + return 0; +} + + +size_t akoRleEncode(size_t blocks_len, size_t trigger_delay, size_t input_size, size_t output_size, const void* input, + void* output) +{ + if ((input_size % (sizeof(ucoeff_t))) != 0 || input_size < sizeof(ucoeff_t) || + (output_size % (sizeof(ucoeff_t))) != 0 || output_size < sizeof(ucoeff_t)) + return 0; + + struct RleEncoderState s = { + .out = output, + .out_end = (ucoeff_t*)output + (output_size / sizeof(ucoeff_t)), + .in_cursor1 = (const ucoeff_t*)input, + .in_cursor2 = (const ucoeff_t*)input, + .mode = MODE_LITERAL, + }; + + const ucoeff_t* in_end = (const ucoeff_t*)input + (input_size / sizeof(ucoeff_t)); + ucoeff_t previous_value = 0; + size_t consecutives = 0; + + if (*s.in_cursor2 == previous_value) // Always start with a literal + s.in_cursor2++; // Magic trick to force conditionals below + + // Main loop + do + { + if (*s.in_cursor2 == previous_value) + { + consecutives++; + + if (s.mode == MODE_LITERAL && consecutives > trigger_delay) + { + if (sSwitchToRle(blocks_len, &s) != 0) + return 0; + + consecutives = 0; + } + } + else + { + if (s.mode == MODE_RLE) + { + if (sSwitchToLiteral(blocks_len, &s) != 0) + return 0; + } + + previous_value = *s.in_cursor2; + } + + } while (++s.in_cursor2 != in_end); + + // Remainder + if (s.mode == MODE_LITERAL) + { + if (sSwitchToRle(blocks_len, &s) != 0) + return 0; + } + else + { + if (sSwitchToLiteral(blocks_len, &s) != 0) + return 0; + } + + // Bye! + return (size_t)(s.out - (ucoeff_t*)output) * sizeof(ucoeff_t); +} + + +size_t akoRleDecode(size_t blocks_len, size_t input_size, size_t output_size, const void* input, void* output) +{ + if ((input_size % (sizeof(ucoeff_t))) != 0 || input_size < sizeof(ucoeff_t) || + (output_size % (sizeof(ucoeff_t))) != 0 || output_size < sizeof(ucoeff_t)) + return 0; + + const ucoeff_t* in = (const ucoeff_t*)input; + const ucoeff_t* in_end = (const ucoeff_t*)input + (input_size / sizeof(ucoeff_t)); + + ucoeff_t* out = output; + ucoeff_t* out_end = out + (output_size / sizeof(ucoeff_t)); + + ucoeff_t last; + ucoeff_t length; + size_t do_rle = 1; + + do + { + // Literal + length = *in - DEV_MAGIC_NUMBER; + if (out + length > out_end || in + length >= in_end) + return 0; + + if (length != 0) + { + do_rle = (size_t)length - blocks_len; // do_rle = (length == blocks_len) ? 0 : 1; + + do + { + *(out++) = *(++in); + } while (--length != 0); + last = *in; + } + else + { + do_rle = 1; + } + + // Rle + if (do_rle != 0) + { + if (++in == in_end) + break; + + length = *in + 1 - DEV_MAGIC_NUMBER; + if (out + length > out_end) + return 0; + + do + { + *(out++) = last; + } while (--length != 0); + } + + } while (++in != in_end); + + return (size_t)(out - (ucoeff_t*)output) * sizeof(ucoeff_t); +} + + +//----------------------------- + + +#define OLD_TRIGGER_LEN 2 + + +static inline void sOldRawWriteRle(ucoeff_t consecutive_no, ucoeff_t** out) +{ + **out = (ucoeff_t)(consecutive_no - OLD_TRIGGER_LEN); + *out = *out + 1; +} + +static inline void sOldRawWriteValue(ucoeff_t value, ucoeff_t** out) +{ + **out = value; + *out = *out + 1; +} + +static inline void sOldRawWriteMultipleValues(ucoeff_t value, ucoeff_t rle_len, ucoeff_t** out) +{ + for (ucoeff_t u = 0; u < rle_len; u++) + { + **out = value; + *out = *out + 1; + } +} + + +size_t akoOldRleEncode(size_t blocks_len, size_t trigger_delay, size_t input_size, size_t output_size, + const void* input, void* output) +{ + (void)blocks_len; // Not a thing in this encoder + (void)trigger_delay; // Hardcoded as both encoder/decoder use it + + const ucoeff_t* in_end = (const ucoeff_t*)((const uint8_t*)input + input_size); + const ucoeff_t* in = (const ucoeff_t*)input; + + const ucoeff_t* out_end = (const ucoeff_t*)((const uint8_t*)output + output_size); + ucoeff_t* out = output; + + ucoeff_t consecutive_no = 0; + ucoeff_t previous_value = 0; + + if (output_size == 0 || input_size == 0) + return 0; + if ((input_size % 2) != 0) + return 0; + + // First value + sOldRawWriteValue(*in, &out); + previous_value = *in; + in++; + + // All others + for (; in < in_end; in++) + { + if (out == out_end) // We have space? + return 0; + + if (*in == previous_value) + { + consecutive_no++; + + if (consecutive_no <= OLD_TRIGGER_LEN) + sOldRawWriteValue(*in, &out); + else if (consecutive_no == 65535) // Oh no, at this rate we are going to overflow! + { + sOldRawWriteRle(consecutive_no, &out); + consecutive_no = 0; + } + } + else + { + if (consecutive_no >= OLD_TRIGGER_LEN) + sOldRawWriteRle(consecutive_no, &out); + + sOldRawWriteValue(*in, &out); + previous_value = *in; + consecutive_no = 0; + } + } + + // Maybe the loop finished with a pending Rle length to emit + if (consecutive_no >= OLD_TRIGGER_LEN) + { + if (out == out_end) + return 0; + + sOldRawWriteRle(consecutive_no, &out); + } + + // Bye! + return (size_t)((uint8_t*)out - (uint8_t*)output); +} + + +size_t akoOldRleDecode(size_t blocks_len, size_t input_size, size_t output_size, const void* input, void* output) +{ + (void)blocks_len; // No configurable in this encoder + + const ucoeff_t* in_end = (const ucoeff_t*)((const uint8_t*)input + input_size); + const ucoeff_t* in = (const ucoeff_t*)input; + + const ucoeff_t* out_end = (const ucoeff_t*)((const uint8_t*)output + output_size); + ucoeff_t* out = output; + + ucoeff_t consecutive_no = 0; + ucoeff_t previous_value = 0; + + if (output_size == 0 || input_size == 0) + return 0; + if ((output_size % 2) != 0) + return 0; + + // First value + sOldRawWriteValue(*in, &out); + previous_value = *in; + in++; + + // All others + for (; in < in_end; in++) + { + if (out == out_end) + return 0; + + if (*in == previous_value) + { + sOldRawWriteValue(*in, &out); + consecutive_no++; + + if (consecutive_no == OLD_TRIGGER_LEN) + { + if (++in == in_end) + return 0; + + const ucoeff_t rle_len = *in; + if ((out + (size_t)rle_len) > out_end) + return 0; + + sOldRawWriteMultipleValues(previous_value, rle_len, &out); + consecutive_no = 0; + } + } + else + { + sOldRawWriteValue(*in, &out); + previous_value = *in; + consecutive_no = 0; + } + } + + // Bye! + return (size_t)((uint8_t*)out - (uint8_t*)output); +} diff --git a/resources/build.ninja b/resources/build.ninja index 1d6dd98..90bc6c6 100644 --- a/resources/build.ninja +++ b/resources/build.ninja @@ -26,20 +26,21 @@ rule Link command = $link $in $lflags -o $out -build ./build/library/compression.o: CompileC ./library/compression.c -build ./build/library/decode.o: CompileC ./library/decode.c -build ./build/library/developer.o: CompileC ./library/developer.c -build ./build/library/encode.o: CompileC ./library/encode.c -build ./build/library/format.o: CompileC ./library/format.c -build ./build/library/head.o: CompileC ./library/head.c -build ./build/library/kagari.o: CompileC ./library/kagari.c -build ./build/library/lifting.o: CompileC ./library/lifting.c -build ./build/library/misc.o: CompileC ./library/misc.c -build ./build/library/quantization.o: CompileC ./library/quantization.c -build ./build/library/version.o: CompileC ./library/version.c -build ./build/library/wavelet-cdf53.o: CompileC ./library/wavelet-cdf53.c -build ./build/library/wavelet-dd137.o: CompileC ./library/wavelet-dd137.c -build ./build/library/wavelet-haar.o: CompileC ./library/wavelet-haar.c +build ./build/library/compression-rle.o: CompileC ./library/compression-rle.c +build ./build/library/compression.o: CompileC ./library/compression.c +build ./build/library/decode.o: CompileC ./library/decode.c +build ./build/library/developer.o: CompileC ./library/developer.c +build ./build/library/encode.o: CompileC ./library/encode.c +build ./build/library/format.o: CompileC ./library/format.c +build ./build/library/head.o: CompileC ./library/head.c +build ./build/library/kagari.o: CompileC ./library/kagari.c +build ./build/library/lifting.o: CompileC ./library/lifting.c +build ./build/library/misc.o: CompileC ./library/misc.c +build ./build/library/quantization.o: CompileC ./library/quantization.c +build ./build/library/version.o: CompileC ./library/version.c +build ./build/library/wavelet-cdf53.o: CompileC ./library/wavelet-cdf53.c +build ./build/library/wavelet-dd137.o: CompileC ./library/wavelet-dd137.c +build ./build/library/wavelet-haar.o: CompileC ./library/wavelet-haar.c build ./build/tools/thirdparty/lodepng.o: CompileCpp ./tools/thirdparty/lodepng.cpp build ./build/tools/akodec.o: CompileCpp ./tools/akodec.cpp @@ -48,9 +49,11 @@ build ./build/tools/akoenc.o: CompileCpp ./tools/akoenc.cpp build ./build/tests/cdf53-test.o: CompileC ./tests/cdf53-test.c build ./build/tests/dd137-test.o: CompileC ./tests/dd137-test.c build ./build/tests/elias-test.o: CompileC ./tests/elias-test.c +build ./build/tests/rle-test.o: CompileC ./tests/rle-test.c build ./akodec: Link $ + ./build/library/compression-rle.o $ ./build/library/compression.o $ ./build/library/decode.o $ ./build/library/developer.o $ @@ -69,6 +72,7 @@ build ./akodec: Link $ ./build/tools/akodec.o build ./akoenc: Link $ + ./build/library/compression-rle.o $ ./build/library/compression.o $ ./build/library/decode.o $ ./build/library/developer.o $ @@ -97,3 +101,7 @@ build ./cdf53-test: Link $ build ./elias-test: Link $ ./build/library/kagari.o $ ./build/tests/elias-test.o + +build ./rle-test: Link $ + ./build/library/compression-rle.o $ + ./build/tests/rle-test.o diff --git a/tests/rle-test.c b/tests/rle-test.c new file mode 100644 index 0000000..536129d --- /dev/null +++ b/tests/rle-test.c @@ -0,0 +1,195 @@ + + +#undef NDEBUG + +#include "ako-private.h" +#include +#include +#include + + +static void sSingleTest(size_t(encode_callback)(size_t, size_t, size_t, size_t, const void*, void*), + size_t(decode_callback)(size_t, size_t, size_t, const void*, void*), size_t blocks_len, + size_t in_len, const ucoeff_t* in) +{ + printf("(len: %zu) ", in_len); + for (size_t i = 0; i < in_len; i++) + printf("%u, ", in[i]); + printf("\n"); + + const size_t buffer_size = sizeof(ucoeff_t) * in_len * 2; + ucoeff_t* buffer_a = malloc(buffer_size); + ucoeff_t* buffer_b = malloc(buffer_size); + assert(buffer_a != NULL); + assert(buffer_b != NULL); + + // Encode + size_t compressed_size; + { + compressed_size = encode_callback(blocks_len, AKO_RLE_DEFAULT_TRIGGER_DELAY, sizeof(ucoeff_t) * in_len, + buffer_size, in, buffer_a); + + assert(compressed_size > 1); + assert((compressed_size % 2) == 0); + + printf("(len: %zu) ", compressed_size / 2); + for (size_t i = 0; i < compressed_size / 2; i++) + printf("%u, ", buffer_a[i]); + printf("\n"); + } + + // Decode + { + const size_t decompressed_size = + decode_callback(blocks_len, compressed_size, sizeof(ucoeff_t) * in_len, buffer_a, buffer_b); + + assert(decompressed_size != 0); + assert((decompressed_size % 2) == 0); + + printf("(len: %zu) ", decompressed_size / 2); + for (size_t i = 0; i < decompressed_size / 2; i++) + printf("%u, ", buffer_b[i]); + printf("\n"); + } + + // Test + for (size_t i = 0; i < in_len; i++) + assert(in[i] == buffer_b[i]); + + // Bye! + printf("\n"); + free(buffer_a); + free(buffer_b); +} + + +static int sFileTest(size_t(encode_callback)(size_t, size_t, size_t, size_t, const void*, void*), + size_t(decode_callback)(size_t, size_t, size_t, const void*, void*), int argc, const char* argv[]) +{ + clock_t start; + clock_t end; + + // Read file + FILE* input_fp = fopen(argv[2], "rb"); + assert(input_fp != NULL); + + fseek(input_fp, 0, SEEK_END); + const long input_size = ftell(input_fp); + assert(input_size != -1L); + + printf("'%s': %.2f Kb -> ", argv[2], (double)input_size / 1000.0); + + void* buffer1 = malloc(input_size); + void* buffer2 = malloc(input_size); + void* buffer3 = malloc(input_size); + assert(buffer1 != NULL); + assert(buffer2 != NULL); + assert(buffer3 != NULL); + + fseek(input_fp, 0, SEEK_SET); + if (fread(buffer1, input_size, 1, input_fp) != 1) + assert(1 == 2); + + // Encode + start = clock(); + const size_t compressed_size = encode_callback(AKO_RLE_BLOCKS_LEN_MAX, AKO_RLE_DEFAULT_TRIGGER_DELAY, input_size, + input_size, buffer1, buffer2); + end = clock(); + + printf("%.2f Kb, ratio: %.2f:1, encoding: %.2f ms, ", (double)compressed_size / 1000.0, + (double)input_size / (double)compressed_size, (double)(end - start) / (double)(CLOCKS_PER_SEC / 1000.0)); + assert(compressed_size != 0); + + // Decode + start = clock(); + const size_t decompressed_size = + decode_callback(AKO_RLE_BLOCKS_LEN_MAX, compressed_size, input_size, buffer2, buffer3); + end = clock(); + + printf("decoding: %.2f ms\n", (double)(end - start) / (double)(CLOCKS_PER_SEC / 1000.0)); + assert(decompressed_size != 0); + + // Save decoded file + if (argc > 3) + { + FILE* output_fp = fopen(argv[3], "wb"); + fwrite(buffer3, decompressed_size, 1, output_fp); + fclose(output_fp); + } + + // Bye! + fclose(input_fp); + free(buffer1); + free(buffer2); + free(buffer3); + + return 0; +} + + +void sGenericTests(size_t(encode_callback)(size_t, size_t, size_t, size_t, const void*, void*), + size_t(decode_callback)(size_t, size_t, size_t, const void*, void*)) +{ + const ucoeff_t data1[10] = {1, 2, 3, 4, 5, 6, 6, 6, 6, 6}; + sSingleTest(encode_callback, decode_callback, AKO_RLE_BLOCKS_LEN_MAX, 10, data1); + + const ucoeff_t data2[16] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2, 2, 1, 2, 3, 4}; + sSingleTest(encode_callback, decode_callback, AKO_RLE_BLOCKS_LEN_MAX, 16, data2); + + const ucoeff_t data3[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}; + sSingleTest(encode_callback, decode_callback, AKO_RLE_BLOCKS_LEN_MAX, 16, data3); + + const ucoeff_t data4[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + sSingleTest(encode_callback, decode_callback, AKO_RLE_BLOCKS_LEN_MAX, 16, data4); + + const ucoeff_t data5[1] = {5}; + sSingleTest(encode_callback, decode_callback, AKO_RLE_BLOCKS_LEN_MAX, 1, data5); + + const ucoeff_t data6[2] = {5, 6}; + sSingleTest(encode_callback, decode_callback, AKO_RLE_BLOCKS_LEN_MAX, 2, data6); + + // Test overflow mechanism, limiting rle and literal sequences to a length of 4 + // To avoid overflows/wrap-arounds the encoder will split sequences and emit literals of length zero to mark stops. + // This indeed ruins compression but in real world sequences should had a maximum length of 2^16 + + const ucoeff_t data7[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + sSingleTest(encode_callback, decode_callback, 4, 16, data7); + + const ucoeff_t data8[16] = {1, 2, 3, 4, 4, 4, 4, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + sSingleTest(encode_callback, decode_callback, 4, 16, data8); + + const ucoeff_t data9[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + sSingleTest(encode_callback, decode_callback, 4, 16, data9); + + const ucoeff_t data10[18] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + sSingleTest(encode_callback, decode_callback, 4, 17, data10); + + const ucoeff_t data11[18] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + sSingleTest(encode_callback, decode_callback, 4, 18, data11); +} + + +int main(int argc, const char* argv[]) +{ + if (argc > 2) + { + if (argv[1][0] == 'n') + { + printf("# NEW RLE:\n"); + return sFileTest(akoRleEncode, akoRleDecode, argc, argv); + } + else if (argv[1][0] == 'o') + { + printf("# OLD RLE:\n"); + return sFileTest(akoOldRleEncode, akoOldRleDecode, argc, argv); + } + } + + printf("# NEW RLE:\n\n"); + sGenericTests(akoRleEncode, akoRleDecode); + + printf("# OLD RLE:\n\n"); + sGenericTests(akoOldRleEncode, akoOldRleDecode); + + return 0; +}