summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Sibille <clements+git@lisible.xyz>2024-03-05 16:02:17 +0900
committerClement Sibille <clements+git@lisible.xyz>2024-03-05 16:45:45 +0900
commitbb6f08f3c79efc7bb7877aca97cebd3dab8b7838 (patch)
tree9b3405e607c1f746a7061730adcf831c50a9f70b
parente1e5b4e92bcd460b43ce1b852560751b6525593e (diff)
Implement PNG decompression
This patch adds PNG decompression for images without interlacing and without alpha channel. Only basic image data is supported. Background, transparency, gamma, paletted images are not supported.
-rw-r--r--lisiblepng-bin/src/main.c5
-rw-r--r--lisiblepng/meson.build7
-rw-r--r--lisiblepng/src/lisiblepng.c290
-rw-r--r--lisiblepng/src/lisiblepng/assert.h (renamed from lisiblepng/src/assert.h)4
-rw-r--r--lisiblepng/src/lisiblepng/bitstream.c (renamed from lisiblepng/src/bitstream.c)0
-rw-r--r--lisiblepng/src/lisiblepng/bitstream.h (renamed from lisiblepng/src/bitstream.h)0
-rw-r--r--lisiblepng/src/lisiblepng/deflate.c (renamed from lisiblepng/src/deflate.c)87
-rw-r--r--lisiblepng/src/lisiblepng/deflate.h (renamed from lisiblepng/src/deflate.h)6
-rw-r--r--lisiblepng/src/lisiblepng/log.h23
-rw-r--r--lisiblepng/src/log.h9
10 files changed, 357 insertions, 74 deletions
diff --git a/lisiblepng-bin/src/main.c b/lisiblepng-bin/src/main.c
index 7979326..62dea31 100644
--- a/lisiblepng-bin/src/main.c
+++ b/lisiblepng-bin/src/main.c
@@ -1,8 +1,11 @@
#include <errno.h>
#include <lisiblepng.h>
-#include <log.h>
+#include <stdio.h>
#include <string.h>
+#define LOG0(msg) fprintf(stderr, msg)
+#define LOGN(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
+
int main(int argc, char **argv) {
if (argc != 2) {
LOG0("Usage: lisiblepng <png filepath>");
diff --git a/lisiblepng/meson.build b/lisiblepng/meson.build
index 224011a..22510fe 100644
--- a/lisiblepng/meson.build
+++ b/lisiblepng/meson.build
@@ -1,3 +1,6 @@
+cc = meson.get_compiler('c')
+m_dep = cc.find_library('m', required: true)
+
lisiblepng_incdir = include_directories('src/')
-lisiblepng_lib = library('lisiblepng', 'src/lisiblepng.c', 'src/deflate.c', 'src/bitstream.c')
-lisiblepng_dep = declare_dependency(include_directories: lisiblepng_incdir, link_with: [lisiblepng_lib])
+lisiblepng_lib = library('lisiblepng', 'src/lisiblepng.c', 'src/lisiblepng/deflate.c', 'src/lisiblepng/bitstream.c', dependencies: [m_dep])
+lisiblepng_dep = declare_dependency(include_directories: lisiblepng_incdir, link_with: [lisiblepng_lib], dependencies: [m_dep])
diff --git a/lisiblepng/src/lisiblepng.c b/lisiblepng/src/lisiblepng.c
index 5ccd300..7a3818b 100644
--- a/lisiblepng/src/lisiblepng.c
+++ b/lisiblepng/src/lisiblepng.c
@@ -1,7 +1,7 @@
#include "lisiblepng.h"
-#include "assert.h"
-#include "deflate.h"
-#include "log.h"
+#include "lisiblepng/assert.h"
+#include "lisiblepng/deflate.h"
+#include "lisiblepng/log.h"
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
@@ -61,8 +61,39 @@ const uint32_t CRC32_TABLE[256] = {
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D};
+typedef enum {
+ ColourType_Greyscale = 0,
+ ColourType_Truecolour = 2,
+ ColourType_IndexedColour = 3,
+ ColourType_GreyscaleWithAlpha = 4,
+ ColourType_TruecolourWithAlpha = 6,
+ ColourType_Unknown
+} ColourType;
+
+uint8_t ColourType_sample_count(const ColourType colour_type) {
+ switch (colour_type) {
+ case ColourType_Greyscale:
+ return 1;
+ case ColourType_Truecolour:
+ return 3;
+ case ColourType_IndexedColour:
+ return 1;
+ case ColourType_GreyscaleWithAlpha:
+ return 2;
+ case ColourType_TruecolourWithAlpha:
+ return 4;
+ default:
+ LPNG_LOG_ERR0("Unknown colour type");
+ abort();
+ }
+}
+
struct Png {
- const uint8_t *data;
+ char *data;
+ size_t width;
+ size_t height;
+ ColourType colour_type;
+ uint8_t bits_per_sample;
};
typedef struct {
@@ -103,7 +134,7 @@ long ParsingContext_cursor_position(DeflateDecompressor *ctx) {
bool ParsingContext_skip_bytes(DeflateDecompressor *ctx, size_t byte_count) {
ASSERT(ctx != NULL);
if (fseek(ctx->stream, byte_count, SEEK_CUR) != 0) {
- LOGN("Couldn't skip bytes: %s", strerror(errno));
+ LPNG_LOG_ERR("Couldn't skip bytes: %s", strerror(errno));
return false;
}
@@ -115,7 +146,7 @@ bool ParsingContext_parse_bytes(DeflateDecompressor *ctx, size_t byte_count,
ASSERT(ctx != NULL);
ASSERT(output_buffer != NULL);
if (fread(output_buffer, 1, byte_count, ctx->stream) < byte_count) {
- LOG0("Couldn't parse bytes, EOF reached");
+ LPNG_LOG_ERR0("Couldn't parse bytes, EOF reached");
return false;
}
@@ -180,16 +211,17 @@ typedef struct {
void ImageHeader_print_image_header(const ImageHeader *image_header) {
ASSERT(image_header != NULL);
- LOGN("Image header:\n"
- "- dimensions: %dx%d\n"
- "- bit depth: %d\n"
- "- colour type: %d\n"
- "- compression method: %d\n"
- "- filter method: %d\n"
- "- interlace method: %d",
- image_header->width, image_header->height, image_header->bit_depth,
- image_header->colour_type, image_header->compression_method,
- image_header->filter_method, image_header->interlace_method);
+ LPNG_LOG_DBG("Image header:\n"
+ "- dimensions: %dx%d\n"
+ "- bit depth: %d\n"
+ "- colour type: %d\n"
+ "- compression method: %d\n"
+ "- filter method: %d\n"
+ "- interlace method: %d",
+ image_header->width, image_header->height,
+ image_header->bit_depth, image_header->colour_type,
+ image_header->compression_method, image_header->filter_method,
+ image_header->interlace_method);
}
bool ParsingContext_validate_crc_if_required(DeflateDecompressor *ctx) {
@@ -198,7 +230,7 @@ bool ParsingContext_validate_crc_if_required(DeflateDecompressor *ctx) {
uint32_t crc;
PARSE_FIELD(uint32_t, crc);
if (computed_crc != crc) {
- LOG0("Invalid CRC checksum");
+ LPNG_LOG_ERR0("Invalid CRC checksum");
return false;
}
#else
@@ -208,6 +240,11 @@ bool ParsingContext_validate_crc_if_required(DeflateDecompressor *ctx) {
return true;
}
+bool is_valid_bit_depth(uint8_t bit_depth) {
+ return bit_depth == 1 || bit_depth == 2 || bit_depth == 4 || bit_depth == 8 ||
+ bit_depth == 16;
+}
+
bool parse_IHDR_chunk(DeflateDecompressor *ctx, ImageHeader *image_header) {
ASSERT(ctx != NULL);
ASSERT(image_header != NULL);
@@ -220,7 +257,7 @@ bool parse_IHDR_chunk(DeflateDecompressor *ctx, ImageHeader *image_header) {
uint32_t type;
PARSE_FIELD(uint32_t, type);
if (type != IHDR_CHUNK_TYPE) {
- LOG0("Expected IHDR chunk");
+ LPNG_LOG_ERR0("Expected IHDR chunk");
return false;
}
@@ -228,6 +265,10 @@ bool parse_IHDR_chunk(DeflateDecompressor *ctx, ImageHeader *image_header) {
PARSE_FIELD(uint32_t, image_header->width);
PARSE_FIELD(uint32_t, image_header->height);
PARSE_FIELD(uint8_t, image_header->bit_depth);
+ if (!is_valid_bit_depth(image_header->bit_depth)) {
+ LPNG_LOG_ERR("Invalid bitdepth: %d", image_header->bit_depth);
+ return false;
+ }
PARSE_FIELD(uint8_t, image_header->colour_type);
PARSE_FIELD(uint8_t, image_header->compression_method);
PARSE_FIELD(uint8_t, image_header->filter_method);
@@ -265,6 +306,119 @@ uint32_t uint32_t_to_le(uint32_t value) {
(value_bytes[2] << 8) + value_bytes[3];
}
+uint8_t none_reconstruction_function(uint8_t recon_a, uint8_t recon_b,
+ uint8_t recon_c, uint8_t x) {
+ (void)recon_a;
+ (void)recon_b;
+ (void)recon_c;
+ return x;
+}
+uint8_t sub_reconstruction_function(uint8_t recon_a, uint8_t recon_b,
+ uint8_t recon_c, uint8_t x) {
+ (void)recon_b;
+ (void)recon_c;
+ return x + recon_a;
+}
+uint8_t up_reconstruction_function(uint8_t recon_a, uint8_t recon_b,
+ uint8_t recon_c, uint8_t x) {
+ (void)recon_a;
+ (void)recon_c;
+ return x + recon_b;
+}
+uint8_t average_reconstruction_function(uint8_t recon_a, uint8_t recon_b,
+ uint8_t recon_c, uint8_t x) {
+ (void)recon_c;
+ uint16_t sum = (recon_a + recon_b) / 2u;
+ return x + sum;
+}
+uint8_t paeth_predictor(uint8_t a, uint8_t b, uint8_t c) {
+ int16_t p = a + b - c;
+ int16_t pa = abs(p - a);
+ int16_t pb = abs(p - b);
+ int16_t pc = abs(p - c);
+ uint8_t pr;
+ if (pa <= pb && pa <= pc) {
+ pr = a;
+ } else if (pb <= pc) {
+ pr = b;
+ } else {
+ pr = c;
+ }
+
+ return pr;
+}
+uint8_t paeth_reconstruction_function(uint8_t recon_a, uint8_t recon_b,
+ uint8_t recon_c, uint8_t x) {
+ return x + paeth_predictor(recon_a, recon_b, recon_c);
+}
+typedef uint8_t (*ReconstructionFn)(uint8_t, uint8_t, uint8_t, uint8_t);
+static const ReconstructionFn reconstruction_functions[] = {
+ none_reconstruction_function, sub_reconstruction_function,
+ up_reconstruction_function, average_reconstruction_function,
+ paeth_reconstruction_function};
+
+void apply_reconstruction_functions_to_scanline(
+ char *output_scanline, const char *input_scanline,
+ const char *previous_output_scanline, size_t filter_type,
+ size_t scanline_size, size_t bytes_per_pixel) {
+ ASSERT(output_scanline != NULL);
+ ASSERT(input_scanline != NULL);
+ ASSERT(filter_type <
+ sizeof(reconstruction_functions) / sizeof(ReconstructionFn));
+
+ for (size_t i = 0; i < scanline_size / bytes_per_pixel; i++) {
+ for (size_t p = 0; p < bytes_per_pixel; p++) {
+ uint8_t a = 0;
+ if (i > 0) {
+ a = output_scanline[i * bytes_per_pixel - bytes_per_pixel + p];
+ }
+
+ uint8_t b = 0;
+ uint8_t c = 0;
+ if (previous_output_scanline != NULL) {
+ b = previous_output_scanline[i * bytes_per_pixel + p];
+ if (i > 0) {
+ c = previous_output_scanline[i * bytes_per_pixel - bytes_per_pixel +
+ p];
+ }
+ }
+
+ uint8_t x = input_scanline[i * bytes_per_pixel + p];
+ output_scanline[i * bytes_per_pixel + p] =
+ reconstruction_functions[filter_type](a, b, c, x);
+ }
+ }
+}
+
+void apply_reconstruction_functions(Png *image,
+ const char *decompressed_data_buffer) {
+ ASSERT(image != NULL);
+ ASSERT(decompressed_data_buffer != NULL);
+ size_t sample_count = ColourType_sample_count(image->colour_type);
+ size_t bits_per_pixel = (image->bits_per_sample * sample_count);
+ size_t bytes_per_pixel = bits_per_pixel / 8;
+ if (image->bits_per_sample < 8) {
+ bytes_per_pixel = 1;
+ }
+ size_t scanline_size = image->width * bits_per_pixel / 8;
+
+ for (size_t scanline = 0; scanline < image->height; scanline++) {
+ size_t output_scanline_start = scanline_size * scanline;
+ size_t input_scanline_start = (scanline_size + 1) * scanline;
+ uint8_t filter_type = decompressed_data_buffer[input_scanline_start];
+ const char *previous_output_scanline = NULL;
+ if (scanline > 0) {
+ size_t previous_output_scanline_start = scanline_size * (scanline - 1);
+ previous_output_scanline = &image->data[previous_output_scanline_start];
+ }
+
+ apply_reconstruction_functions_to_scanline(
+ &image->data[output_scanline_start],
+ &decompressed_data_buffer[input_scanline_start + 1],
+ previous_output_scanline, filter_type, scanline_size, bytes_per_pixel);
+ }
+}
+
Png *lis_Png_parse(FILE *stream) {
Png *png = malloc(sizeof(Png));
DeflateDecompressor ctx;
@@ -273,12 +427,12 @@ Png *lis_Png_parse(FILE *stream) {
uint8_t parsed_png_signature[PNG_SIGNATURE_LENGTH];
if (!ParsingContext_parse_bytes(&ctx, PNG_SIGNATURE_LENGTH,
parsed_png_signature)) {
- LOG0("Couldn't parse signature");
+ LPNG_LOG_ERR0("Couldn't parse signature");
goto err;
}
if (!matches_png_signature(parsed_png_signature)) {
- LOG0("Invalid signature");
+ LPNG_LOG_ERR0("Invalid signature");
goto err;
}
@@ -289,40 +443,64 @@ Png *lis_Png_parse(FILE *stream) {
ImageHeader_print_image_header(&header);
ImageData image_data = {0};
+ size_t parsed_data_chunk_count = 0;
bool end_reached = false;
while (!end_reached) {
uint32_t length;
if (!ParsingContext_parse_uint32_t(&ctx, &length)) {
- LOG0("Couldn't parse chunk length");
+ LPNG_LOG_ERR0("Couldn't parse chunk length");
goto cleanup_data;
}
ParsingContext_crc_reset(&ctx);
uint32_t type;
if (!ParsingContext_parse_uint32_t(&ctx, &type)) {
- LOG0("Couldn't parse chunk type");
+ LPNG_LOG_ERR0("Couldn't parse chunk type");
goto cleanup_data;
}
+#ifdef LPNG_DEBUG_LOG
uint32_t type_le = uint32_t_to_le(type);
- LOGN("Parsing %.4s chunk", (char *)&type_le);
+ LPNG_LOG_DBG("Parsing %.4s chunk", (char *)&type_le);
+#endif
+
switch (type) {
case IDAT_CHUNK_TYPE:
- parse_IDAT_chunk(&ctx, length, &image_data);
+ if (!parse_IDAT_chunk(&ctx, length, &image_data)) {
+ LPNG_LOG_ERR0("Couldn't parse IDAT chunk");
+ goto cleanup_data;
+ }
+ parsed_data_chunk_count++;
break;
case IEND_CHUNK_TYPE:
end_reached = true;
ParsingContext_skip_bytes(&ctx, sizeof(uint32_t));
break;
default:
- LOG0("Unknown chunk type, skipping chunk...");
+ LPNG_LOG_DBG0("Unknown chunk type, skipping chunk...");
ParsingContext_skip_bytes(&ctx, length + sizeof(uint32_t));
break;
}
}
- LOGN("Data length: %zul", image_data.length);
- zlib_decompress(image_data.data, image_data.length);
+ if (parsed_data_chunk_count == 0) {
+ LPNG_LOG_ERR0("No IDAT chunk found, at least one is required");
+ goto cleanup_data;
+ }
+
+ LPNG_LOG_DBG("Data length: %zu", image_data.length);
+ png->width = header.width;
+ png->height = header.height;
+ png->colour_type = header.colour_type;
+ png->bits_per_sample = header.bit_depth;
+ size_t output_length;
+ char *output_buffer =
+ zlib_decompress(image_data.data, image_data.length, &output_length);
+ png->data = output_buffer;
+
+ apply_reconstruction_functions(png, output_buffer);
+
+ free(image_data.data);
return png;
@@ -334,4 +512,60 @@ err:
#undef PARSE_FIELD
void lis_Png_destroy(Png *png) { free(png); }
-void lis_Png_dump_ppm(const Png *png) { ASSERT(png != NULL); }
+void lis_Png_dump_ppm(const Png *png) {
+ ASSERT(png != NULL);
+ printf("P3\n");
+ printf("%zu %zu\n", png->width, png->height);
+ printf("%d\n", (1 << png->bits_per_sample) - 1);
+ size_t sample_count = ColourType_sample_count(png->colour_type);
+ size_t bits_per_pixel = png->bits_per_sample * sample_count;
+ size_t bytes_per_pixel = bits_per_pixel / 8;
+ if (png->bits_per_sample < 8) {
+ bytes_per_pixel = 1;
+ }
+ for (size_t pixel_index = 0; pixel_index < png->height * png->width;
+ pixel_index++) {
+
+ if (png->colour_type == ColourType_Greyscale) {
+ if (bits_per_pixel == 16) {
+ uint8_t hi = png->data[pixel_index * bytes_per_pixel];
+ uint8_t lo = png->data[pixel_index * bytes_per_pixel + 1];
+ uint16_t grey = ((hi & 0xFFu) << 8) | (lo & 0xFFu);
+ printf("%u %u %u\n", grey, grey, grey);
+ } else {
+ size_t absolute_bit_offset = pixel_index * bits_per_pixel;
+ size_t byte_offset = absolute_bit_offset / 8;
+ size_t relative_bit_offset = absolute_bit_offset % 8;
+ unsigned char grey =
+ (png->data[byte_offset] >>
+ (7 - relative_bit_offset - (bits_per_pixel - 1))) &
+ ((1 << bits_per_pixel) - 1);
+ printf("%u %u %u\n", grey, grey, grey);
+ }
+ } else if (png->colour_type == ColourType_Truecolour) {
+ if (png->bits_per_sample == 16) {
+ uint8_t hi_r = png->data[pixel_index * bytes_per_pixel];
+ uint8_t lo_r = png->data[pixel_index * bytes_per_pixel + 1];
+ uint16_t r = ((hi_r & 0xFFu) << 8) | (lo_r & 0xFFu);
+
+ uint8_t hi_g = png->data[pixel_index * bytes_per_pixel + 2];
+ uint8_t lo_g = png->data[pixel_index * bytes_per_pixel + 3];
+ uint16_t g = ((hi_g & 0xFFu) << 8) | (lo_g & 0xFFu);
+
+ uint8_t hi_b = png->data[pixel_index * bytes_per_pixel + 4];
+ uint8_t lo_b = png->data[pixel_index * bytes_per_pixel + 5];
+ uint16_t b = ((hi_b & 0xFFu) << 8) | (lo_b & 0xFFu);
+
+ printf("%u %u %u\n", r, g, b);
+ } else {
+ uint8_t r = png->data[pixel_index * bytes_per_pixel];
+ uint8_t g = png->data[pixel_index * bytes_per_pixel + 1];
+ uint8_t b = png->data[pixel_index * bytes_per_pixel + 2];
+ printf("%u %u %u\n", r, g, b);
+ }
+ } else {
+ LPNG_LOG_ERR0("Unsupported colour type");
+ exit(1);
+ }
+ }
+}
diff --git a/lisiblepng/src/assert.h b/lisiblepng/src/lisiblepng/assert.h
index 7d41b57..1de9c36 100644
--- a/lisiblepng/src/assert.h
+++ b/lisiblepng/src/lisiblepng/assert.h
@@ -6,8 +6,8 @@
#define ASSERT(predicate) \
do { \
if (!(predicate)) { \
- LOGN("Assertion failed in %s:%d:\n\t%s", __FILE__, __LINE__, \
- #predicate); \
+ LPNG_LOG_ERR("Assertion failed in %s:%d:\n\t%s", __FILE__, __LINE__, \
+ #predicate); \
exit(1); \
} \
} while (0)
diff --git a/lisiblepng/src/bitstream.c b/lisiblepng/src/lisiblepng/bitstream.c
index e7ddb79..e7ddb79 100644
--- a/lisiblepng/src/bitstream.c
+++ b/lisiblepng/src/lisiblepng/bitstream.c
diff --git a/lisiblepng/src/bitstream.h b/lisiblepng/src/lisiblepng/bitstream.h
index 72d8295..72d8295 100644
--- a/lisiblepng/src/bitstream.h
+++ b/lisiblepng/src/lisiblepng/bitstream.h
diff --git a/lisiblepng/src/deflate.c b/lisiblepng/src/lisiblepng/deflate.c
index d894a35..50f90e3 100644
--- a/lisiblepng/src/deflate.c
+++ b/lisiblepng/src/lisiblepng/deflate.c
@@ -14,7 +14,7 @@
#define CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT 19
#define MAX_HUFFMAN_CODE_LENGTH 15
-#define MAX_LENGTH_CODES 286
+#define MAX_LENGTH_CODES 288
#define MAX_DISTANCE_CODES 32
#define FIXED_LENGTH_LITERAL_CODE_COUNT 288
@@ -52,7 +52,7 @@ void OutputBuffer_init(OutputBuffer *output_buffer) {
ASSERT(output_buffer != NULL);
output_buffer->buffer = calloc(DEFLATE_OUTPUT_BUFFER_INITIAL_CAPACITY, 1);
if (!output_buffer->buffer) {
- LOG0("Couldn't allocate deflate output buffer (out of memory?)");
+ LPNG_LOG_ERR0("Couldn't allocate deflate output buffer (out of memory?)");
abort();
}
output_buffer->cap = DEFLATE_OUTPUT_BUFFER_INITIAL_CAPACITY;
@@ -64,7 +64,7 @@ void OutputBuffer_expand(OutputBuffer *output_buffer) {
size_t new_cap = output_buffer->cap * 2;
output_buffer->buffer = realloc(output_buffer->buffer, new_cap);
if (!output_buffer->buffer) {
- LOG0("Couldn't reallocate deflate output buffer (out of memory?)");
+ LPNG_LOG_ERR0("Couldn't reallocate deflate output buffer (out of memory?)");
abort();
}
@@ -192,7 +192,7 @@ bool deflate_decompress_(Bitstream *bitstream,
symbol = huffman_table_decode(length_literal_table->symbols,
length_literal_table->counts, bitstream);
if (symbol < 0) {
- LOG0("Unknown symbol decoded");
+ LPNG_LOG_ERR0("Unknown symbol decoded");
return false;
}
if (symbol < 256) {
@@ -228,14 +228,15 @@ bool deflate_decompress(Bitstream *bitstream, OutputBuffer *output) {
uint8_t b_final = 0;
while (!b_final) {
- LOG0("Parse deflate block");
+ LPNG_LOG_DBG0("Parse deflate block");
b_final = Bitstream_next_bits(bitstream, BFINAL_LENGTH_BITS);
const uint8_t b_type = Bitstream_next_bits(bitstream, BTYPE_LENGTH_BITS);
if (b_type == DeflateBlockType_NoCompression) {
- LOG0("Uncompressed deflate blocks aren't supported");
+ LPNG_LOG_ERR0("Uncompressed deflate blocks aren't supported");
abort();
} else {
if (b_type == DeflateBlockType_FixedHuffman) {
+ LPNG_LOG_ERR0("Static huffman table");
static bool are_tables_blank = true;
static LengthLiteralTable length_literal_table = {0};
static DistanceTable distance_table = {0};
@@ -243,6 +244,7 @@ bool deflate_decompress(Bitstream *bitstream, OutputBuffer *output) {
uint16_t lenlit_codelengths[FIXED_LENGTH_LITERAL_CODE_COUNT];
if (are_tables_blank) {
+ LPNG_LOG_ERR0("Computing static huffman table");
for (int symbol = 0; symbol < 144; symbol++) {
lenlit_codelengths[symbol] = 8;
}
@@ -282,11 +284,11 @@ bool deflate_decompress(Bitstream *bitstream, OutputBuffer *output) {
const uint16_t hlit = Bitstream_next_bits(bitstream, 5) + 257;
const uint8_t hdist = Bitstream_next_bits(bitstream, 5) + 1;
const uint8_t hclen = Bitstream_next_bits(bitstream, 4) + 4;
- LOGN("Dynamic table:\n"
- "hlit: %d\n"
- "hdist: %d\n"
- "hclen: %d",
- hlit, hdist, hclen);
+ LPNG_LOG_DBG("Dynamic table:\n"
+ "hlit: %d\n"
+ "hdist: %d\n"
+ "hclen: %d",
+ hlit, hdist, hclen);
CodeLengthTable codelength_table = {0};
build_codelength_table(&codelength_table, bitstream, hclen);
@@ -297,7 +299,7 @@ bool deflate_decompress(Bitstream *bitstream, OutputBuffer *output) {
int symbol = huffman_table_decode(codelength_table.symbols,
codelength_table.counts, bitstream);
if (symbol < 0) {
- LOG0("Unknown symbol decoded using huffman table");
+ LPNG_LOG_ERR0("Unknown symbol decoded using huffman table");
return false;
} else if (symbol < 16) {
lenlit_dist_codelengths[index++] = symbol;
@@ -328,6 +330,7 @@ bool deflate_decompress(Bitstream *bitstream, OutputBuffer *output) {
build_huffman_table_from_codelengths(
distance_table.symbols, distance_table.counts,
lenlit_dist_codelengths + hlit, hdist);
+
deflate_decompress_(bitstream, &length_literal_table, &distance_table,
output);
}
@@ -337,7 +340,8 @@ bool deflate_decompress(Bitstream *bitstream, OutputBuffer *output) {
}
char *zlib_decompress(const uint8_t *compressed_data_buffer,
- const size_t compressed_data_length) {
+ const size_t compressed_data_length,
+ size_t *output_length) {
ASSERT(compressed_data_buffer != NULL);
Bitstream bitstream;
@@ -349,46 +353,67 @@ char *zlib_decompress(const uint8_t *compressed_data_buffer,
uint16_t fdict = Bitstream_next_bits(&bitstream, FDICT_LENGTH_BITS);
uint16_t flevel = Bitstream_next_bits(&bitstream, FLEVEL_LENGTH_BITS);
- LOGN("zlib informations:\n"
- "- CM (compression method): %d\n"
- "- CINFO (compression info): %d\n"
- "- FCHECK (check bits for CMF and FLG): %d\n"
- "- FDICT (preset dictionary): %d\n"
- "- FLEVEL (compression level): %d",
- cm, cinfo, fcheck, fdict, flevel);
+#ifdef LPNG_DEBUG_LOG
+ LPNG_LOG_DBG("zlib informations:\n"
+ "- CM (compression method): %d\n"
+ "- CINFO (compression info): %d\n"
+ "- FCHECK (check bits for CMF and FLG): %d\n"
+ "- FDICT (preset dictionary): %d\n"
+ "- FLEVEL (compression level): %d",
+ cm, cinfo, fcheck, fdict, flevel);
+#else
+ (void)cm;
+ (void)cinfo;
+ (void)fcheck;
+#endif // LPNG_DEBUG_LOG
+
uint16_t cmf = bitstream.data[0];
uint16_t flg = bitstream.data[1];
if ((cmf * 256 + flg) % 31 != 0) {
- LOG0("fcheck validation failed");
+ LPNG_LOG_ERR0("fcheck validation failed");
return false;
}
+ if (flevel > 9) {
+ LPNG_LOG_ERR0("Invalid compression level");
+ return NULL;
+ }
+
if (fdict != 0) {
- LOG0("preset dictionnaries are unsupported");
+ LPNG_LOG_ERR0("preset dictionnaries are unsupported");
return NULL;
}
OutputBuffer output;
OutputBuffer_init(&output);
if (!deflate_decompress(&bitstream, &output)) {
- LOG0("deflate decompression failed");
+ LPNG_LOG_ERR0("deflate decompression failed");
return NULL;
}
- int adler32 = bitstream.data[bitstream.current_byte_index + 4] +
- (bitstream.data[bitstream.current_byte_index + 3] << 8) +
- (bitstream.data[bitstream.current_byte_index + 2] << 16) +
- (bitstream.data[bitstream.current_byte_index + 1] << 24);
- LOGN("Adler32 checksum: %d", adler32);
+ size_t adler32_offset = bitstream.current_byte_index;
+ if (bitstream.current_bit_offset % 8 != 0) {
+ adler32_offset++;
+ }
+ uint32_t adler32 = bitstream.data[adler32_offset + 3] +
+ (bitstream.data[adler32_offset + 2] << 8) +
+ (bitstream.data[adler32_offset + 1] << 16) +
+ (bitstream.data[adler32_offset] << 24);
+ LPNG_LOG_DBG("Adler32 checksum: %u", adler32);
uint32_t a = 1;
uint32_t b = 0;
for (size_t i = 0; i < output.len; i++) {
- a = (a + (unsigned char)output.buffer[i]) % 65521;
+ a = (a + (uint8_t)output.buffer[i]) % 65521;
b = (b + a) % 65521;
}
- int computed_adler32 = (b << 16) | a;
- LOGN("Computed Adler32 checksum: %d", computed_adler32);
+ uint32_t computed_adler32 = (b << 16) | a;
+ LPNG_LOG_DBG("Computed Adler32 checksum: %u", computed_adler32);
+ if (adler32 != computed_adler32) {
+ LPNG_LOG_ERR0("Invalid checksum");
+ exit(1);
+ }
+ *output_length = output.len;
return output.buffer;
}
diff --git a/lisiblepng/src/deflate.h b/lisiblepng/src/lisiblepng/deflate.h
index 77100c6..88a3711 100644
--- a/lisiblepng/src/deflate.h
+++ b/lisiblepng/src/lisiblepng/deflate.h
@@ -7,9 +7,13 @@
/// Decompresses zlib compressed data as defined by RFC 1950
///
+/// @param compressed_data_buffer The compressed data
+/// @param compressed_data_length The length in bytes of the compressed data
+/// @param output_length The pointer to write the output length to
/// @return A pointer to the decompressed data, the caller is responsible to
/// deallocate it using free(). In case of error, NULL is returned.
char *zlib_decompress(const uint8_t *compressed_data_buffer,
- const size_t compressed_data_length);
+ const size_t compressed_data_length,
+ size_t *output_length);
#endif // LISIBLE_PNG_DEFLATE_H
diff --git a/lisiblepng/src/lisiblepng/log.h b/lisiblepng/src/lisiblepng/log.h
new file mode 100644
index 0000000..720acd1
--- /dev/null
+++ b/lisiblepng/src/lisiblepng/log.h
@@ -0,0 +1,23 @@
+#ifndef LISIBLE_PNG_LOG_H
+#define LISIBLE_PNG_LOG_H
+
+#include <stdio.h>
+
+#define LPNG_LOG_PREFIX "LPNG: "
+#define LPNG_DEBUG_LOG_PREFIX "%s:%d " LPNG_LOG_PREFIX
+#define LPNG_LOG_ERR0(fmt) fprintf(stderr, LPNG_LOG_PREFIX fmt "\n")
+#define LPNG_LOG_ERR(fmt, ...) \
+ fprintf(stderr, LPNG_LOG_PREFIX fmt "\n", __VA_ARGS__)
+
+#ifdef LPNG_DEBUG_LOG
+#define LPNG_LOG_DBG0(fmt) \
+ fprintf(stderr, LPNG_DEBUG_LOG_PREFIX fmt "\n", __FILE__, __LINE__)
+#define LPNG_LOG_DBG(fmt, ...) \
+ fprintf(stderr, LPNG_DEBUG_LOG_PREFIX fmt "\n", __FILE__, __LINE__, \
+ __VA_ARGS__)
+#else
+#define LPNG_LOG_DBG0(fmt)
+#define LPNG_LOG_DBG(fmt, ...)
+#endif // LPNG_DEBUG_LOG
+
+#endif // LISIBLE_PNG_LOG_H
diff --git a/lisiblepng/src/log.h b/lisiblepng/src/log.h
deleted file mode 100644
index fba6820..0000000
--- a/lisiblepng/src/log.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef LISIBLE_PNG_LOG_H
-#define LISIBLE_PNG_LOG_H
-
-#include <stdio.h>
-
-#define LOG0(fmt) fprintf(stderr, fmt "\n")
-#define LOGN(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
-
-#endif // LISIBLE_PNG_LOG_H
Go back to lisible.xyz