Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/lvandeve/lodepng.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLode <lvandeve@gmail.com>2022-06-13 21:53:21 +0300
committerLode <lvandeve@gmail.com>2022-06-13 21:53:21 +0300
commit71064f28b6ac8283a3fc529aa5b67f6c027293f7 (patch)
tree01038dfc2404a4924b1ba3ae2523f83afeed5f55
parent3d9fda048393e32cc11d0c3d3caba0a85c1c2dfe (diff)
sBIT chunk support, and a few improved bounds checks
-rw-r--r--Makefile4
-rw-r--r--lodepng.cpp222
-rw-r--r--lodepng.h78
-rw-r--r--lodepng_unittest.cpp234
4 files changed, 485 insertions, 53 deletions
diff --git a/Makefile b/Makefile
index 5b9a871..78afa1b 100644
--- a/Makefile
+++ b/Makefile
@@ -22,13 +22,13 @@ unittest: lodepng.o lodepng_util.o lodepng_unittest.o
$(CXX) $^ $(CXXFLAGS) -o $@
benchmark: lodepng.o lodepng_benchmark.o
- $(CXX) $^ $(CXXFLAGS) -lSDL -o $@
+ $(CXX) $^ $(CXXFLAGS) -lSDL2 -o $@
pngdetail: lodepng.o lodepng_util.o pngdetail.o
$(CXX) $^ $(CXXFLAGS) -o $@
showpng: lodepng.o examples/example_sdl.o
- $(CXX) -I ./ $^ $(CXXFLAGS) -lSDL -o $@
+ $(CXX) -I ./ $^ $(CXXFLAGS) -lSDL2 -o $@
clean:
rm -f unittest benchmark pngdetail showpng lodepng_unittest.o lodepng_benchmark.o lodepng.o lodepng_util.o pngdetail.o examples/example_sdl.o
diff --git a/lodepng.cpp b/lodepng.cpp
index a396be0..466bcc5 100644
--- a/lodepng.cpp
+++ b/lodepng.cpp
@@ -1,5 +1,5 @@
/*
-LodePNG version 20220109
+LodePNG version 20220613
Copyright (c) 2005-2022 Lode Vandevenne
@@ -44,10 +44,10 @@ Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for
#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/
#endif /*_MSC_VER */
-const char* LODEPNG_VERSION_STRING = "20220109";
+const char* LODEPNG_VERSION_STRING = "20220613";
/*
-This source file is built up in the following large parts. The code sections
+This source file is divided into the following large parts. The code sections
with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way.
-Tools for C and common code for PNG and Zlib
-C Code for Zlib (huffman, deflate, ...)
@@ -2505,34 +2505,32 @@ void lodepng_chunk_generate_crc(unsigned char* chunk) {
}
unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) {
- if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/
+ size_t available_size = (size_t)(end - chunk);
+ if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/
if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47
&& chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) {
/* Is PNG magic header at start of PNG file. Jump to first actual chunk. */
return chunk + 8;
} else {
size_t total_chunk_length;
- unsigned char* result;
if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end;
- result = chunk + total_chunk_length;
- if(result < chunk) return end; /*pointer overflow*/
- return result;
+ if(total_chunk_length > available_size) return end; /*outside of range*/
+ return chunk + total_chunk_length;
}
}
const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) {
- if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/
+ size_t available_size = (size_t)(end - chunk);
+ if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/
if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47
&& chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) {
/* Is PNG magic header at start of PNG file. Jump to first actual chunk. */
return chunk + 8;
} else {
size_t total_chunk_length;
- const unsigned char* result;
if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end;
- result = chunk + total_chunk_length;
- if(result < chunk) return end; /*pointer overflow*/
- return result;
+ if(total_chunk_length > available_size) return end; /*outside of range*/
+ return chunk + total_chunk_length;
}
}
@@ -3042,6 +3040,9 @@ void lodepng_info_init(LodePNGInfo* info) {
info->iccp_name = NULL;
info->iccp_profile = NULL;
+ info->sbit_defined = 0;
+ info->sbit_r = info->sbit_g = info->sbit_b = info->sbit_a = 0;
+
LodePNGUnknownChunks_init(info);
#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
}
@@ -3936,7 +3937,7 @@ static unsigned auto_choose_color(LodePNGColorMode* mode_out,
if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize
&& mode_in->bitdepth == mode_out->bitdepth) {
/*If input should have same palette colors, keep original to preserve its order and prevent conversion*/
- lodepng_color_mode_cleanup(mode_out);
+ lodepng_color_mode_cleanup(mode_out); /*clears palette, keeps the above set colortype and bitdepth fields as-is*/
lodepng_color_mode_copy(mode_out, mode_in);
}
} else /*8-bit or 16-bit per channel*/ {
@@ -4723,6 +4724,47 @@ static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings*
if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/
return error;
}
+
+/*significant bits chunk (sBIT)*/
+static unsigned readChunk_sBIT(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) {
+ unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth;
+ if(info->color.colortype == LCT_GREY) {
+ /*error: this chunk must be 1 bytes for grayscale image*/
+ if(chunkLength != 1) return 114;
+ if(data[0] == 0 || data[0] > bitdepth) return 115;
+ info->sbit_defined = 1;
+ info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/
+ } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) {
+ /*error: this chunk must be 3 bytes for RGB and palette image*/
+ if(chunkLength != 3) return 114;
+ if(data[0] == 0 || data[1] == 0 || data[2] == 0) return 115;
+ if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth) return 115;
+ info->sbit_defined = 1;
+ info->sbit_r = data[0];
+ info->sbit_g = data[1];
+ info->sbit_b = data[2];
+ } else if(info->color.colortype == LCT_GREY_ALPHA) {
+ /*error: this chunk must be 2 byte for grayscale with alpha image*/
+ if(chunkLength != 2) return 114;
+ if(data[0] == 0 || data[1] == 0) return 115;
+ if(data[0] > bitdepth || data[1] > bitdepth) return 115;
+ info->sbit_defined = 1;
+ info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/
+ info->sbit_a = data[1];
+ } else if(info->color.colortype == LCT_RGBA) {
+ /*error: this chunk must be 4 bytes for grayscale image*/
+ if(chunkLength != 4) return 114;
+ if(data[0] == 0 || data[1] == 0 || data[2] == 0 || data[3] == 0) return 115;
+ if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth || data[3] > bitdepth) return 115;
+ info->sbit_defined = 1;
+ info->sbit_r = data[0];
+ info->sbit_g = data[1];
+ info->sbit_b = data[2];
+ info->sbit_a = data[3];
+ }
+
+ return 0; /* OK */
+}
#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos,
@@ -4737,7 +4779,7 @@ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos,
chunkLength = lodepng_chunk_length(chunk);
if(chunkLength > 2147483647) return 63;
data = lodepng_chunk_data_const(chunk);
- if(data + chunkLength + 4 > in + insize) return 30;
+ if(chunkLength + 12 > insize - pos) return 30;
if(lodepng_chunk_type_equals(chunk, "PLTE")) {
error = readChunk_PLTE(&state->info_png.color, data, chunkLength);
@@ -4764,6 +4806,8 @@ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos,
error = readChunk_sRGB(&state->info_png, data, chunkLength);
} else if(lodepng_chunk_type_equals(chunk, "iCCP")) {
error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength);
+ } else if(lodepng_chunk_type_equals(chunk, "sBIT")) {
+ error = readChunk_sBIT(&state->info_png, data, chunkLength);
#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
} else {
/* unhandled chunk is ok (is not an error) */
@@ -4782,7 +4826,7 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h,
LodePNGState* state,
const unsigned char* in, size_t insize) {
unsigned char IEND = 0;
- const unsigned char* chunk;
+ const unsigned char* chunk; /*points to beginning of next chunk*/
unsigned char* idat; /*the data from idat chunks, zlib compressed*/
size_t idatsize = 0;
unsigned char* scanlines = 0;
@@ -4818,14 +4862,15 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h,
while(!IEND && !state->error) {
unsigned chunkLength;
const unsigned char* data; /*the data in the chunk*/
+ size_t pos = (size_t)(chunk - in);
- /*error: size of the in buffer too small to contain next chunk*/
- if((size_t)((chunk - in) + 12) > insize || chunk < in) {
+ /*error: next chunk out of bounds of the in buffer*/
+ if(chunk < in || pos + 12 > insize) {
if(state->decoder.ignore_end) break; /*other errors may still happen though*/
CERROR_BREAK(state->error, 30);
}
- /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/
+ /*length of the data of the chunk, excluding the 12 bytes for length, chunk type and CRC*/
chunkLength = lodepng_chunk_length(chunk);
/*error: chunk length larger than the max PNG chunk size*/
if(chunkLength > 2147483647) {
@@ -4833,8 +4878,8 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h,
CERROR_BREAK(state->error, 63);
}
- if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) {
- CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/
+ if(pos + (size_t)chunkLength + 12 > insize || pos + (size_t)chunkLength + 12 < pos) {
+ CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk (or int overflow)*/
}
data = lodepng_chunk_data_const(chunk);
@@ -4908,6 +4953,9 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h,
} else if(lodepng_chunk_type_equals(chunk, "iCCP")) {
state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength);
if(state->error) break;
+ } else if(lodepng_chunk_type_equals(chunk, "sBIT")) {
+ state->error = readChunk_sBIT(&state->info_png, data, chunkLength);
+ if(state->error) break;
#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
} else /*it's not an implemented chunk type, so ignore it: skip over the data*/ {
/*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/
@@ -5143,6 +5191,10 @@ static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) {
unsigned char* chunk;
size_t i, j = 8;
+ if(info->palettesize == 0 || info->palettesize > 256) {
+ return 68; /*invalid palette size, it is only allowed to be 1-256*/
+ }
+
CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE"));
for(i = 0; i != info->palettesize; ++i) {
@@ -5398,6 +5450,42 @@ static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCom
return error;
}
+static unsigned addChunk_sBIT(ucvector* out, const LodePNGInfo* info) {
+ unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth;
+ unsigned char* chunk = 0;
+ if(info->color.colortype == LCT_GREY) {
+ if(info->sbit_r == 0 || info->sbit_r > bitdepth) return 115;
+ CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "sBIT"));
+ chunk[8] = info->sbit_r;
+ } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) {
+ if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0) return 115;
+ if(info->sbit_r > bitdepth || info->sbit_g > bitdepth || info->sbit_b > bitdepth) return 115;
+ CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 3, "sBIT"));
+ chunk[8] = info->sbit_r;
+ chunk[9] = info->sbit_g;
+ chunk[10] = info->sbit_b;
+ } else if(info->color.colortype == LCT_GREY_ALPHA) {
+ if(info->sbit_r == 0 || info->sbit_a == 0) return 115;
+ if(info->sbit_r > bitdepth || info->sbit_a > bitdepth) return 115;
+ CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "sBIT"));
+ chunk[8] = info->sbit_r;
+ chunk[9] = info->sbit_a;
+ } else if(info->color.colortype == LCT_RGBA) {
+ if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0 || info->sbit_a == 0 ||
+ info->sbit_r > bitdepth || info->sbit_g > bitdepth ||
+ info->sbit_b > bitdepth || info->sbit_a > bitdepth) {
+ return 115;
+ }
+ CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "sBIT"));
+ chunk[8] = info->sbit_r;
+ chunk[9] = info->sbit_g;
+ chunk[10] = info->sbit_b;
+ chunk[11] = info->sbit_a;
+ }
+ if(chunk) lodepng_chunk_generate_crc(chunk);
+ return 0;
+}
+
#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline,
@@ -5843,8 +5931,10 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize,
ucvector outv = ucvector_init(NULL, 0);
LodePNGInfo info;
const LodePNGInfo* info_png = &state->info_png;
+ LodePNGColorMode auto_color;
lodepng_info_init(&info);
+ lodepng_color_mode_init(&auto_color);
/*provide some proper output values if error will happen*/
*out = 0;
@@ -5854,6 +5944,10 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize,
/*check input values validity*/
if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette)
&& (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) {
+ /*this error is returned even if auto_convert is enabled and thus encoder could
+ generate the palette by itself: while allowing this could be possible in theory,
+ it may complicate the code or edge cases, and always requiring to give a palette
+ when setting this color type is a simpler contract*/
state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/
goto cleanup;
}
@@ -5874,6 +5968,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize,
lodepng_info_copy(&info, &state->info_png);
if(state->encoder.auto_convert) {
LodePNGColorStats stats;
+ unsigned allow_convert = 1;
lodepng_color_stats_init(&stats);
#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
if(info_png->iccp_defined &&
@@ -5895,23 +5990,85 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize,
/*the background chunk's color must be taken into account as well*/
unsigned r = 0, g = 0, b = 0;
LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16);
- lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color);
+ lodepng_convert_rgb(&r, &g, &b,
+ info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color);
state->error = lodepng_color_stats_add(&stats, r, g, b, 65535);
if(state->error) goto cleanup;
}
#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */
- state->error = auto_choose_color(&info.color, &state->info_raw, &stats);
+ state->error = auto_choose_color(&auto_color, &state->info_raw, &stats);
if(state->error) goto cleanup;
#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
- /*also convert the background chunk*/
- if(info_png->background_defined) {
- if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b,
- info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) {
- state->error = 104;
- goto cleanup;
+ if(info_png->sbit_defined) {
+ /*if sbit is defined, due to strict requirements of which sbit values can be present for which color modes,
+ auto_convert can't be done in many cases. However, do support a few cases here.
+ TODO: more conversions may be possible, and it may also be possible to get a more appropriate color type out of
+ auto_choose_color if knowledge about sbit is used beforehand
+ */
+ unsigned sbit_max = LODEPNG_MAX(LODEPNG_MAX(LODEPNG_MAX(info_png->sbit_r, info_png->sbit_g),
+ info_png->sbit_b), info_png->sbit_a);
+ unsigned equal = (!info_png->sbit_g || info_png->sbit_g == info_png->sbit_r)
+ && (!info_png->sbit_b || info_png->sbit_b == info_png->sbit_r)
+ && (!info_png->sbit_a || info_png->sbit_a == info_png->sbit_r);
+ allow_convert = 0;
+ if(info.color.colortype == LCT_PALETTE &&
+ auto_color.colortype == LCT_PALETTE) {
+ /* input and output are palette, and in this case it may happen that palette data is
+ expected to be copied from info_raw into the info_png */
+ allow_convert = 1;
+ }
+ /*going from 8-bit RGB to palette (or 16-bit as long as sbit_max <= 8) is possible
+ since both are 8-bit RGB for sBIT's purposes*/
+ if(info.color.colortype == LCT_RGB &&
+ auto_color.colortype == LCT_PALETTE && sbit_max <= 8) {
+ allow_convert = 1;
+ }
+ /*going from 8-bit RGBA to palette is also ok but only if sbit_a is exactly 8*/
+ if(info.color.colortype == LCT_RGBA && auto_color.colortype == LCT_PALETTE &&
+ info_png->sbit_a == 8 && sbit_max <= 8) {
+ allow_convert = 1;
+ }
+ /*going from 16-bit RGB(A) to 8-bit RGB(A) is ok if all sbit values are <= 8*/
+ if((info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA) && info.color.bitdepth == 16 &&
+ auto_color.colortype == info.color.colortype && auto_color.bitdepth == 8 &&
+ sbit_max <= 8) {
+ allow_convert = 1;
+ }
+ /*going to less channels is ok if all bit values are equal (all possible values in sbit,
+ as well as the chosen bitdepth of the result). Due to how auto_convert works,
+ we already know that auto_color.colortype has less than or equal amount of channels than
+ info.colortype. Palette is not used here. This conversion is not allowed if
+ info_png->sbit_r < auto_color.bitdepth, because specifically for alpha, non-presence of
+ an sbit value heavily implies that alpha's bit depth is equal to the PNG bit depth (rather
+ than the bit depths set in the r, g and b sbit values, by how the PNG specification describes
+ handling tRNS chunk case with sBIT), so be conservative here about ignoring user input.*/
+ if(info.color.colortype != LCT_PALETTE && auto_color.colortype != LCT_PALETTE &&
+ equal && info_png->sbit_r == auto_color.bitdepth) {
+ allow_convert = 1;
}
}
+#endif
+ if(state->encoder.force_palette) {
+ if(info.color.colortype != LCT_GREY && info.color.colortype != LCT_GREY_ALPHA &&
+ (auto_color.colortype == LCT_GREY || auto_color.colortype == LCT_GREY_ALPHA)) {
+ /*user speficially forced a PLTE palette, so cannot convert to grayscale types because
+ the PNG specification only allows writing a suggested palette in PLTE for truecolor types*/
+ allow_convert = 0;
+ }
+ }
+ if(allow_convert) {
+ lodepng_color_mode_copy(&info.color, &auto_color);
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+ /*also convert the background chunk*/
+ if(info_png->background_defined) {
+ if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b,
+ info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) {
+ state->error = 104;
+ goto cleanup;
+ }
+ }
#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */
+ }
}
#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
if(info_png->iccp_defined) {
@@ -5982,6 +6139,10 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize,
state->error = addChunk_cHRM(&outv, &info);
if(state->error) goto cleanup;
}
+ if(info_png->sbit_defined) {
+ state->error = addChunk_sBIT(&outv, &info);
+ if(state->error) goto cleanup;
+ }
#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
/*PLTE*/
if(info.color.colortype == LCT_PALETTE) {
@@ -6088,6 +6249,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize,
cleanup:
lodepng_info_cleanup(&info);
lodepng_free(data);
+ lodepng_color_mode_cleanup(&auto_color);
/*instead of cleaning the vector up, give it to the output*/
*out = outv.data;
@@ -6282,6 +6444,8 @@ const char* lodepng_error_text(unsigned code) {
/*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents
unreasonable memory consumption when decoding due to impossibly large ICC profile*/
case 113: return "ICC profile unreasonably large";
+ case 114: return "sBIT chunk has wrong size for the color type of the image";
+ case 115: return "sBIT value out of range";
}
return "unknown error code";
}
diff --git a/lodepng.h b/lodepng.h
index 685e785..e83a508 100644
--- a/lodepng.h
+++ b/lodepng.h
@@ -1,5 +1,5 @@
/*
-LodePNG version 20220109
+LodePNG version 20220613
Copyright (c) 2005-2022 Lode Vandevenne
@@ -374,8 +374,10 @@ typedef struct LodePNGColorMode {
The alpha channels must be set as well, set them to 255 for opaque images.
- When decoding, by default you can ignore this palette, since LodePNG already
- fills the palette colors in the pixels of the raw RGBA output.
+ When decoding, with the default settings you can ignore this palette, since
+ LodePNG already fills the palette colors in the pixels of the raw RGBA output,
+ but when decoding to the original PNG color mode it is needed to reconstruct
+ the colors.
The palette is only supported for color type 3.
*/
@@ -465,10 +467,12 @@ typedef struct LodePNGInfo {
with values truncated to the bit depth in the unsigned integer.
For grayscale and palette PNGs, the value is stored in background_r. The values
- in background_g and background_b are then unused.
+ in background_g and background_b are then unused. The decoder will set them
+ equal to background_r, the encoder ignores them in this case.
- So when decoding, you may get these in a different color mode than the one you requested
- for the raw pixels.
+ When decoding, you may get these in a different color mode than the one you requested
+ for the raw pixels: the colortype and bitdepth defined by info_png.color, that is the
+ ones defined in the header of the PNG image, are used.
When encoding with auto_convert, you must use the color model defined in info_png.color for
these values. The encoder normally ignores info_png.color when auto_convert is on, but will
@@ -535,7 +539,7 @@ typedef struct LodePNGInfo {
unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/
/*
- Color profile related chunks: gAMA, cHRM, sRGB, iCPP
+ Color profile related chunks: gAMA, cHRM, sRGB, iCPP, sBIT
LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color
profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please
@@ -598,6 +602,45 @@ typedef struct LodePNGInfo {
unsigned char* iccp_profile;
unsigned iccp_profile_size; /* The size of iccp_profile in bytes */
+ /*
+ sBIT chunk: significant bits. Optional metadata, only set this if needed.
+
+ If defined, these values give the bit depth of the original data. Since PNG only stores 1, 2, 4, 8 or 16-bit
+ per channel data, the significant bits value can be used to indicate the original encoded data has another
+ sample depth, such as 10 or 12.
+
+ Encoders using this value, when storing the pixel data, should use the most significant bits
+ of the data to store the original bits, and use a good sample depth scaling method such as
+ "left bit replication" to fill in the least significant bits, rather than fill zeroes.
+
+ Decoders using this value, if able to work with data that's e.g. 10-bit or 12-bit, should right
+ shift the data to go back to the original bit depth, but decoders are also allowed to ignore
+ sbit and work e.g. with the 8-bit or 16-bit data from the PNG directly, since thanks
+ to the encoder contract, the values encoded in PNG are in valid range for the PNG bit depth.
+
+ For grayscale images, sbit_g and sbit_b are not used, and for images that don't use color
+ type RGBA or grayscale+alpha, sbit_a is not used (it's not used even for palette images with
+ translucent palette values, or images with color key). The values that are used must be
+ greater than zero and smaller than or equal to the PNG bit depth.
+
+ The color type from the header in the PNG image defines these used and unused fields: if
+ decoding with a color mode conversion, such as always decoding to RGBA, this metadata still
+ only uses the color type of the original PNG, and may e.g. lack the alpha channel info
+ if the PNG was RGB. When encoding with auto_convert (as well as without), also always the
+ color model defined in info_png.color determines this.
+
+ NOTE: enabling sbit can hurt compression, because the encoder can then not always use
+ auto_convert to choose a more optimal color mode for the data, because the PNG format has
+ strict requirements for the allowed sbit values in combination with color modes.
+ For example, setting these fields to 10-bit will force the encoder to keep using a 16-bit per channel
+ color mode, even if the pixel data would in fact fit in a more efficient 8-bit mode.
+ */
+ unsigned sbit_defined; /*is significant bits given? if not, the values below are unused*/
+ unsigned sbit_r; /*red or gray component of significant bits*/
+ unsigned sbit_g; /*green component of significant bits*/
+ unsigned sbit_b; /*blue component of significant bits*/
+ unsigned sbit_a; /*alpha component of significant bits*/
+
/* End of color profile related chunks */
@@ -770,7 +813,11 @@ typedef struct LodePNGEncoderSettings {
const unsigned char* predefined_filters;
/*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette).
- If colortype is 3, PLTE is _always_ created.*/
+ If colortype is 3, PLTE is always created. If color type is explicitely set
+ to a grayscale type (1 or 4), this is not done and is ignored. If enabling this,
+ a palette must be present in the info_png.
+ NOTE: enabling this may worsen compression if auto_convert is used to choose
+ optimal color mode, because it cannot use grayscale color modes in this case*/
unsigned force_palette;
#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
/*add LodePNG identifier and version as a text chunk, for debugging*/
@@ -824,8 +871,8 @@ unsigned lodepng_inspect(unsigned* w, unsigned* h,
#endif /*LODEPNG_COMPILE_DECODER*/
/*
-Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it
-read in the state. Returns error code on failure.
+Reads one metadata chunk (other than IHDR, which is handled by lodepng_inspect)
+of the PNG file and outputs what it read in the state. Returns error code on failure.
Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const
to find the desired chunk type, and if non null use lodepng_inspect_chunk (with
chunk_pointer - start_of_file as pos).
@@ -1103,7 +1150,7 @@ TODO:
[.] check compatibility with various compilers - done but needs to be redone for every newer version
[X] converting color to 16-bit per channel types
[X] support color profile chunk types (but never let them touch RGB values by default)
-[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST)
+[ ] support all public PNG chunk types (almost done except sPLT and hIST)
[ ] make sure encoder generates no chunks with size > (2^31)-1
[ ] partial decoding (stream processing)
[X] let the "isFullyOpaque" function check color keys and transparent palettes too
@@ -1230,18 +1277,16 @@ The following features are supported by the decoder:
gAMA: RGB gamma correction
iCCP: ICC color profile
sRGB: rendering intent
+ sBIT: significant bits
1.2. features not supported
---------------------------
-The following features are _not_ supported:
+The following features are not (yet) supported:
*) some features needed to make a conformant PNG-Editor might be still missing.
*) partial loading/stream processing. All data must be available and is processed in one call.
-*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG:
- sBIT
- hIST
- sPLT
+*) The hIST and sPLT public chunks are not (yet) supported but treated as unknown chunks
2. C and C++ version
@@ -1845,6 +1890,7 @@ symbol.
Not all changes are listed here, the commit history in github lists more:
https://github.com/lvandeve/lodepng
+*) 13 jun 2022: added support for the sBIT chunk.
*) 09 jan 2022: minor decoder speed improvements.
*) 27 jun 2021: added warnings that file reading/writing functions don't support
wide-character filenames (support for this is not planned, opening files is
diff --git a/lodepng_unittest.cpp b/lodepng_unittest.cpp
index 91fcdfa..a742e05 100644
--- a/lodepng_unittest.cpp
+++ b/lodepng_unittest.cpp
@@ -1,7 +1,7 @@
/*
LodePNG Unit Test
-Copyright (c) 2005-2020 Lode Vandevenne
+Copyright (c) 2005-2022 Lode Vandevenne
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -188,7 +188,7 @@ void assertEquals(const T& expected, const U& actual, const std::string& message
}
}
-// TODO: turn into ASSERT_TRUE with line number printed
+// TODO: remove, use only ASSERT_TRUE (it prints line number). Requires adding extra message ability to ASSERT_TRUE
void assertTrue(bool value, const std::string& message = "") {
if(!value) {
std::cout << "Error: expected true. " << "Message: " << message << std::endl;
@@ -213,6 +213,13 @@ void assertNoError(unsigned error) {
#define STR_EXPAND(s) #s
#define STR(s) STR_EXPAND(s)
+#define ASSERT_TRUE(v) {\
+ if(!(v)) {\
+ std::cout << std::string("line ") + STR(__LINE__) + ": " + STR(v) + " ASSERT_TRUE failed: ";\
+ std::cout << "Expected true but got " << valtostr(v) << ". " << std::endl;\
+ fail();\
+ }\
+}
#define ASSERT_EQUALS(e, v) {\
if((e) != (v)) {\
std::cout << std::string("line ") + STR(__LINE__) + ": " + STR(v) + " ASSERT_EQUALS failed: ";\
@@ -2504,9 +2511,9 @@ void testBkgdChunk(unsigned r, unsigned g, unsigned b,
ASSERT_EQUALS(b2, info2.background_b);
// compare pixels in the "raw" color model
- LodePNGColorMode mode_temp; lodepng_color_mode_init(&mode_temp); mode_temp.bitdepth = 16; mode_temp.colortype = LCT_RGBA;
+ LodePNGColorMode mode_decoded; lodepng_color_mode_init(&mode_decoded); mode_decoded.bitdepth = 16; mode_decoded.colortype = LCT_RGBA;
std::vector<unsigned char> image3((w * h * lodepng_get_bpp(&mode_raw) + 7) / 8);
- error = lodepng_convert(image3.data(), image2.data(), &mode_raw, &mode_temp, w, h);
+ error = lodepng_convert(image3.data(), image2.data(), &mode_raw, &mode_decoded, w, h);
ASSERT_NO_PNG_ERROR(error);
ASSERT_EQUALS(pixels.size(), image3.size());
for(size_t i = 0; i < image3.size(); i++) {
@@ -2527,10 +2534,10 @@ void testBkgdChunk(unsigned r, unsigned g, unsigned b,
generateTestImageRequiringColorType16(image, type_pixels, bitdepth_pixels, false);
LodePNGColorMode mode_raw; lodepng_color_mode_init(&mode_raw); mode_raw.bitdepth = bitdepth_raw; mode_raw.colortype = type_raw;
- LodePNGColorMode mode_temp; lodepng_color_mode_init(&mode_temp); mode_temp.bitdepth = 16; mode_temp.colortype = LCT_RGBA;
+ LodePNGColorMode mode_test; lodepng_color_mode_init(&mode_test); mode_test.bitdepth = 16; mode_test.colortype = LCT_RGBA;
LodePNGColorMode mode_png; lodepng_color_mode_init(&mode_png); mode_png.bitdepth = bitdepth_png; mode_png.colortype = type_png;
std::vector<unsigned char> temp((image.width * image.height * lodepng_get_bpp(&mode_raw) + 7) / 8);
- error = lodepng_convert(temp.data(), image.data.data(), &mode_raw, &mode_temp, image.width, image.height);
+ error = lodepng_convert(temp.data(), image.data.data(), &mode_raw, &mode_test, image.width, image.height);
ASSERT_NO_PNG_ERROR(error);
image.data = temp;
@@ -2645,6 +2652,220 @@ void testBkgdChunk2() {
ASSERT_EQUALS(LCT_GREY, state2.info_png.color.colortype);
}
+// r, g, b, a are the bit depths to store
+void testSbitChunk(unsigned r, unsigned g, unsigned b, unsigned a,
+ const std::vector<unsigned char>& pixels,
+ unsigned w, unsigned h,
+ const LodePNGColorMode& mode_raw,
+ const LodePNGColorMode& mode_png,
+ bool auto_convert,
+ bool expect_encoder_error = false) {
+ unsigned error;
+
+ lodepng::State state;
+ LodePNGInfo& info = state.info_png;
+ lodepng_color_mode_copy(&info.color, &mode_png);
+ lodepng_color_mode_copy(&state.info_raw, &mode_raw);
+ state.encoder.auto_convert = auto_convert;
+ if(mode_raw.colortype == LCT_PALETTE) {
+ for(size_t i = 0; i < 256; i++) {
+ // TODO: consider allowing to set only 1 of these palettes in lodepng in the case
+ // where both info_raw and info_png have the palette color type
+ lodepng_palette_add(&state.info_raw, i, i, i, 255);
+ lodepng_palette_add(&info.color, i, i, i, 255);
+ }
+ }
+
+ info.sbit_defined = 1;
+ info.sbit_r = r;
+ info.sbit_g = g;
+ info.sbit_b = b;
+ info.sbit_a = a;
+
+ std::vector<unsigned char> png;
+ error = lodepng::encode(png, pixels, w, h, state);
+ if(expect_encoder_error) {
+ ASSERT_NOT_EQUALS(0, error);
+ return;
+ }
+ ASSERT_NO_PNG_ERROR(error);
+
+ lodepng::State state2;
+ LodePNGInfo& info2 = state2.info_png;
+ unsigned w2, h2;
+ std::vector<unsigned char> image2;
+ error = lodepng::decode(image2, w2, h2, state2, &png[0], png.size());
+ ASSERT_NO_PNG_ERROR(error);
+
+ LodePNGColorType type = mode_png.colortype;
+
+ ASSERT_EQUALS(w, w2);
+ ASSERT_EQUALS(h, h2);
+ ASSERT_EQUALS(1, info2.sbit_defined);
+ ASSERT_EQUALS(r, info2.sbit_r);
+ if(type == LCT_RGB || type == LCT_RGBA || type == LCT_PALETTE) {
+ ASSERT_EQUALS(g, info2.sbit_g);
+ ASSERT_EQUALS(b, info2.sbit_b);
+ }
+ if(type == LCT_GREY_ALPHA || type == LCT_RGBA) {
+ ASSERT_EQUALS(a, info2.sbit_a);
+ }
+
+ // compare pixels in a 16-bit color model
+ LodePNGColorMode mode_compare; lodepng_color_mode_init(&mode_compare); mode_compare.bitdepth = 16; mode_compare.colortype = LCT_RGBA;
+ LodePNGColorMode mode_decoded; lodepng_color_mode_init(&mode_decoded); mode_decoded.bitdepth = 8; mode_decoded.colortype = LCT_RGBA;
+ std::vector<unsigned char> image3(w * h * 8);
+ error = lodepng_convert(image3.data(), image2.data(), &mode_compare, &mode_decoded, w, h);
+ std::vector<unsigned char> image4(w * h * 8);
+ error = lodepng_convert(image4.data(), pixels.data(), &mode_compare, &state.info_raw, w, h);
+ ASSERT_NO_PNG_ERROR(error);
+ ASSERT_EQUALS(image4.size(), image3.size());
+ for(size_t i = 0; i < image3.size(); i++) {
+ ASSERT_EQUALS((int)image4[i], (int)image3[i]);
+ }
+}
+
+
+void testSbitChunk(unsigned r, unsigned g, unsigned b, unsigned a,
+ LodePNGColorType type, unsigned bitdepth,
+ bool expect_encoder_error = false) {
+ LodePNGColorMode mode_raw;
+ lodepng_color_mode_init(&mode_raw);
+ mode_raw.bitdepth = bitdepth;
+ mode_raw.colortype = type;
+ LodePNGColorMode mode_png;
+ lodepng_color_mode_init(&mode_png);
+ mode_png.bitdepth = bitdepth;
+ mode_png.colortype = type;
+
+ std::vector<unsigned char> pixels(8, 255); // force all pixels to be white, so encoder tries to use auto_convert as much as possible
+
+ testSbitChunk(r, g, b, a, pixels, 1, 1, mode_raw, mode_png, false, expect_encoder_error);
+ testSbitChunk(r, g, b, a, pixels, 1, 1, mode_raw, mode_png, true, expect_encoder_error);
+}
+
+// type_pixels = what the pixels should require at least for auto_convert
+// type_raw = actual raw pixel type to give to the encoder
+// type_png = PNG type to request from the encoder (if not auto_convert)
+// auto_convert: 0 = no, 1 = yes, 2 = try both
+void testSbitChunk2(unsigned r, unsigned g, unsigned b, unsigned a,
+ LodePNGColorType type_pixels, unsigned bitdepth_pixels,
+ LodePNGColorType type_raw, unsigned bitdepth_raw,
+ LodePNGColorType type_png, unsigned bitdepth_png,
+ int auto_convert,
+ bool expect_encoder_error = false) {
+
+ unsigned error;
+ Image image;
+ generateTestImageRequiringColorType16(image, type_pixels, bitdepth_pixels, false);
+
+ LodePNGColorMode mode_raw; lodepng_color_mode_init(&mode_raw); mode_raw.bitdepth = bitdepth_raw; mode_raw.colortype = type_raw;
+ LodePNGColorMode mode_test; lodepng_color_mode_init(&mode_test); mode_test.bitdepth = 16; mode_test.colortype = LCT_RGBA;
+ LodePNGColorMode mode_png; lodepng_color_mode_init(&mode_png); mode_png.bitdepth = bitdepth_png; mode_png.colortype = type_png;
+ std::vector<unsigned char> temp((image.width * image.height * lodepng_get_bpp(&mode_raw) + 7) / 8);
+ error = lodepng_convert(temp.data(), image.data.data(), &mode_raw, &mode_test, image.width, image.height);
+ ASSERT_NO_PNG_ERROR(error);
+ image.data = temp;
+
+ if(auto_convert == 0 || auto_convert == 2) testSbitChunk(r, g, b, a, image.data, image.width, image.height, mode_raw, mode_png, false, expect_encoder_error);
+ if(auto_convert == 1 || auto_convert == 2) testSbitChunk(r, g, b, a, image.data, image.width, image.height, mode_raw, mode_png, true, expect_encoder_error);
+}
+
+// Test the sBIT chunk for all color types, and for possible combinations of pixel colors where auto_convert conversions occur (only conversions that
+// still allow storing all the sBIT information within the PNG specification limitations may occur)
+void testSbitChunk() {
+ std::cout << "testSbitChunk" << std::endl;
+ testSbitChunk(8, 8, 8, 0, LCT_RGB, 8, false);
+ testSbitChunk(1, 2, 3, 0, LCT_RGB, 8, false);
+ testSbitChunk(0, 2, 3, 0, LCT_RGB, 8, true);
+ testSbitChunk(9, 2, 3, 0, LCT_RGB, 8, true);
+
+ testSbitChunk(8, 8, 8, 8, LCT_RGBA, 8, false);
+ testSbitChunk(1, 2, 3, 4, LCT_RGBA, 8, false);
+ testSbitChunk(0, 2, 3, 4, LCT_RGBA, 8, true);
+ testSbitChunk(9, 2, 3, 4, LCT_RGBA, 8, true);
+
+ testSbitChunk(1, 2, 3, 0, LCT_RGB, 16, false);
+ testSbitChunk(0, 2, 3, 0, LCT_RGB, 16, true);
+ testSbitChunk(9, 2, 3, 0, LCT_RGB, 16, false);
+ testSbitChunk(17, 2, 3, 0, LCT_RGB, 16, true);
+
+ testSbitChunk(1, 2, 3, 4, LCT_RGBA, 16, false);
+ testSbitChunk(0, 2, 3, 4, LCT_RGBA, 16, true);
+ testSbitChunk(9, 2, 3, 4, LCT_RGBA, 16, false);
+ testSbitChunk(17, 2, 3, 4, LCT_RGBA, 16, true);
+
+ testSbitChunk(8, 2, 3, 0, LCT_PALETTE, 8, false);
+ testSbitChunk(8, 2, 3, 0, LCT_PALETTE, 4, false); // 4-bit palette still treats the RGB as 8-bit
+ testSbitChunk(9, 2, 3, 0, LCT_PALETTE, 8, true);
+
+ testSbitChunk(8, 0, 0, 0, LCT_GREY, 8, false);
+ testSbitChunk(8, 0, 0, 0, LCT_GREY, 4, true);
+ testSbitChunk(5, 0, 0, 0, LCT_GREY, 8, false);
+ testSbitChunk(1, 0, 0, 0, LCT_GREY, 1, false);
+ testSbitChunk(3, 0, 0, 0, LCT_GREY, 1, true);
+ testSbitChunk(0, 0, 0, 0, LCT_GREY, 1, true);
+
+ testSbitChunk(16, 0, 0, 0, LCT_GREY, 16, false);
+ testSbitChunk(17, 0, 0, 0, LCT_GREY, 16, true);
+ testSbitChunk(8, 0, 0, 0, LCT_GREY, 16, false);
+ testSbitChunk(5, 0, 0, 0, LCT_GREY, 16, false);
+
+ testSbitChunk(8, 0, 0, 8, LCT_GREY_ALPHA, 8, false);
+ testSbitChunk(8, 0, 0, 0, LCT_GREY_ALPHA, 8, true);
+ testSbitChunk(8, 0, 0, 9, LCT_GREY_ALPHA, 8, true);
+ testSbitChunk(5, 0, 0, 5, LCT_GREY_ALPHA, 8, false);
+ testSbitChunk(5, 0, 0, 8, LCT_GREY_ALPHA, 8, false);
+
+ testSbitChunk(16, 0, 0, 16, LCT_GREY_ALPHA, 16, false);
+ testSbitChunk(16, 0, 0, 8, LCT_GREY_ALPHA, 16, false);
+ testSbitChunk(8, 0, 0, 8, LCT_GREY_ALPHA, 16, false);
+ testSbitChunk(16, 0, 0, 0, LCT_GREY_ALPHA, 16, true);
+ testSbitChunk(16, 0, 0, 17, LCT_GREY_ALPHA, 16, true);
+ testSbitChunk(5, 0, 0, 5, LCT_GREY_ALPHA, 16, false);
+ testSbitChunk(5, 0, 0, 8, LCT_GREY_ALPHA, 16, false);
+
+ testSbitChunk2(8, 8, 8, 0, LCT_RGB, 8, LCT_RGB, 8, LCT_RGB, 8, 2, false);
+ testSbitChunk2(8, 8, 8, 0, LCT_GREY, 8, LCT_RGB, 8, LCT_RGB, 8, 2, false);
+ testSbitChunk2(12, 12, 12, 0, LCT_GREY, 8, LCT_RGB, 16, LCT_RGB, 16, 2, false);
+ testSbitChunk2(12, 12, 12, 8, LCT_GREY, 8, LCT_RGBA, 16, LCT_RGBA, 16, 2, false);
+ testSbitChunk2(8, 8, 8, 0, LCT_GREY, 8, LCT_RGB, 16, LCT_RGB, 16, 2, false);
+ testSbitChunk2(8, 7, 8, 0, LCT_GREY, 8, LCT_RGB, 16, LCT_RGB, 16, 2, false);
+ testSbitChunk2(8, 8, 7, 0, LCT_GREY, 8, LCT_RGB, 16, LCT_RGB, 16, 2, false);
+ testSbitChunk2(8, 7, 8, 0, LCT_GREY, 8, LCT_RGB, 8, LCT_RGB, 8, 2, false);
+ testSbitChunk2(8, 8, 7, 0, LCT_GREY, 8, LCT_RGB, 8, LCT_RGB, 8, 2, false);
+ testSbitChunk2(8, 8, 8, 0, LCT_GREY, 8, LCT_RGB, 8, LCT_GREY_ALPHA, 8, 1, false);
+ testSbitChunk2(8, 8, 8, 8, LCT_GREY, 8, LCT_RGB, 8, LCT_GREY_ALPHA, 8, 0, false);
+
+
+ // test png-suite image cs3n3p08.png, which has an sBIT chunk with RGB values set to 3 bits
+ {
+ std::vector<unsigned char> png, decoded;
+ fromBase64(png, std::string("iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAAYagMeiWXwAAAANzQklUAwMDo5KgQgAAAFRQTFRFkv8AAP+SAP//AP8AANv/AP9t/7YAAG3/tv8A/5IA2/8AAEn//yQA/wAAJP8ASf8AAP/bAP9JAP+2//8AAP8kALb//9sAAJL//20AACT//0kAbf8A33ArFwAAAEtJREFUeJyFyscBggAAALGzYldUsO2/pyMk73SGGE7QF3pDe2gLzdADHA7QDqIfdIUu0AocntAIbaAFdIdu0BIc1tAEvaABOkIf+AMiQDPhd/SuJgAAAABJRU5ErkJggg=="));
+ lodepng::State state;
+ unsigned w, h;
+
+ unsigned error = lodepng::decode(decoded, w, h, state, png);
+ assertNoError(error);
+ ASSERT_EQUALS(1, state.info_png.sbit_defined);
+ ASSERT_EQUALS(3, state.info_png.sbit_r);
+ ASSERT_EQUALS(3, state.info_png.sbit_g);
+ ASSERT_EQUALS(3, state.info_png.sbit_b);
+ }
+
+ // test png-suite image basn0g02.png, which is known to not have an sBIT chunk
+ {
+ std::vector<unsigned char> png, decoded;
+ fromBase64(png, std::string("iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgAAAAAcoT2JAAAABGdBTUEAAYagMeiWXwAAAB9JREFUeJxjYAhd9R+M8TCIUMIAU4aPATMJH2OQuQcAvUl/gYsJiakAAAAASUVORK5CYII="));
+ lodepng::State state;
+ unsigned w, h;
+
+ unsigned error = lodepng::decode(decoded, w, h, state, png);
+ assertNoError(error);
+ ASSERT_EQUALS(0, state.info_png.sbit_defined);
+ }
+}
+
// Test particular cHRM+gAMA conversion to srgb
// gamma = gamma given 100000x multiplied form of PNG, or 0 to set none at all
// wx..by = whitepoint and chromaticities, given in the 100000x multiplied form of PNG
@@ -3570,6 +3791,7 @@ void doMain() {
testColorProfile();
testBkgdChunk();
testBkgdChunk2();
+ testSbitChunk();
//Colors
#ifndef DISABLE_SLOW