/* SPDX-License-Identifier: Apache-2.0 * Copyright 2011-2022 Blender Foundation */ #include "scene/image_oiio.h" #include "util/image.h" #include "util/log.h" #include "util/path.h" CCL_NAMESPACE_BEGIN OIIOImageLoader::OIIOImageLoader(const string &filepath) : filepath(filepath) { } OIIOImageLoader::~OIIOImageLoader() { } bool OIIOImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/, ImageMetaData &metadata) { /* Perform preliminary checks, with meaningful logging. */ if (!path_exists(filepath.string())) { VLOG_WARNING << "File '" << filepath.string() << "' does not exist."; return false; } if (path_is_directory(filepath.string())) { VLOG_WARNING << "File '" << filepath.string() << "' is a directory, can't use as image."; return false; } unique_ptr in(ImageInput::create(filepath.string())); if (!in) { return false; } ImageSpec spec; if (!in->open(filepath.string(), spec)) { return false; } metadata.width = spec.width; metadata.height = spec.height; metadata.depth = spec.depth; metadata.compress_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 */ metadata.channels = spec.nchannels; if (is_half) { metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_HALF4 : IMAGE_DATA_TYPE_HALF; } else if (is_float) { metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_FLOAT4 : IMAGE_DATA_TYPE_FLOAT; } else if (spec.format == TypeDesc::USHORT) { metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_USHORT4 : IMAGE_DATA_TYPE_USHORT; } else { metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_BYTE4 : IMAGE_DATA_TYPE_BYTE; } metadata.colorspace_file_format = in->format_name(); in->close(); return true; } template static void oiio_load_pixels(const ImageMetaData &metadata, const unique_ptr &in, const bool associate_alpha, StorageType *pixels) { const size_t width = metadata.width; const size_t height = metadata.height; const int depth = metadata.depth; const int components = metadata.channels; /* Read pixels through OpenImageIO. */ StorageType *readpixels = pixels; vector tmppixels; if (components > 4) { tmppixels.resize(width * height * components); readpixels = &tmppixels[0]; } if (depth <= 1) { size_t scanlinesize = width * components * sizeof(StorageType); in->read_image(FileFormat, (uchar *)readpixels + (height - 1) * scanlinesize, AutoStride, -scanlinesize, AutoStride); } else { in->read_image(FileFormat, (uchar *)readpixels); } if (components > 4) { size_t dimensions = width * height; for (size_t i = dimensions - 1, pixel = 0; pixel < dimensions; pixel++, i--) { pixels[i * 4 + 3] = tmppixels[i * components + 3]; pixels[i * 4 + 2] = tmppixels[i * components + 2]; pixels[i * 4 + 1] = tmppixels[i * components + 1]; pixels[i * 4 + 0] = tmppixels[i * components + 0]; } tmppixels.clear(); } /* CMYK to RGBA. */ const bool cmyk = strcmp(in->format_name(), "jpeg") == 0 && components == 4; if (cmyk) { const StorageType one = util_image_cast_from_float(1.0f); const size_t num_pixels = width * height * depth; for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) { float c = util_image_cast_to_float(pixels[i * 4 + 0]); float m = util_image_cast_to_float(pixels[i * 4 + 1]); float y = util_image_cast_to_float(pixels[i * 4 + 2]); float k = util_image_cast_to_float(pixels[i * 4 + 3]); pixels[i * 4 + 0] = util_image_cast_from_float((1.0f - c) * (1.0f - k)); pixels[i * 4 + 1] = util_image_cast_from_float((1.0f - m) * (1.0f - k)); pixels[i * 4 + 2] = util_image_cast_from_float((1.0f - y) * (1.0f - k)); pixels[i * 4 + 3] = one; } } if (components == 4 && associate_alpha) { size_t dimensions = width * height; for (size_t i = dimensions - 1, pixel = 0; pixel < dimensions; pixel++, i--) { const StorageType alpha = pixels[i * 4 + 3]; pixels[i * 4 + 0] = util_image_multiply_native(pixels[i * 4 + 0], alpha); pixels[i * 4 + 1] = util_image_multiply_native(pixels[i * 4 + 1], alpha); pixels[i * 4 + 2] = util_image_multiply_native(pixels[i * 4 + 2], alpha); } } } bool OIIOImageLoader::load_pixels(const ImageMetaData &metadata, void *pixels, const size_t, const bool associate_alpha) { unique_ptr in = NULL; /* NOTE: Error logging is done in meta data acquisition. */ if (!path_exists(filepath.string()) || path_is_directory(filepath.string())) { return false; } /* load image from file through OIIO */ in = unique_ptr(ImageInput::create(filepath.string())); 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 the 8bit buffer for PNGs, which leads to too * much precision loss when we load it as half float to do a colorspace * transform. */ config.attribute("oiio:UnassociatedAlpha", 1); if (!in->open(filepath.string(), spec, config)) { return false; } const bool do_associate_alpha = associate_alpha && spec.get_int_attribute("oiio:UnassociatedAlpha", 0); switch (metadata.type) { case IMAGE_DATA_TYPE_BYTE: case IMAGE_DATA_TYPE_BYTE4: oiio_load_pixels(metadata, in, do_associate_alpha, (uchar *)pixels); break; case IMAGE_DATA_TYPE_USHORT: case IMAGE_DATA_TYPE_USHORT4: oiio_load_pixels( metadata, in, do_associate_alpha, (uint16_t *)pixels); break; case IMAGE_DATA_TYPE_HALF: case IMAGE_DATA_TYPE_HALF4: oiio_load_pixels(metadata, in, do_associate_alpha, (half *)pixels); break; case IMAGE_DATA_TYPE_FLOAT: case IMAGE_DATA_TYPE_FLOAT4: oiio_load_pixels(metadata, in, do_associate_alpha, (float *)pixels); break; case IMAGE_DATA_TYPE_NANOVDB_FLOAT: case IMAGE_DATA_TYPE_NANOVDB_FLOAT3: case IMAGE_DATA_TYPE_NANOVDB_FPN: case IMAGE_DATA_TYPE_NANOVDB_FP16: case IMAGE_DATA_NUM_TYPES: break; } in->close(); return true; } string OIIOImageLoader::name() const { return path_filename(filepath.string()); } ustring OIIOImageLoader::osl_filepath() const { return filepath; } bool OIIOImageLoader::equals(const ImageLoader &other) const { const OIIOImageLoader &other_loader = (const OIIOImageLoader &)other; return filepath == other_loader.filepath; } CCL_NAMESPACE_END