summaryrefslogtreecommitdiffstats
path: root/lisiblepng/src/lisiblepng
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 /lisiblepng/src/lisiblepng
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.
Diffstat (limited to 'lisiblepng/src/lisiblepng')
-rw-r--r--lisiblepng/src/lisiblepng/assert.h15
-rw-r--r--lisiblepng/src/lisiblepng/bitstream.c53
-rw-r--r--lisiblepng/src/lisiblepng/bitstream.h20
-rw-r--r--lisiblepng/src/lisiblepng/deflate.c419
-rw-r--r--lisiblepng/src/lisiblepng/deflate.h19
-rw-r--r--lisiblepng/src/lisiblepng/log.h23
6 files changed, 549 insertions, 0 deletions
diff --git a/lisiblepng/src/lisiblepng/assert.h b/lisiblepng/src/lisiblepng/assert.h
new file mode 100644
index 0000000..1de9c36
--- /dev/null
+++ b/lisiblepng/src/lisiblepng/assert.h
@@ -0,0 +1,15 @@
+#ifndef LISIBLE_PNG_ASSET_H
+#define LISIBLE_PNG_ASSET_H
+
+#include "log.h"
+
+#define ASSERT(predicate) \
+ do { \
+ if (!(predicate)) { \
+ LPNG_LOG_ERR("Assertion failed in %s:%d:\n\t%s", __FILE__, __LINE__, \
+ #predicate); \
+ exit(1); \
+ } \
+ } while (0)
+
+#endif // LISIBLE_PNG_ASSET_H
diff --git a/lisiblepng/src/lisiblepng/bitstream.c b/lisiblepng/src/lisiblepng/bitstream.c
new file mode 100644
index 0000000..e7ddb79
--- /dev/null
+++ b/lisiblepng/src/lisiblepng/bitstream.c
@@ -0,0 +1,53 @@
+#include "bitstream.h"
+#include "assert.h"
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+
+void Bitstream_init(Bitstream *bitstream, const uint8_t *data,
+ size_t data_size) {
+ ASSERT(bitstream != NULL);
+ ASSERT(data != NULL);
+ bitstream->data = data;
+ bitstream->data_size = data_size;
+ bitstream->current_byte_index = 0;
+ bitstream->current_bit_offset = 0;
+}
+
+void bitstream_advance(Bitstream *bitstream, size_t bit_count) {
+ ASSERT(bitstream != NULL);
+ uint8_t current_bit = bitstream->current_bit_offset;
+ bitstream->current_byte_index =
+ bitstream->current_byte_index + (current_bit + bit_count) / 8;
+ bitstream->current_bit_offset = (current_bit + bit_count) % 8;
+}
+
+void Bitstream_skip(Bitstream *bitstream, size_t bit_count) {
+ ASSERT(bitstream != NULL);
+ bitstream_advance(bitstream, bit_count);
+}
+
+uint16_t Bitstream_next_bits(Bitstream *bitstream, int bit_count) {
+ ASSERT(bitstream != NULL);
+ ASSERT(bit_count <= 16);
+ ASSERT((bitstream->current_bit_offset + (size_t)bit_count) / 8 <=
+ bitstream->data_size);
+
+ int bit_to_read = bit_count;
+ uint16_t val = bitstream->data[bitstream->current_byte_index] >>
+ bitstream->current_bit_offset;
+
+ size_t bit_read = MIN(bit_count, 8 - bitstream->current_bit_offset);
+ bitstream_advance(bitstream, bit_read);
+ bit_to_read -= bit_read;
+ while (bit_to_read > 0) {
+ val |= (bitstream->data[bitstream->current_byte_index] >>
+ bitstream->current_bit_offset)
+ << bit_read;
+ bit_read = MIN(bit_to_read, 8 - bitstream->current_bit_offset);
+ bitstream_advance(bitstream, bit_read);
+ bit_to_read -= bit_read;
+ }
+
+ return (val & ((1 << bit_count) - 1));
+}
diff --git a/lisiblepng/src/lisiblepng/bitstream.h b/lisiblepng/src/lisiblepng/bitstream.h
new file mode 100644
index 0000000..72d8295
--- /dev/null
+++ b/lisiblepng/src/lisiblepng/bitstream.h
@@ -0,0 +1,20 @@
+#ifndef CUTTERENG_BITSTREAM_H
+#define CUTTERENG_BITSTREAM_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct {
+ const uint8_t *data;
+ size_t data_size;
+
+ size_t current_byte_index;
+ uint8_t current_bit_offset;
+} Bitstream;
+
+void Bitstream_init(Bitstream *bitstream, const uint8_t *data,
+ size_t data_size);
+void Bitstream_skip(Bitstream *bitstream, size_t bit_count);
+uint16_t Bitstream_next_bits(Bitstream *bitstream, int bit_count);
+
+#endif // CUTTERENG_BITSTREAM_H
diff --git a/lisiblepng/src/lisiblepng/deflate.c b/lisiblepng/src/lisiblepng/deflate.c
new file mode 100644
index 0000000..50f90e3
--- /dev/null
+++ b/lisiblepng/src/lisiblepng/deflate.c
@@ -0,0 +1,419 @@
+#include "deflate.h"
+#include "assert.h"
+#include "bitstream.h"
+#include <string.h>
+
+#define CM_LENGTH_BITS 4
+#define CINFO_LENGTH_BITS 4
+#define FCHECK_LENGTH_BITS 5
+#define FDICT_LENGTH_BITS 1
+#define FLEVEL_LENGTH_BITS 2
+
+#define BFINAL_LENGTH_BITS 1
+#define BTYPE_LENGTH_BITS 2
+
+#define CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT 19
+#define MAX_HUFFMAN_CODE_LENGTH 15
+#define MAX_LENGTH_CODES 288
+#define MAX_DISTANCE_CODES 32
+#define FIXED_LENGTH_LITERAL_CODE_COUNT 288
+
+typedef enum {
+ DeflateBlockType_NoCompression,
+ DeflateBlockType_FixedHuffman,
+ DeflateBlockType_DynamicHuffman,
+ DeflateBlockType_Error
+} DeflateBlockType;
+
+typedef struct {
+ uint16_t counts[CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT];
+ uint16_t symbols[CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT];
+} CodeLengthTable;
+
+typedef struct {
+ uint16_t counts[MAX_LENGTH_CODES];
+ uint16_t symbols[MAX_LENGTH_CODES];
+} LengthLiteralTable;
+
+typedef struct {
+ uint16_t counts[MAX_DISTANCE_CODES];
+ uint16_t symbols[MAX_DISTANCE_CODES];
+} DistanceTable;
+
+#define DEFLATE_OUTPUT_BUFFER_INITIAL_CAPACITY (1024 * 256)
+
+typedef struct {
+ char *buffer;
+ size_t len;
+ size_t cap;
+} OutputBuffer;
+
+void OutputBuffer_init(OutputBuffer *output_buffer) {
+ ASSERT(output_buffer != NULL);
+ output_buffer->buffer = calloc(DEFLATE_OUTPUT_BUFFER_INITIAL_CAPACITY, 1);
+ if (!output_buffer->buffer) {
+ LPNG_LOG_ERR0("Couldn't allocate deflate output buffer (out of memory?)");
+ abort();
+ }
+ output_buffer->cap = DEFLATE_OUTPUT_BUFFER_INITIAL_CAPACITY;
+ output_buffer->len = 0;
+}
+
+void OutputBuffer_expand(OutputBuffer *output_buffer) {
+ ASSERT(output_buffer != NULL);
+ size_t new_cap = output_buffer->cap * 2;
+ output_buffer->buffer = realloc(output_buffer->buffer, new_cap);
+ if (!output_buffer->buffer) {
+ LPNG_LOG_ERR0("Couldn't reallocate deflate output buffer (out of memory?)");
+ abort();
+ }
+
+ output_buffer->cap = new_cap;
+}
+
+void OutputBuffer_push(OutputBuffer *output_buffer, char byte) {
+ ASSERT(output_buffer != NULL);
+ if (output_buffer->len == output_buffer->cap) {
+ OutputBuffer_expand(output_buffer);
+ }
+
+ output_buffer->buffer[output_buffer->len++] = byte;
+}
+
+size_t OutputBuffer_length(const OutputBuffer *output_buffer) {
+ ASSERT(output_buffer != NULL);
+ return output_buffer->len;
+}
+
+int huffman_table_decode(const uint16_t *symbols, const uint16_t *counts,
+ Bitstream *bitstream) {
+ ASSERT(symbols != NULL);
+ ASSERT(counts != NULL);
+ ASSERT(bitstream != NULL);
+ int code = 0;
+ int index = 0;
+ int first = 0;
+ for (int i = 1; i <= MAX_HUFFMAN_CODE_LENGTH; i++) {
+ code |= Bitstream_next_bits(bitstream, 1);
+ int count = counts[i];
+ if (code - count < first) {
+ return symbols[index + (code - first)];
+ }
+
+ index += count;
+ first += count;
+ first <<= 1;
+ code <<= 1;
+ }
+
+ return -1;
+}
+
+void build_huffman_table_from_codelengths(uint16_t *symbols, uint16_t *counts,
+ const uint16_t *codelengths,
+ uint16_t codelength_count) {
+ ASSERT(symbols != NULL);
+ ASSERT(counts != NULL);
+ ASSERT(codelengths != NULL);
+
+ for (int i = 0; i < codelength_count; i++) {
+ counts[codelengths[i]]++;
+ }
+ counts[0] = 0;
+
+ uint16_t next_code[MAX_HUFFMAN_CODE_LENGTH + 1] = {0};
+ for (int i = 1; i < MAX_HUFFMAN_CODE_LENGTH; i++) {
+ next_code[i + 1] = next_code[i] + counts[i];
+ }
+
+ for (int i = 0; i < codelength_count; i++) {
+ if (codelengths[i] == 0) {
+ continue;
+ }
+
+ symbols[next_code[codelengths[i]]++] = i;
+ }
+}
+void CodeLengthTable_build_from_codelengths(
+ CodeLengthTable *table,
+ uint16_t codelength_codelengths[CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT]) {
+ ASSERT(table != NULL);
+ ASSERT(codelength_codelengths != NULL);
+ static const uint8_t CODELENGTH_MAPPING[] = {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+ uint16_t mapped_codelengths[CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT] = {0};
+ for (int i = 0; i < CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT; i++) {
+ mapped_codelengths[CODELENGTH_MAPPING[i]] = codelength_codelengths[i];
+ }
+
+ build_huffman_table_from_codelengths(table->symbols, table->counts,
+ mapped_codelengths,
+ CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT);
+}
+
+void build_codelength_table(CodeLengthTable *codelength_table,
+ Bitstream *bitstream, const uint8_t hclen) {
+ ASSERT(codelength_table != NULL);
+ ASSERT(bitstream != NULL);
+
+ uint16_t codelengths_codelengths[CODE_LENGTH_ALPHABET_MAX_SYMBOL_COUNT] = {0};
+ for (int i = 0; i < hclen; i++) {
+ codelengths_codelengths[i] = Bitstream_next_bits(bitstream, 3);
+ }
+
+ CodeLengthTable_build_from_codelengths(codelength_table,
+ codelengths_codelengths);
+}
+
+bool deflate_decompress_(Bitstream *bitstream,
+ const LengthLiteralTable *length_literal_table,
+ const DistanceTable *distance_table,
+ OutputBuffer *output) {
+ ASSERT(bitstream != NULL);
+ ASSERT(output != NULL);
+ ASSERT(length_literal_table != NULL);
+ ASSERT(distance_table != NULL);
+ static const uint16_t length_size_base[29] = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27,
+ 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
+ static const uint16_t length_extra_bits[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 0};
+ static const uint16_t distance_offset_base[30] = {
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25,
+ 33, 49, 65, 97, 129, 193, 257, 385, 513, 769,
+ 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
+ static const uint16_t distance_extra_bits[30] = {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
+ 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
+
+ int symbol = 0;
+ while (symbol != 256) {
+ symbol = huffman_table_decode(length_literal_table->symbols,
+ length_literal_table->counts, bitstream);
+ if (symbol < 0) {
+ LPNG_LOG_ERR0("Unknown symbol decoded");
+ return false;
+ }
+ if (symbol < 256) {
+ OutputBuffer_push(output, (char)symbol);
+ } else if (symbol > 256) {
+ symbol -= 257;
+ int length = length_size_base[symbol] +
+ Bitstream_next_bits(bitstream, length_extra_bits[symbol]);
+ symbol = huffman_table_decode(distance_table->symbols,
+ distance_table->counts, bitstream);
+ if (symbol < 0) {
+ return false;
+ }
+
+ int distance =
+ distance_offset_base[symbol] +
+ Bitstream_next_bits(bitstream, distance_extra_bits[symbol]);
+ while (length > 0) {
+ size_t output_buffer_length = OutputBuffer_length(output);
+ OutputBuffer_push(
+ output, (char)output->buffer[output_buffer_length - distance]);
+ length--;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool deflate_decompress(Bitstream *bitstream, OutputBuffer *output) {
+ ASSERT(bitstream != NULL);
+ ASSERT(output != NULL);
+
+ uint8_t b_final = 0;
+ while (!b_final) {
+ 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) {
+ 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};
+
+ 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;
+ }
+
+ for (int symbol = 144; symbol < 256; symbol++) {
+ lenlit_codelengths[symbol] = 9;
+ }
+
+ for (int symbol = 256; symbol < 280; symbol++) {
+ lenlit_codelengths[symbol] = 7;
+ }
+
+ for (int symbol = 280; symbol < FIXED_LENGTH_LITERAL_CODE_COUNT;
+ symbol++) {
+ lenlit_codelengths[symbol] = 8;
+ }
+
+ build_huffman_table_from_codelengths(
+ length_literal_table.symbols, length_literal_table.counts,
+ lenlit_codelengths, FIXED_LENGTH_LITERAL_CODE_COUNT);
+
+ uint16_t distance_codelengths[MAX_DISTANCE_CODES] = {0};
+ for (int symbol = 0; symbol < MAX_DISTANCE_CODES; symbol++) {
+ distance_codelengths[symbol] = 5;
+ }
+
+ build_huffman_table_from_codelengths(
+ distance_table.symbols, distance_table.counts,
+ distance_codelengths, MAX_DISTANCE_CODES);
+ are_tables_blank = false;
+ }
+ deflate_decompress_(bitstream, &length_literal_table, &distance_table,
+ output);
+ } else {
+ LengthLiteralTable length_literal_table = {0};
+ DistanceTable distance_table = {0};
+ 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;
+ 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);
+
+ uint16_t lenlit_dist_codelengths[MAX_LENGTH_CODES +
+ MAX_DISTANCE_CODES] = {0};
+ int index = 0;
+ while (index < hlit + hdist) {
+ int symbol = huffman_table_decode(codelength_table.symbols,
+ codelength_table.counts, bitstream);
+ if (symbol < 0) {
+ LPNG_LOG_ERR0("Unknown symbol decoded using huffman table");
+ return false;
+ } else if (symbol < 16) {
+ lenlit_dist_codelengths[index++] = symbol;
+ } else {
+ int repeat = 0;
+ int codelength = 0;
+ if (symbol == 16) {
+ repeat = 3 + Bitstream_next_bits(bitstream, 2);
+ codelength = lenlit_dist_codelengths[index - 1];
+ } else if (symbol == 17) {
+ repeat = 3 + Bitstream_next_bits(bitstream, 3);
+ } else {
+ repeat = 11 + Bitstream_next_bits(bitstream, 7);
+ }
+
+ for (int i = 0; i < repeat; i++) {
+ lenlit_dist_codelengths[index++] = codelength;
+ }
+ }
+ }
+
+ // The block must have an end
+ ASSERT(lenlit_dist_codelengths[256] > 0);
+
+ build_huffman_table_from_codelengths(length_literal_table.symbols,
+ length_literal_table.counts,
+ lenlit_dist_codelengths, hlit);
+ 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);
+ }
+ }
+ }
+ return true;
+}
+
+char *zlib_decompress(const uint8_t *compressed_data_buffer,
+ const size_t compressed_data_length,
+ size_t *output_length) {
+ ASSERT(compressed_data_buffer != NULL);
+
+ Bitstream bitstream;
+ Bitstream_init(&bitstream, compressed_data_buffer, compressed_data_length);
+
+ uint16_t cm = Bitstream_next_bits(&bitstream, CM_LENGTH_BITS);
+ uint16_t cinfo = Bitstream_next_bits(&bitstream, CINFO_LENGTH_BITS);
+ uint16_t fcheck = Bitstream_next_bits(&bitstream, FCHECK_LENGTH_BITS);
+ uint16_t fdict = Bitstream_next_bits(&bitstream, FDICT_LENGTH_BITS);
+ uint16_t flevel = Bitstream_next_bits(&bitstream, FLEVEL_LENGTH_BITS);
+
+#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) {
+ LPNG_LOG_ERR0("fcheck validation failed");
+ return false;
+ }
+
+ if (flevel > 9) {
+ LPNG_LOG_ERR0("Invalid compression level");
+ return NULL;
+ }
+
+ if (fdict != 0) {
+ LPNG_LOG_ERR0("preset dictionnaries are unsupported");
+ return NULL;
+ }
+
+ OutputBuffer output;
+ OutputBuffer_init(&output);
+ if (!deflate_decompress(&bitstream, &output)) {
+ LPNG_LOG_ERR0("deflate decompression failed");
+ return NULL;
+ }
+
+ 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 + (uint8_t)output.buffer[i]) % 65521;
+ b = (b + a) % 65521;
+ }
+ 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/lisiblepng/deflate.h b/lisiblepng/src/lisiblepng/deflate.h
new file mode 100644
index 0000000..88a3711
--- /dev/null
+++ b/lisiblepng/src/lisiblepng/deflate.h
@@ -0,0 +1,19 @@
+#ifndef LISIBLE_PNG_DEFLATE_H
+#define LISIBLE_PNG_DEFLATE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/// 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,
+ 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
Go back to lisible.xyz