diff --git a/morph/loadpng.h b/morph/loadpng.h index 939b6623..0efc9df1 100644 --- a/morph/loadpng.h +++ b/morph/loadpng.h @@ -38,7 +38,12 @@ namespace morph { unsigned int w = 0; unsigned int h = 0; // Assume RGBA and bit depth of 8 - lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + unsigned lprtn = lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + if (lprtn != 0) { + std::string err = "morph::loadpng: lodepng::decode returned error code " + + std::to_string(lprtn) + std::string(": ") + std::string(lodepng_error_text (lprtn)); + throw std::runtime_error (err); + } // For return: morph::vec dims = {w, h}; @@ -48,7 +53,7 @@ namespace morph { unsigned int vsz = png.size(); if (vsz % 4 != 0) { - throw std::runtime_error ("Expect png vector to have size divisible by 4."); + throw std::runtime_error ("morph::loadpng: Expect png vector to have size divisible by 4."); } image_data.resize (vsz/4); @@ -76,20 +81,20 @@ namespace morph { // flip[0]==true and flip[1]==true: (v-h flip) // ((dims[0]-r-1) + dims[0]*(dims[1]-c-1)) - if (std::is_same, float>::value == true - || std::is_same, double>::value == true) { + if constexpr (std::is_same, float>::value == true + || std::is_same, double>::value == true) { // monochrome 0-1 values image_data[idx] = (static_cast(png[i] + png[i+1] + png[i+2]))/T{765}; // 3*255 - } else if (std::is_same, unsigned int>::value == true - || std::is_same, unsigned char>::value == true) { + } else if constexpr (std::is_same, unsigned int>::value == true + || std::is_same, unsigned char>::value == true) { // monochrome, 0-255 values image_data[idx] = (static_cast(png[i] + png[i+1] + png[i+2]))/T{3}; } else { // C++-20 mechanism to trigger a compiler error for the else case. Not user friendly! //[]() { static_assert(flag, "no match"); }(); - throw std::runtime_error ("type failure"); + throw std::runtime_error ("morph::loadpng: type failure"); } } } @@ -97,59 +102,90 @@ namespace morph { return dims; } - // Load a colour PNG and return a vector of type T with elements ordered as RGBRGBRGB... - template - static morph::vec loadpng_rgb (const std::string& filename, morph::vvec& image_data, - const morph::vec flip = {false, false}) + /* + * This overload pf loadpng reads the image into a vvec of vecs of dimension 3 or 4. + * + * \tparam T The type of the individual channels. Expected to be unsigned int, + * unsigned char, float or double. + * + * \tparam N The number of channels (3 for RGB; 4 for RGBA, anything else will lead + * to errors) + */ + template + static morph::vec loadpng (const std::string& filename, + morph::vvec>& image_data, + const morph::vec flip = {false, false}) { std::vector png; unsigned int w = 0; unsigned int h = 0; // Assume RGBA and bit depth of 8 - lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + unsigned lprtn = lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + if (lprtn != 0) { + std::string err = "morph::loadpng: lodepng::decode returned error code " + + std::to_string(lprtn) + std::string(": ") + std::string(lodepng_error_text (lprtn)); + throw std::runtime_error (err); + } // For return: morph::vec dims = {w, h}; // Now convert out into a value placed in image_data - // If T is float or double, then for each in RGB, convert to range 0 to 1 - // If T is of integer type, then for each in RGB encode in range 0-255 + // If T is float or double, then get mean RGB, convert to range 0 to 1 + // If T is of integer type, then get mean and encode in range 0-255 unsigned int vsz = png.size(); if (vsz % 4 != 0) { - throw std::runtime_error ("Expect png vector to have size divisible by 4."); + throw std::runtime_error ("morph::loadpng: Expect png vector to have size divisible by 4."); } - image_data.resize (3*vsz/4); + image_data.resize (vsz/4); for (unsigned int c = 0; c < dims[1]; ++c) { for (unsigned int r = 0; r < dims[0]; ++r) { + // Offset into png unsigned int i = 4*r + 4*dims[0]*c; // Offset into image_data depends on what flips the caller wants - unsigned int idx_r = flip[0] == true ? + unsigned int idx = flip[0] == true ? (flip[1]==true ? ((dims[0]-r-1) + dims[0]*(dims[1]-c-1)) : ((dims[0]-r-1) + dims[0]*c)) : (flip[1]==true ? (r + dims[0]*(dims[1]-c-1)) : (r + dims[0]*c)); - idx_r *= 3; // Because our output is rgbrgb... - unsigned int idx_g = flip[0] == true ? idx_r-1 : idx_r+1; - unsigned int idx_b = flip[0] == true ? idx_r-2 : idx_r+2; - if (std::is_same, float>::value == true - || std::is_same, double>::value == true) { - image_data[idx_r] = static_cast(png[i])/T{255}; - image_data[idx_g] = static_cast(png[i+1])/T{255}; - image_data[idx_b] = static_cast(png[i+2])/T{255}; + if constexpr ((std::is_same, float>::value == true + || std::is_same, double>::value == true) && N==3) { + // RGB, 0-1 values + unsigned char p0 = png[i]; + unsigned char p1 = png[i+1]; + unsigned char p2 = png[i+2]; + image_data[idx] = { static_cast(p0), static_cast(p1), static_cast(p2) }; + image_data[idx] /= T{255}; - } else if (std::is_same, unsigned int>::value == true - || std::is_same, unsigned char>::value == true) { - // Copy RGB, 0-255 values - image_data[idx_r] = static_cast(png[i]); - image_data[idx_g] = static_cast(png[i+1]); - image_data[idx_b] = static_cast(png[i+2]); + } else if constexpr ((std::is_same, float>::value == true + || std::is_same, double>::value == true) && N==4) { + // RGBA 0-1 values + image_data[idx][0] = static_cast(png[i]) / T{255}; + image_data[idx][1] = static_cast(png[i+1]) / T{255}; + image_data[idx][2] = static_cast(png[i+2]) / T{255}; + image_data[idx][3] = static_cast(png[i+3]) / T{255}; + + } else if constexpr ((std::is_same, unsigned char>::value == true + || std::is_same, unsigned int>::value == true) && N==3) { + // RGB, 0-255 values + image_data[idx][0] = static_cast(png[i]); + image_data[idx][1] = static_cast(png[i+1]); + image_data[idx][2] = static_cast(png[i+2]); + + } else if constexpr ((std::is_same, unsigned char>::value == true + || std::is_same, unsigned int>::value == true) && N==4) { + // RGBA, 0-255 values + image_data[idx][0] = static_cast(png[i]); + image_data[idx][1] = static_cast(png[i+1]); + image_data[idx][2] = static_cast(png[i+2]); + image_data[idx][3] = static_cast(png[i+3]); } else { // C++-20 mechanism to trigger a compiler error for the else case. Not user friendly! //[]() { static_assert(flag, "no match"); }(); - throw std::runtime_error ("type failure"); + throw std::runtime_error ("morph::loadpng: type failure (or N is not 3 or 4)"); } } } @@ -157,29 +193,34 @@ namespace morph { return dims; } - // Load a colour PNG and return a vector of type T with elements ordered as RGBARGBARGBA... + // Load a colour PNG and return a vector of type T with elements ordered as RGBRGBRGB... template - static morph::vec loadpng_rgba (const std::string& filename, morph::vvec& image_data, - const morph::vec flip = {false, false}) + static morph::vec loadpng_rgb (const std::string& filename, morph::vvec& image_data, + const morph::vec flip = {false, false}) { std::vector png; unsigned int w = 0; unsigned int h = 0; // Assume RGBA and bit depth of 8 - lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + unsigned lprtn = lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + if (lprtn != 0) { + std::string err = "morph::loadpng_rgb: lodepng::decode returned error code " + + std::to_string(lprtn) + std::string(": ") + std::string(lodepng_error_text (lprtn)); + throw std::runtime_error (err); + } // For return: morph::vec dims = {w, h}; // Now convert out into a value placed in image_data - // If T is float or double, then get mean RGB, convert to range 0 to 1 - // If T is of integer type, then get mean and encode in range 0-255 + // If T is float or double, then for each in RGB, convert to range 0 to 1 + // If T is of integer type, then for each in RGB encode in range 0-255 unsigned int vsz = png.size(); if (vsz % 4 != 0) { - throw std::runtime_error ("Expect png vector to have size divisible by 4."); + throw std::runtime_error ("morph::loadpng_rgb: Expect png vector to have size divisible by 4."); } - image_data.resize (vsz); + image_data.resize (3*vsz/4); for (unsigned int c = 0; c < dims[1]; ++c) { for (unsigned int r = 0; r < dims[0]; ++r) { @@ -189,30 +230,27 @@ namespace morph { unsigned int idx_r = flip[0] == true ? (flip[1]==true ? ((dims[0]-r-1) + dims[0]*(dims[1]-c-1)) : ((dims[0]-r-1) + dims[0]*c)) : (flip[1]==true ? (r + dims[0]*(dims[1]-c-1)) : (r + dims[0]*c)); - idx_r *= 4; // Because our output is rgbargba... + idx_r *= 3; // Because our output is rgbrgb... unsigned int idx_g = flip[0] == true ? idx_r-1 : idx_r+1; unsigned int idx_b = flip[0] == true ? idx_r-2 : idx_r+2; - unsigned int idx_a = flip[0] == true ? idx_r-3 : idx_r+3; - if (std::is_same, float>::value == true - || std::is_same, double>::value == true) { + if constexpr (std::is_same, float>::value == true + || std::is_same, double>::value == true) { image_data[idx_r] = static_cast(png[i])/T{255}; image_data[idx_g] = static_cast(png[i+1])/T{255}; image_data[idx_b] = static_cast(png[i+2])/T{255}; - image_data[idx_a] = static_cast(png[i+3])/T{255}; - } else if (std::is_same, unsigned int>::value == true - || std::is_same, unsigned char>::value == true) { + } else if constexpr (std::is_same, unsigned int>::value == true + || std::is_same, unsigned char>::value == true) { // Copy RGB, 0-255 values image_data[idx_r] = static_cast(png[i]); image_data[idx_g] = static_cast(png[i+1]); image_data[idx_b] = static_cast(png[i+2]); - image_data[idx_a] = static_cast(png[i+3]); } else { // C++-20 mechanism to trigger a compiler error for the else case. Not user friendly! //[]() { static_assert(flag, "no match"); }(); - throw std::runtime_error ("type failure"); + throw std::runtime_error ("morph::loadpng_rgb: type failure"); } } } @@ -221,20 +259,22 @@ namespace morph { } // Load a colour PNG and return a vector of type T with elements ordered as RGBARGBARGBA... - template - static morph::vec loadpng_rgba (const std::string& filename, morph::vec& image_data, + template + static morph::vec loadpng_rgba (const std::string& filename, morph::vvec& image_data, const morph::vec flip = {false, false}) { std::vector png; unsigned int w = 0; unsigned int h = 0; // Assume RGBA and bit depth of 8 - lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + unsigned lprtn = lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + if (lprtn != 0) { + std::string err = "morph::loadpng_rgba: lodepng::decode returned error code " + + std::to_string(lprtn) + std::string(": ") + std::string(lodepng_error_text(lprtn)); + throw std::runtime_error (err); + } // For return: morph::vec dims = {w, h}; - if (w != im_w || h != im_h) { - throw std::runtime_error ("Expect png to be the size specified in the template args."); - } // Now convert out into a value placed in image_data // If T is float or double, then get mean RGB, convert to range 0 to 1 @@ -242,9 +282,11 @@ namespace morph { unsigned int vsz = png.size(); if (vsz % 4 != 0) { - throw std::runtime_error ("Expect png vector to have size divisible by 4."); + throw std::runtime_error ("morph::loadpng_rgba: Expect png vector to have size divisible by 4."); } + image_data.resize (vsz); + for (unsigned int c = 0; c < dims[1]; ++c) { for (unsigned int r = 0; r < dims[0]; ++r) { // Offset into png @@ -258,15 +300,15 @@ namespace morph { unsigned int idx_b = flip[0] == true ? idx_r-2 : idx_r+2; unsigned int idx_a = flip[0] == true ? idx_r-3 : idx_r+3; - if (std::is_same, float>::value == true - || std::is_same, double>::value == true) { + if constexpr (std::is_same, float>::value == true + || std::is_same, double>::value == true) { image_data[idx_r] = static_cast(png[i])/T{255}; image_data[idx_g] = static_cast(png[i+1])/T{255}; image_data[idx_b] = static_cast(png[i+2])/T{255}; image_data[idx_a] = static_cast(png[i+3])/T{255}; - } else if (std::is_same, unsigned int>::value == true - || std::is_same, unsigned char>::value == true) { + } else if constexpr (std::is_same, unsigned int>::value == true + || std::is_same, unsigned char>::value == true) { // Copy RGB, 0-255 values image_data[idx_r] = static_cast(png[i]); image_data[idx_g] = static_cast(png[i+1]); @@ -276,7 +318,7 @@ namespace morph { } else { // C++-20 mechanism to trigger a compiler error for the else case. Not user friendly! //[]() { static_assert(flag, "no match"); }(); - throw std::runtime_error ("type failure"); + throw std::runtime_error ("morph::loadpng_rgba: type failure"); } } } @@ -284,18 +326,26 @@ namespace morph { return dims; } - template - static morph::vec loadpng (const std::string& filename, - morph::vvec>& image_data, - const morph::vec flip = {false, false}) + // Load a colour PNG and return a vector of type T with elements ordered as RGBARGBARGBA... + template + static morph::vec loadpng_rgba (const std::string& filename, morph::vec& image_data, + const morph::vec flip = {false, false}) { std::vector png; unsigned int w = 0; unsigned int h = 0; // Assume RGBA and bit depth of 8 - lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + unsigned lprtn = lodepng::decode (png, w, h, filename, LCT_RGBA, 8); + if (lprtn != 0) { + std::string err = "morph::loadpng_rgba: lodepng::decode returned error code " + + std::to_string(lprtn) + std::string(": ") + std::string(lodepng_error_text(lprtn)); + throw std::runtime_error (err); + } // For return: morph::vec dims = {w, h}; + if (w != im_w || h != im_h) { + throw std::runtime_error ("morph::loadpng_rgba: Expect png to be the size specified in the template args."); + } // Now convert out into a value placed in image_data // If T is float or double, then get mean RGB, convert to range 0 to 1 @@ -303,57 +353,41 @@ namespace morph { unsigned int vsz = png.size(); if (vsz % 4 != 0) { - throw std::runtime_error ("Expect png vector to have size divisible by 4."); + throw std::runtime_error ("morph::loadpng_rgba: Expect png vector to have size divisible by 4."); } - image_data.resize (vsz/4); - for (unsigned int c = 0; c < dims[1]; ++c) { for (unsigned int r = 0; r < dims[0]; ++r) { - // Offset into png unsigned int i = 4*r + 4*dims[0]*c; // Offset into image_data depends on what flips the caller wants - unsigned int idx = flip[0] == true ? + unsigned int idx_r = flip[0] == true ? (flip[1]==true ? ((dims[0]-r-1) + dims[0]*(dims[1]-c-1)) : ((dims[0]-r-1) + dims[0]*c)) : (flip[1]==true ? (r + dims[0]*(dims[1]-c-1)) : (r + dims[0]*c)); + idx_r *= 4; // Because our output is rgbargba... + unsigned int idx_g = flip[0] == true ? idx_r-1 : idx_r+1; + unsigned int idx_b = flip[0] == true ? idx_r-2 : idx_r+2; + unsigned int idx_a = flip[0] == true ? idx_r-3 : idx_r+3; - if constexpr ((std::is_same, float>::value == true - || std::is_same, double>::value == true) && N==3) { - // RGB, 0-1 values - unsigned char p0 = png[i]; - unsigned char p1 = png[i+1]; - unsigned char p2 = png[i+2]; - image_data[idx] = { static_cast(p0), static_cast(p1), static_cast(p2) }; - image_data[idx] /= T{255}; - - } else if constexpr ((std::is_same, float>::value == true - || std::is_same, double>::value == true) && N==4) { - // RGBA 0-1 values - image_data[idx][0] = static_cast(png[i]) / T{255}; - image_data[idx][1] = static_cast(png[i+1]) / T{255}; - image_data[idx][2] = static_cast(png[i+2]) / T{255}; - image_data[idx][3] = static_cast(png[i+3]) / T{255}; - - } else if constexpr ((std::is_same, unsigned char>::value == true - || std::is_same, unsigned int>::value == true) && N==3) { - // RGB, 0-255 values - image_data[idx][0] = static_cast(png[i]); - image_data[idx][1] = static_cast(png[i+1]); - image_data[idx][2] = static_cast(png[i+2]); + if constexpr (std::is_same, float>::value == true + || std::is_same, double>::value == true) { + image_data[idx_r] = static_cast(png[i])/T{255}; + image_data[idx_g] = static_cast(png[i+1])/T{255}; + image_data[idx_b] = static_cast(png[i+2])/T{255}; + image_data[idx_a] = static_cast(png[i+3])/T{255}; - } else if constexpr ((std::is_same, unsigned char>::value == true - || std::is_same, unsigned int>::value == true) && N==4) { - // RGBA, 0-255 values - image_data[idx][0] = static_cast(png[i]); - image_data[idx][1] = static_cast(png[i+1]); - image_data[idx][2] = static_cast(png[i+2]); - image_data[idx][3] = static_cast(png[i+3]); + } else if constexpr (std::is_same, unsigned int>::value == true + || std::is_same, unsigned char>::value == true) { + // Copy RGB, 0-255 values + image_data[idx_r] = static_cast(png[i]); + image_data[idx_g] = static_cast(png[i+1]); + image_data[idx_b] = static_cast(png[i+2]); + image_data[idx_a] = static_cast(png[i+3]); } else { // C++-20 mechanism to trigger a compiler error for the else case. Not user friendly! //[]() { static_assert(flag, "no match"); }(); - throw std::runtime_error ("type failure"); + throw std::runtime_error ("morph::loadpng_rgba: type failure"); } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e519afc..9823f529 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -506,3 +506,6 @@ add_test(testGridNeighbours testGridNeighbours) add_executable(testGrid_getabscissae testGrid_getabscissae.cpp) add_test(testGrid_getabscissae testGrid_getabscissae) + +add_executable(testloadpng testloadpng.cpp) +add_test(testloadpng testloadpng) diff --git a/tests/testloadpng.cpp b/tests/testloadpng.cpp new file mode 100644 index 00000000..f0de1e9f --- /dev/null +++ b/tests/testloadpng.cpp @@ -0,0 +1,33 @@ +#include + +int main() +{ + int rtn = 0; + + // Load an image + std::string fn = "../../examples/bike256_65.png"; + + morph::vvec image_data; + try { + morph::vec dims = morph::loadpng (fn, image_data); + std::cout << "Image dims: " << dims << std::endl; + } catch (const std::exception& e) { + // Unexpected error + std::cerr << "Failed to loadpng\n"; + --rtn; + } + + fn = "examples/bad_name.png"; // known bad + try { + morph::vec dims = morph::loadpng (fn, image_data); + std::cout << "Image dims: " << dims << std::endl; + // This should have failed + --rtn; + } catch (const std::exception& e) { + // Expected error + std::cout << "Error: " << e.what() << std::endl; + } + + std::cout << "return rtn = " << rtn << std::endl; + return rtn; +}