Skip to content

Commit

Permalink
Memory and tiles management
Browse files Browse the repository at this point in the history
  • Loading branch information
baAlex committed Jan 6, 2022
1 parent 88d6668 commit 2c060e3
Show file tree
Hide file tree
Showing 9 changed files with 357 additions and 65 deletions.
26 changes: 26 additions & 0 deletions library/ako-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@
#include "ako.h"


#include <stdio.h>
#define DEV_PRINTF(...) printf(__VA_ARGS__)


typedef int16_t wavelet_t; // Technically a 'wavelet coefficient'

struct akoTileHead
{
uint16_t unused;
};

struct akoLiftHead
{
uint16_t quantization;
};


// format.c:

void akoFormatToPlanarI16Yuv(int keep_transparent_pixels, enum akoColorspace, size_t channels, size_t width,
Expand All @@ -20,4 +37,13 @@ enum akoStatus akoHeadWrite(size_t channels, size_t image_w, size_t image_h, con
enum akoStatus akoHeadRead(const void* in, size_t* out_channels, size_t* out_image_w, size_t* out_image_h,
struct akoSettings* out_s);

// misc.c:

size_t akoDividePlusOneRule(size_t x);
size_t akoTileDataSize(size_t tile_w, size_t tile_h);
size_t akoTileDimension(size_t x, size_t image_w, size_t tiles_dimension);

size_t akoImageMaxTileDataSize(size_t image_w, size_t image_h, size_t tiles_dimension);
size_t akoImageTilesNo(size_t image_w, size_t image_h, size_t tiles_dimension);

#endif
14 changes: 8 additions & 6 deletions library/ako.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum akoStatus
AKO_INVALID_MAGIC,
AKO_UNSUPPORTED_VERSION,
AKO_NO_ENOUGH_MEMORY,
AKO_INVALID_FLAGS,
};

enum akoWrap
Expand Down Expand Up @@ -76,17 +77,18 @@ struct akoCallbacks
struct akoHead
{
uint8_t magic[3]; // "Ako"
uint8_t version;
uint8_t version; // 2 (AKO_FORMAT_VERSION)

uint32_t width; // 0 = Invalid
uint32_t height; // Ditto

uint32_t flags;
// bits 0-3 = channels, 0 = Gray, 1 = Gray + Alpha, 2 = RGB, 3 = RGBA
// bits 4-5 = wrap, 0 = Clamp, 1 = Repeat, 2 = Set to zero
// bits 6-7 = wavelet, 0 = DD137, 1 = CDF53, 2 = Haar, 3 = None
// bits 8-9 = colorspace, 0 = YCOCG, 1 = YCOCG-R, 2 = RGB
// bits 10-14 = tiles dimension, 0 = No tiles, 1 = 8x8, 2 = 16x16, 3 = 32x32, 4 = 64x64, etc...
// bits 0-3 : Channels, 0 = Gray, 1 = Gray + Alpha, 2 = RGB, 3 = RGBA
// bits 4-5 : Wrap, 0 = Clamp, 1 = Repeat, 2 = Set to zero
// bits 6-7 : Wavelet, 0 = DD137, 1 = CDF53, 2 = Haar, 3 = None
// bits 8-9 : Colorspace, 0 = YCOCG, 1 = YCOCG-R, 2 = RGB
// bits 10-14 : Tiles dimension, 0 = No tiles, 1 = 8x8, 2 = 16x16, 3 = 32x32, 4 = 64x64, etc...
// bits 15-32 : Unused bits
};


Expand Down
49 changes: 49 additions & 0 deletions library/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ 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;

// Check callbacks
const struct akoCallbacks checked_c = (c != NULL) ? *c : akoDefaultCallbacks();

Expand All @@ -56,7 +59,49 @@ uint8_t* akoDecodeExt(const struct akoCallbacks* c, size_t input_size, const voi
if ((status = akoHeadRead(in, &channels, &image_w, &image_h, &s)) != AKO_OK)
goto return_failure;

// Allocate workareas
const size_t tiles_no = akoImageTilesNo(image_w, image_h, s.tiles_dimension);
const size_t tile_max_size = akoImageMaxTileDataSize(image_w, image_h, s.tiles_dimension) * channels;

workarea_a = checked_c.malloc(tile_max_size);
workarea_b = checked_c.malloc(tile_max_size);

if (workarea_a == NULL || workarea_b == NULL)
{
status = AKO_NO_ENOUGH_MEMORY;
goto return_failure;
}

DEV_PRINTF("D\tTiles no: %zu, Max tile size: %zu\n", tiles_no, tile_max_size);

// Iterate tiles
size_t tile_x = 0;
size_t tile_y = 0;

for (size_t t = 0; t < tiles_no; t++)
{
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;

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);

// Next tile
tile_x += s.tiles_dimension;
if (tile_x >= image_w)
{
tile_x = 0;
tile_y += s.tiles_dimension;
}
}

DEV_PRINTF("\n");

// Bye!
checked_c.free(workarea_a);
checked_c.free(workarea_b);

if (out_s != NULL)
*out_s = s;
if (out_channels != NULL)
Expand All @@ -72,6 +117,10 @@ uint8_t* akoDecodeExt(const struct akoCallbacks* c, size_t input_size, const voi
return checked_c.malloc(1); // TODO

return_failure:
if (workarea_a != NULL)
checked_c.free(workarea_a);
if (workarea_b != NULL)
checked_c.free(workarea_b);
if (out_status != NULL)
*out_status = status;

Expand Down
62 changes: 55 additions & 7 deletions library/encode.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ size_t akoEncodeExt(const struct akoCallbacks* c, const struct akoSettings* s, s
size_t blob_size = 0;
void* blob = NULL;

wavelet_t* workarea_a = NULL;
wavelet_t* workarea_b = NULL;

// Check callbacks and settings
const struct akoCallbacks checked_c = (c != NULL) ? *c : akoDefaultCallbacks();
const struct akoSettings checked_s = (s != NULL) ? *s : akoDefaultSettings();
Expand All @@ -56,28 +59,73 @@ size_t akoEncodeExt(const struct akoCallbacks* c, const struct akoSettings* s, s
goto return_failure;
}

// Create head
// Write head
h = blob;
if ((status = akoHeadWrite(channels, image_w, image_h, &checked_s, h)) != AKO_OK)
goto return_failure;

// Allocate workareas
const size_t tiles_no = akoImageTilesNo(image_w, image_h, checked_s.tiles_dimension);
const size_t tile_max_size = akoImageMaxTileDataSize(image_w, image_h, checked_s.tiles_dimension) * channels;

workarea_a = checked_c.malloc(tile_max_size);
workarea_b = checked_c.malloc(tile_max_size);

if (workarea_a == NULL || workarea_b == NULL)
{
status = AKO_NO_ENOUGH_MEMORY;
goto return_failure;
}

DEV_PRINTF("E\tTiles no: %zu, Max tile size: %zu\n", tiles_no, tile_max_size);

// Iterate tiles
size_t tile_x = 0;
size_t tile_y = 0;

for (size_t t = 0; t < tiles_no; t++)
{
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;

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);

// Next tile
tile_x += checked_s.tiles_dimension;
if (tile_x >= image_w)
{
tile_x = 0;
tile_y += checked_s.tiles_dimension;
}
}

DEV_PRINTF("\n");

// Bye!
checked_c.free(workarea_a);
checked_c.free(workarea_b);

if (out_status != NULL)
*out_status = AKO_OK;

if (out != NULL)
*out = blob;
else
checked_c.free(blob); // Discard encoded data

if (out_status != NULL)
*out_status = AKO_OK;

return blob_size;

return_failure:
if (blob != NULL)
checked_c.free(blob);

if (workarea_a != NULL)
checked_c.free(workarea_a);
if (workarea_b != NULL)
checked_c.free(workarea_b);
if (out_status != NULL)
*out_status = status;
if (blob != NULL)
checked_c.free(blob);

return 0;
}
14 changes: 11 additions & 3 deletions library/head.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ enum akoStatus akoHeadWrite(size_t channels, size_t width, size_t height, const
if (((size_t)1 << binary_tiles_dimension) != s->tiles_dimension)
return AKO_INVALID_TILES_DIMENSIONS;

binary_tiles_dimension -= 3; // As AKO_MIN_TILES_DIMENSION is 8
binary_tiles_dimension -= 2; // As AKO_MIN_TILES_DIMENSION is 8
}

// Validate
Expand Down Expand Up @@ -114,14 +114,22 @@ enum akoStatus akoHeadRead(const void* in, size_t* out_channels, size_t* out_wid
if (h->version != AKO_FORMAT_VERSION)
return AKO_UNSUPPORTED_VERSION;

if ((h->flags >> 15) != 0)
return AKO_INVALID_FLAGS;

const size_t channels = (size_t)((h->flags & 0x000F)) + 1;
const enum akoWrap wrap = (enum akoWrap)((h->flags >> 4) & 0x0003);
const enum akoWavelet wavelet = (enum akoWavelet)((h->flags >> 6) & 0x0003);
const enum akoColorspace colorspace = (enum akoColorspace)((h->flags >> 8) & 0x0003);

size_t tiles_dimension = ((h->flags >> 10) & 0x001F);
if (tiles_dimension > 0 && tiles_dimension < (32 - 3))
tiles_dimension = 1 << (tiles_dimension + 3);
if (tiles_dimension != 0)
{
if (tiles_dimension < (32 - 2))
tiles_dimension = (size_t)1 << (tiles_dimension + 2);
else
return AKO_INVALID_TILES_DIMENSIONS;
}

const enum akoStatus validation =
sValidate(channels, (size_t)h->width, (size_t)h->height, tiles_dimension, wrap, wavelet, colorspace);
Expand Down
98 changes: 98 additions & 0 deletions library/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,106 @@ const char* akoStatusString(enum akoStatus status)
case AKO_INVALID_MAGIC: return "Invalid magic (not an Ako file)";
case AKO_UNSUPPORTED_VERSION: return "Unsupported version";
case AKO_NO_ENOUGH_MEMORY: return "No enough memory";
case AKO_INVALID_FLAGS: return "Invalid flags";
default: break;
}

return "Unknown status code";
}


