From 1d1b55c071591d9537bf5adf67af8d473af6beb8 Mon Sep 17 00:00:00 2001 From: Lode Date: Mon, 28 Nov 2016 01:10:26 +0100 Subject: grey+alpha auto color model detection bugfix --- lodepng.cpp | 24 ++++++++++------ lodepng.h | 9 +++--- lodepng_unittest.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/lodepng.cpp b/lodepng.cpp index 8c78758..bf237df 100644 --- a/lodepng.cpp +++ b/lodepng.cpp @@ -1,5 +1,5 @@ /* -LodePNG version 20160501 +LodePNG version 20161127 Copyright (c) 2005-2016 Lode Vandevenne @@ -39,7 +39,7 @@ 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 = "20160501"; +const char* LODEPNG_VERSION_STRING = "20161127"; /* This source file is built up in the following large parts. The code sections @@ -3534,8 +3534,8 @@ void lodepng_color_profile_init(LodePNGColorProfile* profile) { profile->colored = 0; profile->key = 0; - profile->alpha = 0; profile->key_r = profile->key_g = profile->key_b = 0; + profile->alpha = 0; profile->numcolors = 0; profile->bits = 1; } @@ -3622,8 +3622,8 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, if(a != 65535 && (a != 0 || (profile->key && !matchkey))) { profile->alpha = 1; + profile->key = 0; alpha_done = 1; - if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } else if(a == 0 && !profile->alpha && !profile->key) { @@ -3636,6 +3636,7 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; + profile->key = 0; alpha_done = 1; } } @@ -3651,6 +3652,7 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; + profile->key = 0; alpha_done = 1; } } @@ -3684,6 +3686,7 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, if(a != 255 && (a != 0 || (profile->key && !matchkey))) { profile->alpha = 1; + profile->key = 0; alpha_done = 1; if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } @@ -3698,6 +3701,7 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; + profile->key = 0; alpha_done = 1; if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } @@ -3734,7 +3738,9 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; + profile->key = 0; alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } } } @@ -3760,7 +3766,7 @@ unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, { LodePNGColorProfile prof; unsigned error = 0; - unsigned i, n, palettebits, grey_ok, palette_ok; + unsigned i, n, palettebits, palette_ok; lodepng_color_profile_init(&prof); error = lodepng_get_color_profile(&prof, image, w, h, mode_in); @@ -3770,14 +3776,14 @@ unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, if(prof.key && w * h <= 16) { prof.alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + prof.key = 0; if(prof.bits < 8) prof.bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } - grey_ok = !prof.colored && !prof.alpha; /*grey without alpha, with potentially low bits*/ n = prof.numcolors; palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); - palette_ok = n <= 256 && (n * 2 < w * h) && prof.bits <= 8; + palette_ok = n <= 256 && prof.bits <= 8; if(w * h < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ - if(grey_ok && prof.bits <= palettebits) palette_ok = 0; /*grey is less overhead*/ + if(!prof.colored && prof.bits <= palettebits) palette_ok = 0; /*grey is less overhead*/ if(palette_ok) { @@ -3806,7 +3812,7 @@ unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, mode_out->colortype = prof.alpha ? (prof.colored ? LCT_RGBA : LCT_GREY_ALPHA) : (prof.colored ? LCT_RGB : LCT_GREY); - if(prof.key && !prof.alpha) + if(prof.key) { unsigned mask = (1u << mode_out->bitdepth) - 1u; /*profile always uses 16-bit, mask converts it*/ mode_out->key_r = prof.key_r & mask; diff --git a/lodepng.h b/lodepng.h index fcf9f71..8c634d2 100644 --- a/lodepng.h +++ b/lodepng.h @@ -1,5 +1,5 @@ /* -LodePNG version 20160501 +LodePNG version 20161127 Copyright (c) 2005-2016 Lode Vandevenne @@ -559,11 +559,11 @@ Used internally by default if "auto_convert" is enabled. Public because it's use typedef struct LodePNGColorProfile { unsigned colored; /*not greyscale*/ - unsigned key; /*if true, image is not opaque. Only if true and alpha is false, color key is possible.*/ - unsigned short key_r; /*these values are always in 16-bit bitdepth in the profile*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ unsigned short key_g; unsigned short key_b; - unsigned alpha; /*alpha channel or alpha palette required*/ + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16.*/ unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order*/ unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.*/ @@ -1608,6 +1608,7 @@ yyyymmdd. Some changes aren't backwards compatible. Those are indicated with a (!) symbol. +*) 27 nov 2016: grey+alpha auto color model detection bugfix *) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). *) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within the limits of pure C90). diff --git a/lodepng_unittest.cpp b/lodepng_unittest.cpp index 3d7f8e0..5297a35 100644 --- a/lodepng_unittest.cpp +++ b/lodepng_unittest.cpp @@ -93,6 +93,7 @@ g++ -I ./ lodepng.cpp examples/example_sdl.cpp -Wall -Wextra -pedantic -ansi -O3 *) strip trailing spaces and ensure consistent newlines *) check diff of lodepng.cpp and lodepng.h before submitting +git difftool -y */ @@ -613,7 +614,7 @@ void testColor(int r, int g, int b, int a) // Tests combinations of various colors in different orders void testFewColors() { - std::cout << "codec test colors " << std::endl; + std::cout << "codec test few colors " << std::endl; Image image; image.width = 20; image.height = 20; @@ -621,22 +622,20 @@ void testFewColors() image.bitDepth = 8; image.data.resize(image.width * image.height * 4); std::vector colors; - colors.push_back(0); colors.push_back(0); colors.push_back(0); colors.push_back(255); - colors.push_back(255); colors.push_back(255); colors.push_back(255); colors.push_back(255); - colors.push_back(128); colors.push_back(128); colors.push_back(128); colors.push_back(255); - colors.push_back(0); colors.push_back(0); colors.push_back(255); colors.push_back(255); - colors.push_back(255); colors.push_back(255); colors.push_back(255); colors.push_back(0); - colors.push_back(255); colors.push_back(255); colors.push_back(255); colors.push_back(1); + colors.push_back(0); colors.push_back(0); colors.push_back(0); colors.push_back(255); // black + colors.push_back(255); colors.push_back(255); colors.push_back(255); colors.push_back(255); // white + colors.push_back(128); colors.push_back(128); colors.push_back(128); colors.push_back(255); // grey + colors.push_back(0); colors.push_back(0); colors.push_back(255); colors.push_back(255); // blue + colors.push_back(255); colors.push_back(255); colors.push_back(255); colors.push_back(0); // transparent white + colors.push_back(255); colors.push_back(255); colors.push_back(255); colors.push_back(1); // translucent white for(size_t i = 0; i < colors.size(); i += 4) for(size_t j = 0; j < colors.size(); j += 4) for(size_t k = 0; k < colors.size(); k += 4) for(size_t l = 0; l < colors.size(); l += 4) { + //std::cout << (i/4) << " " << (j/4) << " " << (k/4) << " " << (l/4) << std::endl; for(size_t c = 0; c < 4; c++) { - /*image.data[0 + c] = colors[i + c]; - image.data[4 + c] = colors[j + c]; - image.data[8 + c] = colors[k + c];*/ for(unsigned y = 0; y < image.height; y++) for(unsigned x = 0; x < image.width; x++) { @@ -1778,35 +1777,50 @@ void testAutoColorModel(const std::vector& colors, unsigned inbit ASSERT_EQUALS(colortype, state.info_png.color.colortype); ASSERT_EQUALS(bitdepth, state.info_png.color.bitdepth); ASSERT_EQUALS(key, state.info_png.color.key_defined); + // also check that the PNG decoded correctly and has same colors as input if(inbitdepth == 8) { for(size_t i = 0; i < colors.size(); i++) ASSERT_EQUALS(colors[i], decoded[i]); } else { for(size_t i = 0; i < colors.size() / 2; i++) ASSERT_EQUALS(colors[i * 2], decoded[i]); } } void testAutoColorModels() { + // 1-bit grey std::vector grey1; for(size_t i = 0; i < 2; i++) addColor(grey1, i * 255, i * 255, i * 255, 255); testAutoColorModel(grey1, 8, LCT_GREY, 1, false); + // 2-bit grey std::vector grey2; for(size_t i = 0; i < 4; i++) addColor(grey2, i * 85, i * 85, i * 85, 255); testAutoColorModel(grey2, 8, LCT_GREY, 2, false); - + // 4-bit grey std::vector grey4; for(size_t i = 0; i < 16; i++) addColor(grey4, i * 17, i * 17, i * 17, 255); testAutoColorModel(grey4, 8, LCT_GREY, 4, false); - + // 8-bit grey std::vector grey8; for(size_t i = 0; i < 256; i++) addColor(grey8, i, i, i, 255); testAutoColorModel(grey8, 8, LCT_GREY, 8, false); - + // 16-bit grey std::vector grey16; for(size_t i = 0; i < 257; i++) addColor16(grey16, i, i, i, 65535); testAutoColorModel(grey16, 16, LCT_GREY, 16, false); + // 8-bit grey+alpha + std::vector grey8a; + for(size_t i = 0; i < 17; i++) addColor(grey8a, i, i, i, i); + testAutoColorModel(grey8a, 8, LCT_GREY_ALPHA, 8, false); + + // 16-bit grey+alpha + std::vector grey16a; + for(size_t i = 0; i < 257; i++) addColor16(grey16a, i, i, i, i); + testAutoColorModel(grey16a, 16, LCT_GREY_ALPHA, 16, false); + + + // various palette tests std::vector palette; addColor(palette, 0, 0, 1, 255); testAutoColorModel(palette, 8, LCT_PALETTE, 1, false); @@ -1823,43 +1837,73 @@ void testAutoColorModels() addColor(palette, 0, 0, 18, 1); // translucent testAutoColorModel(palette, 8, LCT_PALETTE, 8, false); + // 1-bit grey + alpha not possible, becomes palette + std::vector grey1a; + for(size_t i = 0; i < 2; i++) addColor(grey1a, i, i, i, 128); + testAutoColorModel(grey1a, 8, LCT_PALETTE, 1, false); + + // 2-bit grey + alpha not possible, becomes palette + std::vector grey2a; + for(size_t i = 0; i < 4; i++) addColor(grey2a, i, i, i, 128); + testAutoColorModel(grey2a, 8, LCT_PALETTE, 2, false); + + // 4-bit grey + alpha not possible, becomes palette + std::vector grey4a; + for(size_t i = 0; i < 16; i++) addColor(grey4a, i, i, i, 128); + testAutoColorModel(grey4a, 8, LCT_PALETTE, 4, false); + + // 8-bit rgb std::vector rgb = grey8; addColor(rgb, 255, 0, 0, 255); testAutoColorModel(rgb, 8, LCT_RGB, 8, false); + // 8-bit rgb + key std::vector rgb_key = rgb; addColor(rgb_key, 128, 0, 0, 0); testAutoColorModel(rgb_key, 8, LCT_RGB, 8, true); + // 8-bit rgb, not key due to edge case: single key color, but opaque color has same RGB value std::vector rgb_key2 = rgb_key; addColor(rgb_key2, 128, 0, 0, 255); // same color but opaque ==> no more key testAutoColorModel(rgb_key2, 8, LCT_RGBA, 8, false); + // 8-bit rgb, not key due to semi translucent std::vector rgb_key3 = rgb_key; addColor(rgb_key3, 128, 0, 0, 255); // semi-translucent ==> no more key testAutoColorModel(rgb_key3, 8, LCT_RGBA, 8, false); + // 8-bit rgb, not key due to multiple transparent colors std::vector rgb_key4 = rgb_key; addColor(rgb_key4, 128, 0, 0, 255); addColor(rgb_key4, 129, 0, 0, 255); // two different transparent colors ==> no more key testAutoColorModel(rgb_key4, 8, LCT_RGBA, 8, false); + // 1-bit grey with key std::vector grey1_key = grey1; grey1_key[7] = 0; testAutoColorModel(grey1_key, 8, LCT_GREY, 1, true); + // 2-bit grey with key std::vector grey2_key = grey2; grey2_key[7] = 0; testAutoColorModel(grey2_key, 8, LCT_GREY, 2, true); + // 4-bit grey with key std::vector grey4_key = grey4; grey4_key[7] = 0; testAutoColorModel(grey4_key, 8, LCT_GREY, 4, true); + // 8-bit grey with key std::vector grey8_key = grey8; grey8_key[7] = 0; testAutoColorModel(grey8_key, 8, LCT_GREY, 8, true); + // 16-bit grey with key + std::vector grey16_key = grey16; + grey16_key[14] = grey16_key[15] = 0; + testAutoColorModel(grey16_key, 16, LCT_GREY, 16, true); + + // a single 16-bit color, can't become palette due to being 16-bit std::vector small16; addColor16(small16, 1, 0, 0, 65535); testAutoColorModel(small16, 16, LCT_RGB, 16, false); @@ -1868,13 +1912,22 @@ void testAutoColorModels() addColor16(small16a, 1, 0, 0, 1); testAutoColorModel(small16a, 16, LCT_RGBA, 16, false); + // what we provide as 16-bit is actually representable as 8-bit, so 8-bit palette expected for single color std::vector not16; addColor16(not16, 257, 257, 257, 0); testAutoColorModel(not16, 16, LCT_PALETTE, 1, false); + // the rgb color is representable as 8-bit, but the alpha channel only as 16-bit, so ensure it uses 16-bit and not palette for this single color std::vector alpha16; addColor16(alpha16, 257, 0, 0, 10000); testAutoColorModel(alpha16, 16, LCT_RGBA, 16, false); + + // 1-bit grey, with attempt to get color key but can't do it due to opaque color with same value + std::vector grey1k; + addColor(grey1k, 0, 0, 0, 255); + addColor(grey1k, 255, 255, 255, 255); + addColor(grey1k, 255, 255, 255, 0); + testAutoColorModel(grey1k, 8, LCT_PALETTE, 2, false); } void testPaletteToPaletteDecode() { -- cgit v1.2.3