From 6ed60c94a63bd2fe603d8fde4d787d249adc7b87 Mon Sep 17 00:00:00 2001 From: Alexander Brandt Date: Fri, 7 Jan 2022 07:28:04 -0300 Subject: [PATCH] SavePgm() and some encoding procedures --- .clang-tidy | 2 + .gitignore | 16 +---- CMakeLists.txt | 1 + library/ako-private.h | 9 ++- library/ako.h | 2 + library/decode.c | 20 ++++-- library/developer.c | 144 ++++++++---------------------------------- library/developer.h | 29 --------- library/encode.c | 66 ++++++++++++++++--- library/format.c | 37 ++--------- library/misc.c | 17 ++--- resources/tidy.sh | 1 + 12 files changed, 123 insertions(+), 221 deletions(-) create mode 100644 .clang-tidy delete mode 100644 library/developer.h diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..db506f6 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,2 @@ + +Checks: "-*, bugprone-*, clang-analyzer-*, cert-*, misc-*, performance-*, portability-*" diff --git a/.gitignore b/.gitignore index 595d057..ca6c069 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,7 @@ -/akoenc -/akodec - -/resources/prob - -/*.a -/*.so - -*.ako -*.pgm -/*.png - -/.ninja_log -/.ninja_deps /build/ /build-debug/ /build-release/ -/objects/ + /dev/ /.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b39bf08..7b3ebea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ endif () set(AKO_SOURCES "./library/decode.c" + "./library/developer.c" "./library/encode.c" "./library/format.c" "./library/head.c" diff --git a/library/ako-private.h b/library/ako-private.h index a06c86b..2cde1f7 100644 --- a/library/ako-private.h +++ b/library/ako-private.h @@ -5,12 +5,13 @@ #include "ako.h" +#if (AKO_FREESTANDING == 0) #include +#include #define DEV_PRINTF(...) printf(__VA_ARGS__) +#endif -typedef int16_t wavelet_t; // Technically a 'wavelet coefficient' - struct akoTileHead { uint16_t unused; @@ -22,6 +23,10 @@ struct akoLiftHead }; +// developer.c: + +void akoSavePgmI16(size_t width, size_t height, size_t in_stride, const int16_t* in, const char* filename); + // format.c: void akoFormatToPlanarI16Yuv(int keep_transparent_pixels, enum akoColorspace, size_t channels, size_t width, diff --git a/library/ako.h b/library/ako.h index 6fdd279..f5d82af 100644 --- a/library/ako.h +++ b/library/ako.h @@ -103,6 +103,8 @@ void akoDefaultFree(void*); const char* akoStatusString(enum akoStatus); +void akoSavePgmU8(size_t width, size_t height, size_t in_stride, const uint8_t* in, const char* filename); + int akoVersionMajor(); int akoVersionMinor(); int akoVersionPatch(); diff --git a/library/decode.c b/library/decode.c index acc514f..ef420e8 100644 --- a/library/decode.c +++ b/library/decode.c @@ -37,8 +37,10 @@ uint8_t* akoDecodeExt(const struct akoCallbacks* c, size_t input_size, const voi size_t image_w; size_t image_h; - wavelet_t* workarea_a = NULL; - wavelet_t* workarea_b = NULL; + uint8_t* in_cursor = in; + + int16_t* workarea_a = NULL; + int16_t* workarea_b = NULL; // Check callbacks const struct akoCallbacks checked_c = (c != NULL) ? *c : akoDefaultCallbacks(); @@ -82,10 +84,18 @@ uint8_t* akoDecodeExt(const struct akoCallbacks* c, size_t input_size, const voi { const size_t tile_w = akoTileDimension(tile_x, image_w, s.tiles_dimension); const size_t tile_h = akoTileDimension(tile_y, image_h, s.tiles_dimension); - const size_t tile_size = akoTileDataSize(tile_w, tile_h) * channels; + const size_t tile_size = (s.wavelet != AKO_WAVELET_NONE) + ? (akoTileDataSize(tile_w, tile_h) * channels) + : (tile_w * tile_h * channels * sizeof(int16_t)); // No wavelet is a rare case - DEV_PRINTF("D\tTile %zu at %zu:%zu, %zux%zu px, size: %zu bytes\n", t, tile_x, tile_y, tile_w, tile_h, - tile_size); + // Developers, developers, developers + if (t < 10) + { + DEV_PRINTF("D\tTile %zu at %zu:%zu, %zux%zu px, size: %zu bytes\n", t, tile_x, tile_y, tile_w, tile_h, + tile_size); + } + else if (t == 11) + DEV_PRINTF("D\t...\n"); // Next tile tile_x += s.tiles_dimension; diff --git a/library/developer.c b/library/developer.c index b3f6e7b..bf7dc33 100644 --- a/library/developer.c +++ b/library/developer.c @@ -1,4 +1,4 @@ -/*----------------------------- +/* MIT License @@ -21,138 +21,44 @@ 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. +*/ -------------------------------- - [developer.c] - - Alexander Brandt 2021 ------------------------------*/ +#include "ako-private.h" -#include -#include -#include -#include "ako.h" -#include "developer.h" - - -#if (AKO_DEV_TINY_BENCHMARK == 1) -inline struct Benchmark DevBenchmarkCreate(const char* name) -{ - return (struct Benchmark){.start = 0, .elapsed = 0, .name = name}; -} - -inline void DevBenchmarkStartResume(struct Benchmark* benchmark) -{ - if (benchmark->start == 0) - benchmark->start = clock(); -} - -inline void DevBenchmarkPause(struct Benchmark* benchmark) -{ - if (benchmark->start != 0) - { - benchmark->elapsed = benchmark->elapsed + (clock() - benchmark->start); - benchmark->start = 0; - } -} - -inline void DevBenchmarkStop(struct Benchmark* benchmark) +#if (AKO_FREESTANDING == 0) +void akoSavePgmU8(size_t width, size_t height, size_t in_stride, const uint8_t* in, const char* filename) { - if (benchmark->start != 0) + FILE* fp = fopen(filename, "wb"); + if (fp != NULL) { - benchmark->elapsed = (clock() - benchmark->start); - benchmark->start = 0; - } -} - -inline void DevBenchmarkPrint(struct Benchmark* benchmark) -{ - DevBenchmarkStop(benchmark); + fprintf(fp, "P5\n%zu\n%zu\n255\n", width, height); - const double ms = (double)benchmark->elapsed / ((double)CLOCKS_PER_SEC / 1000.0f); - const double sec = (double)benchmark->elapsed / (double)CLOCKS_PER_SEC; + for (size_t row = 0; row < height; row++) + fwrite(in + (row * in_stride), 1, width, fp); - if (ms < 1000.0f) - printf("###\t%7.2f ms | %s\n", ms, benchmark->name); - else - printf("###\t%7.2f s | %s\n", sec, benchmark->name); -} -#else -inline struct Benchmark DevBenchmarkCreate(const char* name) -{ - (void)name; - return (struct Benchmark){0}; -} - -inline void DevBenchmarkStartResume(struct Benchmark* benchmark) -{ - (void)benchmark; -} - -inline void DevBenchmarkPause(struct Benchmark* benchmark) -{ - (void)benchmark; -} - -inline void DevBenchmarkStop(struct Benchmark* benchmark) -{ - (void)benchmark; -} - -inline void DevBenchmarkPrint(struct Benchmark* benchmark) -{ - (void)benchmark; + fclose(fp); + } } -#endif -#if (AKO_DEV_SAVE_IMAGES == 1) -void DevSaveGrayPgm(size_t width, size_t height, size_t in_pitch, const int16_t* data, const char* filename_format, ...) +void akoSavePgmI16(size_t width, size_t height, size_t in_stride, const int16_t* in, const char* filename) { - char filename[256]; - va_list args; - - va_start(args, filename_format); - vsprintf(filename, filename_format, args); - va_end(args); - FILE* fp = fopen(filename, "wb"); - fprintf(fp, "P5\n%zu\n%zu\n255\n", width, height); - - for (size_t row = 0; row < height; row++) - for (size_t col = 0; col < width; col++) - { - const int16_t* in = &data[(row * in_pitch) + col]; - uint8_t p = (*in > 0) ? (*in < 255) ? (uint8_t)*in : 255 : 0; - fwrite(&p, 1, 1, fp); - } - - fclose(fp); -} -#else -void DevSaveGrayPgm(size_t width, size_t height, size_t in_pitch, const int16_t* data, const char* filename_format, ...) -{ - (void)width; - (void)height; - (void)in_pitch; - (void)data; - (void)filename_format; -} -#endif + if (fp != NULL) + { + fprintf(fp, "P5\n%zu\n%zu\n255\n", width, height); + for (size_t row = 0; row < height; row++) + for (size_t col = 0; col < width; col++) + { + const int16_t i16 = in[(row * in_stride) + col]; + const uint8_t u8 = (i16 > 0) ? (i16 < 255) ? (uint8_t)i16 : 255 : 0; + fwrite(&u8, 1, 1, fp); + } -#if (AKO_DEV_PRINTF_DEBUG == 1) -void DevPrintf(const char* format, ...) -{ - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); -} -#else -void DevPrintf(const char* format, ...) -{ - (void)format; + fclose(fp); + } } #endif diff --git a/library/developer.h b/library/developer.h deleted file mode 100644 index 3ad7af0..0000000 --- a/library/developer.h +++ /dev/null @@ -1,29 +0,0 @@ -/*----------------------------- - - [developer.h] - - Alexander Brandt 2021 ------------------------------*/ - -#ifndef DEVELOPER_H -#define DEVELOPER_H - -#include - -struct Benchmark -{ - clock_t start; - clock_t elapsed; - const char* name; -}; - -struct Benchmark DevBenchmarkCreate(const char* name); -void DevBenchmarkStartResume(struct Benchmark* benchmark); -void DevBenchmarkPause(struct Benchmark* benchmark); -void DevBenchmarkStop(struct Benchmark* benchmark); -void DevBenchmarkPrint(struct Benchmark* benchmark); - -void DevSaveGrayPgm(size_t width, size_t height, size_t in_pitch, const int16_t* data, const char* filename_format, - ...); -void DevPrintf(const char* format, ...); - -#endif diff --git a/library/encode.c b/library/encode.c index 874c290..4f5238a 100644 --- a/library/encode.c +++ b/library/encode.c @@ -31,13 +31,12 @@ size_t akoEncodeExt(const struct akoCallbacks* c, const struct akoSettings* s, s size_t image_h, const void* in, void** out, enum akoStatus* out_status) { enum akoStatus status; - struct akoHead* h; size_t blob_size = 0; - void* blob = NULL; + uint8_t* blob = NULL; - wavelet_t* workarea_a = NULL; - wavelet_t* workarea_b = NULL; + int16_t* workarea_a = NULL; + int16_t* workarea_b = NULL; // Check callbacks and settings const struct akoCallbacks checked_c = (c != NULL) ? *c : akoDefaultCallbacks(); @@ -60,8 +59,7 @@ size_t akoEncodeExt(const struct akoCallbacks* c, const struct akoSettings* s, s } // Write head - h = blob; - if ((status = akoHeadWrite(channels, image_w, image_h, &checked_s, h)) != AKO_OK) + if ((status = akoHeadWrite(channels, image_w, image_h, &checked_s, blob)) != AKO_OK) goto return_failure; // Allocate workareas @@ -87,12 +85,60 @@ size_t akoEncodeExt(const struct akoCallbacks* c, const struct akoSettings* s, s { const size_t tile_w = akoTileDimension(tile_x, image_w, checked_s.tiles_dimension); const size_t tile_h = akoTileDimension(tile_y, image_h, checked_s.tiles_dimension); - const size_t tile_size = akoTileDataSize(tile_w, tile_h) * channels; + const size_t tile_size = + (checked_s.wavelet != AKO_WAVELET_NONE) + ? (akoTileDataSize(tile_w, tile_h) * channels) + : (tile_w * tile_h * channels * sizeof(int16_t)); // No wavelet is a rare case, so those 16 bits + // (format in which following steps operate) are + // a waste of space + + // 1. Format + akoFormatToPlanarI16Yuv(checked_s.discard_transparent_pixels, checked_s.colorspace, channels, tile_w, tile_h, + image_w, 0, (const uint8_t*)in + ((image_w * tile_y) + tile_x) * channels, workarea_a); + + // 2. Wavelet transform + // if (checked_s.wavelet != AKO_WAVELET_NONE) + { + // TODO + } + + // 3. Compression + // if (checked_s.compression == AKO_COMPRESSION_NONE) + { + // Make space + void* updated_blob = checked_c.realloc(blob, blob_size + tile_size); + if (updated_blob == NULL) + { + status = AKO_NO_ENOUGH_MEMORY; + goto return_failure; + } + + // Copy as is + blob = updated_blob; + for (size_t i = 0; i < tile_size; i++) + blob[blob_size + i] = ((uint8_t*)workarea_a)[i]; + + blob_size += tile_size; // Update + } - DEV_PRINTF("E\tTile %zu at %zu:%zu, %zux%zu px, size: %zu bytes\n", t, tile_x, tile_y, tile_w, tile_h, - tile_size); + // 4. Developers, developers, developers + if (t < 10) + { + char filename[64]; + + DEV_PRINTF("E\tTile %zu at %zu:%zu, %zux%zu px, size: %zu bytes, blob size: %zu bytes\n", t, tile_x, tile_y, + tile_w, tile_h, tile_size, blob_size); + + for (size_t ch = 0; ch < channels; ch++) + { + snprintf(filename, 64, "E tile%zu ch%zu.pgm", t, ch); + akoSavePgmI16(tile_w, tile_h, tile_w, workarea_a + (tile_w * tile_h) * ch, filename); + } + } + else if (t == 11) + DEV_PRINTF("E\t...\n"); - // Next tile + // 5. Next tile tile_x += checked_s.tiles_dimension; if (tile_x >= image_w) { diff --git a/library/format.c b/library/format.c index b1da706..1bb7c44 100644 --- a/library/format.c +++ b/library/format.c @@ -27,56 +27,43 @@ SOFTWARE. #include "ako-private.h" -static inline void sDeinterleave(int keep_transparent_pixels, size_t channels, size_t width, size_t in_stride, +static inline void sDeinterleave(int discard_transparent_pixels, size_t channels, size_t width, size_t in_stride, size_t out_plane, const uint8_t* in, const uint8_t* in_end, int16_t* out) { - if (keep_transparent_pixels != 0) + if (discard_transparent_pixels != 0) { for (; in < in_end; in += in_stride, out += width) for (size_t col = 0; col < width; col++) { out[out_plane * (channels - 1) + col] = (int16_t)(in[col * channels + (channels - 1)]); - // clang-format off if (in[col * channels + (channels - 1)] != 0) { - #ifdef __clang__ - #pragma clang loop unroll_count(4) for (size_t ch = 0; ch < (channels - 1); ch++) out[out_plane * ch + col] = (int16_t)(in[col * channels + ch]); - #endif } else { - #ifdef __clang__ - #pragma clang loop unroll_count(4) for (size_t ch = 0; ch < (channels - 1); ch++) out[out_plane * ch + col] = 0; - #endif } - // clang-format on } } else { - // clang-format off for (; in < in_end; in += in_stride, out += width) for (size_t col = 0; col < width; col++) { - #ifdef __clang__ - #pragma clang loop unroll_count(4) for (size_t ch = 0; ch < channels; ch++) out[out_plane * ch + col] = (int16_t)(in[col * channels + ch]); - #endif } - // clang-format on } } -void akoFormatToPlanarI16Yuv(int keep_transparent_pixels, enum akoColorspace colorspace, size_t channels, size_t width, - size_t height, size_t input_stride, size_t out_planes_spacing, const uint8_t* in, - int16_t* out) +void akoFormatToPlanarI16Yuv(int discard_transparent_pixels, enum akoColorspace colorspace, size_t channels, + size_t width, size_t height, size_t input_stride, size_t out_planes_spacing, + const uint8_t* in, int16_t* out) { // Deinterleave, convert from u8 to i16, and remove (or not) transparent pixels { @@ -88,9 +75,9 @@ void akoFormatToPlanarI16Yuv(int keep_transparent_pixels, enum akoColorspace col if (channels == 3) sDeinterleave(0, 3, width, in_stride, out_plane, in, in_end, out); else if (channels == 4) - sDeinterleave(keep_transparent_pixels, 4, width, in_stride, out_plane, in, in_end, out); + sDeinterleave(discard_transparent_pixels, 4, width, in_stride, out_plane, in, in_end, out); else if (channels == 2) - sDeinterleave(keep_transparent_pixels, 2, width, in_stride, out_plane, in, in_end, out); + sDeinterleave(discard_transparent_pixels, 2, width, in_stride, out_plane, in, in_end, out); else sDeinterleave(0, channels, width, in_stride, out_plane, in, in_end, out); } @@ -189,34 +176,24 @@ static inline void sYCoCgRToRgb(size_t channels, size_t width, size_t height, si static inline void sSaturateRgb(size_t channels, size_t width, size_t height, size_t in_plane, int16_t* in) { - // clang-format off - #ifdef __clang__ - #pragma clang loop unroll_count(4) for (size_t ch = 0; ch < channels; ch++) for (size_t c = 0; c < (width * height); c++) { const int16_t v = *(in + in_plane * ch + c); in[in_plane * ch + c] = (int16_t)((v > 0) ? (v < 255) ? v : 255 : 0); } - #endif - // clang-format on } static inline void sInterleave(size_t channels, size_t width, size_t in_plane, size_t out_stride, const int16_t* in, uint8_t* out, const uint8_t* out_end) { - // clang-format off for (; out < out_end; out += out_stride, in += width) for (size_t col = 0; col < width; col++) { - #ifdef __clang__ - #pragma clang loop unroll_count(4) for (size_t ch = 0; ch < channels; ch++) out[col * channels + ch] = (uint8_t)(in[in_plane * ch + col]); - #endif } - // clang-format on } diff --git a/library/misc.c b/library/misc.c index 2c9c454..6d0f09a 100644 --- a/library/misc.c +++ b/library/misc.c @@ -27,11 +27,6 @@ SOFTWARE. #include "ako-private.h" -#if (AKO_FREESTANDING == 0) -#include -#endif - - struct akoSettings akoDefaultSettings() { struct akoSettings s = {0}; @@ -108,7 +103,7 @@ size_t akoTileDataSize(size_t tile_w, size_t tile_h) TileDataSize(d) = (d * d * W + log2(d / 2) * L + T) Where, d = Tile dimension (either 'tile_w' or 'tile_h') - W = Size of wavelet + W = Size of wavelet coefficient L = Size of lift head T = Size of tile head @@ -125,12 +120,12 @@ size_t akoTileDataSize(size_t tile_w, size_t tile_h) { tile_w = akoDividePlusOneRule(tile_w); tile_h = akoDividePlusOneRule(tile_h); - size += (tile_w * tile_h) * sizeof(wavelet_t) * 3; // Three highpasses... - size += sizeof(struct akoLiftHead); // One lift head... + size += (tile_w * tile_h) * sizeof(int16_t) * 3; // Three highpasses... + size += sizeof(struct akoLiftHead); // One lift head... } - size += (tile_w * tile_h) * sizeof(wavelet_t); // One lowpass - size += sizeof(struct akoTileHead); // And one tile head... + size += (tile_w * tile_h) * sizeof(int16_t); // One lowpass + size += sizeof(struct akoTileHead); // And one tile head... return size; } @@ -163,7 +158,7 @@ size_t akoImageMaxTileDataSize(size_t image_w, size_t image_h, size_t tiles_dime if (tiles_dimension == 0 || (tiles_dimension >= image_w && tiles_dimension >= image_h)) return akoTileDataSize(image_w, image_h); - if ((image_w % tiles_dimension) == 0 && (image_w % tiles_dimension) == 0) + if ((image_w % tiles_dimension) == 0 && (image_h % tiles_dimension) == 0) return akoTileDataSize(tiles_dimension, tiles_dimension); // Tiles of varying size on image borders diff --git a/resources/tidy.sh b/resources/tidy.sh index 53e63e6..fa2b2e3 100755 --- a/resources/tidy.sh +++ b/resources/tidy.sh @@ -3,6 +3,7 @@ cflags="-std=c11 -Wall -Wextra -Wconversion -pedantic -I./library" cfiles="./library/decode.c + ./library/developer.c ./library/encode.c ./library/format.c ./library/head.c