blender/intern/cycles/util/image_metadata.cpp
Brecht Van Lommel 6b80d16653 Cycles: Use minimum 64x64 tile size for texture cache load
Some renderers generate tx files with small tile sizes like 32x32,
which have more overhead than our 64x64 tiles. Now we treat such
files as having 64x64 tiles anyway by loading 4 tiles at once.

There is also an internal debug option to test even larger tile
sizes for future benchmarking.

Pull Request: https://projects.blender.org/blender/blender/pulls/161395
2026-07-18 12:27:38 +02:00

766 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/debug.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;
/* Load multiple small tiles together if min_tile_size is specified. */
uint32_t load_tile_size = spec.tile_width;
uint32_t min_tile_size = DebugFlags().texture_cache.min_tile_size;
if (min_tile_size > 0) {
min_tile_size = is_power_of_two(min_tile_size) ? min_tile_size :
next_power_of_two(min_tile_size);
load_tile_size = std::max(load_tile_size, min_tile_size);
}
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 < load_tile_size && height < load_tile_size) {
/* 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 = load_tile_size;
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