From 9ed20705ecd44ba40a209ed71c2852f1b69bb028 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Thu, 9 Aug 2018 13:58:58 -0700 Subject: [PATCH] Refactor color space utilities. (#58) * Refactor color space utilities. * Repair Android build. --- CMakeLists.txt | 2 +- filament/CMakeLists.txt | 1 + filament/src/Color.cpp | 20 +-- libs/image/CMakeLists.txt | 8 +- libs/image/include/image/ColorSpace.h | 235 ++++++++++++++++++++++++++ libs/image/include/image/Image.h | 14 +- libs/image/include/image/utilities.h | 124 -------------- libs/imageio/src/ImageDecoder.cpp | 36 +--- libs/imageio/src/ImageEncoder.cpp | 59 +------ 9 files changed, 259 insertions(+), 240 deletions(-) create mode 100644 libs/image/include/image/ColorSpace.h delete mode 100644 libs/image/include/image/utilities.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 24e830d354..0049e04939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,7 @@ add_subdirectory(${EXTERNAL}/libgtest/tnt) add_subdirectory(${LIBRARIES}/filabridge) add_subdirectory(${LIBRARIES}/filaflat) add_subdirectory(${LIBRARIES}/filamat) +add_subdirectory(${LIBRARIES}/image) add_subdirectory(${LIBRARIES}/math) add_subdirectory(${LIBRARIES}/utils) add_subdirectory(${FILAMENT}/filament) @@ -222,7 +223,6 @@ if (NOT ANDROID) add_subdirectory(${LIBRARIES}/bluegl) add_subdirectory(${LIBRARIES}/filagui) - add_subdirectory(${LIBRARIES}/image) add_subdirectory(${LIBRARIES}/imageio) add_subdirectory(${FILAMENT}/java) diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index c4a63b7a17..a8683708b7 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -315,6 +315,7 @@ target_link_libraries(${TARGET} PUBLIC math) target_link_libraries(${TARGET} PUBLIC utils) target_link_libraries(${TARGET} PUBLIC filaflat) target_link_libraries(${TARGET} PUBLIC filabridge) +target_link_libraries(${TARGET} PUBLIC image_headers) if (FILAMENT_SUPPORTS_VULKAN) target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc) diff --git a/filament/src/Color.cpp b/filament/src/Color.cpp index f245d4dd8c..08c641d4a8 100644 --- a/filament/src/Color.cpp +++ b/filament/src/Color.cpp @@ -16,9 +16,9 @@ #include -#include +#include -#include +#include using namespace math; @@ -28,23 +28,11 @@ using xyY = math::float3; using XYZ = math::float3; float3 Color::sRGBToLinear(float3 color) noexcept { - float3 linearColor{color}; - #pragma nounroll - for (size_t i = 0; i < linearColor.size(); i++) { - linearColor[i] = (linearColor[i] <= 0.04045f) ? - linearColor[i] / 12.92f : powf((linearColor[i] + 0.055f) / 1.055f, 2.4f); - } - return linearColor; + return image::sRGBToLinear(color); } float3 Color::linearToSRGB(float3 color) noexcept { - float3 sRGBColor{color}; - #pragma nounroll - for (size_t i = 0; i < sRGBColor.size(); i++) { - sRGBColor[i] = (sRGBColor[i] <= 0.0031308f) ? - sRGBColor[i] * 12.92f : (powf(sRGBColor[i], 1.0f / 2.4f) * 1.055f) - 0.055f; - } - return sRGBColor; + return image::linearToSRGB(color); } static inline constexpr XYZ xyY_to_XYZ(xyY const& v) { diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 3a80f46445..4124844e8f 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -9,7 +9,7 @@ set(PUBLIC_HDR_DIR include) # ================================================================================================== set(PUBLIC_HDRS include/image/Image.h - include/image/utilities.h + include/image/ColorSpace.h ) set(SRCS @@ -34,3 +34,9 @@ target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register $<$:-ffast-math> ) + +# ================================================================================================== +# Expose header-only utilities to lean-and-mean client projects. +# ================================================================================================== +add_library(image_headers INTERFACE) +target_include_directories(image_headers INTERFACE ${PUBLIC_HDR_DIR}) diff --git a/libs/image/include/image/ColorSpace.h b/libs/image/include/image/ColorSpace.h new file mode 100644 index 0000000000..d5febf0565 --- /dev/null +++ b/libs/image/include/image/ColorSpace.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMAGE_COLORSPACE_H_ +#define IMAGE_COLORSPACE_H_ + +#include + +#include +#include +#include + +#include + +namespace image { + +template +inline math::float4 linearToRGBM(const T& linear) { + using math::float4; + + float4 RGBM(linear[0], linear[1], linear[2], 1.0f); + + // Linear to gamma space + RGBM.rgb = sqrt(RGBM.rgb); + // Set the range + RGBM.rgb /= 16.0f; + + float maxComponent = std::max(std::max(RGBM.r, RGBM.g), std::max(RGBM.b, 1e-6f)); + // Don't let M go below 1 in the [0..16] range + RGBM.a = math::clamp(maxComponent, 1.0f / 16.0f, 1.0f); + RGBM.a = std::ceil(RGBM.a * 255.0f) / 255.0f; + + RGBM.rgb = saturate(RGBM.rgb / RGBM.a); + + return RGBM; +} + +template +inline math::float3 RGBMtoLinear(const T& rgbm) { + using math::float3; + + float3 linear(rgbm[0], rgbm[1], rgbm[2]); + linear *= rgbm.a * 16.0f; + // Gamma to linear space + return linear * linear; +} + +template +inline math::float3 linearTosRGB(const T& linear) { + using math::float3; + constexpr float a = 0.055f; + constexpr float a1 = 1.055f; + constexpr float p = 1 / 2.4f; + float3 sRGB; + for (size_t i=0 ; i<3 ; i++) { + if (linear[i] <= 0.0031308f) { + sRGB[i] = linear[i] * 12.92f; + } else { + sRGB[i] = a1 * std::pow(linear[i], p) - a; + } + } + return sRGB; +} + +inline float linearTosRGB(float linear) { + if (linear <= 0.0031308f) { + return linear * 12.92f; + } else { + constexpr float a = 0.055f; + constexpr float a1 = 1.055f; + constexpr float p = 1 / 2.4f; + return a1 * std::pow(linear, p) - a; + } +} + +template +T sRGBToLinear(const T& sRGB); + +template<> +inline math::float3 sRGBToLinear(const math::float3& sRGB) { + using math::float3; + constexpr float a = 0.055f; + constexpr float a1 = 1.055f; + constexpr float p = 2.4f; + float3 linear; + for (size_t i=0 ; i<3 ; i++) { + if (sRGB[i] <= 0.04045f) { + linear[i] = sRGB[i] * (1.0f / 12.92f); + } else { + linear[i] = std::pow((sRGB[i] + a) / a1, p); + } + } + return linear; +} + +template<> +inline math::float4 sRGBToLinear(const math::float4& sRGB) { + using math::float4; + constexpr float a = 0.055f; + constexpr float a1 = 1.055f; + constexpr float p = 2.4f; + float4 linear; + for (size_t i=0 ; i<3 ; i++) { + if (sRGB[i] <= 0.04045f) { + linear[i] = sRGB[i] * (1.0f / 12.92f); + } else { + linear[i] = std::pow((sRGB[i] + a) / a1, p); + } + } + linear[3] = sRGB[3]; + return linear; +} + +template +T linearToSRGB(const T& sRGB); + +template<> +inline math::float3 linearToSRGB(const math::float3& color) { + using math::float3; + float3 sRGBColor{color}; + #pragma nounroll + for (size_t i = 0; i < sRGBColor.size(); i++) { + sRGBColor[i] = (sRGBColor[i] <= 0.0031308f) ? + sRGBColor[i] * 12.92f : (powf(sRGBColor[i], 1.0f / 2.4f) * 1.055f) - 0.055f; + } + return sRGBColor; +} + +template +std::unique_ptr fromLinearTosRGB(const Image& image) { + using math::float3; + size_t w = image.getWidth(); + size_t h = image.getHeight(); + size_t channels = image.getChannelsCount(); + std::unique_ptr dst(new uint8_t[w * h * 3 * sizeof(T)]); + T* d = reinterpret_cast(dst.get()); + for (size_t y = 0; y < h; ++y) { + float3 const* p = static_cast(image.getPixelRef(0, y)); + for (size_t x = 0; x < w; ++x, ++p, d += channels) { + float3 l(linearTosRGB(saturate(*p)) * std::numeric_limits::max()); + for (size_t i = 0; i < 3; i++) { + d[i] = T(l[i]); + } + } + } + return dst; +} + +template +std::unique_ptr fromLinearToRGB(const Image& image) { + using math::float3; + size_t w = image.getWidth(); + size_t h = image.getHeight(); + size_t channels = image.getChannelsCount(); + std::unique_ptr dst(new uint8_t[w * h * channels * sizeof(T)]); + T* d = reinterpret_cast(dst.get()); + for (size_t y = 0; y < h; ++y) { + float3 const* p = static_cast(image.getPixelRef(0, y)); + for (size_t x = 0; x < w; ++x, ++p, d += channels) { + float3 l(saturate(*p) * std::numeric_limits::max()); + for (size_t i = 0; i < channels; i++) { + d[i] = T(l[i]); + } + } + } + return dst; +} + +template +std::unique_ptr fromLinearToRGBM(const Image& image) { + using namespace math; + size_t w = image.getWidth(); + size_t h = image.getHeight(); + std::unique_ptr dst(new uint8_t[w * h * 4 * sizeof(T)]); + T* d = reinterpret_cast(dst.get()); + for (size_t y = 0; y < h; ++y) { + float3 const* p = static_cast(image.getPixelRef(0, y)); + for (size_t x = 0; x < w; ++x, ++p, d += 4) { + float4 l(linearToRGBM(*p) * std::numeric_limits::max()); + for (size_t i = 0; i < 4; i++) { + d[i] = T(l[i]); + } + } + } + return dst; +} + +template +static Image 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()); + 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)); +} + +template +static Image 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()); + 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); +} + +} + +#endif // IMAGE_COLORSPACE_H_ diff --git a/libs/image/include/image/Image.h b/libs/image/include/image/Image.h index 07374fc685..e718d3f3aa 100644 --- a/libs/image/include/image/Image.h +++ b/libs/image/include/image/Image.h @@ -46,14 +46,14 @@ public: void setFlags(uint32_t flags); - bool isValid() const { return mData != nullptr; }; - size_t getWidth() const { return mWidth; }; - size_t getHeight() const { return mHeight; }; - size_t getBytesPerRow() const { return mBpr; }; - size_t getBytesPerPixel() const { return mBpp; }; + bool isValid() const { return mData != nullptr; } + size_t getWidth() const { return mWidth; } + size_t getHeight() const { return mHeight; } + size_t getBytesPerRow() const { return mBpr; } + size_t getBytesPerPixel() const { return mBpp; } size_t getChannelsCount() const { return mChannels; } - uint32_t getFlags() const { return mFlags; }; - void* getData() const { return mData; }; + uint32_t getFlags() const { return mFlags; } + void* getData() const { return mData; } void* getPixelRef(size_t x, size_t y) const; diff --git a/libs/image/include/image/utilities.h b/libs/image/include/image/utilities.h deleted file mode 100644 index 4fbd323da3..0000000000 --- a/libs/image/include/image/utilities.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IMAGE_UTILITIES_H_ -#define IMAGE_UTILITIES_H_ - -#include - -namespace image { - -template -inline math::float4 linearToRGBM(const T& linear) { - using math::float4; - - float4 RGBM(linear[0], linear[1], linear[2], 1.0f); - - // Linear to gamma space - RGBM.rgb = sqrt(RGBM.rgb); - // Set the range - RGBM.rgb /= 16.0f; - - float maxComponent = std::max(std::max(RGBM.r, RGBM.g), std::max(RGBM.b, 1e-6f)); - // Don't let M go below 1 in the [0..16] range - RGBM.a = math::clamp(maxComponent, 1.0f / 16.0f, 1.0f); - RGBM.a = std::ceil(RGBM.a * 255.0f) / 255.0f; - - RGBM.rgb = saturate(RGBM.rgb / RGBM.a); - - return RGBM; -} - -template -inline math::float3 RGBMtoLinear(const T& rgbm) { - using math::float3; - - float3 linear(rgbm[0], rgbm[1], rgbm[2]); - linear *= rgbm.a * 16.0f; - // Gamma to linear space - return linear * linear; -} - -template -inline math::float3 linearTosRGB(const T& linear) { - using math::float3; - constexpr float a = 0.055f; - constexpr float a1 = 1.055f; - constexpr float p = 1 / 2.4f; - float3 sRGB; - for (size_t i=0 ; i<3 ; i++) { - if (linear[i] <= 0.0031308f) { - sRGB[i] = linear[i] * 12.92f; - } else { - sRGB[i] = a1 * std::pow(linear[i], p) - a; - } - } - return sRGB; -} - -inline float linearTosRGB(float linear) { - if (linear <= 0.0031308f) { - return linear * 12.92f; - } else { - constexpr float a = 0.055f; - constexpr float a1 = 1.055f; - constexpr float p = 1 / 2.4f; - return a1 * std::pow(linear, p) - a; - } -} - -template -T sRGBToLinear(const T& sRGB); - -template<> -inline math::float3 sRGBToLinear(const math::float3& sRGB) { - using math::float3; - constexpr float a = 0.055f; - constexpr float a1 = 1.055f; - constexpr float p = 2.4f; - float3 linear; - for (size_t i=0 ; i<3 ; i++) { - if (sRGB[i] <= 0.04045f) { - linear[i] = sRGB[i] * (1.0f / 12.92f); - } else { - linear[i] = std::pow((sRGB[i] + a) / a1, p); - } - } - return linear; -} - -template<> -inline math::float4 sRGBToLinear(const math::float4& sRGB) { - using math::float4; - constexpr float a = 0.055f; - constexpr float a1 = 1.055f; - constexpr float p = 2.4f; - float4 linear; - for (size_t i=0 ; i<3 ; i++) { - if (sRGB[i] <= 0.04045f) { - linear[i] = sRGB[i] * (1.0f / 12.92f); - } else { - linear[i] = std::pow((sRGB[i] + a) / a1, p); - } - } - linear[3] = sRGB[3]; - return linear; -} - -} - -#endif // IMAGE_UTILITIES_H_ - diff --git a/libs/imageio/src/ImageDecoder.cpp b/libs/imageio/src/ImageDecoder.cpp index 95f19b08d7..924cd24f36 100644 --- a/libs/imageio/src/ImageDecoder.cpp +++ b/libs/imageio/src/ImageDecoder.cpp @@ -19,6 +19,7 @@ #include #include +#include // for memcmp #include #include #include @@ -40,7 +41,7 @@ #include -#include +#include namespace image { @@ -191,39 +192,6 @@ Image ImageDecoder::decode(std::istream& stream, const std::string& sourceName, // ----------------------------------------------------------------------------------------------- -template -static Image 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()); - 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)); -} - -template -static Image 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()); - 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); -} - - static inline float read32(std::istream& istream) { uint32_t data; istream.read(reinterpret_cast(&data), sizeof(uint32_t)); diff --git a/libs/imageio/src/ImageEncoder.cpp b/libs/imageio/src/ImageEncoder.cpp index 8262ea3559..e3ce875cec 100644 --- a/libs/imageio/src/ImageEncoder.cpp +++ b/libs/imageio/src/ImageEncoder.cpp @@ -18,6 +18,7 @@ #include #include +#include // for memset #include #include #include @@ -38,7 +39,7 @@ #include #include -#include +#include using namespace math; @@ -260,62 +261,6 @@ std::string ImageEncoder::chooseExtension(ImageEncoder::Format format) { } } -template -std::unique_ptr fromLinearTosRGB(const Image& image) { - size_t w = image.getWidth(); - size_t h = image.getHeight(); - size_t channels = image.getChannelsCount(); - std::unique_ptr dst(new uint8_t[w * h * 3 * sizeof(T)]); - T* d = reinterpret_cast(dst.get()); - for (size_t y = 0; y < h; ++y) { - float3 const* p = static_cast(image.getPixelRef(0, y)); - for (size_t x = 0; x < w; ++x, ++p, d += channels) { - float3 l(linearTosRGB(saturate(*p)) * std::numeric_limits::max()); - for (size_t i = 0; i < 3; i++) { - d[i] = T(l[i]); - } - } - } - return dst; -} - -template -std::unique_ptr fromLinearToRGB(const Image& image) { - size_t w = image.getWidth(); - size_t h = image.getHeight(); - size_t channels = image.getChannelsCount(); - std::unique_ptr dst(new uint8_t[w * h * channels * sizeof(T)]); - T* d = reinterpret_cast(dst.get()); - for (size_t y = 0; y < h; ++y) { - float3 const* p = static_cast(image.getPixelRef(0, y)); - for (size_t x = 0; x < w; ++x, ++p, d += channels) { - float3 l(saturate(*p) * std::numeric_limits::max()); - for (size_t i = 0; i < channels; i++) { - d[i] = T(l[i]); - } - } - } - return dst; -} - -template -std::unique_ptr fromLinearToRGBM(const Image& image) { - size_t w = image.getWidth(); - size_t h = image.getHeight(); - std::unique_ptr dst(new uint8_t[w * h * 4 * sizeof(T)]); - T* d = reinterpret_cast(dst.get()); - for (size_t y = 0; y < h; ++y) { - float3 const* p = static_cast(image.getPixelRef(0, y)); - for (size_t x = 0; x < w; ++x, ++p, d += 4) { - float4 l(linearToRGBM(*p) * std::numeric_limits::max()); - for (size_t i = 0; i < 4; i++) { - d[i] = T(l[i]); - } - } - } - return dst; -} - //------------------------------------------------------------------------------------------------- PNGEncoder* PNGEncoder::create(std::ostream& stream, PixelFormat format) {