Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: 2013 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
..
../..
../../../../../intern/utfconv
)
set(INC_SYS
)
set(SRC
openimageio_api.h
openimageio_support.hh
openimageio_api.cpp
openimageio_support.cc
)
if(WITH_WEB)
set(SRC
openimageio_api.h
openimageio_api_stub.cc
)
endif()
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::clog
PRIVATE bf::dependencies::openimageio
PRIVATE bf::dependencies::openexr
PRIVATE bf::dependencies::optional::pugixml
)
blender_add_lib(bf_imbuf_openimageio "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup openimageio
*/
#include "openimageio_api.h"
#include <OpenImageIO/imageio.h>
#include "BLI_threads.h"
namespace blender {
OIIO_NAMESPACE_USING
void OIIO_init()
{
/* Make OIIO thread pool follow Blender number of threads override. */
const int threads_override = BLI_system_num_threads_override_get();
if (threads_override) {
OIIO::attribute("threads", threads_override);
}
/* As of OpenEXR 3.2.1 there are still issues related to the use of OpenEXR Core. */
OIIO::attribute("openexr:core", 0);
/* Allow OpenImageIO to open files up to the size specified. An 80gb limit
* will allow a 4-gigapixel, 5-channel, image to be opened (e.g. like what
* would be encountered with the Cycles "tile" buffer file). */
OIIO::attribute("limits:imagesize_MB", 80 * 1024);
}
int OIIO_getVersionHex()
{
return openimageio_version();
}
} // namespace blender

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup openimageio
*/
#pragma once
namespace blender {
/*
* Initialize OpenImageIO on startup.
*/
void OIIO_init();
/*
* Get OpenImageIO version.
*/
int OIIO_getVersionHex();
} // namespace blender

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "openimageio_api.h"
namespace blender {
void OIIO_init() {}
int OIIO_getVersionHex()
{
return 0;
}
} // namespace blender

View File

@@ -0,0 +1,546 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "openimageio_support.hh"
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
#include <algorithm>
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "BKE_idprop.hh"
#include "DNA_ID.h"
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_metadata.hh"
#include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG_READ = {"image.read"};
static CLG_LogRef LOG_WRITE = {"image.write"};
OIIO_NAMESPACE_USING
using std::string;
using std::unique_ptr;
namespace imbuf {
/* An OIIO IOProxy to write into an in-memory buffer. */
struct MemoryBufferWriter : public Filesystem::IOProxy {
MemoryBufferWriter() : IOProxy("", Write)
{
buffer.reserve(80 * 1024);
}
const char *proxytype() const override
{
return "MemoryBufferWriter";
}
size_t write(const void *buf, size_t size) override
{
size = pwrite(buf, size, m_pos);
m_pos += size;
return size;
}
size_t pwrite(const void *buf, size_t size, int64_t offset) override
{
/* If buffer is too small increase it. */
size_t end = offset + size;
if (end > buffer.size()) {
buffer.resize(end);
}
memcpy(buffer.data() + offset, buf, size);
return size;
}
size_t size() const override
{
return buffer.size();
}
Vector<uint8_t> buffer;
};
/* Utility to in-place expand an n-component pixel buffer into a 4-component buffer. */
template<typename T>
static void fill_all_channels(T *pixels, int width, int height, int components, T alpha)
{
const int64_t pixel_count = int64_t(width) * height;
if (components == 3) {
for (int64_t i = 0; i < pixel_count; i++) {
pixels[i * 4 + 3] = alpha;
}
}
else if (components == 1) {
for (int64_t i = 0; i < pixel_count; i++) {
pixels[i * 4 + 3] = alpha;
pixels[i * 4 + 2] = pixels[i * 4 + 0];
pixels[i * 4 + 1] = pixels[i * 4 + 0];
}
}
else if (components == 2) {
for (int64_t i = 0; i < pixel_count; i++) {
pixels[i * 4 + 3] = pixels[i * 4 + 1];
pixels[i * 4 + 2] = pixels[i * 4 + 0];
pixels[i * 4 + 1] = pixels[i * 4 + 0];
}
}
}
template<typename T>
static ImBuf *load_pixels(
ImageInput *in, int width, int height, int channels, ImBufFlags flags, bool use_all_planes)
{
/* Allocate the ImBuf for the image. */
constexpr bool is_float = sizeof(T) > 1;
const ImBufFlags format_flag = (is_float ? ImBufFlags::FloatData : ImBufFlags::ByteData) |
ImBufFlags::UninitializedPixels;
const ImBufFlags ibuf_flags = flag_is_set(flags, ImBufFlags::Test) ? ImBufFlags::Zero :
format_flag;
ImColorMode color_mode = ImColorMode::RGBA;
if (channels == 2) {
color_mode = ImColorMode::BW_A;
}
else if (!use_all_planes) {
if (channels == 1) {
color_mode = ImColorMode::BW;
}
else if (channels == 3) {
color_mode = ImColorMode::RGB;
}
}
ImBuf *ibuf = IMB_allocImBuf(width, height, ibuf_flags);
if (!ibuf) {
return nullptr;
}
ibuf->color_mode = color_mode;
/* No need to load actual pixel data during the test phase. */
if (flag_is_set(flags, ImBufFlags::Test)) {
return ibuf;
}
/* Calculate an appropriate stride to read n-channels directly into
* the ImBuf 4-channel layout. */
const stride_t ibuf_xstride = sizeof(T) * 4;
const stride_t ibuf_ystride = ibuf_xstride * width;
const TypeDesc format = is_float ? TypeDesc::FLOAT : TypeDesc::UINT8;
uchar *rect = is_float ? reinterpret_cast<uchar *>(ibuf->float_data_for_write()) :
reinterpret_cast<uchar *>(ibuf->byte_data_for_write());
void *ibuf_data = rect + ((stride_t(height) - 1) * ibuf_ystride);
bool ok = in->read_image(
0, 0, 0, channels, format, ibuf_data, ibuf_xstride, -ibuf_ystride, AutoStride);
if (!ok) {
CLOG_ERROR(&LOG_READ, "OpenImageIO read failed: %s", in->geterror().c_str());
IMB_freeImBuf(ibuf);
return nullptr;
}
/* ImBuf always needs 4 channels */
const T alpha_fill = is_float ? 1.0f : 0xFF;
fill_all_channels<T>(reinterpret_cast<T *>(rect), width, height, channels, alpha_fill);
return ibuf;
}
static void set_file_colorspace(ImFileColorSpace &r_colorspace,
const ReadContext &ctx,
const ImageSpec &spec,
bool is_float)
{
/* Guess float data types means HDR colors. File formats can override this later. */
r_colorspace.is_hdr_float = is_float;
/* Override if necessary. */
if (ctx.use_metadata_colorspace) {
string ics = spec.get_string_attribute("oiio:ColorSpace");
STRNCPY_UTF8(r_colorspace.metadata_colorspace, ics.c_str());
}
/* Get colorspace from CICP. */
int cicp[4] = {};
if (spec.getattribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp, true)) {
const ColorSpace *colorspace = IMB_colormanagement_space_from_cicp(
cicp, ColorManagedFileOutput::Image);
if (colorspace) {
STRNCPY_UTF8(r_colorspace.metadata_colorspace,
IMB_colormanagement_colorspace_get_name(colorspace));
}
}
}
/**
* Get an #ImBuf filled in with pixel data and associated metadata using the provided ImageInput.
*/
static ImBuf *get_oiio_ibuf(ImageInput *in, const ReadContext &ctx, ImFileColorSpace &r_colorspace)
{
const ImageSpec &spec = in->spec();
const int width = spec.width;
const int height = spec.height;
const bool has_alpha = spec.alpha_channel != -1;
const bool is_float = spec.format.basesize() > 1;
/* Only a maximum of 4 channels are supported by ImBuf. */
const int channels = spec.nchannels <= 4 ? spec.nchannels : 4;
if (channels < 1) {
return nullptr;
}
if (spec.depth > 1) {
CLOG_ERROR(&LOG_READ, "Image has unsupported depth of %d", spec.depth);
return nullptr;
}
const bool use_all_planes = has_alpha || ctx.use_all_planes;
ImBuf *ibuf = nullptr;
if (is_float) {
ibuf = load_pixels<float>(in, width, height, channels, ctx.flags, use_all_planes);
}
else {
ibuf = load_pixels<uchar>(in, width, height, channels, ctx.flags, use_all_planes);
}
/* Fill in common ibuf properties. */
if (ibuf) {
ibuf->ftype = ctx.file_type;
ibuf->foptions.flag |= (spec.format == TypeDesc::HALF) ? OPENEXR_HALF : 0;
set_file_colorspace(r_colorspace, ctx, spec, is_float);
double x_res = spec.get_float_attribute("XResolution", 0.0f);
double y_res = spec.get_float_attribute("YResolution", 0.0f);
/* Some formats store the resolution as integers. */
if (!(x_res > 0.0f && y_res > 0.0f)) {
x_res = spec.get_int_attribute("XResolution", 0);
y_res = spec.get_int_attribute("YResolution", 0);
}
if (x_res > 0.0f && y_res > 0.0f) {
double scale = 1.0;
auto unit = spec.get_string_attribute("ResolutionUnit", "");
if (ELEM(unit, "in", "inch")) {
scale = 100.0 / 2.54;
}
else if (unit == "cm") {
scale = 100.0;
}
ibuf->ppm[0] = scale * x_res;
ibuf->ppm[1] = scale * y_res;
}
/* Transfer metadata to the ibuf if necessary. */
if (flag_is_set(ctx.flags, ImBufFlags::Metadata)) {
IMB_metadata_ensure(&ibuf->metadata);
ibuf->flags |= spec.extra_attribs.empty() ? ImBufFlags::Zero : ImBufFlags::Metadata;
for (const auto &attrib : spec.extra_attribs) {
if (attrib.name().find("ICCProfile") != string::npos) {
continue;
}
IMB_metadata_set_field(ibuf->metadata, attrib.name().c_str(), attrib.get_string().c_str());
}
}
}
return ibuf;
}
/**
* Returns an #ImageInput for the precise `format` requested using the provided #IOMemReader.
* If successful, the #ImageInput will be opened and ready for operations. Null will be returned if
* the format was not found or if the open call fails.
*/
static unique_ptr<ImageInput> get_oiio_reader(const char *format,
const ImageSpec &config,
Filesystem::IOMemReader &mem_reader,
ImageSpec &r_newspec)
{
/* Attempt to create a reader based on the passed in format. */
unique_ptr<ImageInput> in = ImageInput::create(format);
if (!(in && in->valid_file(&mem_reader))) {
return nullptr;
}
/* Open the reader using the ioproxy. */
in->set_ioproxy(&mem_reader);
bool ok = in->open("", r_newspec, config);
if (!ok) {
return nullptr;
}
return in;
}
bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format)
{
ImageSpec config, spec;
/* This memory proxy must remain alive for the full duration of the read. */
Filesystem::IOMemReader mem_reader(cspan<uchar>(mem, mem_size));
unique_ptr<ImageInput> in = ImageInput::create(file_format);
return in && in->valid_file(&mem_reader);
}
ImBuf *imb_oiio_read(const ReadContext &ctx,
const ImageSpec &config,
ImFileColorSpace &r_colorspace,
ImageSpec &r_newspec)
{
/* This memory proxy must remain alive for the full duration of the read. */
Filesystem::IOMemReader mem_reader(cspan<uchar>(ctx.mem_start, ctx.mem_size));
unique_ptr<ImageInput> in = get_oiio_reader(ctx.file_format, config, mem_reader, r_newspec);
if (!in) {
return nullptr;
}
return get_oiio_ibuf(in.get(), ctx, r_colorspace);
}
static void oiio_write_prepare(const ImageSpec &file_spec, ImageBuf &orig_buf, ImageBuf &final_buf)
{
#if OIIO_VERSION_MAJOR >= 3
const size_t original_channels_count = orig_buf.nchannels();
#else
const int original_channels_count = orig_buf.nchannels();
#endif
if (original_channels_count > 1 && file_spec.nchannels == 1) {
/* Convert to gray-scale image by computing the luminance. Make sure the weight of alpha
* channel is zero since it should not contribute to the luminance. */
float weights[4] = {0.0f, 0.0f, 0.0f, 0.0f};
IMB_colormanagement_get_luminance_coefficients(weights);
ImageBufAlgo::channel_sum(final_buf, orig_buf, {weights, original_channels_count});
}
else if (original_channels_count == 1 && file_spec.nchannels > 1) {
/* Broadcast the gray-scale channel to as many channels as needed, filling the alpha channel
* with ones if needed. 0 channel order mean we will be copying from the first channel, while
* -1 means we will be filling based on the corresponding value from the defined channel
* values. */
const int channel_order[] = {0, 0, 0, -1};
const float channel_values[] = {0.0f, 0.0f, 0.0f, 1.0f};
const std::string channel_names[] = {"R", "G", "B", "A"};
ImageBufAlgo::channels(final_buf,
orig_buf,
file_spec.nchannels,
cspan<int>(channel_order, file_spec.nchannels),
cspan<float>(channel_values, file_spec.nchannels),
cspan<std::string>(channel_names, file_spec.nchannels));
}
else if (file_spec.nchannels == 2 && original_channels_count >= 2) {
/* Gray-scale + alpha output (#ImColorMode::BW_A). The #ImBuf source replicates gray into
* RGB and stores alpha at index 3, so extract {gray, alpha} = {0, 3}. */
const int channel_order[] = {0, 3};
const float channel_values[] = {0.0f, 1.0f};
const std::string channel_names[] = {"Y", "A"};
ImageBufAlgo::channels(final_buf, orig_buf, 2, channel_order, channel_values, channel_names);
}
else if (original_channels_count != file_spec.nchannels) {
/* Either trim or fill new channels based on the needed channels count. */
int channel_order[4];
for (int i = 0; i < 4; i++) {
/* If a channel exists in the original buffer, we copy it, if not, we fill it by supplying
* -1, which is a special value that means filling based on the value in the defined channels
* values. So alpha is filled with 1, and other channels are filled with zero. */
const bool channel_exists = i + 1 <= original_channels_count;
channel_order[i] = channel_exists ? i : -1;
}
const float channel_values[] = {0.0f, 0.0f, 0.0f, 1.0f};
const std::string channel_names[] = {"R", "G", "B", "A"};
ImageBufAlgo::channels(final_buf,
orig_buf,
file_spec.nchannels,
cspan<int>(channel_order, file_spec.nchannels),
cspan<float>(channel_values, file_spec.nchannels),
cspan<std::string>(channel_names, file_spec.nchannels));
}
else {
final_buf = std::move(orig_buf);
}
}
bool imb_oiio_write(const WriteContext &ctx, const char *filepath, const ImageSpec &file_spec)
{
unique_ptr<ImageOutput> out = ImageOutput::create(ctx.file_format);
if (!out) {
return false;
}
ImageBuf orig_buf(ctx.mem_spec, ctx.mem_start, ctx.mem_xstride, -ctx.mem_ystride, AutoStride);
ImageBuf final_buf{};
oiio_write_prepare(file_spec, orig_buf, final_buf);
bool write_ok = false;
bool close_ok = false;
if (out->open(filepath, file_spec)) {
write_ok = final_buf.write(out.get());
close_ok = out->close();
}
const bool all_ok = write_ok && close_ok;
if (!all_ok) {
CLOG_ERROR(&LOG_WRITE, "OpenImageIO write failed: %s", out->geterror().c_str());
errno = 0; /* Prevent higher level layers from calling `perror` unnecessarily. */
}
return all_ok;
}
Vector<uint8_t> imb_oiio_write_buffer(const WriteContext &ctx, const ImageSpec &file_spec)
{
unique_ptr<ImageOutput> out = ImageOutput::create(ctx.file_format);
if (!out) {
return {};
}
ImageBuf orig_buf(ctx.mem_spec, ctx.mem_start, ctx.mem_xstride, -ctx.mem_ystride, AutoStride);
ImageBuf final_buf{};
oiio_write_prepare(file_spec, orig_buf, final_buf);
MemoryBufferWriter writer;
out->set_ioproxy(&writer);
bool write_ok = false;
bool close_ok = false;
if (out->open("", file_spec)) {
write_ok = final_buf.write(out.get());
close_ok = out->close();
}
const bool all_ok = write_ok && close_ok;
if (!all_ok) {
CLOG_ERROR(&LOG_WRITE, "OpenImageIO write failed: %s", out->geterror().c_str());
errno = 0; /* Prevent higher level layers from calling `perror` unnecessarily. */
return {};
}
return std::move(writer.buffer);
}
WriteContext imb_create_write_context(const char *file_format,
ImBuf *ibuf,
ImBufFlags flags,
bool prefer_float)
{
WriteContext ctx{};
ctx.file_format = file_format;
ctx.ibuf = ibuf;
ctx.flags = flags;
const int width = ibuf->x;
const int height = ibuf->y;
const bool use_float = prefer_float && (ibuf->float_data() != nullptr);
if (use_float) {
const int mem_channels = ibuf->channels ? ibuf->channels : 4;
ctx.mem_xstride = sizeof(float) * mem_channels;
ctx.mem_ystride = width * ctx.mem_xstride;
ctx.mem_start = reinterpret_cast<uchar *>(ibuf->float_data_for_write());
ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::FLOAT);
}
else {
const int mem_channels = 4;
ctx.mem_xstride = sizeof(uchar) * mem_channels;
ctx.mem_ystride = width * ctx.mem_xstride;
ctx.mem_start = ibuf->byte_data_for_write();
ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::UINT8);
}
/* We always write using a negative y-stride so ensure we start at the end. */
ctx.mem_start = ctx.mem_start + ((stride_t(height) - 1) * ctx.mem_ystride);
return ctx;
}
ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, TypeDesc data_format)
{
const int width = ctx.ibuf->x;
const int height = ctx.ibuf->y;
ImageSpec file_spec(width, height, file_channels, data_format);
/* Populate the spec with all common attributes.
*
* Care must be taken with the metadata:
* - It should be processed first, before the "Resolution" metadata below, to
* ensure the proper values end up in the #ImageSpec
* - It needs to filter format-specific metadata that may no longer apply to
* the current format being written (e.g. metadata for tiff being written to a `PNG`)
*/
if (ctx.ibuf->metadata) {
for (IDProperty &prop : ctx.ibuf->metadata->data.group) {
if (prop.type == IDP_STRING) {
/* If this property has a prefixed name (oiio:, tiff:, etc.) and it belongs to
* oiio or a different format, then skip. */
if (char *colon = strchr(prop.name, ':')) {
std::string prefix(prop.name, colon);
Strutil::to_lower(prefix);
if (prefix == "oiio" ||
(!STREQ(prefix.c_str(), ctx.file_format) && OIIO::is_imageio_format_name(prefix)))
{
/* Skip this attribute. */
continue;
}
}
file_spec.attribute(prop.name, IDP_string_get(&prop));
}
}
}
if (ctx.ibuf->ppm[0] > 0.0 && ctx.ibuf->ppm[1] > 0.0) {
if (STREQ(ctx.file_format, "bmp")) {
/* BMP only supports meters as integers. */
file_spec.attribute("ResolutionUnit", "m");
file_spec.attribute("XResolution", int(round(ctx.ibuf->ppm[0])));
file_spec.attribute("YResolution", int(round(ctx.ibuf->ppm[1])));
}
else {
/* More OIIO formats support inch than meter. */
file_spec.attribute("ResolutionUnit", "in");
file_spec.attribute("XResolution", float(ctx.ibuf->ppm[0] * 0.0254));
file_spec.attribute("YResolution", float(ctx.ibuf->ppm[1] * 0.0254));
}
}
/* Write ICC profile and/or CICP if there is one associated with the colorspace. */
const ColorSpace *colorspace = (ctx.mem_spec.format == TypeDesc::FLOAT) ?
ctx.ibuf->float_buffer.colorspace :
ctx.ibuf->byte_buffer.colorspace;
if (colorspace) {
Vector<char> icc_profile = IMB_colormanagement_space_to_icc_profile(colorspace);
if (!icc_profile.is_empty()) {
file_spec.attribute("ICCProfile",
OIIO::TypeDesc(OIIO::TypeDesc::UINT8, icc_profile.size()),
icc_profile.data());
}
/* PNG only supports RGB matrix. For AVIF and HEIF we want to use a YUV matrix
* as these are based on video codecs designed to use them. */
const bool rgb_matrix = STREQ(ctx.file_format, "png");
int cicp[4];
if (IMB_colormanagement_space_to_cicp(
colorspace, ColorManagedFileOutput::Image, rgb_matrix, cicp))
{
file_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
}
}
return file_spec;
}
} // namespace imbuf
} // namespace blender