size_t akoDividePlusOneRule(size_t x)
{
return (x % 2 == 0) ? (x / 2) : ((x + 1) / 2);
}


size_t akoTileDataSize(size_t tile_w, size_t tile_h)
{
/*
If 'tile_w' and 'tile_h' equals, are a power-of-two (2, 4, 8, 16, etc), and
function 'log2' operates on integers whitout rounding errors; then:
TileDataSize(d) = (d * d * W + log2(d / 2) * L + T)
Where, d = Tile dimension (either 'tile_w' or 'tile_h')
W = Size of wavelet
L = Size of lift head
T = Size of tile head
If not, is a recursive function, where we add 1 to tile dimensions
on lift steps that are not divisible by 2 (DividePlusOne rule). Lift
steps with their respective head. And finally one tile head at the end.
This later approach is used in below C code.
*/

size_t size = 0;

while (tile_w > 2 && tile_h > 2) // For each lift step...
{
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(wavelet_t); // One lowpass
size += sizeof(struct akoTileHead); // And one tile head...

return size;
}


size_t akoTileDimension(size_t x, size_t image_w, size_t tiles_dimension)
{
if (tiles_dimension == 0)
return image_w;

if (x + tiles_dimension > image_w)
return (image_w % tiles_dimension);

return tiles_dimension;
}


static size_t sMax(size_t a, size_t b)
{
return (a > b) ? a : b;
}

static size_t sMin(size_t a, size_t b)
{
return (a < b) ? a : b;
}

size_t akoImageMaxTileDataSize(size_t image_w, size_t image_h, size_t tiles_dimension)
{
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)
return akoTileDataSize(tiles_dimension, tiles_dimension);

// Tiles of varying size on image borders
// (in this horrible way because TileDataSize() is recursive, and my brain hurts)
const size_t a = akoTileDataSize(tiles_dimension, tiles_dimension);
const size_t b = akoTileDataSize(sMin(tiles_dimension, (image_w % tiles_dimension)), tiles_dimension);
const size_t c = akoTileDataSize(tiles_dimension, sMin(tiles_dimension, (image_h % tiles_dimension)));

return sMax(sMax(a, b), c);
}


size_t akoImageTilesNo(size_t image_w, size_t image_h, size_t tiles_dimension)
{
if (tiles_dimension == 0)
return 1;

size_t tiles_x = (image_w / tiles_dimension);
size_t tiles_y = (image_h / tiles_dimension);
tiles_x = (image_w % tiles_dimension != 0) ? (tiles_x + 1) : tiles_x;
tiles_y = (image_h % tiles_dimension != 0) ? (tiles_y + 1) : tiles_y;

return (tiles_x * tiles_y);
}
12 changes: 12 additions & 0 deletions resources/tidy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

cflags="-std=c11 -Wall -Wextra -Wconversion -pedantic -I./library"

cfiles="./library/decode.c
./library/encode.c
./library/format.c
./library/head.c
./library/misc.c
./library/version.c"

clang-tidy-12 $cfiles -- $cflags
Loading

0 comments on commit 2c060e3

Please sign in to comment.