Files
workinf_Blender_Wasm/blender-5.2.0/intern/cycles/util/image_metadata.cpp
2026-08-12 04:47:48 -04:00

757 lines
25 KiB
C++

/* SPDX-FileCopyrightText: 2011-2026 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include <cassert>
#include <csetjmp>
#include <cstdio>
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/typedesc.h>
#include "util/color.h"
#include "util/colorspace.h"
#include "util/image.h"
#include "util/image_maketx.h"
#include "util/image_metadata.h"
#include "util/log.h"
#include "util/param.h"
#include "util/path.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
ImageMetaData::ImageMetaData() = default;
bool ImageMetaData::operator==(const ImageMetaData &other) const
{
return channels == other.channels && width == other.width && height == other.height &&
use_transform_3d == other.use_transform_3d &&
(!use_transform_3d || transform_3d == other.transform_3d) && type == other.type &&
colorspace == other.colorspace &&
is_compressible_as_srgb == other.is_compressible_as_srgb;
}
bool ImageMetaData::is_float() const
{
return (type == IMAGE_DATA_TYPE_FLOAT || type == IMAGE_DATA_TYPE_FLOAT4 ||
type == IMAGE_DATA_TYPE_HALF || type == IMAGE_DATA_TYPE_HALF4);
}
bool ImageMetaData::is_half() const
{
return (type == IMAGE_DATA_TYPE_HALF || type == IMAGE_DATA_TYPE_HALF4);
}
bool ImageMetaData::is_rgba() const
{
return (type == IMAGE_DATA_TYPE_BYTE4 || type == IMAGE_DATA_TYPE_USHORT4 ||
type == IMAGE_DATA_TYPE_HALF4 || type == IMAGE_DATA_TYPE_FLOAT4);
}
TypeDesc ImageMetaData::typedesc() const
{
switch (type) {
case IMAGE_DATA_TYPE_BYTE4:
case IMAGE_DATA_TYPE_BYTE:
return OIIO::TypeUInt8;
case IMAGE_DATA_TYPE_USHORT:
case IMAGE_DATA_TYPE_USHORT4:
return OIIO::TypeUInt16;
case IMAGE_DATA_TYPE_HALF4:
case IMAGE_DATA_TYPE_HALF:
return OIIO::TypeHalf;
case IMAGE_DATA_TYPE_FLOAT4:
case IMAGE_DATA_TYPE_FLOAT:
return OIIO::TypeFloat;
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT4:
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
case IMAGE_DATA_TYPE_NANOVDB_EMPTY:
case IMAGE_DATA_NUM_TYPES:
assert(!"Unknown data type");
break;
}
return TypeUnknown;
}
size_t ImageMetaData::pixel_memory_size() const
{
switch (type) {
case IMAGE_DATA_TYPE_BYTE:
return sizeof(uint8_t);
case IMAGE_DATA_TYPE_BYTE4:
return sizeof(uint8_t) * 4;
case IMAGE_DATA_TYPE_USHORT:
return sizeof(uint16_t);
case IMAGE_DATA_TYPE_USHORT4:
return sizeof(uint16_t) * 4;
case IMAGE_DATA_TYPE_HALF:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
return sizeof(half);
case IMAGE_DATA_TYPE_HALF4:
return sizeof(half) * 4;
case IMAGE_DATA_TYPE_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
return sizeof(float);
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
return sizeof(float) * 3;
case IMAGE_DATA_TYPE_FLOAT4:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT4:
return sizeof(float) * 4;
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_EMPTY:
case IMAGE_DATA_NUM_TYPES:
break;
}
return 0;
}
size_t ImageMetaData::memory_size() const
{
/* For NanoVDB, the byte size is stored directly. */
if (is_nanovdb_type(type)) {
return nanovdb_byte_size;
}
return width * height * pixel_memory_size();
}
void ImageMetaData::finalize(const ImageAlphaType alpha_type)
{
/* Convert used specified color spaces to one we know how to handle. */
colorspace = ColorSpaceManager::detect_known_colorspace(
colorspace, colorspace_file_hint.c_str(), colorspace_file_format, is_float());
if (colorspace == u_colorspace_scene_linear || colorspace == u_colorspace_data) {
/* Nothing to do. */
}
else if (colorspace == u_colorspace_scene_linear_srgb) {
/* Keep sRGB colorspace stored as sRGB, to save memory and/or loading time
* for the common case of 8bit sRGB images like PNG. */
is_compressible_as_srgb = true;
}
else {
/* If colorspace conversion needed, use half instead of short so we can
* represent HDR values that might result from conversion. */
if (type == IMAGE_DATA_TYPE_BYTE || type == IMAGE_DATA_TYPE_USHORT) {
type = IMAGE_DATA_TYPE_HALF;
}
else if (type == IMAGE_DATA_TYPE_BYTE4 || type == IMAGE_DATA_TYPE_USHORT4) {
type = IMAGE_DATA_TYPE_HALF4;
}
}
/* For Blender tx files, match colorspace used by maketx to generate the file.
* THis is needed because TIFF files can not store arbitrary colorspace metadata. */
if (!tile_need_conform) {
colorspace = make_tx_get_file_colorspace(*this);
}
ignore_alpha = alpha_type == IMAGE_ALPHA_IGNORE;
is_channel_packed = alpha_type == IMAGE_ALPHA_CHANNEL_PACKED;
/* No alpha conversion needed for data, associated, no alpha or channel packed. */
if (ColorSpaceManager::colorspace_is_data(colorspace) || alpha_type == IMAGE_ALPHA_ASSOCIATED ||
alpha_type == IMAGE_ALPHA_IGNORE || alpha_type == IMAGE_ALPHA_CHANNEL_PACKED)
{
is_unassociated_alpha = false;
}
/* Always convert if explicitly specified as unassociated. */
else if (alpha_type == IMAGE_ALPHA_UNASSOCIATED) {
is_unassociated_alpha = true;
}
else {
/* Leave automatically detected is_unassociated_alpha unchanged. */
}
/* Convert average color to scene linear colorspace. */
if (!is_zero(average_color) && colorspace != u_colorspace_data &&
colorspace != u_colorspace_scene_linear)
{
if (colorspace == u_colorspace_scene_linear_srgb) {
average_color = color_srgb_to_linear_v4(average_color);
}
else {
ColorSpaceManager::to_scene_linear(
colorspace, &average_color.x, 1, 1, 0, true, false, ignore_alpha || is_channel_packed);
}
}
}
void ImageMetaData::make_float()
{
switch (type) {
case IMAGE_DATA_TYPE_BYTE:
case IMAGE_DATA_TYPE_USHORT:
case IMAGE_DATA_TYPE_HALF:
type = IMAGE_DATA_TYPE_FLOAT;
break;
case IMAGE_DATA_TYPE_BYTE4:
case IMAGE_DATA_TYPE_USHORT4:
case IMAGE_DATA_TYPE_HALF4:
type = IMAGE_DATA_TYPE_FLOAT4;
break;
case IMAGE_DATA_TYPE_FLOAT:
case IMAGE_DATA_TYPE_FLOAT4:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT4:
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
case IMAGE_DATA_TYPE_NANOVDB_EMPTY:
case IMAGE_DATA_NUM_TYPES:
break;
}
}
static bool load_metadata_color(const ImageSpec &spec, const char *name, float4 &r_color)
{
string_view metadata_color = spec.get_string_attribute(name);
if (metadata_color.size() == 0) {
return false;
}
vector<float> color;
while (metadata_color.size()) {
float val;
if (!OIIO::Strutil::parse_float(metadata_color, val)) {
break;
}
color.push_back(val);
if (!OIIO::Strutil::parse_char(metadata_color, ',')) {
break;
}
}
if (color.size() != size_t(spec.nchannels)) {
return false;
}
switch (spec.nchannels) {
case 1:
r_color = make_float4(color[0], color[0], color[0], 1.0f);
return true;
case 2:
r_color = make_float4(color[0], color[0], color[0], color[1]);
return true;
case 3:
r_color = make_float4(color[0], color[1], color[2], 1.0f);
return true;
case 4:
r_color = make_float4(color[0], color[1], color[2], color[3]);
return true;
default:
return false;
}
}
void ImageMetaData::detect_tiles(ImageInput &input,
const ImageSpec &spec,
OIIO::string_view filepath)
{
/* 1x1 file can be used as tx file regardless of how it was generated,
* could be a constant color tx or just regular file. */
is_tx_file = (width == 1 && height == 1);
if (spec.tile_width == 0) {
return;
}
const std::string software = spec.get_string_attribute("Software");
int tx_file_format_version = INT_MAX;
sscanf(software.c_str(), "Blender maketx v%d", &tx_file_format_version);
if (tx_file_format_version != INT_MAX &&
(tx_file_format_version < 0 || tx_file_format_version > TX_FILE_FORMAT_VERSION))
{
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but file format version " << tx_file_format_version
<< " is not supported by this version of Cycles";
return;
}
if (tx_file_format_version == INT_MAX) {
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but is missing blender:TxFileFormatVersion";
tile_need_conform = true;
}
else if (!(channels == 1 || channels == 4)) {
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but expected 1 or 4 channels, found " << channels;
tile_need_conform = true;
}
else {
tile_need_conform = false;
}
bool has_tiles = false;
bool is_small_image = false;
if (!is_power_of_two(spec.tile_width)) {
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but tile size not power of two (" << spec.tile_width << ")";
}
else if (spec.tile_width != spec.tile_height) {
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but tile size is not square (" << spec.tile_width << "x"
<< spec.tile_height << ")";
}
else if (spec.tile_depth != 1) {
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but depth is not 1";
}
else if (spec.tile_width < KERNEL_IMAGE_TEX_PADDING * 4) {
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but tile size too small (found " << spec.tile_width << ", minimum "
<< KERNEL_IMAGE_TEX_PADDING * 4 << ")";
}
else if (width < spec.tile_width && height < spec.tile_width) {
/* We don't currently supporting using tiles for images smaller than the tile
* size, and it's also unnecessary. To enable this, we'd need to solve the
* problem where interpolation at tile pixel centers does not match full image
* sampling due to padding offset. */
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but image resolution is smaller than tile size";
has_tiles = true;
is_small_image = true;
}
else {
tile_size = spec.tile_width;
has_tiles = true;
}
/* Check if mip levels are complete. */
has_tiles_and_mipmaps = has_tiles && tile_size;
if (has_tiles_and_mipmaps) {
for (int miplevel = 0;; miplevel++) {
if (!input.seek_subimage(0, miplevel)) {
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath)
<< " has tiles, but missing mip levels";
has_tiles_and_mipmaps = false;
break;
}
const int mip_width = width >> miplevel;
const int mip_height = height >> miplevel;
if (mip_width <= tile_size && mip_height <= tile_size) {
break;
}
}
input.seek_subimage(0, 0);
}
/* Tiled tx files need to either have mipmaps, or be small enough not to need mipmaps. */
if (has_tiles && (is_small_image || has_tiles_and_mipmaps)) {
is_tx_file = true;
}
}
bool ImageMetaData::oiio_load_metadata(OIIO::string_view filepath, OIIO::ImageSpec *r_spec)
{
/* Perform preliminary checks, with meaningful logging. */
if (!OIIO::Filesystem::exists(filepath)) {
LOG_WARNING << "Image file " << filepath << " does not exist.";
return false;
}
if (OIIO::Filesystem::is_directory(filepath)) {
LOG_WARNING << "Image file " << filepath << " is a directory, cannot use as image.";
return false;
}
std::unique_ptr<ImageInput> in(ImageInput::create(filepath));
if (!in) {
LOG_WARNING << "Image file " << filepath << " failed to load.";
return false;
}
ImageSpec spec;
ImageSpec config;
/* Load without automatic OIIO alpha conversion. */
config.attribute("oiio:UnassociatedAlpha", 1);
if (!in->open(filepath, spec, config)) {
LOG_WARNING << "Image file " << filepath << " failed to open.";
return false;
}
if (spec.depth > 1) {
LOG_WARNING << "Image file " << filepath << " has unsupported depth of " << spec.depth;
return false;
}
width = spec.width;
height = spec.height;
is_compressible_as_srgb = false;
/* Check the main format, and channel formats. */
size_t channel_size = spec.format.basesize();
bool is_float = false;
bool is_half = false;
if (spec.format.is_floating_point()) {
is_float = true;
}
for (size_t channel = 0; channel < spec.channelformats.size(); channel++) {
channel_size = max(channel_size, spec.channelformats[channel].basesize());
if (spec.channelformats[channel].is_floating_point()) {
is_float = true;
}
}
/* check if it's half float */
if (spec.format == TypeDesc::HALF) {
is_half = true;
}
/* set type and channels */
channels = spec.nchannels;
if (is_half) {
type = (channels > 1) ? IMAGE_DATA_TYPE_HALF4 : IMAGE_DATA_TYPE_HALF;
}
else if (is_float) {
type = (channels > 1) ? IMAGE_DATA_TYPE_FLOAT4 : IMAGE_DATA_TYPE_FLOAT;
}
else if (spec.format == TypeDesc::USHORT) {
type = (channels > 1) ? IMAGE_DATA_TYPE_USHORT4 : IMAGE_DATA_TYPE_USHORT;
}
else {
type = (channels > 1) ? IMAGE_DATA_TYPE_BYTE4 : IMAGE_DATA_TYPE_BYTE;
}
colorspace_file_format = in->format_name();
colorspace_file_hint = spec.get_string_attribute("oiio:ColorSpace");
is_unassociated_alpha = spec.get_int_attribute("oiio:UnassociatedAlpha", 0);
if (!is_unassociated_alpha && spec.alpha_channel != -1) {
/* Workaround OIIO not detecting TGA file alpha the same as Blender (since #3019).
* We want anything not marked as premultiplied alpha to get associated. */
if (strcmp(in->format_name(), "targa") == 0) {
is_unassociated_alpha = spec.get_int_attribute("targa:alpha_type", -1) != 4;
}
/* OIIO DDS reader never sets UnassociatedAlpha attribute. */
if (strcmp(in->format_name(), "dds") == 0) {
is_unassociated_alpha = true;
}
/* Workaround OIIO bug that sets oiio:UnassociatedAlpha on the last layer
* but not composite image that we read. */
if (strcmp(in->format_name(), "psd") == 0) {
is_unassociated_alpha = true;
}
}
/* Load constant or average color. */
if (load_metadata_color(spec, "oiio:ConstantColor", average_color)) {
/* Could avoid loading tiles entirely for a bit more memory saving, or even
* constant folding in the shader nodes. */
}
else {
load_metadata_color(spec, "oiio:AverageColor", average_color);
}
detect_tiles(*in, spec, filepath);
LOG_DEBUG << "Image " << OIIO::Filesystem::filename(filepath) << ", " << width << "x" << height
<< ", " << (tile_size ? "tiled" : "untiled");
if (r_spec) {
*r_spec = spec;
}
return true;
}
template<typename StorageType>
static void conform_pixels_to_metadata_type(const ImageMetaData &metadata,
StorageType *pixels,
const int64_t width,
const int64_t height,
const int64_t x_stride,
const int64_t in_y_stride,
const int64_t out_y_stride)
{
/* The kernel can handle 1 and 4 channel images. Anything that is not a single
* channel image is converted to RGBA format. */
const int channels = metadata.channels;
const bool is_rgba = metadata.is_rgba();
/* Associate alpha. */
if (channels == 4 && metadata.is_unassociated_alpha) {
for (int64_t j = 0; j < height; j++) {
StorageType *pixel = pixels + j * in_y_stride;
for (int64_t i = 0; i < width; i++, pixel += 4) {
const StorageType alpha = pixel[3];
pixel[0] = util_image_multiply_native(pixel[0], alpha);
pixel[1] = util_image_multiply_native(pixel[1], alpha);
pixel[2] = util_image_multiply_native(pixel[2], alpha);
}
}
}
if (is_rgba) {
const StorageType one = util_image_cast_from_float<StorageType>(1.0f);
if (channels == 2) {
/* Grayscale + alpha to RGBA. */
for (int64_t j = height - 1; j >= 0; j--) {
StorageType *out_pixels = pixels + j * out_y_stride;
StorageType *in_pixels = pixels + j * in_y_stride;
for (int64_t i = width - 1; i >= 0; i--) {
out_pixels[i * 4 + 3] = in_pixels[i * x_stride + 1];
out_pixels[i * 4 + 2] = in_pixels[i * x_stride + 0];
out_pixels[i * 4 + 1] = in_pixels[i * x_stride + 0];
out_pixels[i * 4 + 0] = in_pixels[i * x_stride + 0];
}
}
}
else if (channels == 3) {
/* RGB to RGBA. */
for (int64_t j = height - 1; j >= 0; j--) {
StorageType *out_pixels = pixels + j * out_y_stride;
StorageType *in_pixels = pixels + j * in_y_stride;
for (int64_t i = width - 1; i >= 0; i--) {
out_pixels[i * 4 + 3] = one;
out_pixels[i * 4 + 2] = in_pixels[i * x_stride + 2];
out_pixels[i * 4 + 1] = in_pixels[i * x_stride + 1];
out_pixels[i * 4 + 0] = in_pixels[i * x_stride + 0];
}
}
}
else if (channels == 1) {
/* Grayscale to RGBA. */
for (int64_t j = height - 1; j >= 0; j--) {
StorageType *out_pixels = pixels + j * out_y_stride;
StorageType *in_pixels = pixels + j * in_y_stride;
for (int64_t i = width - 1; i >= 0; i--) {
out_pixels[i * 4 + 3] = one;
out_pixels[i * 4 + 2] = in_pixels[i * x_stride];
out_pixels[i * 4 + 1] = in_pixels[i * x_stride];
out_pixels[i * 4 + 0] = in_pixels[i * x_stride];
}
}
}
/* Disable alpha if requested by the user. */
if (metadata.ignore_alpha) {
for (int64_t j = 0; j < height; j++) {
StorageType *out_pixels = pixels + j * out_y_stride;
for (int64_t i = 0; i < width; i++) {
out_pixels[i * 4 + 3] = one;
}
}
}
}
if (metadata.colorspace != u_colorspace_scene_linear &&
metadata.colorspace != u_colorspace_scene_linear_srgb &&
metadata.colorspace != u_colorspace_data)
{
/* Convert to scene linear. */
ColorSpaceManager::to_scene_linear(metadata.colorspace,
pixels,
width,
height,
out_y_stride,
is_rgba,
metadata.is_compressible_as_srgb,
metadata.ignore_alpha || metadata.is_channel_packed);
}
/* Make sure we don't have buggy values. */
if constexpr (std::is_same_v<float, StorageType> || std::is_same_v<half, StorageType>) {
/* For RGBA buffers we put all channels to 0 if either of them is not
* finite. This way we avoid possible artifacts caused by fully changed
* hue. */
if (is_rgba) {
for (int64_t j = 0; j < height; j++) {
StorageType *pixel = pixels + j * out_y_stride;
for (int64_t i = 0; i < width; i++, pixel += 4) {
if (!util_image_is_finite(pixel[0]) || !util_image_is_finite(pixel[1]) ||
!util_image_is_finite(pixel[2]) || !util_image_is_finite(pixel[3]))
{
pixel[0] = 0;
pixel[1] = 0;
pixel[2] = 0;
pixel[3] = 0;
}
}
}
}
else {
for (int64_t j = 0; j < height; j++) {
StorageType *pixel = pixels + j * out_y_stride;
for (int64_t i = 0; i < width; i++, pixel++) {
if (!util_image_is_finite(pixel[0])) {
pixel[0] = 0;
}
}
}
}
}
}
void ImageMetaData::conform_pixels(void *pixels,
const int64_t width,
const int64_t height,
const int64_t x_stride,
const int64_t in_y_stride,
const int64_t out_y_stride) const
{
switch (type) {
case IMAGE_DATA_TYPE_BYTE4:
case IMAGE_DATA_TYPE_BYTE:
conform_pixels_to_metadata_type<uchar>(
*this, static_cast<uchar *>(pixels), width, height, x_stride, in_y_stride, out_y_stride);
break;
case IMAGE_DATA_TYPE_USHORT:
case IMAGE_DATA_TYPE_USHORT4:
conform_pixels_to_metadata_type<uint16_t>(*this,
static_cast<uint16_t *>(pixels),
width,
height,
x_stride,
in_y_stride,
out_y_stride);
break;
case IMAGE_DATA_TYPE_HALF4:
case IMAGE_DATA_TYPE_HALF:
conform_pixels_to_metadata_type<half>(
*this, static_cast<half *>(pixels), width, height, x_stride, in_y_stride, out_y_stride);
break;
case IMAGE_DATA_TYPE_FLOAT4:
case IMAGE_DATA_TYPE_FLOAT:
conform_pixels_to_metadata_type<float>(
*this, static_cast<float *>(pixels), width, height, x_stride, in_y_stride, out_y_stride);
break;
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT4:
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
case IMAGE_DATA_TYPE_NANOVDB_EMPTY:
case IMAGE_DATA_NUM_TYPES:
break;
}
}
void ImageMetaData::conform_pixels(void *pixels) const
{
conform_pixels(pixels, width, height, channels, width * channels, width * (is_rgba() ? 4 : 1));
}
template<TypeDesc::BASETYPE FileFormat, typename StorageType>
static bool load_pixels_oiio(const ImageMetaData &metadata,
const std::unique_ptr<ImageInput> &in,
StorageType *pixels,
const bool flip_y)
{
const int64_t width = metadata.width;
const int64_t height = metadata.height;
const int channels = metadata.channels;
const int64_t num_pixels = width * height;
int64_t read_y_stride = width * channels * sizeof(StorageType);
/* Read pixels through OpenImageIO. */
StorageType *readpixels = pixels;
vector<StorageType> tmppixels;
if (channels > 4) {
tmppixels.resize(num_pixels * channels);
readpixels = &tmppixels[0];
}
if (!in->read_image(0,
0,
0,
channels,
FileFormat,
(flip_y) ? (uchar *)readpixels + (height - 1) * read_y_stride :
(uchar *)readpixels,
AutoStride,
(flip_y) ? -read_y_stride : read_y_stride,
AutoStride))
{
return false;
}
if (channels > 4) {
for (int64_t j = 0; j < height; j++) {
const StorageType *in_pixels = tmppixels.data() + j * width * channels;
StorageType *out_pixels = pixels + j * width * 4;
for (int64_t i = 0; i < width; i++) {
out_pixels[i * 4 + 3] = in_pixels[i * channels + 3];
out_pixels[i * 4 + 2] = in_pixels[i * channels + 2];
out_pixels[i * 4 + 1] = in_pixels[i * channels + 1];
out_pixels[i * 4 + 0] = in_pixels[i * channels + 0];
}
}
tmppixels.clear();
}
return true;
}
bool ImageMetaData::oiio_load_pixels(OIIO::string_view filepath,
void *pixels,
const bool flip_y) const
{
/* load image from file through OIIO */
std::unique_ptr<ImageInput> in = ImageInput::create(filepath);
if (!in) {
return false;
}
ImageSpec spec = ImageSpec();
ImageSpec config = ImageSpec();
/* Load without automatic OIIO alpha conversion, we do it ourselves. OIIO
* will associate alpha in the 8bit buffer for PNGs, which leads to too
* much precision loss when we load it as half float to do a color-space transform. */
config.attribute("oiio:UnassociatedAlpha", 1);
if (!in->open(filepath, spec, config)) {
return false;
}
if (spec.depth > 1) {
LOG_WARNING << "Image file " << filepath << " has unsupported depth " << spec.depth;
return false;
}
switch (type) {
case IMAGE_DATA_TYPE_BYTE:
case IMAGE_DATA_TYPE_BYTE4:
return load_pixels_oiio<TypeDesc::UINT8, uchar>(*this, in, (uchar *)pixels, flip_y);
case IMAGE_DATA_TYPE_USHORT:
case IMAGE_DATA_TYPE_USHORT4:
return load_pixels_oiio<TypeDesc::USHORT, uint16_t>(*this, in, (uint16_t *)pixels, flip_y);
case IMAGE_DATA_TYPE_HALF:
case IMAGE_DATA_TYPE_HALF4:
return load_pixels_oiio<TypeDesc::HALF, half>(*this, in, (half *)pixels, flip_y);
case IMAGE_DATA_TYPE_FLOAT:
case IMAGE_DATA_TYPE_FLOAT4:
return load_pixels_oiio<TypeDesc::FLOAT, float>(*this, in, (float *)pixels, flip_y);
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT4:
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
case IMAGE_DATA_TYPE_NANOVDB_EMPTY:
case IMAGE_DATA_NUM_TYPES:
break;
}
return false;
}
CCL_NAMESPACE_END