diff options
Diffstat (limited to 'lisiblepng/src/lisiblepng.c')
| -rw-r--r-- | lisiblepng/src/lisiblepng.c | 290 |
1 files changed, 262 insertions, 28 deletions
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); + } + } +} |
