diff --git a/src/image-util.cc b/src/image-util.cc index 2cc4c8fe..1574548c 100644 --- a/src/image-util.cc +++ b/src/image-util.cc @@ -5,6 +5,7 @@ // - [ ] Optimize Rec.709 conversion // #include +#include #ifdef __clang__ #pragma clang diagnostic push @@ -24,11 +25,18 @@ #include "image-util.hh" #include "value-types.hh" +#include "common-macros.inc" +#include "tiny-format.hh" #if defined(TINYUSDZ_WITH_COLORIO) #include "external/tiny-color-io.h" #endif +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + // From https://www.nayuki.io/page/srgb-transform-library -------------------- /* * sRGB transform (C++) @@ -336,26 +344,35 @@ float Rec709ToLinear(uint8_t v) { bool linear_f32_to_srgb_8bit(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img) { + std::vector *out_img, std::string *err) { - if ((width == 0) || - (height == 0) || - (channels == 0) || - (out_img == nullptr)) { - return false; + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } + + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); + } + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); + } + + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } if (channel_stride == 0) { channel_stride = channels; } else { if (channel_stride < channels) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("channel_stride {} is smaller than input channels {}", channel_stride, channels)); } } size_t dest_size = size_t(width) * size_t(height) * channel_stride; if (dest_size > in_img.size()) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Insufficient input buffer size. must be the same or larger than {} but has {}", dest_size, in_img.size())); } out_img->resize(dest_size); @@ -382,26 +399,35 @@ bool linear_f32_to_srgb_8bit(const std::vector &in_img, size_t width, bool srgb_8bit_to_linear_f32(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img) { + std::vector *out_img, std::string *err) { - if ((width == 0) || - (height == 0) || - (channels == 0) || - (out_img == nullptr)) { - return false; + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } + + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); + } + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); + } + + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } if (channel_stride == 0) { channel_stride = channels; } else { if (channel_stride < channels) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("channel_stride {} is smaller than input channels {}", channel_stride, channels)); } } size_t dest_size = size_t(width) * size_t(height) * channel_stride; if (dest_size > in_img.size()) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Insufficient input buffer size. must be the same or larger than {} but has {}", dest_size, in_img.size())); } out_img->resize(dest_size); @@ -430,26 +456,35 @@ bool srgb_8bit_to_linear_f32(const std::vector &in_img, size_t width, bool srgb_f32_to_linear_f32(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img, const float scale_factor, const float bias, const float alpha_scale_factor, const float alpha_bias) { + std::vector *out_img, const float scale_factor, const float bias, const float alpha_scale_factor, const float alpha_bias, std::string *err) { + + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } - if ((width == 0) || - (height == 0) || - (channels == 0) || - (out_img == nullptr)) { - return false; + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); + } + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); + } + + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } if (channel_stride == 0) { channel_stride = channels; } else { if (channel_stride < channels) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("channel_stride {} is smaller than input channels {}", channel_stride, channels)); } } size_t dest_size = size_t(width) * size_t(height) * channel_stride; if (dest_size > in_img.size()) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Insufficient input buffer size. must be the same or larger than {} but has {}", dest_size, in_img.size())); } out_img->resize(dest_size); @@ -479,26 +514,35 @@ bool srgb_f32_to_linear_f32(const std::vector &in_img, size_t width, bool srgb_8bit_to_linear_8bit(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img) { + std::vector *out_img, std::string *err) { + + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } + + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); + } + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); + } - if ((width == 0) || - (height == 0) || - (channels == 0) || - (out_img == nullptr)) { - return false; + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } if (channel_stride == 0) { channel_stride = channels; } else { if (channel_stride < channels) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("channel_stride {} is smaller than input channels {}", channel_stride, channels)); } } size_t dest_size = size_t(width) * size_t(height) * channel_stride; if (dest_size > in_img.size()) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Insufficient input buffer size. must be the same or larger than {} but has {}", dest_size, in_img.size())); } out_img->resize(dest_size); @@ -532,17 +576,26 @@ bool srgb_8bit_to_linear_8bit(const std::vector &in_img, size_t width, bool u8_to_f32_image(const std::vector &in_img, size_t width, size_t height, size_t channels, - std::vector *out_img) { - if ((width == 0) || - (height == 0) || - (channels == 0) || - (out_img == nullptr)) { - return false; + std::vector *out_img, std::string *err) { + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } + + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); + } + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); + } + + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } size_t num_pixels = size_t(width) * size_t(height) * channels; if (num_pixels > in_img.size()) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Insufficient input buffer size. must be the same or larger than {} but has {}", num_pixels, in_img.size())); } out_img->resize(num_pixels); @@ -557,17 +610,26 @@ bool u8_to_f32_image(const std::vector &in_img, size_t width, bool f32_to_u8_image(const std::vector &in_img, size_t width, size_t height, size_t channels, - std::vector *out_img, float scale, float bias) { - if ((width == 0) || - (height == 0) || - (channels == 0) || - (out_img == nullptr)) { - return false; + std::vector *out_img, float scale, float bias, std::string *err) { + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } + + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); + } + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); + } + + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } size_t num_pixels = size_t(width) * size_t(height) * channels; if (num_pixels > in_img.size()) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Insufficient input buffer size. must be the same or larger than {} but has {}", num_pixels, in_img.size())); } out_img->resize(num_pixels); @@ -582,37 +644,46 @@ bool f32_to_u8_image(const std::vector &in_img, size_t width, bool linear_displayp3_to_linear_sRGB(const std::vector &in_img, size_t width, size_t height, size_t channels, - std::vector *out_img) { + std::vector *out_img, std::string *err) { - // http://endavid.com/index.php?entry=79 - // https://tech.metail.com/introduction-colour-spaces-dci-p3/ - + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } - if (!out_img) { - return false; + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); } - - if (channels > 4) { - return false; + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); } if ((channels != 3) && (channels != 4)) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("channels must be 3 or 4, but got {}", channels)); + } + + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } if (in_img.size() != (width * height * channels)) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Input buffer size must be {}, but got {}", (width * height * channels), in_img.size())); } out_img->resize(in_img.size()); + // http://endavid.com/index.php?entry=79 + // https://tech.metail.com/introduction-colour-spaces-dci-p3/ + + + if (channels == 3) { for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < width; x++) { float r, g, b; - r = in_img[3 * (y * width + x) + 0]; - g = in_img[3 * (y * width + x) + 1]; - b = in_img[3 * (y * width + x) + 2]; + r = in_img[3 * (y * width + x) + 0]; + g = in_img[3 * (y * width + x) + 1]; + b = in_img[3 * (y * width + x) + 2]; float out_rgb[3]; out_rgb[0] = 1.2249f * r - 0.2247f * g; @@ -634,10 +705,10 @@ bool linear_displayp3_to_linear_sRGB(const std::vector &in_img, size_t wi for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < width; x++) { float r, g, b, a; - r = in_img[4 * (y * width + x) + 0]; - g = in_img[4 * (y * width + x) + 1]; - b = in_img[4 * (y * width + x) + 2]; - a = in_img[4 * (y * width + x) + 3]; + r = in_img[4 * (y * width + x) + 0]; + g = in_img[4 * (y * width + x) + 1]; + b = in_img[4 * (y * width + x) + 2]; + a = in_img[4 * (y * width + x) + 3]; float out_rgb[3]; out_rgb[0] = 1.2249f * r - 0.2247f * g; @@ -662,37 +733,45 @@ bool linear_displayp3_to_linear_sRGB(const std::vector &in_img, size_t wi bool linear_sRGB_to_linear_displayp3(const std::vector &in_img, size_t width, size_t height, size_t channels, - std::vector *out_img) { + std::vector *out_img, std::string *err) { - // http://endavid.com/index.php?entry=79 - // https://tech.metail.com/introduction-colour-spaces-dci-p3/ - + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } - if (!out_img) { - return false; + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); } - - if (channels > 4) { - return false; + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); } if ((channels != 3) && (channels != 4)) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("channels must be 3 or 4, but got {}", channels)); } + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); + } + + if (in_img.size() != (width * height * channels)) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Input buffer size must be {}, but got {}", (width * height * channels), in_img.size())); } out_img->resize(in_img.size()); + // http://endavid.com/index.php?entry=79 + // https://tech.metail.com/introduction-colour-spaces-dci-p3/ + if (channels == 3) { for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < width; x++) { float r, g, b; - r = in_img[3 * (y * width + x) + 0]; - g = in_img[3 * (y * width + x) + 1]; - b = in_img[3 * (y * width + x) + 2]; + r = in_img[3 * (y * width + x) + 0]; + g = in_img[3 * (y * width + x) + 1]; + b = in_img[3 * (y * width + x) + 2]; float out_rgb[3]; out_rgb[0] = 0.8225f * r + 0.1774f * g; @@ -714,10 +793,10 @@ bool linear_sRGB_to_linear_displayp3(const std::vector &in_img, size_t wi for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < width; x++) { float r, g, b, a; - r = in_img[4 * (y * width + x) + 0]; - g = in_img[4 * (y * width + x) + 1]; - b = in_img[4 * (y * width + x) + 2]; - a = in_img[4 * (y * width + x) + 3]; + r = in_img[4 * (y * width + x) + 0]; + g = in_img[4 * (y * width + x) + 1]; + b = in_img[4 * (y * width + x) + 2]; + a = in_img[4 * (y * width + x) + 3]; float out_rgb[3]; out_rgb[0] = 0.8225f * r + 0.1774f * g; @@ -743,26 +822,35 @@ bool linear_sRGB_to_linear_displayp3(const std::vector &in_img, size_t wi bool displayp3_f16_to_linear_f32(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img, const float scale_factor, const float bias, const float alpha_scale_factor, const float alpha_bias) { + std::vector *out_img, const float scale_factor, const float bias, const float alpha_scale_factor, const float alpha_bias, std::string *err) { + + if (width == 0) { + PUSH_ERROR_AND_RETURN("width is zero."); + } + + if (height == 0) { + PUSH_ERROR_AND_RETURN("height is zero."); + } + + if (channels == 0) { + PUSH_ERROR_AND_RETURN("channels is zero."); + } - if ((width == 0) || - (height == 0) || - (channels == 0) || - (out_img == nullptr)) { - return false; + if (out_img == nullptr) { + PUSH_ERROR_AND_RETURN("`out_img` is nullptr."); } if (channel_stride == 0) { channel_stride = channels; } else { if (channel_stride < channels) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("channel_stride {} is smaller than input channels {}", channel_stride, channels)); } } size_t dest_size = size_t(width) * size_t(height) * channel_stride; if (dest_size > in_img.size()) { - return false; + PUSH_ERROR_AND_RETURN(fmt::format("Insufficient input buffer size. must be the same or larger than {} but has {}", dest_size, in_img.size())); } out_img->resize(dest_size); diff --git a/src/image-util.hh b/src/image-util.hh index 64fd5e34..11b6c0c1 100644 --- a/src/image-util.hh +++ b/src/image-util.hh @@ -13,6 +13,7 @@ #include #include #include +#include namespace tinyusdz { @@ -25,7 +26,7 @@ struct half; /// [0, 255] => [0.0, 1.0] /// bool u8_to_f32_image(const std::vector &in_img, - size_t width, size_t height, size_t channels, std::vector *out_img); + size_t width, size_t height, size_t channels, std::vector *out_img, std::string *err = nullptr); /// /// Apply x' = `scale_factor * x + bias` @@ -34,7 +35,8 @@ bool u8_to_f32_image(const std::vector &in_img, bool f32_to_u8_image(const std::vector &in_img, size_t width, size_t height, size_t channels, std::vector *out_img, float scale_factor=1.0f, - float bias=0.0f); + float bias=0.0f, + std::string *err = nullptr); /// /// Convert fp32 image in linear space to 8bit image in sRGB color space. @@ -50,11 +52,12 @@ bool f32_to_u8_image(const std::vector &in_img, /// linear for RGBA image. /// @param[out] out_image Image in sRGB colorspace. Image size is same with /// `in_image` +/// @param[out] err Error message. /// /// @return true upon success. false when any parameter is invalid. bool linear_f32_to_srgb_8bit(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img); + std::vector *out_img, std::string *err = nullptr); /// /// Convert 8bit image in sRGB to fp32 image in linear sRGB color space. @@ -74,20 +77,20 @@ bool linear_f32_to_srgb_8bit(const std::vector &in_img, size_t width, /// @return true upon success. false when any parameter is invalid. bool srgb_8bit_to_linear_f32(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img); + std::vector *out_img, std::string *err = nullptr); bool srgb_8bit_to_linear_8bit(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img); + std::vector *out_img, std::string *err = nullptr); // Input texel value is transformed as: x' = in_img * scale_factor + bias for RGB // alpha' = in_img * alpha_scale_factor + alpha_bias for alpha channel. bool srgb_f32_to_linear_f32(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img, const float scale_factor = 1.0f, const float bias = 0.0f, const float alpha_scale_factor = 1.0f, const float alpha_bias = 0.0f); + std::vector *out_img, const float scale_factor = 1.0f, const float bias = 0.0f, const float alpha_scale_factor = 1.0f, const float alpha_bias = 0.0f, std::string *err = nullptr); /// -/// Convert 8bit image in Rec.709 to fp32 image in linear color space. +/// Convert 8bit image in Rec.709(gamma-applied) to fp32 image in linear color space(linear sRGB). /// /// @param[in] in_img Input image in Rec.709 color space. Image size = /// [width_byte_stride, height, channel_stride] @@ -98,23 +101,23 @@ bool srgb_f32_to_linear_f32(const std::vector &in_img, size_t width, /// @param[in] chanels Pixel channels to apply conversion. must be less than or /// equal to `channel_stride` /// @param[in] chanel_stride channel stride. For example, channels=3 and -/// channel_stride=4 to apply inverse Rec.709 convertion to RGB channel but +/// channel_stride=4 to apply inverse Rec.709 gamma(transfer function) to RGB channel but /// apply linear conversion to alpha channel for RGBA image. -/// @param[out] out_image Image in linear color space. Image size is same with +/// @param[out] out_image Image in linear Rec.709(=linear sRGB) color space. Image size is same with /// `in_image` /// /// @return true upon success. false when any parameter is invalid. bool rec709_8bit_to_linear_f32(const std::vector &in_img, size_t width, size_t width_byte_stride, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img); + std::vector *out_img, std::string *err = nullptr); /// -/// Convert fp16 image in Display P3(P3-D65) to fp32 image in linear Display P3 color space. +/// Convert fp16 image in Display P3(P3-D65, gamma-applied) to fp32 image in linear Display P3 color space. /// -/// The conversion is identical to sRGB -> linear sRGB, since Display P3 uses same gamma curve(transfer function) with sRGB. +/// The conversion is identical to sRGB -> linear sRGB, since Display P3 uses the same gamma curve(transfer function) with sRGB. /// -/// Input value is scaled by x' = x * scale + bias. +/// Linearlized value is further modified by x' = x * scale + bias. /// /// @param[in] in_img Input image in Display P3 color space. Image size = /// [width_byte_stride, height, channel_stride] @@ -135,7 +138,7 @@ bool rec709_8bit_to_linear_f32(const std::vector &in_img, size_t width, /// @return true upon success. false when any parameter is invalid. bool displayp3_f16_to_linear_f32(const std::vector &in_img, size_t width, size_t height, size_t channels, size_t channel_stride, - std::vector *out_img, const float scale_factor = 1.0f, const float bias = 0.0f, const float alpha_scale_factor = 1.0f, const float alpha_bias = 0.0f); + std::vector *out_img, const float scale_factor = 1.0f, const float bias = 0.0f, const float alpha_scale_factor = 1.0f, const float alpha_bias = 0.0f, std::string *err = nullptr); /// /// Convert fp32 image in linear Display P3 color space to 10bit Display P3(10 bit for RGB, 2 bit for alpha, 32bit in total) @@ -155,7 +158,7 @@ bool displayp3_f16_to_linear_f32(const std::vector &in_img, size_t /// `in_image` bool linear_f32_to_displayp3_u10(const std::vector &in_img, size_t width, size_t height, size_t channels, - std::vector *out_img); + std::vector *out_img, std::string *err = nullptr); /// /// Convert linear Display P3 color space to linear sRGB color space. @@ -173,7 +176,7 @@ bool linear_f32_to_displayp3_u10(const std::vector &in_img, size_t width, /// `in_image` bool linear_displayp3_to_linear_sRGB(const std::vector &in_img, size_t width, size_t height, size_t channels, - std::vector *out_img); + std::vector *out_img, std::string *err = nullptr); /// /// Convert linear sRGB color space to linear Display P3 color space. @@ -191,7 +194,7 @@ bool linear_displayp3_to_linear_sRGB(const std::vector &in_img, size_t wi /// `in_image` bool linear_sRGB_to_linear_displayp3(const std::vector &in_img, size_t width, size_t height, size_t channels, - std::vector *out_img); + std::vector *out_img, std::string *err = nullptr); /// /// Resize fp32 image in linear color space. @@ -217,7 +220,7 @@ bool resize_image_f32(const std::vector &src_img, size_t src_width, size_t dest_width, size_t dest_width_byte_stride, size_t dest_height, - size_t channels, std::vector *dest_img); + size_t channels, std::vector *dest_img, std::string *err = nullptr); /// /// Resize uint8 image in sRGB color space. @@ -243,6 +246,6 @@ bool resize_image_u8_srgb(const std::vector &src_img, size_t src_width, size_t dest_width, size_t dest_width_byte_stride, size_t dest_height, - size_t channels, std::vector *dest_img); + size_t channels, std::vector *dest_img, std::string *err =nullptr); } // namespace tinyusdz