Files
2026-08-12 04:47:48 -04:00

479 lines
12 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
/* It's become a bit messy... Basically, only the IMB_ prefixed files
* should remain. */
#include <algorithm>
#include <cstddef>
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_colormanagement_intern.hh"
#include "IMB_metadata.hh"
#include "imbuf.hh"
#include "MEM_guardedalloc.h"
#include "GPU_context.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
#include "OCIO_colorspace.hh"
#include "CLG_log.h"
#include "atomic_ops.h"
namespace blender {
static CLG_LogRef LOG = {"image.buffer"};
/* Allocate pixel storage of the given buffer. The buffer owns the allocated memory.
* Returns true of allocation succeeded, false otherwise. */
template<class BufferType>
bool imb_alloc_buffer(BufferType &buffer,
const uint x,
const uint y,
const uint channels,
const size_t type_size,
bool initialize_pixels)
{
void *data = imb_alloc_pixels(x, y, channels, type_size, initialize_pixels, __func__);
if (!data) {
return false;
}
buffer.data = static_cast<decltype(BufferType::data)>(data);
buffer.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data));
return true;
}
uint8_t *ImBuf::byte_data_for_write()
{
if (!this->byte_buffer.data) {
return nullptr;
}
if (this->byte_buffer.sharing_info->is_mutable()) {
this->byte_buffer.sharing_info->tag_ensured_mutable();
}
else {
const size_t size = size_t(this->x) * size_t(this->y) * 4;
uint8_t *new_data = MEM_new_array_uninitialized<uint8_t>(size, __func__);
std::copy_n(this->byte_buffer.data, size, new_data);
this->byte_buffer.data = new_data;
this->byte_buffer.sharing_info = ImplicitSharingPtr<>(
implicit_sharing::info_for_mem_free(new_data));
}
return const_cast<uint8_t *>(this->byte_buffer.data);
}
float *ImBuf::float_data_for_write()
{
if (!this->float_buffer.data) {
return nullptr;
}
if (this->float_buffer.sharing_info->is_mutable()) {
this->float_buffer.sharing_info->tag_ensured_mutable();
}
else {
const size_t size = size_t(this->x) * size_t(this->y) * this->channels;
float *new_data = MEM_new_array_uninitialized<float>(size, __func__);
std::copy_n(this->float_buffer.data, size, new_data);
this->float_buffer.data = new_data;
this->float_buffer.sharing_info = ImplicitSharingPtr<>(
implicit_sharing::info_for_mem_free(new_data));
}
return const_cast<float *>(this->float_buffer.data);
}
void IMB_free_float_pixels(ImBuf *ibuf)
{
if (ibuf == nullptr) {
return;
}
ibuf->float_buffer = {};
}
void IMB_free_byte_pixels(ImBuf *ibuf)
{
if (ibuf == nullptr) {
return;
}
ibuf->byte_buffer = {};
}
void IMB_free_all_data(ImBuf *ibuf)
{
IMB_free_byte_pixels(ibuf);
IMB_free_float_pixels(ibuf);
}
void IMB_free_gpu_textures(ImBuf *ibuf)
{
if (!ibuf || !ibuf->gpu.texture) {
return;
}
GPU_texture_free(ibuf->gpu.texture);
ibuf->gpu.texture = nullptr;
}
void IMB_freeImBuf(ImBuf *ibuf)
{
if (ibuf == nullptr) {
return;
}
bool needs_free = atomic_sub_and_fetch_int32(&ibuf->refcounter, 1) < 0;
if (needs_free) {
/* Include this check here as the path may be manipulated after creation. */
BLI_assert_msg(!(ibuf->filepath[0] == '/' && ibuf->filepath[1] == '/'),
"'.blend' relative \"//\" must not be used in ImBuf!");
IMB_free_all_data(ibuf);
IMB_free_gpu_textures(ibuf);
IMB_metadata_free(ibuf->metadata);
MEM_delete(ibuf);
}
}
void IMB_refImBuf(ImBuf *ibuf)
{
atomic_add_and_fetch_int32(&ibuf->refcounter, 1);
}
ImBuf *IMB_makeSingleUser(ImBuf *ibuf)
{
if (ibuf == nullptr) {
return nullptr;
}
const bool is_single = (atomic_load_int32(&ibuf->refcounter) == 0);
if (is_single) {
return ibuf;
}
ImBuf *rval = IMB_dupImBuf(ibuf);
IMB_metadata_copy(rval, ibuf);
IMB_freeImBuf(ibuf);
return rval;
}
void *imb_alloc_pixels(
uint x, uint y, uint channels, size_t typesize, bool initialize_pixels, const char *alloc_name)
{
/* Protect against buffer overflow vulnerabilities from files specifying
* a width and height that overflow and alloc too little memory. */
if (!(uint64_t(x) * uint64_t(y) < (SIZE_MAX / (channels * typesize)))) {
return nullptr;
}
size_t size = size_t(x) * size_t(y) * size_t(channels) * typesize;
return initialize_pixels ? MEM_new_zeroed(size, alloc_name) :
MEM_new_uninitialized(size, alloc_name);
}
bool IMB_alloc_float_pixels(ImBuf *ibuf, const uint channels, bool initialize_pixels)
{
if (ibuf == nullptr) {
return false;
}
if (ibuf->float_data()) {
IMB_free_float_pixels(ibuf);
}
if (!imb_alloc_buffer(
ibuf->float_buffer, ibuf->x, ibuf->y, channels, sizeof(float), initialize_pixels))
{
return false;
}
ibuf->channels = channels;
return true;
}
bool IMB_alloc_byte_pixels(ImBuf *ibuf, bool initialize_pixels)
{
/* Question; why also add ZBUF (when `planes > 32`)? */
if (ibuf == nullptr) {
return false;
}
ibuf->byte_buffer = {};
if (!imb_alloc_buffer(
ibuf->byte_buffer, ibuf->x, ibuf->y, 4, sizeof(uint8_t), initialize_pixels))
{
return false;
}
return true;
}
void ImBuf::assign_byte_data(uint8_t *data)
{
this->byte_buffer = {};
if (data) {
this->byte_buffer.data = data;
this->byte_buffer.sharing_info = ImplicitSharingPtr<>(
implicit_sharing::info_for_mem_free(data));
}
}
void ImBuf::assign_float_data(float *data)
{
this->float_buffer = {};
if (data) {
this->float_buffer.data = data;
this->float_buffer.sharing_info = ImplicitSharingPtr<>(
implicit_sharing::info_for_mem_free(data));
}
}
bool ImBuf::colorspace_is_data() const
{
if (this->float_buffer.data) {
return this->float_buffer.colorspace && this->float_buffer.colorspace->is_data();
}
return this->byte_buffer.colorspace && this->byte_buffer.colorspace->is_data();
}
void ImBuf::assign_byte_data(const uint8_t *data, ImplicitSharingPtr<> sharing_ptr)
{
BLI_assert(data != nullptr);
BLI_assert(sharing_ptr.get() != nullptr);
this->byte_buffer.data = data;
this->byte_buffer.sharing_info = std::move(sharing_ptr);
}
void ImBuf::assign_float_data(const float *data, ImplicitSharingPtr<> sharing_ptr)
{
BLI_assert(data != nullptr);
BLI_assert(sharing_ptr.get() != nullptr);
this->float_buffer.data = data;
this->float_buffer.sharing_info = std::move(sharing_ptr);
}
void IMB_assign_gpu_texture(ImBuf *ibuf, gpu::Texture *texture)
{
IMB_free_gpu_textures(ibuf);
ibuf->gpu.texture = texture;
}
void IMB_ensure_host_buffer(ImBuf *ibuf)
{
if (!ibuf || !ibuf->gpu.texture) {
return;
}
/* The host buffers are already up-to-date. */
if (!(ibuf->userflags & IB_HOST_BUFFER_INVALID)) {
return;
}
ibuf->userflags &= ~IB_HOST_BUFFER_INVALID;
const bool need_secondary_context = !GPU_context_active_get();
if (need_secondary_context) {
IMB_activate_gpu_context();
}
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
float *output_buffer = static_cast<float *>(
GPU_texture_read(ibuf->gpu.texture, GPU_DATA_FLOAT, 0));
const ColorSpace *float_colorspace = ibuf->float_buffer.colorspace;
ibuf->assign_float_data(output_buffer);
ibuf->float_buffer.colorspace = float_colorspace;
if (need_secondary_context) {
IMB_deactivate_gpu_context();
}
}
ImBuf *IMB_allocFromBufferOwn(
uint8_t *byte_buffer, float *float_buffer, uint w, uint h, uint channels)
{
if (!(byte_buffer || float_buffer)) {
return nullptr;
}
ImBuf *ibuf = IMB_allocImBuf(w, h, ImBufFlags::Zero);
ibuf->channels = channels;
if (float_buffer) {
/* TODO(sergey): The 4 channels is the historical code. Should probably be `channels`, but
* needs a dedicated investigation. */
BLI_assert(MEM_allocN_len(float_buffer) == sizeof(float[4]) * w * h);
ibuf->assign_float_data(float_buffer);
}
if (byte_buffer) {
BLI_assert(MEM_allocN_len(byte_buffer) == sizeof(uint8_t[4]) * w * h);
ibuf->assign_byte_data(byte_buffer);
}
return ibuf;
}
ImBuf *IMB_allocFromBuffer(
const uint8_t *byte_buffer, const float *float_buffer, uint w, uint h, uint channels)
{
ImBuf *ibuf = nullptr;
if (!(byte_buffer || float_buffer)) {
return nullptr;
}
ibuf = IMB_allocImBuf(w, h, ImBufFlags::Zero);
ibuf->channels = channels;
/* NOTE: Avoid #MEM_dupalloc since the buffers might not be allocated using guarded-allocation.
*/
if (float_buffer) {
/* TODO(sergey): The 4 channels is the historical code. Should probably be `channels`, but
* needs a dedicated investigation. */
imb_alloc_buffer(ibuf->float_buffer, w, h, 4, sizeof(float), false);
memcpy(ibuf->float_data_for_write(), float_buffer, sizeof(float[4]) * w * h);
}
if (byte_buffer) {
imb_alloc_buffer(ibuf->byte_buffer, w, h, 4, sizeof(uint8_t), false);
memcpy(ibuf->byte_data_for_write(), byte_buffer, sizeof(uint8_t[4]) * w * h);
}
return ibuf;
}
ImBuf *IMB_allocImBuf(uint x, uint y, ImBufFlags flags)
{
ImBuf *ibuf = MEM_new<ImBuf>("ImBuf_struct");
if (ibuf) {
if (!IMB_initImBuf(ibuf, x, y, flags)) {
IMB_freeImBuf(ibuf);
return nullptr;
}
}
return ibuf;
}
bool IMB_initImBuf(ImBuf *ibuf, uint x, uint y, ImBufFlags flags)
{
*ibuf = ImBuf{};
ibuf->x = x;
ibuf->y = y;
ibuf->color_mode = ImColorMode::RGBA;
ibuf->ftype = IMB_FTYPE_PNG;
/* float option, is set to other values when buffers get assigned. */
ibuf->channels = 4;
/* IMB_DPI_DEFAULT -> pixels-per-meter. */
ibuf->ppm[0] = ibuf->ppm[1] = IMB_DPI_DEFAULT / 0.0254;
const bool init_pixels = !flag_is_set(flags, ImBufFlags::UninitializedPixels);
if (flag_is_set(flags, ImBufFlags::ByteData)) {
if (!IMB_alloc_byte_pixels(ibuf, init_pixels)) {
return false;
}
}
if (flag_is_set(flags, ImBufFlags::FloatData)) {
if (!IMB_alloc_float_pixels(ibuf, ibuf->channels, init_pixels)) {
return false;
}
}
/* assign default spaces */
colormanage_imbuf_set_default_spaces(ibuf);
return true;
}
ImBuf *IMB_dupImBuf(const ImBuf *ibuf1)
{
if (ibuf1 == nullptr) {
return nullptr;
}
ImBuf *ibuf2 = IMB_allocImBuf(ibuf1->x, ibuf1->y, ImBufFlags::Zero);
if (ibuf2 == nullptr) {
return nullptr;
}
ibuf2->x = ibuf1->x;
ibuf2->y = ibuf1->y;
ibuf2->display_size[0] = ibuf1->display_size[0];
ibuf2->display_size[1] = ibuf1->display_size[1];
ibuf2->data_offset[0] = ibuf1->data_offset[0];
ibuf2->data_offset[1] = ibuf1->data_offset[1];
ibuf2->display_offset[0] = ibuf1->display_offset[0];
ibuf2->display_offset[1] = ibuf1->display_offset[1];
ibuf2->color_mode = ibuf1->color_mode;
ibuf2->channels = ibuf1->channels;
ibuf2->flags = ibuf1->flags;
ibuf2->byte_buffer = ibuf1->byte_buffer;
ibuf2->float_buffer = ibuf1->float_buffer;
/* GPU textures can not be easily copied, as it is not guaranteed that this function is called
* from within an active GPU context. */
ibuf2->gpu.texture = nullptr;
ibuf2->ppm[0] = ibuf1->ppm[0];
ibuf2->ppm[1] = ibuf1->ppm[1];
ibuf2->dither = ibuf1->dither;
ibuf2->index = ibuf1->index;
ibuf2->userflags = ibuf1->userflags;
ibuf2->userflags = ibuf1->userflags;
/* for now don't duplicate metadata */
ibuf2->metadata = nullptr;
ibuf2->exrhandle = ibuf1->exrhandle;
ibuf2->ftype = ibuf1->ftype;
ibuf2->foptions = ibuf1->foptions;
ibuf2->filepath = ibuf1->filepath;
ibuf2->fileframe = ibuf1->fileframe;
ibuf2->refcounter = 0;
return ibuf2;
}
size_t IMB_get_pixel_count(const ImBuf *ibuf)
{
return size_t(ibuf->x) * size_t(ibuf->y);
}
size_t IMB_get_size_in_memory(const ImBuf *ibuf)
{
size_t size = 0, channel_size = 0;
size += sizeof(ImBuf);
if (ibuf->byte_data()) {
channel_size += sizeof(char);
}
if (ibuf->float_data()) {
channel_size += sizeof(float);
}
size += channel_size * IMB_get_pixel_count(ibuf) * size_t(ibuf->channels);
return size;
}
} // namespace blender