diff --git a/libs/image/include/image/ColorTransform.h b/libs/image/include/image/ColorTransform.h index fb6f3cb665..dcbb87dcbd 100644 --- a/libs/image/include/image/ColorTransform.h +++ b/libs/image/include/image/ColorTransform.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef IMAGE_COLORSPACE_H_ -#define IMAGE_COLORSPACE_H_ +#ifndef IMAGE_COLORTRANSFORM_H_ +#define IMAGE_COLORTRANSFORM_H_ #include #include @@ -270,11 +270,14 @@ std::unique_ptr fromLinearToRGBM(const LinearImage& image) { return dst; } +// Constructs a 3-channel LinearImage from an untyped data blob. +// The "proc" lambda converts a single color component into a float. +// The "transform" lambda performs an arbitrary float-to-float transformation. template -static Image toLinear(size_t w, size_t h, size_t bpr, +static LinearImage toLinear(size_t w, size_t h, size_t bpr, const std::unique_ptr& src, PROCESS proc, TRANSFORM transform) { - std::unique_ptr dst(new uint8_t[w * h * sizeof(math::float3)]); - math::float3* d = reinterpret_cast(dst.get()); + LinearImage result(w, h, 3); + math::float3* d = reinterpret_cast(result.getPixelRef()); for (size_t y = 0; y < h; ++y) { T const* p = reinterpret_cast(src.get() + y * bpr); for (size_t x = 0; x < w; ++x, p += 3) { @@ -283,14 +286,17 @@ static Image toLinear(size_t w, size_t h, size_t bpr, *d++ = transform(sRGB); } } - return Image(std::move(dst), w, h, w * sizeof(math::float3), sizeof(math::float3)); + return result; } +// Constructs a 4-channel LinearImage from an untyped data blob. +// The "proc" lambda converts a single color component into a float. +// the "transform" lambda performs an arbitrary float-to-float transformation. template -static Image toLinearWithAlpha(size_t w, size_t h, size_t bpr, +static LinearImage toLinearWithAlpha(size_t w, size_t h, size_t bpr, const std::unique_ptr& src, PROCESS proc, TRANSFORM transform) { - std::unique_ptr dst(new uint8_t[w * h * sizeof(math::float4)]); - math::float4* d = reinterpret_cast(dst.get()); + LinearImage result(w, h, 4); + math::float4* d = reinterpret_cast(result.getPixelRef()); for (size_t y = 0; y < h; ++y) { T const* p = reinterpret_cast(src.get() + y * bpr); for (size_t x = 0; x < w; ++x, p += 4) { @@ -299,9 +305,9 @@ static Image toLinearWithAlpha(size_t w, size_t h, size_t bpr, *d++ = transform(sRGB); } } - return Image(std::move(dst), w, h, w * sizeof(math::float4), sizeof(math::float4), 4); + return result; } } -#endif // IMAGE_COLORSPACE_H_ +#endif // IMAGE_COLORTRANSFORM_H_ diff --git a/libs/image/tests/test_image.cpp b/libs/image/tests/test_image.cpp index d231a5fb67..c9865ad108 100644 --- a/libs/image/tests/test_image.cpp +++ b/libs/image/tests/test_image.cpp @@ -27,7 +27,9 @@ #include #include + #include +#include #include #include @@ -35,9 +37,11 @@ using std::istringstream; using std::string; -using math::float3; using std::swap; +using math::float3; +using math::float4; + using namespace image; class ImageTest : public testing::Test {}; @@ -226,17 +230,65 @@ TEST_F(ImageTest, ImageOps) { // NOLINT updateOrCompare(atlas, "imageops.png"); } +TEST_F(ImageTest, ColorTransformRGB) { // NOLINT + constexpr size_t w = 2; + constexpr size_t h = 3; + constexpr uint16_t texels[] = { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9, 10, 11, + 12, 13, 14, + 20000, 40000, 60000, + }; + constexpr size_t bpr = w * sizeof(uint16_t) * 3; + std::unique_ptr data(new uint8_t[h * bpr]); + memcpy(data.get(), texels, sizeof(texels)); + LinearImage img = image::toLinear(w, h, bpr, data, + [ ](uint16_t v) -> uint16_t { return v; }, + sRGBToLinear); + auto pixels = reinterpret_cast(img.getPixelRef()); + ASSERT_NEAR(pixels[0].x, 0.0f, 0.001f); + ASSERT_NEAR(pixels[0].y, 0.0f, 0.001f); + ASSERT_NEAR(pixels[0].z, 0.0f, 0.001f); + ASSERT_NEAR(pixels[5].x, 0.07583023f, 0.001f); + ASSERT_NEAR(pixels[5].y, 0.33077413f, 0.001f); + ASSERT_NEAR(pixels[5].z, 0.81851715f, 0.001f); +} + +TEST_F(ImageTest, ColorTransformRGBA) { // NOLINT + constexpr size_t w = 4; + constexpr size_t h = 1; + constexpr uint16_t texels[] = { + 10000, 20000, 40000, 60000, + 11000, 21000, 41000, 61000, + 13000, 23000, 43000, 63000, + 15000, 25000, 45000, 65000, + }; + constexpr size_t bpr = w * sizeof(uint16_t) * 4; + std::unique_ptr data(new uint8_t[h * bpr]); + memcpy(data.get(), texels, sizeof(texels)); + LinearImage img = image::toLinearWithAlpha(w, h, bpr, data, + [ ](uint16_t v) -> uint16_t { return v; }, + sRGBToLinear); + auto pixels = reinterpret_cast(img.getPixelRef()); + ASSERT_NEAR(pixels[3].x, 0.04282892f, 0.001f); + ASSERT_NEAR(pixels[3].y, 0.12025354f, 0.001f); + ASSERT_NEAR(pixels[3].z, 0.42922019f, 0.001f); + ASSERT_NEAR(pixels[3].w, 0.99183642f, 0.001f); +} + static void printUsage(const char* name) { - std::string exec_name(utils::Path(name).getName()); - std::string usage( + string exec_name(utils::Path(name).getName()); + string usage( "TEST is a unit test runner for the Filament image library\n" "Usages:\n" " TEST compare [gtest options]\n" " TEST update [gtest options]\n" " TEST [gtest options]\n" "\n"); - const std::string from("TEST"); - for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) { + const string from("TEST"); + for (size_t pos = usage.find(from); pos != string::npos; pos = usage.find(from, pos)) { usage.replace(pos, from.length(), exec_name); } printf("%s", usage.c_str()); diff --git a/libs/imageio/src/ImageDecoder.cpp b/libs/imageio/src/ImageDecoder.cpp index e7265300ac..0bd0e22e61 100644 --- a/libs/imageio/src/ImageDecoder.cpp +++ b/libs/imageio/src/ImageDecoder.cpp @@ -231,6 +231,40 @@ PNGDecoder::~PNGDecoder() { png_destroy_read_struct(&mPNG, &mInfo, NULL); } +// TODO: remove after migrating imageio to LinearImage +template +static Image toLinearDeprecated(size_t w, size_t h, size_t bpr, + const std::unique_ptr& src, PROCESS proc, TRANSFORM transform) { + std::unique_ptr dst(new uint8_t[w * h * sizeof(math::float3)]); + math::float3* d = reinterpret_cast(dst.get()); + for (size_t y = 0; y < h; ++y) { + T const* p = reinterpret_cast(src.get() + y * bpr); + for (size_t x = 0; x < w; ++x, p += 3) { + math::float3 sRGB(proc(p[0]), proc(p[1]), proc(p[2])); + sRGB /= std::numeric_limits::max(); + *d++ = transform(sRGB); + } + } + return Image(std::move(dst), w, h, w * sizeof(math::float3), sizeof(math::float3)); +} + +// TODO: remove after migrating imageio to LinearImage +template +static Image toLinearWithAlphaDeprecated(size_t w, size_t h, size_t bpr, + const std::unique_ptr& src, PROCESS proc, TRANSFORM transform) { + std::unique_ptr dst(new uint8_t[w * h * sizeof(math::float4)]); + math::float4* d = reinterpret_cast(dst.get()); + for (size_t y = 0; y < h; ++y) { + T const* p = reinterpret_cast(src.get() + y * bpr); + for (size_t x = 0; x < w; ++x, p += 4) { + math::float4 sRGB(proc(p[0]), proc(p[1]), proc(p[2]), proc(p[3])); + sRGB /= std::numeric_limits::max(); + *d++ = transform(sRGB); + } + } + return Image(std::move(dst), w, h, w * sizeof(math::float4), sizeof(math::float4), 4); +} + Image PNGDecoder::decode() { std::unique_ptr imageData; try { @@ -270,22 +304,22 @@ Image PNGDecoder::decode() { if (colorType == PNG_COLOR_TYPE_RGBA) { if (getColorSpace() == ImageDecoder::ColorSpace::SRGB) { - return toLinearWithAlpha(width, height, rowBytes, imageData, + return toLinearWithAlphaDeprecated(width, height, rowBytes, imageData, [ ](uint16_t v) -> uint16_t { return ntohs(v); }, sRGBToLinear); } else { - return toLinearWithAlpha(width, height, rowBytes, imageData, + return toLinearWithAlphaDeprecated(width, height, rowBytes, imageData, [ ](uint16_t v) -> uint16_t { return ntohs(v); }, [ ](const math::float4& color) -> math::float4 { return color; }); } } else { // Convert to linear float (PNG 16 stores data in network order (big endian). if (getColorSpace() == ImageDecoder::ColorSpace::SRGB) { - return toLinear(width, height, rowBytes, imageData, + return toLinearDeprecated(width, height, rowBytes, imageData, [ ](uint16_t v) -> uint16_t { return ntohs(v); }, sRGBToLinear); } else { - return toLinear(width, height, rowBytes, imageData, + return toLinearDeprecated(width, height, rowBytes, imageData, [ ](uint16_t v) -> uint16_t { return ntohs(v); }, [ ](const math::float3& color) -> math::float3 { return color; }); }