View File

@@ -0,0 +1,114 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/* Include our own math header first to avoid warnings about M_PI
* redefinition between OpenImageIO and Windows headers. */
#include "BLI_math_base.h" // IWYU pragma: keep
#include "BLI_sys_types.h"
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/imageio.h>
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
struct ImFileColorSpace;
namespace imbuf {
/**
* Parameters and settings used while reading image formats.
*/
struct ReadContext {
const uchar *mem_start;
const size_t mem_size;
const char *file_format;
const eImbFileType file_type;
const ImBufFlags flags;
/** Allocate and use all #ImBuf image planes even if the image has fewer. */
bool use_all_planes = false;
/** Use the `colorspace` provided in the image metadata when available. */
bool use_metadata_colorspace = false;
};
/**
* Parameters and settings used while writing image formats.
*/
struct WriteContext {
const char *file_format;
ImBuf *ibuf;
ImBufFlags flags;
uchar *mem_start;
OIIO::stride_t mem_xstride;
OIIO::stride_t mem_ystride;
OIIO::ImageSpec mem_spec;
};
/**
* Check to see if we can load and open the given file format.
*/
bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format);
/**
* The primary method for reading data into an #ImBuf.
*
* During the `ImBufFlags::Test` phase of loading, the `colorspace` parameter will be populated
* with the appropriate `colorspace` name.
*
* Upon return, the `r_newspec` parameter will contain image format information
* which can be inspected afterwards if necessary.
*/
ImBuf *imb_oiio_read(const ReadContext &ctx,
const OIIO::ImageSpec &config,
ImFileColorSpace &r_colorspace,
OIIO::ImageSpec &r_newspec);
/**
* The primary method for writing data from an #ImBuf to a file.
*
* The `file_spec` parameter will typically come from #imb_create_write_spec.
*/
bool imb_oiio_write(const WriteContext &ctx,
const char *filepath,
const OIIO::ImageSpec &file_spec);
/**
* The primary method for writing data from an #ImBuf to an in-memory buffer.
*
* The `file_spec` parameter will typically come from #imb_create_write_spec.
*/
Vector<uint8_t> imb_oiio_write_buffer(const WriteContext &ctx, const OIIO::ImageSpec &file_spec);
/**
* Create a #WriteContext based on the provided #ImBuf and format information.
*
* If the provided #ImBuf contains both byte and float buffers, the `prefer_float`
* flag controls which buffer to use. By default, if a float buffer exists it will
* be used.
*/
WriteContext imb_create_write_context(const char *file_format,
ImBuf *ibuf,
ImBufFlags flags,
bool prefer_float = true);
/**
* Returns an #ImageSpec filled in with all common attributes associated with the #ImBuf
* provided as part of the #WriteContext.
*
* This includes optional metadata that has been attached to the #ImBuf and which should be
* written to the new file as necessary.
*/
OIIO::ImageSpec imb_create_write_spec(const WriteContext &ctx,
int file_channels,
OIIO::TypeDesc data_format);
} // namespace imbuf
} // namespace blender