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,38 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#pragma once
namespace blender {
namespace ocio {
class ColorSpace;
class CPUProcessor;
} // namespace ocio
using ColorSpace = ocio::ColorSpace;
struct ImBuf;
enum class ColorManagedFileOutput;
#define MAX_COLORSPACE_NAME 64
/* ** Initialization / De-initialization ** */
void colormanagement_init();
void colormanagement_exit();
const ColorSpace *colormanage_colorspace_get_named(const char *name);
const ColorSpace *colormanage_colorspace_get_roled(int role);
void colormanage_imbuf_set_default_spaces(ImBuf *ibuf);
void colormanage_imbuf_make_linear(ImBuf *ibuf,
const char *from_colorspace,
ColorManagedFileOutput output);
} // namespace blender

View File

@@ -0,0 +1,382 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#pragma once
#include "IMB_imbuf.hh"
namespace blender {
struct ImBuf;
struct ImFileColorSpace;
/* -------------------------------------------------------------------- */
/** \name Generic File Type
* \{ */
#define IM_FTYPE_FLOAT 1
struct ImFileType {
/** Optional, called once when initializing. */
void (*init)();
/** Optional, called once when exiting. */
void (*exit)();
/**
* Check if the data matches this file types 'magic',
* \note that this may only read in a small part of the files header,
* see: #IMB_test_image_type for details.
*/
bool (*is_a)(const unsigned char *buf, size_t size);
/** Load an image from memory. */
ImBuf *(*load)(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
/** Load an image from a file. */
ImBuf *(*load_filepath)(const char *filepath, ImBufFlags flags, ImFileColorSpace &r_colorspace);
/**
* Load/Create a thumbnail image from a filepath. `max_thumb_size` is maximum size of either
* dimension, so can return less on either or both. Should, if possible and performant, return
* dimensions of the full-size image in r_width & r_height.
*/
ImBuf *(*load_filepath_thumbnail)(const char *filepath,
ImBufFlags flags,
size_t max_thumb_size,
ImFileColorSpace &r_colorspace,
size_t *r_width,
size_t *r_height);
/** Save to a file. */
bool (*save)(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
/** Save to a memory buffer. */
Vector<uint8_t> (*save_buffer)(ImBuf *ibuf, ImBufFlags flags);
int flag;
/** Combination of #eImFileTypeCapability flags for reading. */
eImFileTypeCapability capability_read;
/** Combination of #eImFileTypeCapability flags for writing. */
eImFileTypeCapability capability_write;
eImbFileType filetype;
/** Upper case ID, used as a unique identifier for the file format. */
const char *filetype_id;
/**
* Null-terminated list of file extensions (including the dot).
* When non-null, at least one string *must* be set.
*/
const char **file_extensions;
int default_save_role;
};
/** Color space information provided by the file. */
struct ImFileColorSpace {
/** Color space from metadata. */
char metadata_colorspace[IM_MAX_SPACE] = "";
/** Is image HDR with range potentially outside 0..1? */
bool is_hdr_float = false;
};
extern const ImFileType IMB_FILE_TYPES[];
extern const ImFileType *IMB_FILE_TYPES_LAST;
const ImFileType *IMB_file_type_from_ftype(eImbFileType ftype);
const ImFileType *IMB_file_type_from_ibuf(const ImBuf *ibuf);
void imb_filetypes_init();
void imb_filetypes_exit();
/** \} */
/* Type Specific Functions */
/* -------------------------------------------------------------------- */
/** \name Format: PNG (#IMB_FTYPE_PNG)
* \{ */
extern const char *imb_file_extensions_png[];
bool imb_is_a_png(const unsigned char *mem, size_t size);
ImBuf *imb_load_png(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
bool imb_save_png(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_png(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: TARGA (#IMB_FTYPE_TGA)
* \{ */
extern const char *imb_file_extensions_tga[];
bool imb_is_a_tga(const unsigned char *mem, size_t size);
ImBuf *imb_load_tga(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
bool imb_save_tga(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_tga(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: IRIS (#IMB_FTYPE_IRIS)
* \{ */
extern const char *imb_file_extensions_iris[];
bool imb_is_a_iris(const unsigned char *mem, size_t size);
/**
* Read in a B/W RGB or RGBA iris image file and return an image buffer.
*/
ImBuf *imb_loadiris(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
bool imb_saveiris(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_iris(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: JP2 (#IMB_FTYPE_JP2)
* \{ */
extern const char *imb_file_extensions_jp2[];
bool imb_is_a_jp2(const unsigned char *buf, size_t size);
ImBuf *imb_load_jp2(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
ImBuf *imb_load_jp2_filepath(const char *filepath,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
bool imb_save_jp2(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: JPEG (#IMB_FTYPE_JPG)
* \{ */
extern const char *imb_file_extensions_jpeg[];
bool imb_is_a_jpeg(const unsigned char *mem, size_t size);
bool imb_savejpeg(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
ImBuf *imb_load_jpeg(const unsigned char *buffer,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
ImBuf *imb_thumbnail_jpeg(const char *filepath,
ImBufFlags flags,
size_t max_thumb_size,
ImFileColorSpace &r_colorspace,
size_t *r_width,
size_t *r_height);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: BMP (#IMB_FTYPE_BMP)
* \{ */
extern const char *imb_file_extensions_bmp[];
bool imb_is_a_bmp(const unsigned char *mem, size_t size);
ImBuf *imb_load_bmp(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
bool imb_save_bmp(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_bmp(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: CINEON (#IMB_FTYPE_CINEON)
* \{ */
extern const char *imb_file_extensions_cineon[];
bool imb_is_a_cineon(const unsigned char *mem, size_t size);
bool imb_save_cineon(ImBuf *buf, const char *filepath, ImBufFlags flags);
ImBuf *imb_load_cineon(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: DPX (#IMB_FTYPE_DPX)
* \{ */
extern const char *imb_file_extensions_dpx[];
bool imb_is_a_dpx(const unsigned char *mem, size_t size);
bool imb_save_dpx(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_dpx(ImBuf *ibuf, ImBufFlags flags);
ImBuf *imb_load_dpx(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: HDR (#IMB_FTYPE_RADHDR)
* \{ */
extern const char *imb_file_extensions_hdr[];
bool imb_is_a_hdr(const unsigned char *mem, size_t size);
ImBuf *imb_load_hdr(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
bool imb_save_hdr(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_hdr(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: TIFF (#IMB_FTYPE_TIF)
* \{ */
extern const char *imb_file_extensions_tiff[];
bool imb_is_a_tiff(const unsigned char *mem, size_t size);
/**
* Loads a TIFF file.
* \param mem: Memory containing the TIFF file.
* \param size: Size of the mem buffer.
* \param flags: If flags has ImBufFlags::Test set then the file is not actually loaded,
* but all other operations take place.
*
* \return A newly allocated #ImBuf structure if successful, otherwise NULL.
*/
ImBuf *imb_load_tiff(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
/**
* Saves a TIFF file.
*
* #ImBuf structures with 1, 3 or 4 bytes per pixel (GRAY, RGB, RGBA respectively)
* are accepted, and interpreted correctly. Note that the TIFF convention is to use
* pre-multiplied alpha, which can be achieved within Blender by setting `premul` alpha handling.
* Other alpha conventions are not strictly correct, but are permitted anyhow.
*
* \param ibuf: Image buffer.
* \param filepath: Name of the TIFF file to create.
* \param flags: Currently largely ignored.
*
* \return 1 if the function is successful, 0 on failure.
*/
bool imb_save_tiff(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_tiff(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: WEBP (#IMB_FTYPE_WEBP)
* \{ */
extern const char *imb_file_extensions_webp[];
bool imb_is_a_webp(const unsigned char *mem, size_t size);
ImBuf *imb_loadwebp(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
ImBuf *imb_load_filepath_thumbnail_webp(const char *filepath,
const ImBufFlags flags,
const size_t max_thumb_size,
ImFileColorSpace &r_colorspace,
size_t *r_width,
size_t *r_height);
bool imb_savewebp(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_webp(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: DDS (#IMB_FTYPE_DDS)
* \{ */
extern const char *imb_file_extensions_dds[];
void imb_init_dds();
bool imb_is_a_dds(const unsigned char *mem, size_t size);
ImBuf *imb_load_dds(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
uint8_t *imb_load_dds_compressed_data(const char *filepath,
int width,
int height,
int &r_mipcount);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: PSD (#IMB_FTYPE_PSD)
* \{ */
extern const char *imb_file_extensions_psd[];
bool imb_is_a_psd(const unsigned char *mem, size_t size);
ImBuf *imb_load_psd(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: AVIF (#IMB_FTYPE_AVIF)
* \{ */
extern const char *imb_file_extensions_avif[];
bool imb_is_a_avif(const unsigned char *mem, size_t size);
ImBuf *imb_load_avif(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
bool imb_save_avif(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_avif(ImBuf *ibuf, ImBufFlags flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: SVG - Only for thumbnails.
* \{ */
ImBuf *imb_load_filepath_thumbnail_svg(const char *filepath,
const ImBufFlags flags,
const size_t max_thumb_size,
ImFileColorSpace &r_colorspace,
size_t *r_width,
size_t *r_height);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
* \brief Function declarations for `filter.cc`.
*/
#pragma once
#include <cstdint>
#include "DNA_image_enums.h"
namespace blender {
struct ImBuf;
void IMB_premultiply_rect(uint8_t *rect, ImColorMode color_mode, int w, int h);
void IMB_premultiply_rect_float(float *rect_float, int channels, int w, int h);
void IMB_unpremultiply_rect(uint8_t *rect, ImColorMode color_mode, int w, int h);
void IMB_unpremultiply_rect_float(float *rect_float, int channels, int w, int h);
} // namespace blender

View File

@@ -0,0 +1,478 @@
/* 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

View File

@@ -0,0 +1,616 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#undef DEBUG_MESSAGES
#include <cstdlib> /* for qsort */
#include <memory.h>
#include <mutex>
#include "MEM_CacheLimiterC-Api.h"
#include "MEM_guardedalloc.h"
#include "BLI_ghash.h"
#include "BLI_mempool.h"
#include "BLI_string.h"
#include "IMB_cache.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
#ifdef DEBUG_MESSAGES
# if defined __GNUC__
# define PRINT(format, args...) printf(format, ##args)
# else
# define PRINT(format, ...) printf(__VA_ARGS__)
# endif
#else
# define PRINT(format, ...)
#endif
static MEM_CacheLimiterC *limitor = nullptr;
/* Image buffers managed by a ImBufCache might be using their own caches (used by color
* management). In practice this means that, for example, freeing ImBufCache used by MovieClip
* will request freeing ImBufCache owned by ImBuf. Freeing ImBufCache needs to be thread-safe,
* so regular mutex will not work here, hence the recursive lock. */
static std::recursive_mutex limitor_lock;
struct ImBufCache {
char name[64];
GHash *hash;
GHashHashFP hashfp;
GHashCmpFP cmpfp;
ImBufCacheGetKeyDataFP getdatafp;
ImBufCacheGetPriorityDataFP getprioritydatafp;
ImBufCacheGetItemPriorityFP getitempriorityfp;
ImBufCachePriorityDeleterFP prioritydeleterfp;
BLI_mempool *keys_pool;
BLI_mempool *items_pool;
BLI_mempool *userkeys_pool;
int keysize;
void *last_userkey;
int totseg, *points, proxy, render_flags; /* for visual statistics optimization */
int pad;
};
struct ImBufCacheKey {
ImBufCache *cache_owner;
void *userkey;
};
struct ImBufCacheItem {
ImBufCache *cache_owner;
ImBuf *ibuf;
MEM_CacheLimiterHandleC *c_handle;
void *priority_data;
/* Indicates that #ibuf is null, because there was an error during load. */
bool added_empty;
};
static uint imbufcache_hashhash(const void *keyv)
{
const ImBufCacheKey *key = static_cast<const ImBufCacheKey *>(keyv);
return key->cache_owner->hashfp(key->userkey);
}
static bool imbufcache_hashcmp(const void *av, const void *bv)
{
const ImBufCacheKey *a = static_cast<const ImBufCacheKey *>(av);
const ImBufCacheKey *b = static_cast<const ImBufCacheKey *>(bv);
return a->cache_owner->cmpfp(a->userkey, b->userkey);
}
static void imbufcache_keyfree(void *val)
{
ImBufCacheKey *key = static_cast<ImBufCacheKey *>(val);
BLI_mempool_free(key->cache_owner->userkeys_pool, key->userkey);
BLI_mempool_free(key->cache_owner->keys_pool, key);
}
static void imbufcache_valfree(void *val)
{
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(val);
ImBufCache *cache = item->cache_owner;
PRINT("%s: cache '%s' free item %p buffer %p\n", __func__, cache->name, item, item->ibuf);
if (item->c_handle) {
limitor_lock.lock();
MEM_CacheLimiter_unmanage(item->c_handle);
limitor_lock.unlock();
}
if (item->ibuf) {
IMB_freeImBuf(item->ibuf);
}
if (item->priority_data && cache->prioritydeleterfp) {
cache->prioritydeleterfp(item->priority_data);
}
BLI_mempool_free(item->cache_owner->items_pool, item);
}
static void check_unused_keys(ImBufCache *cache)
{
GHashIterator gh_iter;
BLI_ghashIterator_init(&gh_iter, cache->hash);
while (!BLI_ghashIterator_done(&gh_iter)) {
const ImBufCacheKey *key = static_cast<const ImBufCacheKey *>(
BLI_ghashIterator_getKey(&gh_iter));
const ImBufCacheItem *item = static_cast<const ImBufCacheItem *>(
BLI_ghashIterator_getValue(&gh_iter));
BLI_ghashIterator_step(&gh_iter);
if (item->added_empty) {
/* Don't remove entries that have been added empty. Those indicate that the image couldn't be
* loaded correctly. */
continue;
}
bool remove = !item->ibuf;
if (remove) {
PRINT("%s: cache '%s' remove item %p without buffer\n", __func__, cache->name, item);
}
if (remove) {
BLI_ghash_remove(cache->hash, key, imbufcache_keyfree, imbufcache_valfree);
}
}
}
static int compare_int(const void *av, const void *bv)
{
const int *a = static_cast<int *>(const_cast<void *>(av));
const int *b = static_cast<int *>(const_cast<void *>(bv));
return *a - *b;
}
static void imbufcache_destructor(void *p)
{
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(p);
if (item) {
item->c_handle = nullptr;
if (item->ibuf) {
ImBufCache *cache = item->cache_owner;
PRINT("%s: cache '%s' destroy item %p buffer %p\n", __func__, cache->name, item, item->ibuf);
IMB_freeImBuf(item->ibuf);
item->ibuf = nullptr;
/* force cached segments to be updated */
MEM_SAFE_DELETE(cache->points);
}
}
}
static size_t get_size_in_memory(ImBuf *ibuf)
{
/* Keep textures in the memory to avoid constant file reload on viewport update. */
if (ibuf->userflags & IB_PERSISTENT) {
return 0;
}
return IMB_get_size_in_memory(ibuf);
}
static size_t get_item_size(void *p)
{
size_t size = sizeof(ImBufCacheItem);
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(p);
if (item->ibuf) {
size += get_size_in_memory(item->ibuf);
}
return size;
}
static int get_item_priority(void *item_v, int default_priority)
{
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(item_v);
ImBufCache *cache = item->cache_owner;
int priority;
if (!cache->getitempriorityfp) {
PRINT("%s: cache '%s' item %p use default priority %d\n",
__func__,
cache->name,
item,
default_priority);
return default_priority;
}
priority = cache->getitempriorityfp(cache->last_userkey, item->priority_data);
PRINT("%s: cache '%s' item %p priority %d\n", __func__, cache->name, item, priority);
return priority;
}
static bool get_item_destroyable(void *item_v)
{
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(item_v);
if (item->ibuf == nullptr) {
return true;
}
/* IB_BITMAPDIRTY means image was modified from inside blender and
* changes are not saved to disk.
*
* Such buffers are never to be freed.
*/
if ((item->ibuf->userflags & IB_BITMAPDIRTY) || (item->ibuf->userflags & IB_PERSISTENT)) {
return false;
}
return true;
}
void IMB_cache_init()
{
limitor = new_MEM_CacheLimiter(imbufcache_destructor, get_item_size);
MEM_CacheLimiter_ItemPriority_Func_set(limitor, get_item_priority);
MEM_CacheLimiter_ItemDestroyable_Func_set(limitor, get_item_destroyable);
}
void IMB_cache_destruct()
{
if (limitor) {
delete_MEM_CacheLimiter(limitor);
limitor = nullptr;
}
}
ImBufCache *IMB_cache_create(const char *name, int keysize, GHashHashFP hashfp, GHashCmpFP cmpfp)
{
ImBufCache *cache;
PRINT("%s: cache '%s' create\n", __func__, name);
cache = MEM_new_zeroed<ImBufCache>("ImBufCache");
STRNCPY(cache->name, name);
cache->keys_pool = BLI_mempool_create(sizeof(ImBufCacheKey), 0, 64, BLI_MEMPOOL_NOP);
cache->items_pool = BLI_mempool_create(sizeof(ImBufCacheItem), 0, 64, BLI_MEMPOOL_NOP);
cache->userkeys_pool = BLI_mempool_create(keysize, 0, 64, BLI_MEMPOOL_NOP);
cache->hash = BLI_ghash_new(
imbufcache_hashhash, imbufcache_hashcmp, "MovieClip ImBuf cache hash");
cache->keysize = keysize;
cache->hashfp = hashfp;
cache->cmpfp = cmpfp;
cache->proxy = -1;
return cache;
}
void IMB_cache_set_getdata_callback(ImBufCache *cache, ImBufCacheGetKeyDataFP getdatafp)
{
cache->getdatafp = getdatafp;
}
void IMB_cache_set_priority_callback(ImBufCache *cache,
ImBufCacheGetPriorityDataFP getprioritydatafp,
ImBufCacheGetItemPriorityFP getitempriorityfp,
ImBufCachePriorityDeleterFP prioritydeleterfp)
{
cache->last_userkey = MEM_new_uninitialized(cache->keysize, "movie cache last user key");
cache->getprioritydatafp = getprioritydatafp;
cache->getitempriorityfp = getitempriorityfp;
cache->prioritydeleterfp = prioritydeleterfp;
}
static void do_imbufcache_put(ImBufCache *cache, void *userkey, ImBuf *ibuf, bool need_lock)
{
ImBufCacheKey *key;
ImBufCacheItem *item;
if (!limitor) {
IMB_cache_init();
}
if (ibuf != nullptr) {
IMB_refImBuf(ibuf);
}
key = static_cast<ImBufCacheKey *>(BLI_mempool_alloc(cache->keys_pool));
key->cache_owner = cache;
key->userkey = BLI_mempool_alloc(cache->userkeys_pool);
memcpy(key->userkey, userkey, cache->keysize);
item = static_cast<ImBufCacheItem *>(BLI_mempool_alloc(cache->items_pool));
PRINT("%s: cache '%s' put %p, item %p\n", __func__, cache->name, ibuf, item);
item->ibuf = ibuf;
item->cache_owner = cache;
item->c_handle = nullptr;
item->priority_data = nullptr;
item->added_empty = ibuf == nullptr;
if (cache->getprioritydatafp) {
item->priority_data = cache->getprioritydatafp(userkey);
}
BLI_ghash_reinsert(cache->hash, key, item, imbufcache_keyfree, imbufcache_valfree);
if (cache->last_userkey) {
memcpy(cache->last_userkey, userkey, cache->keysize);
}
if (need_lock) {
limitor_lock.lock();
}
item->c_handle = MEM_CacheLimiter_insert(limitor, item);
MEM_CacheLimiter_ref(item->c_handle);
MEM_CacheLimiter_enforce_limits(limitor);
MEM_CacheLimiter_unref(item->c_handle);
if (need_lock) {
limitor_lock.unlock();
}
/* cache limiter can't remove unused keys which points to destroyed values */
check_unused_keys(cache);
MEM_SAFE_DELETE(cache->points);
}
void IMB_cache_put(ImBufCache *cache, void *userkey, ImBuf *ibuf)
{
do_imbufcache_put(cache, userkey, ibuf, true);
}
bool IMB_cache_put_if_possible(ImBufCache *cache, void *userkey, ImBuf *ibuf)
{
size_t mem_in_use, mem_limit, elem_size;
bool result = false;
elem_size = (ibuf == nullptr) ? 0 : get_size_in_memory(ibuf);
mem_limit = MEM_CacheLimiter_get_maximum();
limitor_lock.lock();
mem_in_use = MEM_CacheLimiter_get_memory_in_use(limitor);
if (mem_in_use + elem_size <= mem_limit) {
do_imbufcache_put(cache, userkey, ibuf, false);
result = true;
}
limitor_lock.unlock();
return result;
}
void IMB_cache_remove(ImBufCache *cache, void *userkey)
{
ImBufCacheKey key;
key.cache_owner = cache;
key.userkey = userkey;
BLI_ghash_remove(cache->hash, &key, imbufcache_keyfree, imbufcache_valfree);
}
ImBuf *IMB_cache_get(ImBufCache *cache, void *userkey, bool *r_is_cached_empty)
{
ImBufCacheKey key;
ImBufCacheItem *item;
key.cache_owner = cache;
key.userkey = userkey;
item = static_cast<ImBufCacheItem *>(BLI_ghash_lookup(cache->hash, &key));
if (r_is_cached_empty) {
*r_is_cached_empty = false;
}
if (item) {
if (item->ibuf) {
std::lock_guard lock(limitor_lock);
/* Check again, the condition might have changed before we acquired the lock. */
if (item->ibuf) {
MEM_CacheLimiter_touch(item->c_handle);
IMB_refImBuf(item->ibuf);
return item->ibuf;
}
}
if (r_is_cached_empty && item->added_empty) {
*r_is_cached_empty = true;
}
}
return nullptr;
}
bool IMB_cache_has_frame(ImBufCache *cache, void *userkey)
{
ImBufCacheKey key;
ImBufCacheItem *item;
key.cache_owner = cache;
key.userkey = userkey;
item = static_cast<ImBufCacheItem *>(BLI_ghash_lookup(cache->hash, &key));
return item != nullptr;
}
void IMB_cache_free(ImBufCache *cache)
{
PRINT("%s: cache '%s' free\n", __func__, cache->name);
BLI_ghash_free(cache->hash, imbufcache_keyfree, imbufcache_valfree);
BLI_mempool_destroy(cache->keys_pool);
BLI_mempool_destroy(cache->items_pool);
BLI_mempool_destroy(cache->userkeys_pool);
if (cache->points) {
MEM_delete(cache->points);
}
if (cache->last_userkey) {
MEM_delete_void(cache->last_userkey);
}
MEM_delete(cache);
}
void IMB_cache_cleanup(ImBufCache *cache,
bool(cleanup_check_cb)(ImBuf *ibuf, void *userkey, void *userdata),
void *userdata)
{
GHashIterator gh_iter;
check_unused_keys(cache);
BLI_ghashIterator_init(&gh_iter, cache->hash);
while (!BLI_ghashIterator_done(&gh_iter)) {
ImBufCacheKey *key = static_cast<ImBufCacheKey *>(BLI_ghashIterator_getKey(&gh_iter));
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(BLI_ghashIterator_getValue(&gh_iter));
BLI_ghashIterator_step(&gh_iter);
if (cleanup_check_cb(item->ibuf, key->userkey, userdata)) {
PRINT("%s: cache '%s' remove item %p\n", __func__, cache->name, item);
BLI_ghash_remove(cache->hash, key, imbufcache_keyfree, imbufcache_valfree);
}
}
}
void IMB_cache_get_cache_segments(
ImBufCache *cache, int proxy, int render_flags, int *r_totseg, int **r_points)
{
*r_totseg = 0;
*r_points = nullptr;
if (!cache->getdatafp) {
return;
}
if (cache->proxy != proxy || cache->render_flags != render_flags) {
MEM_SAFE_DELETE(cache->points);
}
if (cache->points) {
*r_totseg = cache->totseg;
*r_points = cache->points;
}
else {
int totframe = BLI_ghash_len(cache->hash);
int *frames = MEM_new_array_zeroed<int>(totframe, "movieclip cache frames");
int a, totseg = 0;
GHashIterator gh_iter;
a = 0;
GHASH_ITER (gh_iter, cache->hash) {
ImBufCacheKey *key = static_cast<ImBufCacheKey *>(BLI_ghashIterator_getKey(&gh_iter));
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(BLI_ghashIterator_getValue(&gh_iter));
int framenr, curproxy, curflags;
if (item->ibuf) {
cache->getdatafp(key->userkey, &framenr, &curproxy, &curflags);
if (curproxy == proxy && curflags == render_flags) {
frames[a++] = framenr;
}
}
}
qsort(frames, totframe, sizeof(int), compare_int);
/* count */
for (a = 0; a < totframe; a++) {
if (a && frames[a] - frames[a - 1] != 1) {
totseg++;
}
if (a == totframe - 1) {
totseg++;
}
}
if (totseg) {
int b, *points;
points = MEM_new_array_zeroed<int>(2 * size_t(totseg), "movieclip cache segments");
/* fill */
for (a = 0, b = 0; a < totframe; a++) {
if (a == 0) {
points[b++] = frames[a];
}
if (a && frames[a] - frames[a - 1] != 1) {
points[b++] = frames[a - 1];
points[b++] = frames[a];
}
if (a == totframe - 1) {
points[b++] = frames[a];
}
}
*r_totseg = totseg;
*r_points = points;
cache->totseg = totseg;
cache->points = points;
cache->proxy = proxy;
cache->render_flags = render_flags;
}
MEM_delete(frames);
}
}
ImBufCacheIter *IMB_cacheIter_new(ImBufCache *cache)
{
GHashIterator *iter;
check_unused_keys(cache);
iter = BLI_ghashIterator_new(cache->hash);
return reinterpret_cast<ImBufCacheIter *>(iter);
}
void IMB_cacheIter_free(ImBufCacheIter *iter)
{
BLI_ghashIterator_free(reinterpret_cast<GHashIterator *>(iter));
}
bool IMB_cacheIter_done(ImBufCacheIter *iter)
{
return BLI_ghashIterator_done(reinterpret_cast<GHashIterator *>(iter));
}
void IMB_cacheIter_step(ImBufCacheIter *iter)
{
BLI_ghashIterator_step(reinterpret_cast<GHashIterator *>(iter));
}
ImBuf *IMB_cacheIter_getImBuf(ImBufCacheIter *iter)
{
ImBufCacheItem *item = static_cast<ImBufCacheItem *>(
BLI_ghashIterator_getValue(reinterpret_cast<GHashIterator *>(iter)));
return item->ibuf;
}
void *IMB_cacheIter_getUserKey(ImBufCacheIter *iter)
{
ImBufCacheKey *key = static_cast<ImBufCacheKey *>(
BLI_ghashIterator_getKey(reinterpret_cast<GHashIterator *>(iter)));
return key->userkey;
}
} // namespace blender

View File

@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
..
../..
)
set(INC_SYS
)
set(SRC
cineonlib.h
dpxlib.h
logImageCore.h
logmemfile.h
cineon_dpx.cc
cineonlib.cc
dpxlib.cc
logImageCore.cc
logmemfile.cc
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::intern::guardedalloc
)
if(WITH_IMAGE_CINEON)
add_definitions(-DWITH_IMAGE_CINEON)
endif()
blender_add_lib(bf_imbuf_cineon "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,8 @@
Files:
logImageLib.h, logImageLib.c: combined cineon/dpx image library
dpxlib.h, dpxlib.c: dpx specific library
dpxfile.h: dpx file structure
cineonlib.h, cineonlib.c: cineon specific library
cineonfile.h: cineon file structure
logImageCore.h, logImageCore.c: log image routines common to cineon amd dpx

View File

@@ -0,0 +1,196 @@
/* SPDX-FileCopyrightText: 2006 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*/
#include "logImageCore.h"
#include <cstdio>
#include <cstring>
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "BKE_global.hh"
#include "MEM_guardedalloc.h"
namespace blender {
static ImBuf *imb_load_dpx_cineon(const uchar *mem,
size_t size,
int use_cineon,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImBuf *ibuf;
LogImageFile *image;
int width, height, depth;
logImageSetVerbose((G.debug & G_DEBUG) ? 1 : 0);
image = logImageOpenFromMemory(mem, size);
if (image == nullptr) {
printf("DPX/Cineon: error opening image.\n");
return nullptr;
}
logImageGetSize(image, &width, &height, &depth);
ibuf = IMB_allocImBuf(width, height, ImBufFlags::FloatData | flags);
if (ibuf == nullptr) {
logImageClose(image);
return nullptr;
}
if (!flag_is_set(flags, ImBufFlags::Test)) {
if (logImageGetDataRGBA(image, ibuf->float_data_for_write(), 1) != 0) {
logImageClose(image);
IMB_freeImBuf(ibuf);
return nullptr;
}
IMB_flipy(ibuf);
}
logImageClose(image);
ibuf->ftype = use_cineon ? IMB_FTYPE_CINEON : IMB_FTYPE_DPX;
if (flag_is_set(flags, ImBufFlags::AlphaDetect)) {
ibuf->flags |= ImBufFlags::AlphaPremul;
}
r_colorspace.is_hdr_float = true;
return ibuf;
}
static int imb_save_dpx_cineon(ImBuf *ibuf, const char *filepath, int use_cineon)
{
LogImageFile *logImage;
float *fbuf;
float *fbuf_ptr;
const uchar *rect_ptr;
int x, y, bitspersample, rvalue;
logImageSetVerbose((G.debug & G_DEBUG) ? 1 : 0);
if (!ELEM(ibuf->color_mode, ImColorMode::RGB, ImColorMode::RGBA)) {
printf("DPX/Cineon: only RGB/RGBA is supported, file: '%s'\n", filepath);
return 0;
}
if (use_cineon) {
/* Only 10bit is supported. */
bitspersample = 10;
}
else {
if (ibuf->foptions.flag & CINEON_10BIT) {
bitspersample = 10;
}
else if (ibuf->foptions.flag & CINEON_12BIT) {
bitspersample = 12;
}
else if (ibuf->foptions.flag & CINEON_16BIT) {
bitspersample = 16;
}
else {
bitspersample = 8;
}
}
const bool has_alpha = ibuf->color_mode == ImColorMode::RGBA;
logImage = logImageCreate(filepath,
use_cineon,
ibuf->x,
ibuf->y,
bitspersample,
has_alpha,
(ibuf->foptions.flag & CINEON_LOG),
-1,
-1,
-1,
"Blender");
if (logImage == nullptr) {
printf("DPX/Cineon: error creating file.\n");
return 0;
}
if (ibuf->float_data() != nullptr && bitspersample != 8) {
/* Don't use the float buffer to save 8 BPP picture to prevent color banding
* (there's no dithering algorithm behind the #logImageSetDataRGBA function). */
fbuf = MEM_new_array_uninitialized<float>(4 * size_t(ibuf->x) * size_t(ibuf->y),
"fbuf in imb_save_dpx_cineon");
for (y = 0; y < ibuf->y; y++) {
float *dst_ptr = fbuf + (4 * (size_t(ibuf->y - y - 1) * size_t(ibuf->x)));
const float *src_ptr = ibuf->float_data() + (4 * (size_t(y) * size_t(ibuf->x)));
memcpy(dst_ptr, src_ptr, 4 * ibuf->x * sizeof(float));
}
rvalue = (logImageSetDataRGBA(logImage, fbuf, 1) == 0);
MEM_delete(fbuf);
}
else {
if (ibuf->byte_data()) {
IMB_byte_from_float(ibuf);
}
fbuf = MEM_new_array_uninitialized<float>(4 * size_t(ibuf->x) * size_t(ibuf->y),
"fbuf in imb_save_dpx_cineon");
if (fbuf == nullptr) {
printf("DPX/Cineon: error allocating memory.\n");
logImageClose(logImage);
return 0;
}
const uint8_t *byte_data = ibuf->byte_data();
for (y = 0; y < ibuf->y; y++) {
fbuf_ptr = fbuf + (4 * (size_t(ibuf->y - y - 1) * size_t(ibuf->x)));
rect_ptr = byte_data + (4 * (size_t(y) * size_t(ibuf->x)));
for (x = 0; x < ibuf->x; x++) {
fbuf_ptr[0] = float(rect_ptr[0]) / 255.0f;
fbuf_ptr[1] = float(rect_ptr[1]) / 255.0f;
fbuf_ptr[2] = float(rect_ptr[2]) / 255.0f;
fbuf_ptr[3] = has_alpha ? (float(rect_ptr[3]) / 255.0f) : 1.0f;
fbuf_ptr += 4;
rect_ptr += 4;
}
}
rvalue = (logImageSetDataRGBA(logImage, fbuf, 0) == 0);
MEM_delete(fbuf);
}
logImageClose(logImage);
return rvalue;
}
bool imb_save_cineon(ImBuf *buf, const char *filepath, ImBufFlags /*flags*/)
{
return imb_save_dpx_cineon(buf, filepath, 1);
}
bool imb_is_a_cineon(const uchar *mem, size_t size)
{
return logImageIsCineon(mem, size);
}
ImBuf *imb_load_cineon(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
if (!imb_is_a_cineon(mem, size)) {
return nullptr;
}
return imb_load_dpx_cineon(mem, size, 1, flags, r_colorspace);
}
} // namespace blender

View File

@@ -0,0 +1,429 @@
/* SPDX-FileCopyrightText: 1999-2001 David Hodson <hodsond@acm.org>.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*
* Cineon image file format library routines.
*/
#include "cineonlib.h"
#include "logmemfile.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <sys/types.h>
#include "BLI_fileops.h"
#include "BLI_string.h"
#include "MEM_guardedalloc.h"
namespace blender {
/*
* For debug purpose
*/
static int verbose = 0;
void cineonSetVerbose(int verbosity)
{
verbose = verbosity;
}
static void fillCineonMainHeader(LogImageFile *cineon,
CineonMainHeader *header,
const char *filepath,
const char *creator)
{
time_t fileClock;
const tm *fileTime;
int i;
memset(header, 0, sizeof(CineonMainHeader));
/* --- File header --- */
header->fileHeader.magic_num = swap_uint(CINEON_FILE_MAGIC, cineon->isMSB);
header->fileHeader.offset = swap_uint(cineon->element[0].dataOffset, cineon->isMSB);
header->fileHeader.gen_hdr_size = swap_uint(
sizeof(CineonFileHeader) + sizeof(CineonImageHeader) + sizeof(CineonOriginationHeader),
cineon->isMSB);
header->fileHeader.ind_hdr_size = 0;
header->fileHeader.user_data_size = 0;
header->fileHeader.file_size = swap_uint(
cineon->element[0].dataOffset +
cineon->height * getRowLength(cineon->width, &cineon->element[0]),
cineon->isMSB);
STRNCPY(header->fileHeader.version, "v4.5");
STRNCPY(header->fileHeader.file_name, filepath);
fileClock = time(nullptr);
fileTime = localtime(&fileClock);
strftime(header->fileHeader.creation_date, 12, "%Y:%m:%d", fileTime);
strftime(header->fileHeader.creation_time, 12, "%H:%M:%S%Z", fileTime);
header->fileHeader.creation_time[11] = 0;
/* --- Image header --- */
header->imageHeader.orientation = 0;
header->imageHeader.elements_per_image = cineon->depth;
for (i = 0; i < 3; i++) {
header->imageHeader.element[i].descriptor1 = 0;
header->imageHeader.element[i].descriptor2 = i;
header->imageHeader.element[i].bits_per_sample = cineon->element[0].bitsPerSample;
header->imageHeader.element[i].pixels_per_line = swap_uint(cineon->width, cineon->isMSB);
header->imageHeader.element[i].lines_per_image = swap_uint(cineon->height, cineon->isMSB);
header->imageHeader.element[i].ref_low_data = swap_uint(cineon->element[0].refLowData,
cineon->isMSB);
header->imageHeader.element[i].ref_low_quantity = swap_float(cineon->element[0].refLowQuantity,
cineon->isMSB);
header->imageHeader.element[i].ref_high_data = swap_uint(cineon->element[0].refHighData,
cineon->isMSB);
header->imageHeader.element[i].ref_high_quantity = swap_float(
cineon->element[0].refHighQuantity, cineon->isMSB);
}
header->imageHeader.white_point_x = swap_float(0.0f, cineon->isMSB);
header->imageHeader.white_point_y = swap_float(0.0f, cineon->isMSB);
header->imageHeader.red_primary_x = swap_float(0.0f, cineon->isMSB);
header->imageHeader.red_primary_y = swap_float(0.0f, cineon->isMSB);
header->imageHeader.green_primary_x = swap_float(0.0f, cineon->isMSB);
header->imageHeader.green_primary_y = swap_float(0.0f, cineon->isMSB);
header->imageHeader.blue_primary_x = swap_float(0.0f, cineon->isMSB);
header->imageHeader.blue_primary_y = swap_float(0.0f, cineon->isMSB);
STRNCPY(header->imageHeader.label, creator);
header->imageHeader.interleave = 0;
header->imageHeader.data_sign = 0;
header->imageHeader.sense = 0;
header->imageHeader.line_padding = swap_uint(0, cineon->isMSB);
header->imageHeader.element_padding = swap_uint(0, cineon->isMSB);
switch (cineon->element[0].packing) {
case 0:
header->imageHeader.packing = 0;
break;
case 1:
header->imageHeader.packing = 5;
break;
case 2:
header->imageHeader.packing = 6;
break;
}
/* --- Origination header --- */
/* we leave it blank */
/* --- Film header --- */
/* we leave it blank */
}
LogImageFile *cineonOpen(const uchar *byteStuff, int fromMemory, size_t bufferSize)
{
CineonMainHeader header;
LogImageFile *cineon = MEM_new_uninitialized<LogImageFile>(__func__);
const char *filepath = reinterpret_cast<const char *>(byteStuff);
int i;
uint dataOffset;
if (cineon == nullptr) {
if (verbose) {
printf("Cineon: Failed to malloc cineon file structure.\n");
}
return nullptr;
}
/* zero the header */
memset(&header, 0, sizeof(CineonMainHeader));
/* for close routine */
cineon->file = nullptr;
if (fromMemory == 0) {
/* byteStuff is then the filepath */
cineon->file = BLI_fopen(filepath, "rb");
if (cineon->file == nullptr) {
if (verbose) {
printf("Cineon: Failed to open file \"%s\".\n", filepath);
}
logImageClose(cineon);
return nullptr;
}
/* not used in this case */
cineon->memBuffer = nullptr;
cineon->memCursor = nullptr;
cineon->memBufferSize = 0;
}
else {
cineon->memBuffer = const_cast<uchar *>(byteStuff);
cineon->memCursor = const_cast<uchar *>(byteStuff);
cineon->memBufferSize = bufferSize;
}
if (logimage_fread(&header, sizeof(header), 1, cineon) == 0) {
if (verbose) {
printf("Cineon: Not enough data for header in \"%s\".\n", byteStuff);
}
logImageClose(cineon);
return nullptr;
}
/* endianness determination */
if (header.fileHeader.magic_num == swap_uint(CINEON_FILE_MAGIC, 1)) {
cineon->isMSB = 1;
if (verbose) {
printf("Cineon: File is MSB.\n");
}
}
else if (header.fileHeader.magic_num == CINEON_FILE_MAGIC) {
cineon->isMSB = 0;
if (verbose) {
printf("Cineon: File is LSB.\n");
}
}
else {
if (verbose) {
printf("Cineon: Bad magic number %lu in \"%s\".\n",
ulong(header.fileHeader.magic_num),
byteStuff);
}
logImageClose(cineon);
return nullptr;
}
cineon->width = swap_uint(header.imageHeader.element[0].pixels_per_line, cineon->isMSB);
cineon->height = swap_uint(header.imageHeader.element[0].lines_per_image, cineon->isMSB);
if (cineon->width == 0 || cineon->height == 0) {
if (verbose) {
printf("Cineon: Wrong image dimension: %dx%d\n", cineon->width, cineon->height);
}
logImageClose(cineon);
return nullptr;
}
cineon->depth = header.imageHeader.elements_per_image;
cineon->srcFormat = format_Cineon;
if (header.imageHeader.interleave == 0) {
cineon->numElements = 1;
}
else if (header.imageHeader.interleave == 2) {
cineon->numElements = header.imageHeader.elements_per_image;
}
else {
if (verbose) {
printf("Cineon: Data interleave not supported: %d\n", header.imageHeader.interleave);
}
logImageClose(cineon);
return nullptr;
}
if (cineon->depth == 1) {
/* Gray-scale image. */
cineon->element[0].descriptor = descriptor_Luminance;
cineon->element[0].transfer = transfer_Linear;
cineon->element[0].depth = 1;
}
else if (cineon->depth == 3) {
/* RGB image. */
if (cineon->numElements == 1) {
cineon->element[0].descriptor = descriptor_RGB;
cineon->element[0].transfer = transfer_PrintingDensity;
cineon->element[0].depth = 3;
}
else if (cineon->numElements == 3) {
cineon->element[0].descriptor = descriptor_Red;
cineon->element[0].transfer = transfer_PrintingDensity;
cineon->element[0].depth = 1;
cineon->element[1].descriptor = descriptor_Green;
cineon->element[1].transfer = transfer_PrintingDensity;
cineon->element[1].depth = 1;
cineon->element[2].descriptor = descriptor_Blue;
cineon->element[2].transfer = transfer_PrintingDensity;
cineon->element[2].depth = 1;
}
}
else {
if (verbose) {
printf("Cineon: Cineon image depth unsupported: %d\n", cineon->depth);
}
logImageClose(cineon);
return nullptr;
}
dataOffset = swap_uint(header.fileHeader.offset, cineon->isMSB);
for (i = 0; i < cineon->numElements; i++) {
cineon->element[i].bitsPerSample = header.imageHeader.element[i].bits_per_sample;
cineon->element[i].maxValue = powf(2, cineon->element[i].bitsPerSample) - 1.0f;
cineon->element[i].refLowData = swap_uint(header.imageHeader.element[i].ref_low_data,
cineon->isMSB);
cineon->element[i].refLowQuantity = swap_float(header.imageHeader.element[i].ref_low_quantity,
cineon->isMSB);
cineon->element[i].refHighData = swap_uint(header.imageHeader.element[i].ref_high_data,
cineon->isMSB);
cineon->element[i].refHighQuantity = swap_float(
header.imageHeader.element[i].ref_high_quantity, cineon->isMSB);
switch (header.imageHeader.packing) {
case 0:
cineon->element[i].packing = 0;
break;
case 5:
cineon->element[i].packing = 1;
break;
case 6:
cineon->element[i].packing = 2;
break;
default:
/* Not supported */
if (verbose) {
printf("Cineon: packing unsupported: %d\n", header.imageHeader.packing);
}
logImageClose(cineon);
return nullptr;
}
if (cineon->element[i].refLowData == CINEON_UNDEFINED_U32) {
cineon->element[i].refLowData = 0;
}
if (cineon->element[i].refHighData == CINEON_UNDEFINED_U32) {
cineon->element[i].refHighData = uint(cineon->element[i].maxValue);
}
if (cineon->element[i].refLowQuantity == CINEON_UNDEFINED_R32 ||
std::isnan(cineon->element[i].refLowQuantity))
{
cineon->element[i].refLowQuantity = 0.0f;
}
if (cineon->element[i].refHighQuantity == CINEON_UNDEFINED_R32 ||
std::isnan(cineon->element[i].refHighQuantity))
{
if (cineon->element[i].transfer == transfer_PrintingDensity) {
cineon->element[i].refHighQuantity = 2.048f;
}
else {
cineon->element[i].refHighQuantity = cineon->element[i].maxValue;
}
}
cineon->element[i].dataOffset = dataOffset;
dataOffset += cineon->height * getRowLength(cineon->width, &cineon->element[i]);
}
cineon->referenceBlack = 95.0f / 1023.0f * cineon->element[0].maxValue;
cineon->referenceWhite = 685.0f / 1023.0f * cineon->element[0].maxValue;
cineon->gamma = 1.7f;
if (verbose) {
printf("size %d x %d x %d elements\n", cineon->width, cineon->height, cineon->numElements);
for (i = 0; i < cineon->numElements; i++) {
printf(" Element %d:\n", i);
printf(" Bits per sample: %d\n", cineon->element[i].bitsPerSample);
printf(" Depth: %d\n", cineon->element[i].depth);
printf(" Transfer characteristics: %d\n", cineon->element[i].transfer);
printf(" Packing: %d\n", cineon->element[i].packing);
printf(" Descriptor: %d\n", cineon->element[i].descriptor);
printf(" Data offset: %d\n", cineon->element[i].dataOffset);
printf(" Reference low data: %u\n", cineon->element[i].refLowData);
printf(" Reference low quantity: %f\n", cineon->element[i].refLowQuantity);
printf(" Reference high data: %u\n", cineon->element[i].refHighData);
printf(" Reference high quantity: %f\n", cineon->element[i].refHighQuantity);
printf("\n");
}
printf("Gamma: %f\n", cineon->gamma);
printf("Reference black: %f\n", cineon->referenceBlack);
printf("Reference white: %f\n", cineon->referenceWhite);
printf("Orientation: %d\n", header.imageHeader.orientation);
printf("----------------------------\n");
}
return cineon;
}
LogImageFile *cineonCreate(
const char *filepath, int width, int height, int bitsPerSample, const char *creator)
{
CineonMainHeader header;
const char *shortFilename = nullptr;
// uchar pad[6044];
LogImageFile *cineon = MEM_new_uninitialized<LogImageFile>(__func__);
if (cineon == nullptr) {
if (verbose) {
printf("cineon: Failed to malloc cineon file structure.\n");
}
return nullptr;
}
/* Only 10 bits Cineon are supported */
if (bitsPerSample != 10) {
if (verbose) {
printf("cineon: Only 10 bits Cineon are supported.\n");
}
logImageClose(cineon);
return nullptr;
}
cineon->width = width;
cineon->height = height;
cineon->element[0].bitsPerSample = 10;
cineon->element[0].dataOffset = sizeof(CineonMainHeader);
cineon->element[0].maxValue = 1023;
cineon->isMSB = 1;
cineon->numElements = 1;
cineon->element[0].packing = 1;
cineon->depth = 3;
cineon->element[0].depth = 3;
cineon->element[0].descriptor = descriptor_RGB;
cineon->element[0].transfer = transfer_PrintingDensity;
cineon->element[0].refHighQuantity = 2.048f;
cineon->element[0].refLowQuantity = 0;
cineon->element[0].refLowData = 0;
cineon->element[0].refHighData = cineon->element[0].maxValue;
cineon->referenceWhite = 685.0f;
cineon->referenceBlack = 95.0f;
cineon->gamma = 1.7f;
shortFilename = strrchr(filepath, PATHSEP_CHAR);
if (shortFilename == nullptr) {
shortFilename = filepath;
}
else {
shortFilename++;
}
cineon->file = BLI_fopen(filepath, "wb");
if (cineon->file == nullptr) {
if (verbose) {
printf("cineon: Couldn't open file %s\n", filepath);
}
logImageClose(cineon);
return nullptr;
}
fillCineonMainHeader(cineon, &header, shortFilename, creator);
if (fwrite(&header, sizeof(header), 1, cineon->file) == 0) {
if (verbose) {
printf("cineon: Couldn't write image header\n");
}
logImageClose(cineon);
return nullptr;
}
return cineon;
}
} // namespace blender

View File

@@ -0,0 +1,118 @@
/* SPDX-FileCopyrightText: 1999-2001 David Hodson <hodsond@acm.org>.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*
* Cineon image file format library definitions.
* Also handles DPX files (almost)
*/
#pragma once
#include "logImageCore.h"
namespace blender {
#define CINEON_FILE_MAGIC 0x802A5FD7
#define CINEON_UNDEFINED_U8 0xFF
#define CINEON_UNDEFINED_U16 0xFFFF
#define CINEON_UNDEFINED_U32 0xFFFFFFFF
#define CINEON_UNDEFINED_R32 0x7F800000
#define CINEON_UNDEFINED_CHAR 0
struct CineonFileHeader {
unsigned int magic_num;
unsigned int offset;
unsigned int gen_hdr_size;
unsigned int ind_hdr_size;
unsigned int user_data_size;
unsigned int file_size;
char version[8];
char file_name[100];
char creation_date[12];
char creation_time[12];
char reserved[36];
};
struct CineonElementHeader {
uchar descriptor1;
uchar descriptor2;
uchar bits_per_sample;
uchar filler;
unsigned int pixels_per_line;
unsigned int lines_per_image;
unsigned int ref_low_data;
float ref_low_quantity;
unsigned int ref_high_data;
float ref_high_quantity;
};
struct CineonImageHeader {
uchar orientation;
uchar elements_per_image;
unsigned short filler;
CineonElementHeader element[8];
float white_point_x;
float white_point_y;
float red_primary_x;
float red_primary_y;
float green_primary_x;
float green_primary_y;
float blue_primary_x;
float blue_primary_y;
char label[200];
char reserved[28];
uchar interleave;
uchar packing;
uchar data_sign;
uchar sense;
unsigned int line_padding;
unsigned int element_padding;
char reserved2[20];
};
struct CineonOriginationHeader {
int x_offset;
int y_offset;
char file_name[100];
char creation_date[12];
char creation_time[12];
char input_device[64];
char model_number[32];
char input_serial_number[32];
float x_input_samples_per_mm;
float y_input_samples_per_mm;
float input_device_gamma;
char reserved[40];
};
struct CineonFilmHeader {
uchar film_code;
uchar film_type;
uchar edge_code_perforation_offset;
uchar filler;
unsigned int prefix;
unsigned int count;
char format[32];
unsigned int frame_position;
float frame_rate;
char attribute[32];
char slate[200];
char reserved[740];
};
struct CineonMainHeader {
CineonFileHeader fileHeader;
CineonImageHeader imageHeader;
CineonOriginationHeader originationHeader;
CineonFilmHeader filmHeader;
};
void cineonSetVerbose(int);
LogImageFile *cineonOpen(const uchar *byteStuff, int fromMemory, size_t bufferSize);
LogImageFile *cineonCreate(
const char *filepath, int width, int height, int bitsPerSample, const char *creator);
} // namespace blender

View File

@@ -0,0 +1,550 @@
/* SPDX-FileCopyrightText: 1999-2002 David Hodson <hodsond@acm.org>.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*
* DPX image file format library routines.
*/
#include "dpxlib.h"
#include "logmemfile.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <sys/types.h>
#include "BLI_fileops.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
namespace blender {
/*
* For debug purpose
*/
static int verbose = 0;
void dpxSetVerbose(int verbosity)
{
verbose = verbosity;
}
/*
* Headers
*/
static void fillDpxMainHeader(LogImageFile *dpx,
DpxMainHeader *header,
const char *filename,
const char *creator)
{
time_t fileClock;
const tm *fileTime;
memset(header, 0, sizeof(DpxMainHeader));
/* --- File header --- */
header->fileHeader.magic_num = swap_uint(DPX_FILE_MAGIC, dpx->isMSB);
header->fileHeader.offset = swap_uint(dpx->element[0].dataOffset, dpx->isMSB);
STRNCPY(header->fileHeader.version, "V2.0");
header->fileHeader.file_size = swap_uint(
dpx->element[0].dataOffset + dpx->height * getRowLength(dpx->width, &dpx->element[0]),
dpx->isMSB);
header->fileHeader.ditto_key = 0;
header->fileHeader.gen_hdr_size = swap_uint(
sizeof(DpxFileHeader) + sizeof(DpxImageHeader) + sizeof(DpxOrientationHeader), dpx->isMSB);
header->fileHeader.ind_hdr_size = swap_uint(sizeof(DpxFilmHeader) + sizeof(DpxTelevisionHeader),
dpx->isMSB);
header->fileHeader.user_data_size = DPX_UNDEFINED_U32;
STRNCPY(header->fileHeader.file_name, filename);
fileClock = time(nullptr);
fileTime = localtime(&fileClock);
strftime(header->fileHeader.creation_date, 24, "%Y:%m:%d:%H:%M:%S%Z", fileTime);
header->fileHeader.creation_date[23] = 0;
STRNCPY(header->fileHeader.creator, creator);
header->fileHeader.project[0] = 0;
header->fileHeader.copyright[0] = 0;
header->fileHeader.key = 0xFFFFFFFF;
/* --- Image header --- */
header->imageHeader.orientation = 0;
header->imageHeader.elements_per_image = swap_ushort(1, dpx->isMSB);
header->imageHeader.pixels_per_line = swap_uint(dpx->width, dpx->isMSB);
header->imageHeader.lines_per_element = swap_uint(dpx->height, dpx->isMSB);
/* Fills element */
header->imageHeader.element[0].data_sign = 0;
header->imageHeader.element[0].ref_low_data = swap_uint(dpx->element[0].refLowData, dpx->isMSB);
header->imageHeader.element[0].ref_low_quantity = swap_float(dpx->element[0].refLowQuantity,
dpx->isMSB);
header->imageHeader.element[0].ref_high_data = swap_uint(dpx->element[0].refHighData,
dpx->isMSB);
header->imageHeader.element[0].ref_high_quantity = swap_float(dpx->element[0].refHighQuantity,
dpx->isMSB);
header->imageHeader.element[0].descriptor = dpx->element[0].descriptor;
header->imageHeader.element[0].transfer = dpx->element[0].transfer;
header->imageHeader.element[0].colorimetric = 0;
header->imageHeader.element[0].bits_per_sample = dpx->element[0].bitsPerSample;
header->imageHeader.element[0].packing = swap_ushort(dpx->element[0].packing, dpx->isMSB);
header->imageHeader.element[0].encoding = 0;
header->imageHeader.element[0].data_offset = swap_uint(dpx->element[0].dataOffset, dpx->isMSB);
header->imageHeader.element[0].line_padding = 0;
header->imageHeader.element[0].element_padding = 0;
header->imageHeader.element[0].description[0] = 0;
/* --- Orientation header --- */
/* we leave it blank */
/* --- Television header --- */
header->televisionHeader.time_code = DPX_UNDEFINED_U32;
header->televisionHeader.user_bits = DPX_UNDEFINED_U32;
header->televisionHeader.interlace = DPX_UNDEFINED_U8;
header->televisionHeader.field_number = DPX_UNDEFINED_U8;
header->televisionHeader.video_signal = DPX_UNDEFINED_U8;
header->televisionHeader.padding = DPX_UNDEFINED_U8;
header->televisionHeader.horizontal_sample_rate = swap_float(DPX_UNDEFINED_R32, dpx->isMSB);
header->televisionHeader.vertical_sample_rate = swap_float(DPX_UNDEFINED_R32, dpx->isMSB);
header->televisionHeader.frame_rate = swap_float(DPX_UNDEFINED_R32, dpx->isMSB);
header->televisionHeader.time_offset = swap_float(DPX_UNDEFINED_R32, dpx->isMSB);
header->televisionHeader.gamma = swap_float(dpx->gamma, dpx->isMSB);
header->televisionHeader.black_level = swap_float(dpx->referenceBlack, dpx->isMSB);
header->televisionHeader.black_gain = swap_float(DPX_UNDEFINED_R32, dpx->isMSB);
header->televisionHeader.breakpoint = swap_float(DPX_UNDEFINED_R32, dpx->isMSB);
header->televisionHeader.white_level = swap_float(dpx->referenceWhite, dpx->isMSB);
header->televisionHeader.integration_times = swap_float(DPX_UNDEFINED_R32, dpx->isMSB);
}
LogImageFile *dpxOpen(const uchar *byteStuff, int fromMemory, size_t bufferSize)
{
DpxMainHeader header;
LogImageFile *dpx = MEM_new_uninitialized<LogImageFile>(__func__);
const char *filepath = reinterpret_cast<const char *>(byteStuff);
int i;
if (dpx == nullptr) {
if (verbose) {
printf("DPX: Failed to malloc dpx file structure.\n");
}
return nullptr;
}
/* zero the header */
memset(&header, 0, sizeof(DpxMainHeader));
/* for close routine */
dpx->file = nullptr;
if (fromMemory == 0) {
/* byteStuff is then the filepath */
dpx->file = BLI_fopen(filepath, "rb");
if (dpx->file == nullptr) {
if (verbose) {
printf("DPX: Failed to open file \"%s\".\n", filepath);
}
logImageClose(dpx);
return nullptr;
}
/* not used in this case */
dpx->memBuffer = nullptr;
dpx->memCursor = nullptr;
dpx->memBufferSize = 0;
}
else {
dpx->memBuffer = const_cast<uchar *>(byteStuff);
dpx->memCursor = const_cast<uchar *>(byteStuff);
dpx->memBufferSize = bufferSize;
}
if (logimage_fread(&header, sizeof(header), 1, dpx) == 0) {
if (verbose) {
printf("DPX: Not enough data for header in \"%s\".\n", byteStuff);
}
logImageClose(dpx);
return nullptr;
}
/* endianness determination */
if (header.fileHeader.magic_num == swap_uint(DPX_FILE_MAGIC, 1)) {
dpx->isMSB = 1;
if (verbose) {
printf("DPX: File is MSB.\n");
}
}
else if (header.fileHeader.magic_num == DPX_FILE_MAGIC) {
dpx->isMSB = 0;
if (verbose) {
printf("DPX: File is LSB.\n");
}
}
else {
if (verbose) {
printf("DPX: Bad magic number %u in \"%s\".\n", header.fileHeader.magic_num, byteStuff);
}
logImageClose(dpx);
return nullptr;
}
dpx->srcFormat = format_DPX;
dpx->numElements = swap_ushort(header.imageHeader.elements_per_image, dpx->isMSB);
size_t max_elements = ARRAY_SIZE(header.imageHeader.element);
if (dpx->numElements == 0 || dpx->numElements >= max_elements) {
if (verbose) {
printf("DPX: Wrong number of elements: %d\n", dpx->numElements);
}
logImageClose(dpx);
return nullptr;
}
dpx->width = swap_uint(header.imageHeader.pixels_per_line, dpx->isMSB);
dpx->height = swap_uint(header.imageHeader.lines_per_element, dpx->isMSB);
if (dpx->width == 0 || dpx->height == 0) {
if (verbose) {
printf("DPX: Wrong image dimension: %dx%d\n", dpx->width, dpx->height);
}
logImageClose(dpx);
return nullptr;
}
dpx->depth = 0;
for (i = 0; i < dpx->numElements; i++) {
dpx->element[i].descriptor = header.imageHeader.element[i].descriptor;
switch (dpx->element[i].descriptor) {
case descriptor_Red:
case descriptor_Green:
case descriptor_Blue:
case descriptor_Alpha:
case descriptor_Luminance:
case descriptor_Chrominance:
dpx->depth++;
dpx->element[i].depth = 1;
break;
case descriptor_CbYCrY:
dpx->depth += 2;
dpx->element[i].depth = 2;
break;
case descriptor_RGB:
case descriptor_CbYCr:
case descriptor_CbYACrYA:
dpx->depth += 3;
dpx->element[i].depth = 3;
break;
case descriptor_RGBA:
case descriptor_ABGR:
case descriptor_CbYCrA:
dpx->depth += 4;
dpx->element[i].depth = 4;
break;
case descriptor_Depth:
case descriptor_Composite:
/* unsupported */
break;
}
if (dpx->depth == 0 || dpx->depth > 4) {
if (verbose) {
printf("DPX: Unsupported image depth: %d\n", dpx->depth);
}
logImageClose(dpx);
return nullptr;
}
dpx->element[i].bitsPerSample = header.imageHeader.element[i].bits_per_sample;
if (!ELEM(dpx->element[i].bitsPerSample, 1, 8, 10, 12, 16)) {
if (verbose) {
printf("DPX: Unsupported bitsPerSample for elements %d: %d\n",
i,
dpx->element[i].bitsPerSample);
}
logImageClose(dpx);
return nullptr;
}
dpx->element[i].maxValue = powf(2, dpx->element[i].bitsPerSample) - 1.0f;
dpx->element[i].packing = swap_ushort(header.imageHeader.element[i].packing, dpx->isMSB);
if (dpx->element[i].packing > 2) {
if (verbose) {
printf("DPX: Unsupported packing for element %d: %d\n", i, dpx->element[i].packing);
}
logImageClose(dpx);
return nullptr;
}
/* Sometimes, the offset is not set correctly in the header */
dpx->element[i].dataOffset = swap_uint(header.imageHeader.element[i].data_offset, dpx->isMSB);
if (dpx->element[i].dataOffset == 0 && dpx->numElements == 1) {
dpx->element[i].dataOffset = swap_uint(header.fileHeader.offset, dpx->isMSB);
}
if (dpx->element[i].dataOffset == 0) {
if (verbose) {
printf("DPX: Image header is corrupted.\n");
}
logImageClose(dpx);
return nullptr;
}
dpx->element[i].transfer = header.imageHeader.element[i].transfer;
/* if undefined, assign default */
dpx->element[i].refLowData = swap_uint(header.imageHeader.element[i].ref_low_data, dpx->isMSB);
dpx->element[i].refLowQuantity = swap_float(header.imageHeader.element[i].ref_low_quantity,
dpx->isMSB);
dpx->element[i].refHighData = swap_uint(header.imageHeader.element[i].ref_high_data,
dpx->isMSB);
dpx->element[i].refHighQuantity = swap_float(header.imageHeader.element[i].ref_high_quantity,
dpx->isMSB);
switch (dpx->element[i].descriptor) {
case descriptor_Red:
case descriptor_Green:
case descriptor_Blue:
case descriptor_Alpha:
case descriptor_RGB:
case descriptor_RGBA:
case descriptor_ABGR:
if (dpx->element[i].refLowData == DPX_UNDEFINED_U32) {
dpx->element[i].refLowData = 0;
}
if (dpx->element[i].refHighData == DPX_UNDEFINED_U32) {
dpx->element[i].refHighData = uint(dpx->element[i].maxValue);
}
if (IS_DPX_UNDEFINED_R32(dpx->element[i].refLowQuantity)) {
dpx->element[i].refLowQuantity = 0.0f;
}
if (IS_DPX_UNDEFINED_R32(dpx->element[i].refHighQuantity)) {
if (ELEM(dpx->element[i].transfer, transfer_PrintingDensity, transfer_Logarithmic)) {
dpx->element[i].refHighQuantity = 2.048f;
}
else {
dpx->element[i].refHighQuantity = dpx->element[i].maxValue;
}
}
break;
case descriptor_Luminance:
case descriptor_Chrominance:
case descriptor_CbYCrY:
case descriptor_CbYCr:
case descriptor_CbYACrYA:
case descriptor_CbYCrA:
if (dpx->element[i].refLowData == DPX_UNDEFINED_U32) {
dpx->element[i].refLowData = 16.0f / 255.0f * dpx->element[i].maxValue;
}
if (dpx->element[i].refHighData == DPX_UNDEFINED_U32) {
dpx->element[i].refHighData = 235.0f / 255.0f * dpx->element[i].maxValue;
}
if (IS_DPX_UNDEFINED_R32(dpx->element[i].refLowQuantity)) {
dpx->element[i].refLowQuantity = 0.0f;
}
if (IS_DPX_UNDEFINED_R32(dpx->element[i].refHighQuantity)) {
dpx->element[i].refHighQuantity = 0.7f;
}
break;
default:
break;
}
}
dpx->referenceBlack = swap_float(header.televisionHeader.black_level, dpx->isMSB);
dpx->referenceWhite = swap_float(header.televisionHeader.white_level, dpx->isMSB);
dpx->gamma = swap_float(header.televisionHeader.gamma, dpx->isMSB);
if (IS_DPX_UNDEFINED_R32(dpx->referenceBlack) ||
(dpx->referenceWhite <= dpx->referenceBlack || IS_DPX_UNDEFINED_R32(dpx->referenceWhite)) ||
(dpx->gamma <= 0 || IS_DPX_UNDEFINED_R32(dpx->gamma)))
{
dpx->referenceBlack = 95.0f / 1023.0f * dpx->element[0].maxValue;
dpx->referenceWhite = 685.0f / 1023.0f * dpx->element[0].maxValue;
dpx->gamma = 1.7f;
}
if (verbose) {
printf("size %d x %d x %d elements\n", dpx->width, dpx->height, dpx->numElements);
for (i = 0; i < dpx->numElements; i++) {
printf(" Element %d:\n", i);
printf(" Bits per sample: %d\n", dpx->element[i].bitsPerSample);
printf(" Depth: %d\n", dpx->element[i].depth);
printf(" Transfer characteristics: %d\n", dpx->element[i].transfer);
printf(" Packing: %d\n", dpx->element[i].packing);
printf(" Descriptor: %d\n", dpx->element[i].descriptor);
printf(" Data offset: %d\n", dpx->element[i].dataOffset);
printf(" Reference low data: %u\n", dpx->element[i].refLowData);
printf(" Reference low quantity: %f\n", dpx->element[i].refLowQuantity);
printf(" Reference high data: %u\n", dpx->element[i].refHighData);
printf(" Reference high quantity: %f\n", dpx->element[i].refHighQuantity);
printf("\n");
}
printf("Gamma: %f\n", dpx->gamma);
printf("Reference black: %f\n", dpx->referenceBlack);
printf("Reference white: %f\n", dpx->referenceWhite);
printf("Orientation: %d\n", header.imageHeader.orientation);
printf("----------------------------\n");
}
return dpx;
}
LogImageFile *dpxCreate(const char *filepath,
int width,
int height,
int bitsPerSample,
int hasAlpha,
int isLogarithmic,
int referenceWhite,
int referenceBlack,
float gamma,
const char *creator)
{
DpxMainHeader header;
const char *shortFilename = nullptr;
uchar pad[6044];
LogImageFile *dpx = MEM_new_uninitialized<LogImageFile>(__func__);
if (dpx == nullptr) {
if (verbose) {
printf("DPX: Failed to malloc dpx file structure.\n");
}
return nullptr;
}
dpx->width = width;
dpx->height = height;
dpx->element[0].bitsPerSample = bitsPerSample;
dpx->element[0].dataOffset = 8092;
dpx->element[0].maxValue = powf(2, dpx->element[0].bitsPerSample) - 1.0f;
dpx->isMSB = 1;
dpx->numElements = 1;
switch (bitsPerSample) {
case 8:
case 16:
dpx->element[0].packing = 0;
break;
case 10:
case 12:
/* Packed Type A padding is the most common 10/12 bits format */
dpx->element[0].packing = 1;
break;
default:
if (verbose) {
printf("DPX: bitsPerSample not supported: %d\n", bitsPerSample);
}
logImageClose(dpx);
return nullptr;
}
if (hasAlpha == 0) {
dpx->depth = 3;
dpx->element[0].depth = 3;
dpx->element[0].descriptor = descriptor_RGB;
}
else {
dpx->depth = 4;
dpx->element[0].depth = 4;
dpx->element[0].descriptor = descriptor_RGBA;
}
if (isLogarithmic == 0) {
dpx->element[0].transfer = transfer_Linear;
dpx->element[0].refHighQuantity = dpx->element[0].maxValue;
}
else {
dpx->element[0].transfer = transfer_PrintingDensity;
dpx->element[0].refHighQuantity = 2.048f;
}
dpx->element[0].refLowQuantity = 0;
dpx->element[0].refLowData = 0;
dpx->element[0].refHighData = dpx->element[0].maxValue;
if (referenceWhite > 0) {
dpx->referenceWhite = referenceWhite;
}
else {
dpx->referenceWhite = 685.0f / 1023.0f * dpx->element[0].maxValue;
}
if (referenceBlack > 0) {
dpx->referenceBlack = referenceBlack;
}
else {
dpx->referenceBlack = 95.0f / 1023.0f * dpx->element[0].maxValue;
}
if (gamma > 0.0f) {
dpx->gamma = gamma;
}
else {
dpx->gamma = 1.7f;
}
shortFilename = strrchr(filepath, PATHSEP_CHAR);
if (shortFilename == nullptr) {
shortFilename = filepath;
}
else {
shortFilename++;
}
dpx->file = BLI_fopen(filepath, "wb");
if (dpx->file == nullptr) {
if (verbose) {
printf("DPX: Couldn't open file %s\n", filepath);
}
logImageClose(dpx);
return nullptr;
}
fillDpxMainHeader(dpx, &header, shortFilename, creator);
if (fwrite(&header, sizeof(header), 1, dpx->file) == 0) {
if (verbose) {
printf("DPX: Couldn't write image header\n");
}
logImageClose(dpx);
return nullptr;
}
/* Header should be rounded to next 8k block
* 6044 = 8092 - sizeof(DpxMainHeader) */
memset(&pad, 0, 6044);
if (fwrite(&pad, 6044, 1, dpx->file) == 0) {
if (verbose) {
printf("DPX: Couldn't write image header\n");
}
logImageClose(dpx);
return nullptr;
}
return dpx;
}
} // namespace blender

View File

@@ -0,0 +1,146 @@
/* SPDX-FileCopyrightText: 1999-2002 David Hodson <hodsond@acm.org>.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*
* DPX image file format library definitions.
*/
#pragma once
#include <cmath>
#include "logImageCore.h"
namespace blender {
#define DPX_FILE_MAGIC 0x53445058
#define DPX_UNDEFINED_U8 0xFF
#define DPX_UNDEFINED_U16 0xFFFF
#define DPX_UNDEFINED_U32 0xFFFFFFFF
#define DPX_UNDEFINED_R32 NAN
#define IS_DPX_UNDEFINED_R32(x) std::isnan(x)
#define DPX_UNDEFINED_CHAR 0
struct DpxFileHeader {
unsigned int magic_num;
unsigned int offset;
char version[8];
unsigned int file_size;
unsigned int ditto_key;
unsigned int gen_hdr_size;
unsigned int ind_hdr_size;
unsigned int user_data_size;
char file_name[100];
char creation_date[24];
char creator[100];
char project[200];
char copyright[200];
unsigned int key;
char reserved[104];
};
struct DpxElementHeader {
unsigned int data_sign;
unsigned int ref_low_data;
float ref_low_quantity;
unsigned int ref_high_data;
float ref_high_quantity;
unsigned char descriptor;
unsigned char transfer;
unsigned char colorimetric;
unsigned char bits_per_sample;
unsigned short packing;
unsigned short encoding;
unsigned int data_offset;
unsigned int line_padding;
unsigned int element_padding;
char description[32];
};
struct DpxImageHeader {
unsigned short orientation;
unsigned short elements_per_image;
unsigned int pixels_per_line;
unsigned int lines_per_element;
DpxElementHeader element[8];
char reserved[52];
};
struct DpxOrientationHeader {
unsigned int x_offset;
unsigned int y_offset;
float x_center;
float y_center;
unsigned int x_original_size;
unsigned int y_original_size;
char file_name[100];
char creation_time[24];
char input_device[32];
char input_serial_number[32];
unsigned short border_validity[4];
unsigned int pixel_aspect_ratio[2];
char reserved[28];
};
struct DpxFilmHeader {
char film_manufacturer_id[2];
char film_type[2];
char edge_code_perforation_offset[2];
char edge_code_prefix[6];
char edge_code_count[4];
char film_format[32];
unsigned int frame_position;
unsigned int sequence_length;
unsigned int held_count;
float frame_rate;
float shutter_angle;
char frame_identification[32];
char slate_info[100];
char reserved[56];
};
struct DpxTelevisionHeader {
unsigned int time_code;
unsigned int user_bits;
unsigned char interlace;
unsigned char field_number;
unsigned char video_signal;
unsigned char padding;
float horizontal_sample_rate;
float vertical_sample_rate;
float frame_rate;
float time_offset;
float gamma;
float black_level;
float black_gain;
float breakpoint;
float white_level;
float integration_times;
unsigned char reserved[76];
};
struct DpxMainHeader {
DpxFileHeader fileHeader;
DpxImageHeader imageHeader;
DpxOrientationHeader orientationHeader;
DpxFilmHeader filmHeader;
DpxTelevisionHeader televisionHeader;
};
void dpxSetVerbose(int verbosity);
LogImageFile *dpxOpen(const unsigned char *byteStuff, int fromMemory, size_t bufferSize);
LogImageFile *dpxCreate(const char *filepath,
int width,
int height,
int bitsPerSample,
int hasAlpha,
int isLogarithmic,
int referenceWhite,
int referenceBlack,
float gamma,
const char *creator);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,272 @@
/* SPDX-FileCopyrightText: 1999-2001 David Hodson <hodsond@acm.org>.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*
* Cineon image file format library definitions.
* Cineon and DPX common structures.
*
* This header file contains private details.
* User code should generally use `cineonlib.h` and `dpxlib.h` only.
* Hmm. I thought the two formats would have more in common!
*/
#pragma once
#include <cstdio>
#include "BLI_compiler_compat.h"
#include "BLI_sys_types.h"
namespace blender {
#ifdef _WIN32
# define PATHSEP_CHAR '\\'
#else
# define PATHSEP_CHAR '/'
#endif
/*
* Image structure
*/
/* There are some differences between DPX and Cineon
* so we need to know from what type of file the data came from. */
enum format {
format_DPX,
format_Cineon,
};
struct LogImageElement {
int depth;
int bitsPerSample;
int dataOffset;
int packing;
int transfer;
int descriptor;
unsigned int refLowData;
unsigned int refHighData;
float refLowQuantity;
float refHighQuantity;
float maxValue; /* = 2^bitsPerSample - 1 (used internally, doesn't come from the file header) */
};
struct LogImageFile {
/* specified in header */
int width;
int height;
int numElements;
int depth;
LogImageElement element[8];
/* used for log <-> lin conversion */
float referenceBlack;
float referenceWhite;
float gamma;
/* IO stuff. */
FILE *file;
unsigned char *memBuffer;
uintptr_t memBufferSize;
unsigned char *memCursor;
/* is the file LSB or MSB ? */
int isMSB;
/* DPX or Cineon ? */
int srcFormat;
};
/* The SMPTE defines this code:
* 0 - User-defined
* 1 - Printing density
* 2 - Linear
* 3 - Logarithmic
* 4 - Unspecified video
* 5 - SMPTE 240M
* 6 - CCIR 709-1
* 7 - CCIR 601-2 system B or G
* 8 - CCIR 601-2 system M
* 9 - NTSC composite video
* 10 - PAL composite video
* 11 - Z linear
* 12 - homogeneous
*
* Note that transfer_characteristics is U8, don't need
* check the byte order.
*/
enum transfer {
transfer_UserDefined,
transfer_PrintingDensity,
transfer_Linear,
transfer_Logarithmic,
transfer_Unspecified,
transfer_Smpte240M,
transfer_Ccir7091,
transfer_Ccir6012BG,
transfer_Ccir6012M,
transfer_NTSC,
transfer_PAL,
transfer_ZLinear,
transfer_Homogeneous,
};
/* The SMPTE defines this code:
* 0 - User-defined
* 1 - Red
* 2 - Green
* 3 - Blue
* 4 - Alpha
* 6 - Luminance
* 7 - Chrominance
* 8 - Depth
* 9 - Composite video
* 50 - RGB
* 51 - RGBA
* 52 - ABGR
* 100 - CbYCrY
* 101 - CbYACrYA
* 102 - CbYCr
* 103 - CbYCrA
* 150 - User-defined 2-component element
* 151 - User-defined 3-component element
* 152 - User-defined 4-component element
* 153 - User-defined 5-component element
* 154 - User-defined 6-component element
* 155 - User-defined 7-component element
* 156 - User-defined 8-component element
*/
enum descriptor {
descriptor_UserDefined,
descriptor_Red,
descriptor_Green,
descriptor_Blue,
descriptor_Alpha,
descriptor_Luminance = 6, /* don't ask me why there's no 5 */
descriptor_Chrominance,
descriptor_Depth,
descriptor_Composite,
descriptor_RGB = 50,
descriptor_RGBA,
descriptor_ABGR,
descriptor_CbYCrY = 100,
descriptor_CbYACrYA,
descriptor_CbYCr,
descriptor_CbYCrA,
descriptor_UserDefined2Elt = 150,
descriptor_UserDefined3Elt,
descriptor_UserDefined4Elt,
descriptor_UserDefined5Elt,
descriptor_UserDefined6Elt,
descriptor_UserDefined7Elt,
descriptor_UserDefined8Elt,
/* following descriptors are for internal use only */
descriptor_YA,
};
/* int functions return 0 for OK */
void logImageSetVerbose(int verbosity);
int logImageIsDpx(const void *buffer, unsigned int size);
int logImageIsCineon(const void *buffer, unsigned int size);
LogImageFile *logImageOpenFromMemory(const unsigned char *buffer, unsigned int size);
LogImageFile *logImageOpenFromFile(const char *filepath, int cineon);
void logImageGetSize(const LogImageFile *logImage, int *width, int *height, int *depth);
LogImageFile *logImageCreate(const char *filepath,
int cineon,
int width,
int height,
int bitsPerSample,
int hasAlpha,
int isLogarithmic,
int referenceWhite,
int referenceBlack,
float gamma,
const char *creator);
void logImageClose(LogImageFile *logImage);
/* Data handling */
size_t getRowLength(size_t width, const LogImageElement *logElement);
int logImageSetDataRGBA(LogImageFile *logImage, const float *data, int dataIsLinearRGB);
int logImageGetDataRGBA(LogImageFile *logImage, float *data, int dataIsLinearRGB);
/*
* Inline routines
*/
/* Endianness swapping */
BLI_INLINE unsigned short swap_ushort(unsigned short x, int swap)
{
if (swap != 0) {
return (x >> 8) | (x << 8);
}
return x;
}
BLI_INLINE unsigned int swap_uint(unsigned int x, int swap)
{
if (swap != 0) {
return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24);
}
return x;
}
BLI_INLINE float swap_float(float x, int swap)
{
if (swap != 0) {
union {
float f;
unsigned char b[4];
} dat1, dat2;
dat1.f = x;
dat2.b[0] = dat1.b[3];
dat2.b[1] = dat1.b[2];
dat2.b[2] = dat1.b[1];
dat2.b[3] = dat1.b[0];
return dat2.f;
}
return x;
}
/* Other */
BLI_INLINE unsigned int clamp_uint(unsigned int x, unsigned int low, unsigned int high)
{
if (x > high) {
return high;
}
if (x < low) {
return low;
}
return x;
}
BLI_INLINE float clamp_float(float x, float low, float high)
{
if (x > high) {
return high;
}
if (x < low) {
return low;
}
return x;
}
BLI_INLINE unsigned int float_uint(float value, unsigned int max)
{
if (value < 0.0f) {
return 0;
}
if (value > (1.0f - 0.5f / float(max))) {
return max;
}
return static_cast<unsigned int>((float(max) * value) + 0.5f);
}
} // namespace blender

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2006 Joseph Eagar <joeedh@gmail.com>.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*
* Cineon image file format library routines.
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "logImageCore.h"
#include "logmemfile.h"
namespace blender {
int logimage_fseek(LogImageFile *logFile, intptr_t offset, int origin)
{
if (logFile->file) {
fseek(logFile->file, offset, origin);
}
else { /* we're seeking in memory */
if (origin == SEEK_SET) {
if (offset > logFile->memBufferSize) {
return 1;
}
logFile->memCursor = logFile->memBuffer + offset;
}
else if (origin == SEEK_END) {
if (offset > logFile->memBufferSize) {
return 1;
}
logFile->memCursor = (logFile->memBuffer + logFile->memBufferSize) - offset;
}
else if (origin == SEEK_CUR) {
uintptr_t pos = uintptr_t(logFile->memCursor) - uintptr_t(logFile->memBuffer);
if (pos + offset > logFile->memBufferSize) {
return 1;
}
logFile->memCursor += offset;
}
}
return 0;
}
int logimage_fwrite(const void *buffer, size_t size, uint count, LogImageFile *logFile)
{
if (logFile->file) {
return fwrite(buffer, size, count, logFile->file);
}
/* we're writing to memory */
/* do nothing as this isn't supported yet */
return count;
}
int logimage_fread(void *buffer, size_t size, uint count, LogImageFile *logFile)
{
if (logFile->file) {
return fread(buffer, size, count, logFile->file);
}
/* we're reading from memory */
uchar *buf = static_cast<uchar *>(buffer);
uintptr_t pos = uintptr_t(logFile->memCursor) - uintptr_t(logFile->memBuffer);
size_t total_size = size * count;
if (pos + total_size > logFile->memBufferSize) {
/* how many elements can we read without overflow ? */
count = (logFile->memBufferSize - pos) / size;
/* recompute the size */
total_size = size * count;
}
if (total_size != 0) {
memcpy(buf, logFile->memCursor, total_size);
}
return count;
}
int logimage_read_uchar(uchar *x, LogImageFile *logFile)
{
uintptr_t pos = uintptr_t(logFile->memCursor) - uintptr_t(logFile->memBuffer);
if (pos + sizeof(uchar) > logFile->memBufferSize) {
return 1;
}
*x = *static_cast<uchar *>(logFile->memCursor);
logFile->memCursor += sizeof(uchar);
return 0;
}
int logimage_read_ushort(ushort *x, LogImageFile *logFile)
{
uintptr_t pos = uintptr_t(logFile->memCursor) - uintptr_t(logFile->memBuffer);
if (pos + sizeof(ushort) > logFile->memBufferSize) {
return 1;
}
*x = *reinterpret_cast<ushort *>(logFile->memCursor);
logFile->memCursor += sizeof(ushort);
return 0;
}
int logimage_read_uint(uint *x, LogImageFile *logFile)
{
uintptr_t pos = uintptr_t(logFile->memCursor) - uintptr_t(logFile->memBuffer);
if (pos + sizeof(uint) > logFile->memBufferSize) {
return 1;
}
*x = *reinterpret_cast<uint *>(logFile->memCursor);
logFile->memCursor += sizeof(uint);
return 0;
}
} // namespace blender

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2006 Joseph Eagar <joeedh@gmail.com>.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbcineon
*
* Cineon image file format library routines.
*/
#pragma once
#include "logImageCore.h"
#include <cstdlib>
namespace blender {
int logimage_fseek(LogImageFile *logFile, intptr_t offset, int origin);
int logimage_fwrite(const void *buffer, size_t size, unsigned int count, LogImageFile *logFile);
int logimage_fread(void *buffer, size_t size, unsigned int count, LogImageFile *logFile);
int logimage_read_uchar(unsigned char *x, LogImageFile *logFile);
int logimage_read_ushort(unsigned short *x, LogImageFile *logFile);
int logimage_read_uint(unsigned int *x, LogImageFile *logFile);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#pragma once
#include "BLI_colorspace.hh"
#include "BLI_math_color.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "IMB_colormanagement_intern.hh"
namespace blender {
void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3])
{
copy_v3_v3(r_rgb, colorspace::luma_coefficients);
}
float IMB_colormanagement_get_luminance(const float rgb[3])
{
return dot_v3v3(colorspace::luma_coefficients, rgb);
}
uchar IMB_colormanagement_get_luminance_byte(const uchar rgb[3])
{
float rgbf[3];
float val;
rgb_uchar_to_float(rgbf, rgb);
val = dot_v3v3(colorspace::luma_coefficients, rgbf);
return unit_float_to_uchar_clamp(val);
}
void IMB_colormanagement_xyz_to_scene_linear(float scene_linear[3], const float xyz[3])
{
mul_v3_m3v3(scene_linear, colorspace::xyz_to_scene_linear.ptr(), xyz);
}
void IMB_colormanagement_scene_linear_to_xyz(float xyz[3], const float scene_linear[3])
{
mul_v3_m3v3(xyz, colorspace::scene_linear_to_xyz.ptr(), scene_linear);
}
void IMB_colormanagement_rec709_to_scene_linear(float scene_linear[3], const float rec709[3])
{
if (colorspace::scene_linear_is_rec709) {
copy_v3_v3(scene_linear, rec709);
}
else {
mul_v3_m3v3(scene_linear, colorspace::rec709_to_scene_linear.ptr(), rec709);
}
}
void IMB_colormanagement_scene_linear_to_rec709(float rec709[3], const float scene_linear[3])
{
if (colorspace::scene_linear_is_rec709) {
copy_v3_v3(rec709, scene_linear);
}
else {
mul_v3_m3v3(rec709, colorspace::scene_linear_to_rec709.ptr(), scene_linear);
}
}
void IMB_colormanagement_scene_linear_to_srgb_v3(float srgb[3], const float scene_linear[3])
{
if (colorspace::scene_linear_is_rec709) {
copy_v3_v3(srgb, scene_linear);
}
else {
mul_v3_m3v3(srgb, colorspace::scene_linear_to_rec709.ptr(), scene_linear);
}
linearrgb_to_srgb_v3_v3(srgb, srgb);
}
void IMB_colormanagement_srgb_to_scene_linear_v3(float scene_linear[3], const float srgb[3])
{
srgb_to_linearrgb_v3_v3(scene_linear, srgb);
if (!colorspace::scene_linear_is_rec709) {
mul_m3_v3(colorspace::rec709_to_scene_linear.ptr(), scene_linear);
}
}
void IMB_colormanagement_aces_to_scene_linear(float scene_linear[3], const float aces[3])
{
mul_v3_m3v3(scene_linear, colorspace::aces_to_scene_linear.ptr(), aces);
}
void IMB_colormanagement_scene_linear_to_aces(float aces[3], const float scene_linear[3])
{
mul_v3_m3v3(aces, colorspace::scene_linear_to_aces.ptr(), scene_linear);
}
void IMB_colormanagement_acescg_to_scene_linear(float scene_linear[3], const float acescg[3])
{
mul_v3_m3v3(scene_linear, colorspace::acescg_to_scene_linear.ptr(), acescg);
}
void IMB_colormanagement_scene_linear_to_acescg(float acescg[3], const float scene_linear[3])
{
mul_v3_m3v3(acescg, colorspace::scene_linear_to_acescg.ptr(), scene_linear);
}
void IMB_colormanagement_rec2020_to_scene_linear(float scene_linear[3], const float rec2020[3])
{
mul_v3_m3v3(scene_linear, colorspace::rec2020_to_scene_linear.ptr(), rec2020);
}
void IMB_colormanagement_scene_linear_to_rec2020(float rec2020[3], const float scene_linear[3])
{
mul_v3_m3v3(rec2020, colorspace::scene_linear_to_rec2020.ptr(), scene_linear);
}
} // namespace blender

View File

@@ -0,0 +1,449 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "BLI_array.hh"
#include "BLI_rect.h"
#include "BLI_task.hh"
#include "IMB_filter.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_colormanagement.hh"
#include "MEM_guardedalloc.h"
#include "OCIO_colorspace.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Generic Buffer Conversion
* \{ */
MINLINE uchar ftochar(float value)
{
return unit_float_to_uchar_clamp(value);
}
MINLINE void float_to_byte_dither_v4(uchar b[4], const float f[4], float dither, int x, int y)
{
float dither_value = dither_random_value(x, y) * 0.0033f * dither;
b[0] = ftochar(dither_value + f[0]);
b[1] = ftochar(dither_value + f[1]);
b[2] = ftochar(dither_value + f[2]);
b[3] = unit_float_to_uchar_clamp(f[3]);
}
bool IMB_alpha_affects_rgb(const ImBuf *ibuf)
{
return ibuf && !flag_is_set(ibuf->flags, ImBufFlags::AlphaChannelPacked);
}
void IMB_buffer_byte_from_float(uchar *dest,
const float *src,
int src_channels,
float dither,
bool predivide,
int width,
int height,
int stride,
int start_y)
{
for (int y = 0; y < height; y++) {
const float *from = src + size_t(stride) * y * src_channels;
uchar *to = dest + size_t(stride) * y * 4;
if (src_channels == 1) {
/* single channel input */
for (int x = 0; x < width; x++, from++, to += 4) {
to[0] = to[1] = to[2] = to[3] = unit_float_to_uchar_clamp(from[0]);
}
}
else if (src_channels == 3) {
/* RGB input */
for (int x = 0; x < width; x++, from += 3, to += 4) {
rgb_float_to_uchar(to, from);
to[3] = 255;
}
}
else if (src_channels == 4) {
/* RGBA input */
if (dither && predivide) {
float straight[4];
for (int x = 0; x < width; x++, from += 4, to += 4) {
premul_to_straight_v4_v4(straight, from);
float_to_byte_dither_v4(to, straight, dither, x, y + start_y);
}
}
else if (dither) {
for (int x = 0; x < width; x++, from += 4, to += 4) {
float_to_byte_dither_v4(to, from, dither, x, y + start_y);
}
}
else if (predivide) {
for (int x = 0; x < width; x++, from += 4, to += 4) {
premul_float_to_straight_uchar(to, from);
}
}
else {
for (int x = 0; x < width; x++, from += 4, to += 4) {
rgba_float_to_uchar(to, from);
}
}
}
}
}
void IMB_buffer_byte_from_float_mask(uchar *dest,
const float *src,
int src_channels,
float dither,
int width,
int height,
const char *mask)
{
for (int y = 0; y < height; y++) {
const float *from = src + size_t(width) * y * src_channels;
uchar *to = dest + size_t(width) * y * 4;
if (src_channels == 1) {
/* single channel input */
for (int x = 0; x < width; x++, from++, to += 4) {
if (*mask++ == FILTER_MASK_USED) {
to[0] = to[1] = to[2] = to[3] = unit_float_to_uchar_clamp(from[0]);
}
}
}
else if (src_channels == 3) {
/* RGB input */
for (int x = 0; x < width; x++, from += 3, to += 4) {
if (*mask++ == FILTER_MASK_USED) {
rgb_float_to_uchar(to, from);
to[3] = 255;
}
}
}
else if (src_channels == 4) {
/* RGBA input */
if (dither) {
for (int x = 0; x < width; x++, from += 4, to += 4) {
if (*mask++ == FILTER_MASK_USED) {
float_to_byte_dither_v4(to, from, dither, x, y);
}
}
}
else {
for (int x = 0; x < width; x++, from += 4, to += 4) {
if (*mask++ == FILTER_MASK_USED) {
rgba_float_to_uchar(to, from);
}
}
}
}
}
}
void IMB_buffer_float_from_byte(
float *dest, const uchar *src, int width, int height, int dest_stride, int src_stride)
{
for (int y = 0; y < height; y++) {
const uchar *from = src + size_t(src_stride) * y * 4;
float *to = dest + size_t(dest_stride) * y * 4;
for (int x = 0; x < width; x++, from += 4, to += 4) {
rgba_uchar_to_float(to, from);
}
}
}
void IMB_buffer_float_rgba_from_float(
float *dest, const float *src, int src_channels, int width, int height)
{
if (src_channels == 1) {
/* single channel input */
for (int y = 0; y < height; y++) {
const float *from = src + size_t(width) * y;
float *to = dest + size_t(width) * y * 4;
for (int x = 0; x < width; x++, from++, to += 4) {
to[0] = to[1] = to[2] = to[3] = from[0];
}
}
}
else if (src_channels == 3) {
/* RGB input */
for (int y = 0; y < height; y++) {
const float *from = src + size_t(width) * y * 3;
float *to = dest + size_t(width) * y * 4;
for (int x = 0; x < width; x++, from += 3, to += 4) {
copy_v3_v3(to, from);
to[3] = 1.0f;
}
}
}
else if (src_channels == 4) {
/* RGBA input */
for (int y = 0; y < height; y++) {
const float *from = src + size_t(width) * y * 4;
float *to = dest + size_t(width) * y * 4;
memcpy(to, from, sizeof(float) * size_t(4) * width);
}
}
}
void IMB_buffer_float_rgba_from_float_mask(
float *dest, const float *src, int src_channels, int width, int height, const char *mask)
{
if (src_channels == 1) {
/* single channel input */
for (int y = 0; y < height; y++) {
const float *from = src + size_t(width) * y;
float *to = dest + size_t(width) * y * 4;
for (int x = 0; x < width; x++, from++, to += 4) {
if (*mask++ == FILTER_MASK_USED) {
to[0] = to[1] = to[2] = to[3] = from[0];
}
}
}
}
else if (src_channels == 3) {
/* RGB input */
for (int y = 0; y < height; y++) {
const float *from = src + size_t(width) * y * 3;
float *to = dest + size_t(width) * y * 4;
for (int x = 0; x < width; x++, from += 3, to += 4) {
if (*mask++ == FILTER_MASK_USED) {
copy_v3_v3(to, from);
to[3] = 1.0f;
}
}
}
}
else if (src_channels == 4) {
/* RGBA input */
for (int y = 0; y < height; y++) {
const float *from = src + size_t(width) * y * 4;
float *to = dest + size_t(width) * y * 4;
for (int x = 0; x < width; x++, from += 4, to += 4) {
if (*mask++ == FILTER_MASK_USED) {
copy_v4_v4(to, from);
}
}
}
}
}
void IMB_buffer_float_rgba_srgb_to_linear(float *buffer, int width, int height)
{
threading::parallel_for(IndexRange(height), 64, [&](const IndexRange y_range) {
for (int y : y_range) {
float *ptr = buffer + size_t(width) * y * 4;
for (int x = 0; x < width; x++, ptr += 4) {
srgb_to_linearrgb_predivide_v4(ptr, ptr);
}
}
});
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name ImBuf Conversion
* \{ */
void IMB_byte_from_float(ImBuf *ibuf)
{
/* Nothing to do if there's no float buffer */
const float *float_data = ibuf->float_data();
if (float_data == nullptr) {
return;
}
/* Allocate byte buffer if needed. */
if (ibuf->byte_data() == nullptr) {
if (!IMB_alloc_byte_pixels(ibuf, false)) {
return;
}
}
const char *from_colorspace = IMB_colormanagement_get_float_colorspace(ibuf);
const char *to_colorspace = IMB_colormanagement_get_byte_colorspace(ibuf);
const bool predivide = IMB_alpha_affects_rgb(ibuf);
ColormanageProcessor processor = ColormanageProcessor::colorspace_processor_new(from_colorspace,
to_colorspace);
/* At 4 floats per pixel, this is 32KB of data, and fits into typical CPU L1 cache. */
static constexpr int grain_size = 2048;
uchar *byte_data = ibuf->byte_data_for_write();
threading::parallel_for(
IndexRange(IMB_get_pixel_count(ibuf)), grain_size, [&](const IndexRange range) {
/* Copy chunk of source float pixels into a local buffer. */
Array<float, grain_size * 4> buffer(range.size() * ibuf->channels);
buffer.as_mutable_span().copy_from(
Span(float_data + range.first() * ibuf->channels, buffer.size()));
/* Unpremultiply alpha if needed. */
if (predivide) {
IMB_unpremultiply_rect_float(buffer.data(), ibuf->channels, range.size(), 1);
}
/* Convert to byte color space if needed. */
if (!processor.is_noop()) {
processor.apply(buffer.data(), range.size(), 1, ibuf->channels, false);
}
/* Convert to bytes. */
IMB_buffer_byte_from_float(byte_data + range.first() * 4,
buffer.data(),
ibuf->channels,
ibuf->dither,
false,
range.size(),
1,
ibuf->x);
});
/* ensure user flag is reset */
ibuf->userflags &= ~IB_RECT_INVALID;
}
void IMB_float_from_byte_ex(ImBuf *dst, const ImBuf *src, const rcti *region_to_update)
{
BLI_assert_msg(dst->float_data() != nullptr,
"Destination buffer should have a float buffer assigned.");
BLI_assert_msg(src->byte_data() != nullptr, "Source buffer should have a byte buffer assigned.");
BLI_assert_msg(dst->x == src->x, "Source and destination buffer should have the same dimension");
BLI_assert_msg(dst->y == src->y, "Source and destination buffer should have the same dimension");
BLI_assert_msg(dst->channels = 4, "Destination buffer should have 4 channels.");
BLI_assert_msg(region_to_update->xmin >= 0,
"Region to update should be clipped to the given buffers.");
BLI_assert_msg(region_to_update->ymin >= 0,
"Region to update should be clipped to the given buffers.");
BLI_assert_msg(region_to_update->xmax <= dst->x,
"Region to update should be clipped to the given buffers.");
BLI_assert_msg(region_to_update->ymax <= dst->y,
"Region to update should be clipped to the given buffers.");
const int region_width = BLI_rcti_size_x(region_to_update);
const int region_height = BLI_rcti_size_y(region_to_update);
const bool premultiply_alpha = IMB_alpha_affects_rgb(src);
const uchar *byte_data = src->byte_data();
float *float_data = dst->float_data_for_write();
threading::parallel_for(
IndexRange(region_to_update->ymin, region_height), 64, [&](const IndexRange y_range) {
const uchar *src_ptr = byte_data + (region_to_update->xmin + y_range.first() * dst->x) * 4;
float *dst_ptr = float_data + (region_to_update->xmin + y_range.first() * dst->x) * 4;
/* Convert byte -> float without color or alpha conversions. */
IMB_buffer_float_from_byte(dst_ptr, src_ptr, region_width, y_range.size(), src->x, dst->x);
/* Convert to scene linear color space, and premultiply alpha if needed. */
float *dst_ptr_line = dst_ptr;
for ([[maybe_unused]] const int64_t y : y_range) {
IMB_colormanagement_colorspace_to_scene_linear(
dst_ptr_line, region_width, 1, dst->channels, src->byte_buffer.colorspace, false);
if (premultiply_alpha) {
IMB_premultiply_rect_float(dst_ptr_line, dst->channels, region_width, 1);
}
dst_ptr_line += 4 * dst->x;
}
});
}
void IMB_float_from_byte(ImBuf *ibuf)
{
/* Nothing to do if there's no byte buffer. */
if (ibuf->byte_data() == nullptr) {
return;
}
/* Allocate float buffer if needed. */
if (ibuf->float_data() == nullptr) {
if (!IMB_alloc_float_pixels(ibuf, 4, false)) {
return;
}
}
rcti region_to_update;
BLI_rcti_init(&region_to_update, 0, ibuf->x, 0, ibuf->y);
IMB_float_from_byte_ex(ibuf, ibuf, &region_to_update);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Color to Gray-Scale
* \{ */
void IMB_color_to_bw(ImBuf *ibuf)
{
float *rct_fl = ibuf->float_data_for_write();
uchar *rct = ibuf->byte_data_for_write();
size_t i;
if (rct_fl) {
if (ibuf->channels >= 3) {
for (i = IMB_get_pixel_count(ibuf); i > 0; i--, rct_fl += ibuf->channels) {
rct_fl[0] = rct_fl[1] = rct_fl[2] = IMB_colormanagement_get_luminance(rct_fl);
}
}
}
if (rct) {
for (i = IMB_get_pixel_count(ibuf); i > 0; i--, rct += 4) {
rct[0] = rct[1] = rct[2] = IMB_colormanagement_get_luminance_byte(rct);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Alter Saturation
* \{ */
void IMB_saturation(ImBuf *ibuf, float sat)
{
const size_t pixel_count = IMB_get_pixel_count(ibuf);
if (uchar *byte_data = ibuf->byte_data_for_write()) {
threading::parallel_for(IndexRange(pixel_count), 64 * 1024, [&](IndexRange range) {
uchar *ptr = byte_data + range.first() * 4;
float rgb[3];
float hsv[3];
for ([[maybe_unused]] const int64_t i : range) {
rgb_uchar_to_float(rgb, ptr);
rgb_to_hsv_v(rgb, hsv);
hsv_to_rgb(hsv[0], hsv[1] * sat, hsv[2], rgb + 0, rgb + 1, rgb + 2);
rgb_float_to_uchar(ptr, rgb);
ptr += 4;
}
});
}
float *float_data = ibuf->float_data_for_write();
if (float_data != nullptr && ibuf->channels >= 3) {
threading::parallel_for(IndexRange(pixel_count), 64 * 1024, [&](IndexRange range) {
const int channels = ibuf->channels;
float *ptr = float_data + range.first() * channels;
float hsv[3];
for ([[maybe_unused]] const int64_t i : range) {
rgb_to_hsv_v(ptr, hsv);
hsv_to_rgb(hsv[0], hsv[1] * sat, hsv[2], ptr + 0, ptr + 1, ptr + 2);
ptr += channels;
}
});
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,410 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <cstddef>
#include "BLI_utildefines.h"
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "oiio/openimageio_api.h"
#include "openexr/openexr_api.h"
namespace blender {
const ImFileType IMB_FILE_TYPES[] = {
#ifndef WITH_WEB
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_jpeg,
/*load*/ imb_load_jpeg,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ imb_thumbnail_jpeg,
/*save*/ imb_savejpeg,
/*save_buffer*/ nullptr,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ eImFileTypeCapability::File,
/*filetype*/ IMB_FTYPE_JPG,
/*filetype_id*/ "JPEG",
/*file_extensions*/ imb_file_extensions_jpeg,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_png,
/*load*/ imb_load_png,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_png,
/*save_buffer*/ imb_save_buffer_png,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_PNG,
/*filetype_id*/ "PNG",
/*file_extensions*/ imb_file_extensions_png,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_bmp,
/*load*/ imb_load_bmp,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_bmp,
/*save_buffer*/ imb_save_buffer_bmp,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_BMP,
/*filetype_id*/ "BMP",
/*file_extensions*/ imb_file_extensions_bmp,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_tga,
/*load*/ imb_load_tga,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_tga,
/*save_buffer*/ imb_save_buffer_tga,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_TGA,
/*filetype_id*/ "TGA",
/*file_extensions*/ imb_file_extensions_tga,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_iris,
/*load*/ imb_loadiris,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_saveiris,
/*save_buffer*/ imb_save_buffer_iris,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_IRIS,
/*filetype_id*/ "IRIS",
/*file_extensions*/ imb_file_extensions_iris,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
#ifdef WITH_IMAGE_CINEON
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_dpx,
/*load*/ imb_load_dpx,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_dpx,
/*save_buffer*/ imb_save_buffer_dpx,
/*flag*/ IM_FTYPE_FLOAT,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_DPX,
/*filetype_id*/ "DPX",
/*file_extensions*/ imb_file_extensions_dpx,
/*default_save_role*/ COLOR_ROLE_DEFAULT_FLOAT,
},
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_cineon,
/*load*/ imb_load_cineon,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_cineon,
/*save_buffer*/ nullptr,
/*flag*/ IM_FTYPE_FLOAT,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ eImFileTypeCapability::File,
/*filetype*/ IMB_FTYPE_CINEON,
/*filetype_id*/ "CINEON",
/*file_extensions*/ imb_file_extensions_cineon,
/*default_save_role*/ COLOR_ROLE_DEFAULT_FLOAT,
},
#endif
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_tiff,
/*load*/ imb_load_tiff,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_tiff,
/*save_buffer*/ imb_save_buffer_tiff,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_TIF,
/*filetype_id*/ "TIFF",
/*file_extensions*/ imb_file_extensions_tiff,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_hdr,
/*load*/ imb_load_hdr,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_hdr,
/*save_buffer*/ imb_save_buffer_hdr,
/*flag*/ IM_FTYPE_FLOAT,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_RADHDR,
/*filetype_id*/ "HDR",
/*file_extensions*/ imb_file_extensions_hdr,
/*default_save_role*/ COLOR_ROLE_DEFAULT_FLOAT,
},
{
/*init*/ imb_initopenexr,
/*exit*/ imb_exitopenexr,
/*is_a*/ imb_is_a_openexr,
/*load*/ imb_load_openexr,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ imb_load_filepath_thumbnail_openexr,
/*save*/ imb_save_openexr,
/*save_buffer*/ imb_save_buffer_openexr,
/*flag*/ IM_FTYPE_FLOAT,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_OPENEXR,
/*filetype_id*/ "OPEN_EXR",
/*file_extensions*/ imb_file_extensions_openexr,
/*default_save_role*/ COLOR_ROLE_DEFAULT_FLOAT,
},
#ifdef WITH_IMAGE_OPENJPEG
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_jp2,
/*load*/ imb_load_jp2,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_jp2,
/*save_buffer*/ nullptr,
/*flag*/ IM_FTYPE_FLOAT,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ eImFileTypeCapability::File,
/*filetype*/ IMB_FTYPE_JP2,
/*filetype_id*/ "JPEG2000",
/*file_extensions*/ imb_file_extensions_jp2,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
#endif
{
/*init*/ imb_init_dds,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_dds,
/*load*/ imb_load_dds,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ nullptr,
/*save_buffer*/ nullptr,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ eImFileTypeCapability::Zero,
/*filetype*/ IMB_FTYPE_DDS,
/*filetype_id*/ "DDS",
/*file_extensions*/ imb_file_extensions_dds,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_psd,
/*load*/ imb_load_psd,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ nullptr,
/*save_buffer*/ nullptr,
/*flag*/ IM_FTYPE_FLOAT,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ eImFileTypeCapability::Zero,
/*filetype*/ IMB_FTYPE_PSD,
/*filetype_id*/ "PSD",
/*file_extensions*/ imb_file_extensions_psd,
/*default_save_role*/ COLOR_ROLE_DEFAULT_FLOAT,
},
#ifdef WITH_IMAGE_WEBP
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_webp,
/*load*/ imb_loadwebp,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ imb_load_filepath_thumbnail_webp,
/*save*/ imb_savewebp,
/*save_buffer*/ imb_save_buffer_webp,
/*flag*/ 0,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_WEBP,
/*filetype_id*/ "WEBP",
/*file_extensions*/ imb_file_extensions_webp,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
#endif
{
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ imb_is_a_avif,
/*load*/ imb_load_avif,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ nullptr,
/*save*/ imb_save_avif,
/*save_buffer*/ imb_save_buffer_avif,
/*flag*/ IM_FTYPE_FLOAT,
/*capability_read*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*capability_write*/ (eImFileTypeCapability::File | eImFileTypeCapability::Memory),
/*filetype*/ IMB_FTYPE_AVIF,
/*filetype_id*/ "AVIF",
/*file_extensions*/ imb_file_extensions_avif,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
{
/* Only implementing thumbnailing for SVG file type to support specialized importers.
* General file loading, if wanted, would require a better library and would have to
* support features like user-specified resolution. */
/*init*/ nullptr,
/*exit*/ nullptr,
/*is_a*/ nullptr,
/*load*/ nullptr,
/*load_filepath*/ nullptr,
/*load_filepath_thumbnail*/ imb_load_filepath_thumbnail_svg,
/*save*/ nullptr,
/*save_buffer*/ nullptr,
/*flag*/ 0,
/*capability_read*/ eImFileTypeCapability::Zero,
/*capability_write*/ eImFileTypeCapability::Zero,
/*filetype*/ IMB_FTYPE_NONE,
/*filetype_id*/ nullptr,
/*file_extensions*/ nullptr,
/*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE,
},
#endif
{
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
/*save_buffer*/ nullptr,
0,
eImFileTypeCapability::Zero,
eImFileTypeCapability::Zero,
IMB_FTYPE_NONE,
nullptr,
nullptr,
0,
},
};
const ImFileType *IMB_FILE_TYPES_LAST = &IMB_FILE_TYPES[ARRAY_SIZE(IMB_FILE_TYPES) - 1];
const ImFileType *IMB_file_type_from_ftype(eImbFileType ftype)
{
for (const ImFileType *type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (ftype == type->filetype) {
return type;
}
}
return nullptr;
}
const ImFileType *IMB_file_type_from_ibuf(const ImBuf *ibuf)
{
return IMB_file_type_from_ftype(ibuf->ftype);
}
bool IMB_ftype_is_supported(eImbFileType ftype)
{
return IMB_file_type_from_ftype(ftype) != nullptr;
}
const char *IMB_ftype_to_id(eImbFileType ftype)
{
const ImFileType *type = IMB_file_type_from_ftype(ftype);
return type ? type->filetype_id : nullptr;
}
eImbFileType IMB_ftype_from_id(const char *id)
{
for (const ImFileType *type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (type->filetype_id && STREQ(id, type->filetype_id)) {
return type->filetype;
}
}
return IMB_FTYPE_NONE;
}
const char **IMB_ftype_file_extensions(eImbFileType ftype)
{
const ImFileType *type = IMB_file_type_from_ftype(ftype);
return type ? type->file_extensions : nullptr;
}
eImFileTypeCapability IMB_ftype_capability_read(eImbFileType ftype)
{
const ImFileType *type = IMB_file_type_from_ftype(ftype);
return type ? type->capability_read : eImFileTypeCapability::Zero;
}
eImFileTypeCapability IMB_ftype_capability_write(eImbFileType ftype)
{
const ImFileType *type = IMB_file_type_from_ftype(ftype);
return type ? type->capability_write : eImFileTypeCapability::Zero;
}
void imb_filetypes_init()
{
const ImFileType *type;
#ifndef WITH_WEB
OIIO_init();
#endif
for (type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (type->init) {
type->init();
}
}
}
void imb_filetypes_exit()
{
const ImFileType *type;
for (type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (type->exit) {
type->exit();
}
}
}
} // namespace blender

View File

@@ -0,0 +1,459 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <cmath>
#include "MEM_guardedalloc.h"
#include "BLI_math_base.h"
#include "BLI_utildefines.h"
#include "IMB_filter.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
static void filtcolum(uchar *point, int y, int skip)
{
uint c1, c2, c3, error;
uchar *point2;
if (y > 1) {
c1 = c2 = *point;
point2 = point;
error = 2;
for (y--; y > 0; y--) {
point2 += skip;
c3 = *point2;
c1 += (c2 << 1) + c3 + error;
error = c1 & 3;
*point = c1 >> 2;
point = point2;
c1 = c2;
c2 = c3;
}
*point = (c1 + (c2 << 1) + c2 + error) >> 2;
}
}
static void filtcolumf(float *point, int y, int skip)
{
float c1, c2, c3, *point2;
if (y > 1) {
c1 = c2 = *point;
point2 = point;
for (y--; y > 0; y--) {
point2 += skip;
c3 = *point2;
c1 += (c2 * 2) + c3;
*point = 0.25f * c1;
point = point2;
c1 = c2;
c2 = c3;
}
*point = 0.25f * (c1 + (c2 * 2) + c2);
}
}
void IMB_filtery(ImBuf *ibuf)
{
uchar *point = ibuf->byte_data_for_write();
float *pointf = ibuf->float_data_for_write();
int x = ibuf->x;
int y = ibuf->y;
int skip = x << 2;
for (; x > 0; x--) {
if (point) {
if (ibuf->color_mode == ImColorMode::RGBA) {
filtcolum(point, y, skip);
}
point++;
filtcolum(point, y, skip);
point++;
filtcolum(point, y, skip);
point++;
filtcolum(point, y, skip);
point++;
}
if (pointf) {
if (ibuf->color_mode == ImColorMode::RGBA) {
filtcolumf(pointf, y, skip);
}
pointf++;
filtcolumf(pointf, y, skip);
pointf++;
filtcolumf(pointf, y, skip);
pointf++;
filtcolumf(pointf, y, skip);
pointf++;
}
}
}
void IMB_mask_filter_extend(char *mask, int width, int height)
{
const char *row1, *row2, *row3;
int rowlen, x, y;
char *temprect;
rowlen = width;
/* make a copy, to prevent flooding */
temprect = MEM_dupalloc(mask);
for (y = 1; y <= height; y++) {
/* setup rows */
row1 = static_cast<char *>(temprect + (y - 2) * rowlen);
row2 = row1 + rowlen;
row3 = row2 + rowlen;
if (y == 1) {
row1 = row2;
}
else if (y == height) {
row3 = row2;
}
for (x = 0; x < rowlen; x++) {
if (mask[((y - 1) * rowlen) + x] == 0) {
if (*row1 || *row2 || *row3 || *(row1 + 1) || *(row3 + 1)) {
mask[((y - 1) * rowlen) + x] = FILTER_MASK_MARGIN;
}
else if ((x != rowlen - 1) && (*(row1 + 2) || *(row2 + 2) || *(row3 + 2))) {
mask[((y - 1) * rowlen) + x] = FILTER_MASK_MARGIN;
}
}
if (x != 0) {
row1++;
row2++;
row3++;
}
}
}
MEM_delete(temprect);
}
void IMB_mask_clear(ImBuf *ibuf, const char *mask, int val)
{
int x, y;
if (float *float_data = ibuf->float_data_for_write()) {
for (x = 0; x < ibuf->x; x++) {
for (y = 0; y < ibuf->y; y++) {
if (mask[ibuf->x * y + x] == val) {
float *col = float_data + 4 * (ibuf->x * y + x);
col[0] = col[1] = col[2] = col[3] = 0.0f;
}
}
}
}
else {
uchar *byte_data = ibuf->byte_data_for_write();
/* char buffer */
for (x = 0; x < ibuf->x; x++) {
for (y = 0; y < ibuf->y; y++) {
if (mask[ibuf->x * y + x] == val) {
char *col = reinterpret_cast<char *>(byte_data + 4 * ibuf->x * y + x);
col[0] = col[1] = col[2] = col[3] = 0;
}
}
}
}
}
static int filter_make_index(const int x, const int y, const int w, const int h)
{
if (x < 0 || x >= w || y < 0 || y >= h) {
return -1; /* return bad index */
}
return y * w + x;
}
static int check_pixel_assigned(
const void *buffer, const char *mask, const int index, const int depth, const bool is_float)
{
int res = 0;
if (index >= 0) {
const int alpha_index = depth * index + (depth - 1);
if (mask != nullptr) {
res = mask[index] != 0 ? 1 : 0;
}
else if ((is_float && (static_cast<const float *>(buffer))[alpha_index] != 0.0f) ||
(!is_float && (static_cast<const uchar *>(buffer))[alpha_index] != 0))
{
res = 1;
}
}
return res;
}
void IMB_filter_extend(ImBuf *ibuf, char *mask, int filter)
{
const int width = ibuf->x;
const int height = ibuf->y;
const int depth = 4; /* always 4 channels */
const int chsize = ibuf->float_data() ? sizeof(float) : sizeof(uchar);
const size_t bsize = size_t(width) * height * depth * chsize;
const bool is_float = (ibuf->float_data() != nullptr);
void *dstbuf = ibuf->float_data() ? static_cast<void *>(MEM_dupalloc(ibuf->float_data())) :
static_cast<void *>(MEM_dupalloc(ibuf->byte_data()));
char *dstmask = mask == nullptr ? nullptr : MEM_dupalloc(mask);
void *srcbuf = ibuf->float_data() ? static_cast<void *>(ibuf->float_data_for_write()) :
static_cast<void *>(ibuf->byte_data_for_write());
char *srcmask = mask;
int cannot_early_out = 1, r, n, k, i, j, c;
float weight[25];
/* build a weights buffer */
n = 1;
#if 0
k = 0;
for (i = -n; i <= n; i++) {
for (j = -n; j <= n; j++) {
weight[k++] = sqrt(float(i) * i + j * j);
}
}
#endif
weight[0] = 1;
weight[1] = 2;
weight[2] = 1;
weight[3] = 2;
weight[4] = 0;
weight[5] = 2;
weight[6] = 1;
weight[7] = 2;
weight[8] = 1;
/* run passes */
for (r = 0; cannot_early_out == 1 && r < filter; r++) {
int x, y;
cannot_early_out = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
const int index = filter_make_index(x, y, width, height);
/* only update unassigned pixels */
if (!check_pixel_assigned(srcbuf, srcmask, index, depth, is_float)) {
float tmp[4];
float wsum = 0;
float acc[4] = {0, 0, 0, 0};
k = 0;
if (check_pixel_assigned(
srcbuf, srcmask, filter_make_index(x - 1, y, width, height), depth, is_float) ||
check_pixel_assigned(
srcbuf, srcmask, filter_make_index(x + 1, y, width, height), depth, is_float) ||
check_pixel_assigned(
srcbuf, srcmask, filter_make_index(x, y - 1, width, height), depth, is_float) ||
check_pixel_assigned(
srcbuf, srcmask, filter_make_index(x, y + 1, width, height), depth, is_float))
{
for (i = -n; i <= n; i++) {
for (j = -n; j <= n; j++) {
if (i != 0 || j != 0) {
const int tmpindex = filter_make_index(x + i, y + j, width, height);
if (check_pixel_assigned(srcbuf, srcmask, tmpindex, depth, is_float)) {
if (is_float) {
for (c = 0; c < depth; c++) {
tmp[c] = (static_cast<const float *>(srcbuf))[depth * tmpindex + c];
}
}
else {
for (c = 0; c < depth; c++) {
tmp[c] = float((static_cast<const uchar *>(srcbuf))[depth * tmpindex + c]);
}
}
wsum += weight[k];
for (c = 0; c < depth; c++) {
acc[c] += weight[k] * tmp[c];
}
}
}
k++;
}
}
if (wsum != 0) {
for (c = 0; c < depth; c++) {
acc[c] /= wsum;
}
if (is_float) {
for (c = 0; c < depth; c++) {
(static_cast<float *>(dstbuf))[depth * index + c] = acc[c];
}
}
else {
for (c = 0; c < depth; c++) {
(static_cast<uchar *>(dstbuf))[depth * index + c] =
acc[c] > 255 ? 255 : (acc[c] < 0 ? 0 : uchar(roundf(acc[c])));
}
}
if (dstmask != nullptr) {
dstmask[index] = FILTER_MASK_MARGIN; /* assigned */
}
cannot_early_out = 1;
}
}
}
}
}
/* keep the original buffer up to date. */
memcpy(srcbuf, dstbuf, bsize);
if (dstmask != nullptr) {
memcpy(srcmask, dstmask, size_t(width) * height);
}
}
/* free memory */
MEM_delete_void(dstbuf);
if (dstmask != nullptr) {
MEM_delete(dstmask);
}
}
void IMB_premultiply_rect(uint8_t *rect, ImColorMode color_mode, int w, int h)
{
uint8_t *cp;
int x, y, val;
if (color_mode == ImColorMode::RGB) { /* put alpha at 255 */
cp = rect;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, cp += 4) {
cp[3] = 255;
}
}
}
else {
cp = rect;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, cp += 4) {
val = cp[3];
cp[0] = (cp[0] * val) >> 8;
cp[1] = (cp[1] * val) >> 8;
cp[2] = (cp[2] * val) >> 8;
}
}
}
}
void IMB_premultiply_rect_float(float *rect_float, int channels, int w, int h)
{
float val, *cp;
int x, y;
if (channels == 4) {
cp = rect_float;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, cp += 4) {
val = cp[3];
cp[0] = cp[0] * val;
cp[1] = cp[1] * val;
cp[2] = cp[2] * val;
}
}
}
}
void IMB_premultiply_alpha(ImBuf *ibuf)
{
if (ibuf == nullptr) {
return;
}
if (uchar *byte_data = ibuf->byte_data_for_write()) {
IMB_premultiply_rect(byte_data, ibuf->color_mode, ibuf->x, ibuf->y);
}
if (float *float_data = ibuf->float_data_for_write()) {
IMB_premultiply_rect_float(float_data, ibuf->channels, ibuf->x, ibuf->y);
}
}
void IMB_unpremultiply_rect(uint8_t *rect, ImColorMode color_mode, int w, int h)
{
uchar *cp;
int x, y;
float val;
if (color_mode == ImColorMode::RGB) { /* put alpha at 255 */
cp = rect;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, cp += 4) {
cp[3] = 255;
}
}
}
else {
cp = rect;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, cp += 4) {
val = cp[3] != 0 ? 1.0f / float(cp[3]) : 1.0f;
cp[0] = unit_float_to_uchar_clamp(cp[0] * val);
cp[1] = unit_float_to_uchar_clamp(cp[1] * val);
cp[2] = unit_float_to_uchar_clamp(cp[2] * val);
}
}
}
}
void IMB_unpremultiply_rect_float(float *rect_float, int channels, int w, int h)
{
float val, *fp;
int x, y;
if (channels == 4) {
fp = rect_float;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++, fp += 4) {
val = fp[3] != 0.0f ? 1.0f / fp[3] : 1.0f;
fp[0] = fp[0] * val;
fp[1] = fp[1] * val;
fp[2] = fp[2] * val;
}
}
}
}
void IMB_unpremultiply_alpha(ImBuf *ibuf)
{
if (ibuf == nullptr) {
return;
}
if (uchar *byte_data = ibuf->byte_data_for_write()) {
IMB_unpremultiply_rect(byte_data, ibuf->color_mode, ibuf->x, ibuf->y);
}
if (float *float_data = ibuf->float_data_for_write()) {
IMB_unpremultiply_rect_float(float_data, ibuf->channels, ibuf->x, ibuf->y);
}
}
} // namespace blender

View File

@@ -0,0 +1,160 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <cstring>
#include <fmt/format.h>
#include "oiio/openimageio_support.hh"
#include "MEM_guardedalloc.h"
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_avif[] = {".avif", nullptr};
OIIO_NAMESPACE_USING
using namespace imbuf;
bool imb_is_a_avif(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "heif");
}
ImBuf *imb_load_avif(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{.mem_start = mem,
.mem_size = size,
.file_format = "heif",
.file_type = IMB_FTYPE_AVIF,
.flags = flags};
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
if (ibuf) {
const int bits_per_sample = spec.get_int_attribute("oiio:BitsPerSample", 8);
if (bits_per_sample == 10) {
ibuf->foptions.flag |= AVIF_10BIT;
}
else if (bits_per_sample == 12) {
ibuf->foptions.flag |= AVIF_12BIT;
}
}
/* Assume SDR by default, CICP will indicate if it's HDR and set a colorspace. */
r_colorspace.is_hdr_float = false;
return ibuf;
}
/** Block size (in pixels) that the AV1 encoder processes at a time. */
static constexpr int AVIF_BLOCK_SIZE = 64;
/**
* Work around a buffer overflow when saving AVIF images whose width
* is not a multiple of #AVIF_BLOCK_SIZE.
*
* The encoder may read up to one block past the end of the last row.
* Interior rows are unaffected because their over-read lands in the next row.
* Allocate a padded copy so the over-read stays in bounds.
*/
static uchar *imb_save_avif_padding_workaround_begin(const ImBuf *ibuf,
WriteContext &ctx,
const bool prefer_float)
{
/* The bug only affects the 8-bit path (the 10/12-bit path copies per-pixel). */
const bool use_float = prefer_float && (ibuf->float_data() != nullptr);
if (use_float || (ibuf->x % AVIF_BLOCK_SIZE) == 0) {
return nullptr;
}
const size_t size_orig = size_t(ibuf->y) * ctx.mem_ystride;
const size_t size_pad = size_orig + (AVIF_BLOCK_SIZE * ctx.mem_xstride);
const uchar *src_base = ibuf->byte_data();
uchar *buf_padded = MEM_new_array_uninitialized<uchar>(size_pad, __func__);
memcpy(buf_padded, src_base, size_orig);
/* `mem_start` points to the last row (images are stored bottom-to-top). */
const size_t y_flip_offset = size_t(ibuf->y - 1) * ctx.mem_ystride;
ctx.mem_start = buf_padded + y_flip_offset;
return buf_padded;
}
static void imb_save_avif_padding_workaround_end(const uchar *buf_padded)
{
/* May be null. */
MEM_delete(buf_padded);
}
static std::tuple<WriteContext, ImageSpec, bool> prepare_save_avif(ImBuf *ibuf, ImBufFlags flags)
{
const int bits_per_sample = (ibuf->foptions.flag & AVIF_10BIT) ? 10 :
(ibuf->foptions.flag & AVIF_12BIT) ? 12 :
8;
const bool use_float = bits_per_sample > 8;
int file_channels = ibuf->color_mode_channels_get();
/* AVIF/HEIF does not support 2-channel (gray + alpha) writes; promote to RGBA. */
if (file_channels == 2) {
file_channels = 4;
}
const TypeDesc data_format = use_float ? TypeDesc::UINT16 : TypeDesc::UINT8;
WriteContext ctx = imb_create_write_context("heif", ibuf, flags, use_float);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
/* Skip if the float buffer was managed already. */
if (use_float && (ibuf->float_buffer.colorspace || ibuf->colorspace_is_data())) {
file_spec.attribute("oiio:UnassociatedAlpha", 0);
}
else {
file_spec.attribute("oiio:UnassociatedAlpha", 1);
}
file_spec.attribute("Compression", fmt::format("avif:{}", int(ibuf->foptions.quality)));
file_spec.attribute("oiio:BitsPerSample", bits_per_sample);
return {ctx, file_spec, use_float};
}
bool imb_save_avif(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
auto [ctx, file_spec, use_float] = prepare_save_avif(ibuf, flags);
const uchar *buf_padded = nullptr;
if (OIIO_VERSION_LESS(3, 1, 12)) {
buf_padded = imb_save_avif_padding_workaround_begin(ibuf, ctx, use_float);
}
bool result = imb_oiio_write(ctx, filepath, file_spec);
imb_save_avif_padding_workaround_end(buf_padded);
return result;
}
Vector<uint8_t> imb_save_buffer_avif(ImBuf *ibuf, ImBufFlags flags)
{
auto [ctx, file_spec, use_float] = prepare_save_avif(ibuf, flags);
const uchar *buf_padded = nullptr;
if (OIIO_VERSION_LESS(3, 1, 12)) {
buf_padded = imb_save_avif_padding_workaround_begin(ibuf, ctx, use_float);
}
Vector<uint8_t> result = imb_oiio_write_buffer(ctx, file_spec);
imb_save_avif_padding_workaround_end(buf_padded);
return result;
}
}; // namespace blender

View File

@@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "oiio/openimageio_support.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_bmp[] = {".bmp", ".dib", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_bmp(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "bmp");
}
ImBuf *imb_load_bmp(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
/* Keep historical behavior - do not use a 1-channel format for a black-white image. */
config.attribute("bmp:monochrome_detect", 0);
ReadContext ctx{mem, size, "bmp", IMB_FTYPE_BMP, flags};
return imb_oiio_read(ctx, config, r_colorspace, spec);
}
static std::tuple<WriteContext, ImageSpec> prepare_save_bmp(ImBuf *ibuf, ImBufFlags flags)
{
int file_channels = ibuf->color_mode_channels_get();
/* BMP does not support 2-channel (gray + alpha) writes; promote to RGBA. */
if (file_channels == 2) {
file_channels = 4;
}
const TypeDesc data_format = TypeDesc::UINT8;
WriteContext ctx = imb_create_write_context("bmp", ibuf, flags, false);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
return {ctx, file_spec};
}
bool imb_save_bmp(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_bmp(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_bmp(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_bmp(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

View File

@@ -0,0 +1,374 @@
/* SPDX-FileCopyrightText: 2009 Google Inc. All rights reserved. (BSD-3-Clause)
* SPDX-FileCopyrightText: 2023-2024 Blender Authors (GPL-2.0-or-later).
*
* SPDX-License-Identifier: GPL-2.0-or-later AND BSD-3-Clause */
/** \file
* \ingroup imbuf
*
* Some portions of this file are from the Chromium project and have been adapted
* for Blender use when flipping DDS images to the OpenGL convention.
*/
#include <algorithm>
#include <fcntl.h>
#if defined(WIN32)
# include <io.h>
#else
# include <unistd.h>
#endif
#include "oiio/openimageio_support.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
#include "BLI_fileops.h"
#include "BLI_math_base.h"
#include "BLI_mmap.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "MEM_guardedalloc.h"
namespace blender {
const char *imb_file_extensions_dds[] = {".dds", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
void imb_init_dds()
{
/* To match historical behavior for DDS file loading, tell OpenImageIO
* to process BC5 compressed textures as normal maps. But only do so
* if the environment does not already contain a directive that might
* say otherwise. */
const char *bc5normal = "dds:bc5normal";
const char *oiio_env = BLI_getenv("OPENIMAGEIO_OPTIONS");
if (!oiio_env || !BLI_strcasestr(oiio_env, bc5normal)) {
OIIO::attribute(bc5normal, 1);
}
}
bool imb_is_a_dds(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "dds");
}
ImBuf *imb_load_dds(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
ReadContext ctx{mem, size, "dds", IMB_FTYPE_DDS, flags};
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
if (ibuf) {
std::string compression = spec.get_string_attribute("compression", "");
if (compression == "DXT1") {
ibuf->foptions.flag |= DDS_COMPRESSED_DXT1;
}
if (compression == "DXT3") {
ibuf->foptions.flag |= DDS_COMPRESSED_DXT3;
}
if (compression == "DXT5") {
ibuf->foptions.flag |= DDS_COMPRESSED_DXT5;
}
}
return ibuf;
}
/* A function that flips a DXTC block. */
using FlipBlockFunction = void (*)(uint8_t *block);
/* Flips a full DXT1 block in the y direction. */
static void FlipDXT1BlockFull(uint8_t *block)
{
/* A DXT1 block layout is:
* [0-1] color0.
* [2-3] color1.
* [4-7] color bitmap, 2 bits per pixel.
* So each of the 4-7 bytes represents one line, flipping a block is just
* flipping those bytes. */
uint8_t tmp = block[4];
block[4] = block[7];
block[7] = tmp;
tmp = block[5];
block[5] = block[6];
block[6] = tmp;
}
/* Flips the first 2 lines of a DXT1 block in the y direction. */
static void FlipDXT1BlockHalf(uint8_t *block)
{
/* See layout above. */
uint8_t tmp = block[4];
block[4] = block[5];
block[5] = tmp;
}
/* Flips a full DXT3 block in the y direction. */
static void FlipDXT3BlockFull(uint8_t *block)
{
/* A DXT3 block layout is:
* [0-7] alpha bitmap, 4 bits per pixel.
* [8-15] a DXT1 block. */
/* We can flip the alpha bits at the byte level (2 bytes per line). */
uint8_t tmp = block[0];
block[0] = block[6];
block[6] = tmp;
tmp = block[1];
block[1] = block[7];
block[7] = tmp;
tmp = block[2];
block[2] = block[4];
block[4] = tmp;
tmp = block[3];
block[3] = block[5];
block[5] = tmp;
/* And flip the DXT1 block using the above function. */
FlipDXT1BlockFull(block + 8);
}
/* Flips the first 2 lines of a DXT3 block in the y direction. */
static void FlipDXT3BlockHalf(uint8_t *block)
{
/* See layout above. */
uint8_t tmp = block[0];
block[0] = block[2];
block[2] = tmp;
tmp = block[1];
block[1] = block[3];
block[3] = tmp;
FlipDXT1BlockHalf(block + 8);
}
/* Flips a full DXT5 block in the y direction. */
static void FlipDXT5BlockFull(uint8_t *block)
{
/* A DXT5 block layout is:
* [0] alpha0.
* [1] alpha1.
* [2-7] alpha bitmap, 3 bits per pixel.
* [8-15] a DXT1 block. */
/* The alpha bitmap doesn't easily map lines to bytes, so we have to
* interpret it correctly. Extracted from
* http://www.opengl.org/registry/specs/EXT/texture_compression_s3tc.txt :
*
* The 6 "bits" bytes of the block are decoded into one 48-bit integer:
*
* bits = bits_0 + 256 * (bits_1 + 256 * (bits_2 + 256 * (bits_3 +
* 256 * (bits_4 + 256 * bits_5))))
*
* bits is a 48-bit unsigned-integer, from which a three-bit control code
* is extracted for a texel at location (x,y) in the block using:
*
* code(x,y) = bits[3*(4*y+x)+1..3*(4*y+x)+0]
*
* where bit 47 is the most significant and bit 0 is the least
* significant bit. */
uint line_0_1 = block[2] + 256 * (block[3] + 256 * block[4]);
uint line_2_3 = block[5] + 256 * (block[6] + 256 * block[7]);
/* swap lines 0 and 1 in line_0_1. */
uint line_1_0 = ((line_0_1 & 0x000fff) << 12) | ((line_0_1 & 0xfff000) >> 12);
/* swap lines 2 and 3 in line_2_3. */
uint line_3_2 = ((line_2_3 & 0x000fff) << 12) | ((line_2_3 & 0xfff000) >> 12);
block[2] = line_3_2 & 0xff;
block[3] = (line_3_2 & 0xff00) >> 8;
block[4] = (line_3_2 & 0xff0000) >> 16;
block[5] = line_1_0 & 0xff;
block[6] = (line_1_0 & 0xff00) >> 8;
block[7] = (line_1_0 & 0xff0000) >> 16;
/* And flip the DXT1 block using the above function. */
FlipDXT1BlockFull(block + 8);
}
/* Flips the first 2 lines of a DXT5 block in the y direction. */
static void FlipDXT5BlockHalf(uint8_t *block)
{
/* See layout above. */
uint line_0_1 = block[2] + 256 * (block[3] + 256 * block[4]);
uint line_1_0 = ((line_0_1 & 0x000fff) << 12) | ((line_0_1 & 0xfff000) >> 12);
block[2] = line_1_0 & 0xff;
block[3] = (line_1_0 & 0xff00) >> 8;
block[4] = (line_1_0 & 0xff0000) >> 16;
FlipDXT1BlockHalf(block + 8);
}
static constexpr uint32_t fourcc_dxt1 = 0x31545844; /* D, X, T, 1 */
static constexpr uint32_t fourcc_dxt3 = 0x33545844; /* D, X, T, 3 */
static constexpr uint32_t fourcc_dxt5 = 0x35545844; /* D, X, T, 5 */
/**
* Flips a DXTC image, by flipping and swapping DXTC blocks as appropriate.
* Use to flip vertically to fit OpenGL convention.
* Returns number of valid mip levels.
*/
static int flip_dxtc_image(
uint8_t *data, size_t data_size, int width, int height, int levels, uint32_t fourcc)
{
/* Must have valid dimensions. */
if (width == 0 || height == 0) {
return 0;
}
/* Height must be a power-of-two: not because something within DXT/S3TC
* needs it. Only because we want to flip the image upside down, by
* swapping and flipping block rows, and that in general case (with mipmaps)
* is only possible for POT height. */
if (!is_power_of_2_i(height)) {
return 0;
}
FlipBlockFunction full_block_function;
FlipBlockFunction half_block_function;
size_t block_bytes = 0;
switch (fourcc) {
case fourcc_dxt1:
full_block_function = FlipDXT1BlockFull;
half_block_function = FlipDXT1BlockHalf;
block_bytes = 8;
break;
case fourcc_dxt3:
full_block_function = FlipDXT3BlockFull;
half_block_function = FlipDXT3BlockHalf;
block_bytes = 16;
break;
case fourcc_dxt5:
full_block_function = FlipDXT5BlockFull;
half_block_function = FlipDXT5BlockHalf;
block_bytes = 16;
break;
default:
return 0;
}
int mip_width = width;
int mip_height = height;
const uint8_t *data_end = data + data_size;
for (int level = 0; level < levels; level++) {
int blocks_per_row = (mip_width + 3) / 4;
int blocks_per_col = (mip_height + 3) / 4;
int blocks = blocks_per_row * blocks_per_col;
if (data + block_bytes * blocks > data_end) {
/* Stop flipping when running out of data to be modified, avoiding possible buffer overrun
* on a malformed files. */
return level;
}
if (mip_height == 1) {
/* no flip to do, and we're done. */
break;
}
if (mip_height == 2) {
/* flip the first 2 lines in each block. */
for (int i = 0; i < blocks_per_row; i++) {
half_block_function(data + i * block_bytes);
}
}
else {
/* flip each block. */
for (int i = 0; i < blocks; i++) {
full_block_function(data + i * block_bytes);
}
/* Swap each block line in the first half of the image with the
* corresponding one in the second half.
* note that this is a no-op if mip height is 4. */
size_t row_bytes = block_bytes * blocks_per_row;
uint8_t *temp_line = new uint8_t[row_bytes];
for (int y = 0; y < blocks_per_col / 2; y++) {
uint8_t *line1 = data + y * row_bytes;
uint8_t *line2 = data + (blocks_per_col - y - 1) * row_bytes;
memcpy(temp_line, line1, row_bytes);
memcpy(line1, line2, row_bytes);
memcpy(line2, temp_line, row_bytes);
}
delete[] temp_line;
}
/* Advance to next mip level. */
data += block_bytes * blocks;
mip_width = std::max(1, mip_width >> 1);
mip_height = std::max(1, mip_height >> 1);
}
return levels;
}
uint8_t *imb_load_dds_compressed_data(const char *filepath, int width, int height, int &r_mipcount)
{
r_mipcount = 0;
/* Due to upside down flipping, we only support compressed DDS files with power of two heights.
*/
if (!is_power_of_2_i(height)) {
return nullptr;
}
const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
if (file == -1) {
return nullptr;
}
BLI_mmap_file *mmap_file = BLI_mmap_open(file);
close(file);
if (mmap_file == nullptr) {
return nullptr;
}
uint8_t *result = nullptr;
const uchar *file_data = static_cast<const uchar *>(BLI_mmap_get_pointer(mmap_file));
const size_t file_size = BLI_mmap_get_length(mmap_file);
constexpr size_t dds_header_size = 128;
if (file_size < dds_header_size) {
BLI_mmap_free(mmap_file);
return nullptr;
}
/* Pull out pixel format and mipmap count flags from DDS header. */
uint32_t flags = 0, mipcount = 0, fourcc = 0;
memcpy(&flags, file_data + 8, 4);
memcpy(&mipcount, file_data + 28, 4);
memcpy(&fourcc, file_data + 84, 4);
/* Due to upside down flipping, we only support DXT1/DXT3/DXT5. Newer formats like BC7 can't
* be flipped upside down due to non-symmetrical partition shapes. */
if (ELEM(fourcc, fourcc_dxt1, fourcc_dxt3, fourcc_dxt5)) {
constexpr uint32_t DDSD_MIPMAPCOUNT = 0x00020000U;
if ((flags & DDSD_MIPMAPCOUNT) == 0) {
mipcount = 1;
}
size_t pixel_data_size = file_size - dds_header_size;
result = MEM_new_array_uninitialized<uint8_t>(pixel_data_size, "DDS compressed data");
memcpy(result, file_data + dds_header_size, pixel_data_size);
r_mipcount = flip_dxtc_image(result, pixel_data_size, width, height, mipcount, fourcc);
}
BLI_mmap_free(mmap_file);
return result;
}
} // namespace blender

View File

@@ -0,0 +1,107 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "oiio/openimageio_support.hh"
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_dpx[] = {".dpx", nullptr};
const char *imb_file_extensions_cineon[] = {".cin", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_dpx(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "dpx");
}
ImBuf *imb_load_dpx(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
ReadContext ctx{mem, size, "dpx", IMB_FTYPE_DPX, flags};
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
if (ibuf) {
if (flag_is_set(flags, ImBufFlags::AlphaDetect)) {
ibuf->flags |= ImBufFlags::AlphaPremul;
}
}
r_colorspace.is_hdr_float = true;
return ibuf;
}
static std::tuple<WriteContext, ImageSpec> prepare_save_dpx(ImBuf *ibuf, ImBufFlags flags)
{
int bits_per_sample = 8;
if (ibuf->foptions.flag & CINEON_10BIT) {
bits_per_sample = 10;
}
else if (ibuf->foptions.flag & CINEON_12BIT) {
bits_per_sample = 12;
}
else if (ibuf->foptions.flag & CINEON_16BIT) {
bits_per_sample = 16;
}
const int file_channels = ibuf->color_mode_channels_get();
const TypeDesc data_format = bits_per_sample == 8 ? TypeDesc::UINT8 : TypeDesc::UINT16;
WriteContext ctx = imb_create_write_context("dpx", ibuf, flags);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
const float max_value = powf(2, bits_per_sample) - 1.0f;
file_spec.attribute("oiio:BitsPerSample", bits_per_sample);
file_spec.attribute("dpx:WhiteLevel", 685.0f / 1023.0f * max_value);
file_spec.attribute("dpx:BlackLevel", 95.0f / 1023.0f * max_value);
file_spec.attribute("dpx:HighData", max_value);
file_spec.attribute("dpx:LowData", 0);
file_spec.attribute("dpx:LowQuantity", 0.0f);
if (ibuf->foptions.flag & CINEON_LOG) {
/* VERIFY: This matches previous code but seems odd. Needs a comment if confirmed. */
file_spec.attribute("dpx:Transfer", "Printing density");
file_spec.attribute("dpx:HighQuantity", 2.048f);
}
else {
file_spec.attribute("dpx:Transfer", "Linear");
file_spec.attribute("dpx:HighQuantity", max_value);
}
if (ELEM(bits_per_sample, 8, 16)) {
file_spec.attribute("dpx:Packing", "Packed");
}
else {
file_spec.attribute("dpx:Packing", "Filled, method A");
}
return {ctx, file_spec};
}
bool imb_save_dpx(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_dpx(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_dpx(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_dpx(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "oiio/openimageio_support.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_hdr[] = {".hdr", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_hdr(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "hdr");
}
ImBuf *imb_load_hdr(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
ReadContext ctx{mem, size, "hdr", IMB_FTYPE_RADHDR, flags};
/* Always create ImBufs with a 4th alpha channel despite the format only supporting 3. */
ctx.use_all_planes = true;
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
if (ibuf) {
if (flag_is_set(flags, ImBufFlags::AlphaDetect)) {
ibuf->flags |= ImBufFlags::AlphaPremul;
}
if (flag_is_set(flags, ImBufFlags::ByteData)) {
IMB_byte_from_float(ibuf);
}
}
return ibuf;
}
static std::tuple<WriteContext, ImageSpec> prepare_save_hdr(ImBuf *ibuf, ImBufFlags flags)
{
const int file_channels = 3;
const TypeDesc data_format = TypeDesc::FLOAT;
WriteContext ctx = imb_create_write_context("hdr", ibuf, flags);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
return {ctx, file_spec};
}
bool imb_save_hdr(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_hdr(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_hdr(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_hdr(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

View File

@@ -0,0 +1,75 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*
* The SGI Image File Format.
* https://en.wikipedia.org/wiki/Silicon_Graphics_Image
*
* \note this format uses big-endian values.
*/
#include "oiio/openimageio_support.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
const char *imb_file_extensions_iris[] = {".sgi", ".rgb", ".rgba", ".bw", nullptr};
bool imb_is_a_iris(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "rgb");
}
ImBuf *imb_loadiris(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{mem, size, "rgb", IMB_FTYPE_IRIS, flags};
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
/* Both 8 and 16 bit iris should be in default byte colorspace. */
r_colorspace.is_hdr_float = false;
return ibuf;
}
static std::tuple<WriteContext, ImageSpec> prepare_save_iris(ImBuf *ibuf, ImBufFlags flags)
{
const int file_channels = ibuf->color_mode_channels_get();
const TypeDesc data_format = TypeDesc::UINT8;
WriteContext ctx = imb_create_write_context("rgb", ibuf, flags, false);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
/* Always use RLE compression to match historical behavior. */
file_spec.attribute("compression", "rle");
return {ctx, file_spec};
}
bool imb_saveiris(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_iris(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_iris(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_iris(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,827 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
/* This little block needed for linking to Blender... */
#include <algorithm>
#include <csetjmp>
#include <cstdio>
#include "MEM_guardedalloc.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BKE_idprop.hh"
#include "DNA_ID.h" /* ID property definitions. */
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_metadata.hh"
#include "CLG_log.h"
#include <cstring>
#include <jerror.h>
#include <jpeglib.h>
namespace blender {
const char *imb_file_extensions_jpeg[] = {".jpg", ".jpeg", nullptr};
static CLG_LogRef LOG = {"image.jpeg"};
/* the types are from the jpeg lib */
static void jpeg_error(j_common_ptr cinfo) ATTR_NORETURN;
static void init_source(j_decompress_ptr cinfo);
static boolean fill_input_buffer(j_decompress_ptr cinfo);
static void skip_input_data(j_decompress_ptr cinfo, long num_bytes);
static void term_source(j_decompress_ptr cinfo);
static void memory_source(j_decompress_ptr cinfo, const uchar *buffer, size_t size);
static boolean handle_app1(j_decompress_ptr cinfo);
static const uchar jpeg_default_quality = 75;
static uchar ibuf_quality;
/* -------------------------------------------------------------------- */
/** \name JPG Magic Check
* \{ */
bool imb_is_a_jpeg(const uchar *mem, const size_t size)
{
const char magic[2] = {0xFF, 0xD8};
if (size < sizeof(magic)) {
return false;
}
return memcmp(mem, magic, sizeof(magic)) == 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name JPG Error Handling
* \{ */
struct my_error_mgr {
jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
};
using my_error_ptr = my_error_mgr *;
static void jpeg_error(j_common_ptr cinfo)
{
my_error_ptr err = reinterpret_cast<my_error_ptr>(cinfo->err);
/* Always display the message */
(*cinfo->err->output_message)(cinfo);
/* Let the memory manager delete any temp files before we die */
jpeg_destroy(cinfo);
/* return control to the setjmp point */
longjmp(err->setjmp_buffer, 1);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Input Handler from Memory
* \{ */
struct my_source_mgr {
jpeg_source_mgr pub; /* public fields */
const uchar *buffer;
int size;
JOCTET terminal[2];
};
using my_src_ptr = my_source_mgr *;
static void init_source(j_decompress_ptr cinfo)
{
(void)cinfo; /* unused */
}
static boolean fill_input_buffer(j_decompress_ptr cinfo)
{
my_src_ptr src = reinterpret_cast<my_src_ptr>(cinfo->src);
/* Since we have given all we have got already
* we simply fake an end of file
*/
src->pub.next_input_byte = src->terminal;
src->pub.bytes_in_buffer = 2;
src->terminal[0] = JOCTET(0xFF);
src->terminal[1] = JOCTET(JPEG_EOI);
return true;
}
static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
my_src_ptr src = reinterpret_cast<my_src_ptr>(cinfo->src);
if (num_bytes > 0) {
/* prevent skipping over file end */
size_t skip_size = size_t(num_bytes) <= src->pub.bytes_in_buffer ? num_bytes :
src->pub.bytes_in_buffer;
src->pub.next_input_byte = src->pub.next_input_byte + skip_size;
src->pub.bytes_in_buffer = src->pub.bytes_in_buffer - skip_size;
}
}
static void term_source(j_decompress_ptr cinfo)
{
(void)cinfo; /* unused */
}
static void memory_source(j_decompress_ptr cinfo, const uchar *buffer, size_t size)
{
my_src_ptr src;
if (cinfo->src == nullptr) { /* first time for this JPEG object? */
cinfo->src = static_cast<jpeg_source_mgr *>((*cinfo->mem->alloc_small)(
reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(my_source_mgr)));
}
src = reinterpret_cast<my_src_ptr>(cinfo->src);
src->pub.init_source = init_source;
src->pub.fill_input_buffer = fill_input_buffer;
src->pub.skip_input_data = skip_input_data;
src->pub.resync_to_restart = jpeg_resync_to_restart;
src->pub.term_source = term_source;
src->pub.bytes_in_buffer = size;
src->pub.next_input_byte = buffer;
src->buffer = buffer;
src->size = size;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name APP1 Marker Handling
* \{ */
#define MAKESTMT(stuff) \
do { \
stuff \
} while (0)
#define INPUT_VARS(cinfo) \
jpeg_source_mgr *datasrc = (cinfo)->src; \
const JOCTET *next_input_byte = datasrc->next_input_byte; \
size_t bytes_in_buffer = datasrc->bytes_in_buffer
/* Unload the local copies --- do this only at a restart boundary */
#define INPUT_SYNC(cinfo) \
(datasrc->next_input_byte = next_input_byte, datasrc->bytes_in_buffer = bytes_in_buffer)
/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */
#define INPUT_RELOAD(cinfo) \
(next_input_byte = datasrc->next_input_byte, bytes_in_buffer = datasrc->bytes_in_buffer)
/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available.
* Note we do *not* do INPUT_SYNC before calling fill_input_buffer,
* but we must reload the local copies after a successful fill.
*/
#define MAKE_BYTE_AVAIL(cinfo, action) \
if (bytes_in_buffer == 0) { \
if (!(*datasrc->fill_input_buffer)(cinfo)) { \
action; \
} \
INPUT_RELOAD(cinfo); \
} \
(void)0
/* Read a byte into variable V.
* If must suspend, take the specified action (typically "return false").
*/
#define INPUT_BYTE(cinfo, V, action) \
MAKESTMT(MAKE_BYTE_AVAIL(cinfo, action); bytes_in_buffer--; V = GETJOCTET(*next_input_byte++);)
/* As above, but read two bytes interpreted as an unsigned 16-bit integer.
* V should be declared `uint` or perhaps INT32.
*/
#define INPUT_2BYTES(cinfo, V, action) \
MAKESTMT(MAKE_BYTE_AVAIL(cinfo, action); bytes_in_buffer--; \
V = uint(GETJOCTET(*next_input_byte++)) << 8; \
MAKE_BYTE_AVAIL(cinfo, action); \
bytes_in_buffer--; \
V += GETJOCTET(*next_input_byte++);)
struct NeoGeo_Word {
uchar pad1;
uchar pad2;
uchar pad3;
uchar quality;
};
BLI_STATIC_ASSERT(sizeof(NeoGeo_Word) == 4, "Must be 4 bytes");
static boolean handle_app1(j_decompress_ptr cinfo)
{
INT32 length; /* initialized by the macro */
INT32 i;
char neogeo[128];
INPUT_VARS(cinfo);
INPUT_2BYTES(cinfo, length, return false);
length -= 2;
if (length < 16) {
for (i = 0; i < length; i++) {
INPUT_BYTE(cinfo, neogeo[i], return false);
}
length = 0;
if (STRPREFIX(neogeo, "NeoGeo")) {
NeoGeo_Word *neogeo_word = reinterpret_cast<NeoGeo_Word *>(neogeo + 6);
ibuf_quality = neogeo_word->quality;
}
}
INPUT_SYNC(cinfo); /* do before skip_input_data */
if (length > 0) {
(*cinfo->src->skip_input_data)(cinfo, length);
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Load JPG Image
* \{ */
static ImBuf *ibJpegImageFromCinfo(jpeg_decompress_struct *cinfo,
ImBufFlags flags,
int max_size,
size_t *r_width,
size_t *r_height)
{
JSAMPARRAY row_pointer;
JSAMPLE *buffer = nullptr;
int row_stride;
int x, y, depth, r, g, b, k;
ImBuf *ibuf = nullptr;
uchar *rect;
jpeg_saved_marker_ptr marker;
char *str, *key, *value;
/* install own app1 handler */
ibuf_quality = jpeg_default_quality;
jpeg_set_marker_processor(cinfo, 0xe1, handle_app1);
cinfo->dct_method = JDCT_FLOAT;
jpeg_save_markers(cinfo, JPEG_COM, 0xffff);
if (jpeg_read_header(cinfo, false) == JPEG_HEADER_OK) {
depth = cinfo->num_components;
if (cinfo->jpeg_color_space == JCS_YCCK) {
cinfo->out_color_space = JCS_CMYK;
}
if (r_width) {
*r_width = cinfo->image_width;
}
if (r_height) {
*r_height = cinfo->image_height;
}
if (max_size > 0) {
/* `libjpeg` can more quickly decompress while scaling down to 1/2, 1/4, 1/8,
* while `libjpeg-turbo` can also do 3/8, 5/8, etc. But max is 1/8. */
float scale = float(max_size) / std::max(cinfo->image_width, cinfo->image_height);
cinfo->scale_denom = 8;
cinfo->scale_num = max_uu(1, min_uu(8, ceill(scale * float(cinfo->scale_denom))));
cinfo->dct_method = JDCT_FASTEST;
cinfo->dither_mode = JDITHER_ORDERED;
}
jpeg_start_decompress(cinfo);
x = cinfo->output_width;
y = cinfo->output_height;
ImColorMode color_mode = ImColorMode::RGBA;
if (depth == 1) {
color_mode = ImColorMode::BW;
}
else if (depth == 3) {
color_mode = ImColorMode::RGB;
}
if (flag_is_set(flags, ImBufFlags::Test)) {
jpeg_abort_decompress(cinfo);
ibuf = IMB_allocImBuf(x, y, ImBufFlags::Zero);
if (ibuf) {
ibuf->color_mode = color_mode;
}
}
else if ((ibuf = IMB_allocImBuf(
x, y, ImBufFlags::ByteData | ImBufFlags::UninitializedPixels)) == nullptr)
{
jpeg_abort_decompress(cinfo);
}
else {
ibuf->color_mode = color_mode;
row_stride = cinfo->output_width * depth;
row_pointer = (*cinfo->mem->alloc_sarray)(
reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, row_stride, 1);
uchar *byte_data = ibuf->byte_data_for_write();
for (y = ibuf->y - 1; y >= 0; y--) {
jpeg_read_scanlines(cinfo, row_pointer, 1);
rect = byte_data + 4 * y * size_t(ibuf->x);
buffer = row_pointer[0];
switch (depth) {
case 1:
for (x = ibuf->x; x > 0; x--) {
rect[3] = 255;
rect[0] = rect[1] = rect[2] = *buffer++;
rect += 4;
}
break;
case 3:
for (x = ibuf->x; x > 0; x--) {
rect[3] = 255;
rect[0] = *buffer++;
rect[1] = *buffer++;
rect[2] = *buffer++;
rect += 4;
}
break;
case 4:
for (x = ibuf->x; x > 0; x--) {
r = *buffer++;
g = *buffer++;
b = *buffer++;
k = *buffer++;
r = (r * k) / 255;
g = (g * k) / 255;
b = (b * k) / 255;
rect[3] = 255;
rect[2] = b;
rect[1] = g;
rect[0] = r;
rect += 4;
}
break;
}
}
marker = cinfo->marker_list;
while (marker) {
if (marker->marker != JPEG_COM) {
goto next_stamp_marker;
}
/*
* JPEG marker strings are not meant to be null-terminated,
* create a null-terminated copy before going further.
*
* Files saved from Blender pre v4.0 were null terminated,
* use `BLI_strnlen` to prevent assertion on passing in too short a string. */
str = BLI_strdupn(
reinterpret_cast<const char *>(marker->data),
BLI_strnlen(reinterpret_cast<const char *>(marker->data), marker->data_length));
/*
* Because JPEG format don't support the
* pair "key/value" like PNG, we store the
* stamp-info in a single "encode" string:
* "Blender:key:value"
*
* That is why we need split it to the
* common key/value here.
*/
if (!STRPREFIX(str, "Blender")) {
/*
* Maybe the file have text that
* we don't know "what it's", in that
* case we keep the text (with a
* key "None").
* This is only for don't "lose"
* the information when we write
* it back to disk.
*/
IMB_metadata_ensure(&ibuf->metadata);
IMB_metadata_set_field(ibuf->metadata, "None", str);
ibuf->flags |= ImBufFlags::Metadata;
MEM_delete(str);
goto next_stamp_marker;
}
key = strchr(str, ':');
/*
* A little paranoid, but the file maybe
* is broken... and a "extra" check is better then a segfault :)
*/
if (!key) {
MEM_delete(str);
goto next_stamp_marker;
}
key++;
value = strchr(key, ':');
if (!value) {
MEM_delete(str);
goto next_stamp_marker;
}
*value = '\0'; /* need finish the key string */
value++;
IMB_metadata_ensure(&ibuf->metadata);
IMB_metadata_set_field(ibuf->metadata, key, value);
ibuf->flags |= ImBufFlags::Metadata;
MEM_delete(str);
next_stamp_marker:
marker = marker->next;
}
jpeg_finish_decompress(cinfo);
}
if (ibuf) {
/* Density_unit may be 0 for unknown, 1 for dots/inch, or 2 for dots/cm. */
if (cinfo->density_unit == 1) {
/* Convert inches to meters. */
ibuf->ppm[0] = double(cinfo->X_density) / 0.0254;
ibuf->ppm[1] = double(cinfo->Y_density) / 0.0254;
}
else if (cinfo->density_unit == 2) {
ibuf->ppm[0] = double(cinfo->X_density) * 100.0;
ibuf->ppm[1] = double(cinfo->Y_density) * 100.0;
}
ibuf->ftype = IMB_FTYPE_JPG;
ibuf->foptions.quality = std::min<char>(ibuf_quality, 100);
}
jpeg_destroy(reinterpret_cast<j_common_ptr>(cinfo));
}
return ibuf;
}
ImBuf *imb_load_jpeg(const uchar *buffer,
size_t size,
ImBufFlags flags,
ImFileColorSpace & /*r_colorspace*/)
{
jpeg_decompress_struct _cinfo, *cinfo = &_cinfo;
my_error_mgr jerr;
ImBuf *ibuf;
if (!imb_is_a_jpeg(buffer, size)) {
return nullptr;
}
cinfo->err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpeg_error;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp(jerr.setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error.
* We need to clean up the JPEG object, close the input file, and return.
*/
jpeg_destroy_decompress(cinfo);
return nullptr;
}
jpeg_create_decompress(cinfo);
memory_source(cinfo, buffer, size);
ibuf = ibJpegImageFromCinfo(cinfo, flags, -1, nullptr, nullptr);
return ibuf;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Load JPG Thumbnail
* \{ */
/* Defines for JPEG Header markers and segment size. */
#define JPEG_MARKER_MSB (0xFF)
#define JPEG_MARKER_SOI (0xD8)
#define JPEG_MARKER_APP1 (0xE1)
#define JPEG_APP1_MAX (1 << 16)
ImBuf *imb_thumbnail_jpeg(const char *filepath,
const ImBufFlags flags,
const size_t max_thumb_size,
ImFileColorSpace &r_colorspace,
size_t *r_width,
size_t *r_height)
{
jpeg_decompress_struct _cinfo, *cinfo = &_cinfo;
my_error_mgr jerr;
FILE *infile = nullptr;
cinfo->err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpeg_error;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp(jerr.setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error.
* We need to clean up the JPEG object, close the input file, and return.
*/
jpeg_destroy_decompress(cinfo);
return nullptr;
}
if ((infile = BLI_fopen(filepath, "rb")) == nullptr) {
CLOG_ERROR(&LOG, "Cannot open \"%s\"", filepath);
return nullptr;
}
/* If file contains an embedded thumbnail, let's return that instead. */
if ((fgetc(infile) == JPEG_MARKER_MSB) && (fgetc(infile) == JPEG_MARKER_SOI) &&
(fgetc(infile) == JPEG_MARKER_MSB) && (fgetc(infile) == JPEG_MARKER_APP1))
{
/* This is a JPEG in EXIF format (SOI + APP1), not JFIF (SOI + APP0). */
uint i = JPEG_APP1_MAX;
/* All EXIF data is within this 64K header segment. Skip ahead until next SOI for thumbnail. */
while (!((fgetc(infile) == JPEG_MARKER_MSB) && (fgetc(infile) == JPEG_MARKER_SOI)) &&
!feof(infile) && i--)
{
}
if (i > 0 && !feof(infile)) {
/* We found a JPEG thumbnail inside this image. */
ImBuf *ibuf = nullptr;
uchar *buffer = MEM_new_array_zeroed<uchar>(JPEG_APP1_MAX, "thumbbuffer");
/* Just put SOI directly in buffer rather than seeking back 2 bytes. */
buffer[0] = JPEG_MARKER_MSB;
buffer[1] = JPEG_MARKER_SOI;
if (fread(buffer + 2, JPEG_APP1_MAX - 2, 1, infile) == 1) {
ibuf = imb_load_jpeg(buffer, JPEG_APP1_MAX, flags, r_colorspace);
}
MEM_SAFE_DELETE(buffer);
if (ibuf) {
fclose(infile);
return ibuf;
}
}
}
/* No embedded thumbnail found, so let's create a new one. */
fseek(infile, 0, SEEK_SET);
jpeg_create_decompress(cinfo);
jpeg_stdio_src(cinfo, infile);
ImBuf *ibuf = ibJpegImageFromCinfo(cinfo, flags, max_thumb_size, r_width, r_height);
fclose(infile);
return ibuf;
}
#undef JPEG_MARKER_MSB
#undef JPEG_MARKER_SOI
#undef JPEG_MARKER_APP1
#undef JPEG_APP1_MAX
/** \} */
/* -------------------------------------------------------------------- */
/** \name Save JPG Image
* \{ */
/* `libjpeg` has a maximum comment/marker length of 65533, however it does not provide a definition
* for that, so we hard-code it ourselves. */
#define MAX_LIBJPEG_MARKER_LENGTH 65533
static void write_jpeg(jpeg_compress_struct *cinfo, ImBuf *ibuf)
{
JSAMPLE *buffer = nullptr;
JSAMPROW row_pointer[1];
int x, y;
char neogeo[128];
NeoGeo_Word *neogeo_word;
jpeg_start_compress(cinfo, true);
STRNCPY_UTF8(neogeo, "NeoGeo");
neogeo_word = reinterpret_cast<NeoGeo_Word *>(neogeo + 6);
memset(neogeo_word, 0, sizeof(*neogeo_word));
neogeo_word->quality = ibuf->foptions.quality;
jpeg_write_marker(cinfo, 0xe1, reinterpret_cast<JOCTET *>(neogeo), 10);
if (ibuf->metadata) {
/* Static storage array for the short metadata. */
char static_text[1024];
const size_t static_text_size = ARRAY_SIZE(static_text);
for (IDProperty &prop : ibuf->metadata->data.group) {
if (prop.type == IDP_STRING) {
size_t text_len;
if (STREQ(prop.name, "None")) {
jpeg_write_marker(
cinfo, JPEG_COM, reinterpret_cast<JOCTET *> IDP_string_get(&prop), prop.len);
}
char *text = static_text;
size_t text_size = static_text_size;
/* 7 is for Blender, 2 colon separators, length of property
* name and property value, followed by the nullptr-terminator
* which isn't needed by JPEG but #BLI_snprintf_rlen requires it. */
const size_t text_length_required = 7 + 2 + strlen(prop.name) +
strlen(IDP_string_get(&prop)) + 1;
if (text_length_required > static_text_size) {
text = MEM_new_array_uninitialized<char>(text_length_required, "jpeg metadata field");
text_size = text_length_required;
}
/*
* The JPEG format don't support a pair "key/value"
* like PNG, so we "encode" the stamp in a
* single string:
* "Blender:key:value"
*
* The first "Blender" is a simple identify to help
* in the read process.
*/
text_len = BLI_snprintf_utf8_rlen(
text, text_size, "Blender:%s:%s", prop.name, IDP_string_get(&prop));
/* Truncate the data if it does not fit in a single marker, as giving a buffer to `libjpeg`
* bigger that #MAX_LIBJPEG_MARKER_LENGTH will result in the JPEG file not being written.
* See #158751.
*/
if (text_len > MAX_LIBJPEG_MARKER_LENGTH) {
CLOG_WARN(&LOG, "Writing truncated data for \"%s\"", prop.name);
text_len = MAX_LIBJPEG_MARKER_LENGTH;
}
/* Don't write the null byte (not expected by the JPEG format). */
jpeg_write_marker(cinfo, JPEG_COM, reinterpret_cast<JOCTET *>(text), uint(text_len));
/* TODO(sergey): Ideally we will try to re-use allocation as
* much as possible. In practice, such long fields don't happen
* often. */
if (text != static_text) {
MEM_delete(text);
}
}
}
}
/* Write ICC profile if there is one associated with the colorspace. */
const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
if (colorspace) {
Vector<char> icc_profile = IMB_colormanagement_space_to_icc_profile(colorspace);
if (!icc_profile.is_empty()) {
icc_profile.prepend({'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', 0, 0, 1});
jpeg_write_marker(cinfo,
JPEG_APP0 + 2,
reinterpret_cast<const JOCTET *>(icc_profile.data()),
icc_profile.size());
}
}
row_pointer[0] = MEM_new_array_uninitialized<std::remove_pointer_t<JSAMPROW>>(
size_t(cinfo->input_components) * size_t(cinfo->image_width), "jpeg row_pointer");
const uchar *byte_data = ibuf->byte_data();
for (y = ibuf->y - 1; y >= 0; y--) {
const uchar *rect = byte_data + 4 * y * size_t(ibuf->x);
buffer = row_pointer[0];
switch (cinfo->in_color_space) {
case JCS_RGB:
for (x = 0; x < ibuf->x; x++) {
*buffer++ = rect[0];
*buffer++ = rect[1];
*buffer++ = rect[2];
rect += 4;
}
break;
case JCS_GRAYSCALE:
for (x = 0; x < ibuf->x; x++) {
*buffer++ = rect[0];
rect += 4;
}
break;
case JCS_UNKNOWN:
memcpy(buffer, rect, 4 * ibuf->x);
break;
/* default was missing... intentional ? */
default:
/* do nothing */
break;
}
jpeg_write_scanlines(cinfo, row_pointer, 1);
}
jpeg_finish_compress(cinfo);
MEM_delete(row_pointer[0]);
}
static int init_jpeg(FILE *outfile, jpeg_compress_struct *cinfo, ImBuf *ibuf)
{
int quality;
quality = ibuf->foptions.quality;
if (quality <= 0) {
quality = jpeg_default_quality;
}
quality = std::min(quality, 100);
jpeg_create_compress(cinfo);
jpeg_stdio_dest(cinfo, outfile);
cinfo->image_width = ibuf->x;
cinfo->image_height = ibuf->y;
cinfo->in_color_space = JCS_RGB;
if (ibuf->color_mode == ImColorMode::BW) {
cinfo->in_color_space = JCS_GRAYSCALE;
}
switch (cinfo->in_color_space) {
case JCS_RGB:
cinfo->input_components = 3;
break;
case JCS_GRAYSCALE:
cinfo->input_components = 1;
break;
case JCS_UNKNOWN:
cinfo->input_components = 4;
break;
/* default was missing... intentional ? */
default:
/* do nothing */
break;
}
jpeg_set_defaults(cinfo);
/* own settings */
cinfo->dct_method = JDCT_FLOAT;
jpeg_set_quality(cinfo, quality, true);
return 0;
}
static bool save_stdjpeg(const char *filepath, ImBuf *ibuf)
{
FILE *outfile;
jpeg_compress_struct _cinfo, *cinfo = &_cinfo;
my_error_mgr jerr;
if ((outfile = BLI_fopen(filepath, "wb")) == nullptr) {
return false;
}
cinfo->err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpeg_error;
/* Establish the setjmp return context for jpeg_error to use. */
if (setjmp(jerr.setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error.
* We need to clean up the JPEG object, close the input file, and return.
*/
jpeg_destroy_compress(cinfo);
fclose(outfile);
remove(filepath);
return false;
}
init_jpeg(outfile, cinfo, ibuf);
write_jpeg(cinfo, ibuf);
fclose(outfile);
jpeg_destroy_compress(cinfo);
return true;
}
bool imb_savejpeg(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
ibuf->flags = flags;
return save_stdjpeg(filepath, ibuf);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "oiio/openimageio_support.hh"
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_png[] = {".png", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_png(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "png");
}
ImBuf *imb_load_png(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{mem, size, "png", IMB_FTYPE_PNG, flags};
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
if (ibuf) {
if (spec.format == TypeDesc::UINT16) {
ibuf->foptions.flag |= PNG_16BIT;
}
}
/* Both 8 and 16 bit PNGs should be in default byte colorspace. */
r_colorspace.is_hdr_float = false;
return ibuf;
}
static std::tuple<WriteContext, ImageSpec> prepare_save_png(ImBuf *ibuf, ImBufFlags flags)
{
const bool is_16bit = (ibuf->foptions.flag & PNG_16BIT);
const int file_channels = ibuf->color_mode_channels_get();
const TypeDesc data_format = is_16bit ? TypeDesc::UINT16 : TypeDesc::UINT8;
WriteContext ctx = imb_create_write_context("png", ibuf, flags, is_16bit);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
/* Skip if the float buffer was managed already. */
if (is_16bit && (ibuf->float_buffer.colorspace || ibuf->colorspace_is_data())) {
file_spec.attribute("oiio:UnassociatedAlpha", 0);
}
else {
file_spec.attribute("oiio:UnassociatedAlpha", 1);
}
int compression = int(float(ibuf->foptions.compress) / 11.1111f);
compression = compression < 0 ? 0 : (compression > 9 ? 9 : compression);
file_spec.attribute("png:compressionLevel", compression);
return {ctx, file_spec};
}
bool imb_save_png(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_png(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_png(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_png(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "oiio/openimageio_support.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_psd[] = {".psd", ".pdd", ".psb", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_psd(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "psd");
}
ImBuf *imb_load_psd(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{mem, size, "psd", IMB_FTYPE_PSD, flags};
/* PSD should obey color space information embedded in the file. */
ctx.use_metadata_colorspace = true;
return imb_oiio_read(ctx, config, r_colorspace, spec);
}
} // namespace blender

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*
* SVG vector graphics format support for the purpose of thumbnail-display.
* While loading these as an #ImBuf is trivial to support, it would expose
* limitations of NANOSVG and users may end up needing more advanced options
* specific to loading vector graphics (such as resolution control), see #109567 for details.
*/
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
#include "nanosvgrast.h"
namespace blender {
ImBuf *imb_load_filepath_thumbnail_svg(const char *filepath,
const ImBufFlags /*flags*/,
const size_t max_thumb_size,
ImFileColorSpace & /*r_colorspace*/,
size_t *r_width,
size_t *r_height)
{
NSVGimage *image = nsvgParseFromFile(filepath, "px", 96.0f);
if (image == nullptr) {
return nullptr;
}
if (image->width == 0 || image->height == 0) {
nsvgDelete(image);
return nullptr;
}
int w = int(image->width);
int h = int(image->height);
/* Return full size of the image. */
*r_width = size_t(w);
*r_height = size_t(h);
NSVGrasterizer *rast = nsvgCreateRasterizer();
if (rast == nullptr) {
nsvgDelete(image);
return nullptr;
}
const float scale = float(max_thumb_size) / std::max(w, h);
const int dest_w = std::max(int(w * scale), 1);
const int dest_h = std::max(int(h * scale), 1);
ImBuf *ibuf = IMB_allocImBuf(dest_w, dest_h, ImBufFlags::ByteData);
if (ibuf != nullptr) {
nsvgRasterize(
rast, image, 0, 0, scale, ibuf->byte_data_for_write(), dest_w, dest_h, dest_w * 4);
IMB_flipy(ibuf);
}
nsvgDeleteRasterizer(rast);
nsvgDelete(image);
return ibuf;
}
} // namespace blender

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "oiio/openimageio_support.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_tga[] = {".tga", ".tpic", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_tga(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "tga");
}
ImBuf *imb_load_tga(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{mem, size, "tga", IMB_FTYPE_TGA, flags};
return imb_oiio_read(ctx, config, r_colorspace, spec);
}
static std::tuple<WriteContext, ImageSpec> prepare_save_tga(ImBuf *ibuf, ImBufFlags flags)
{
const int file_channels = ibuf->color_mode_channels_get();
const TypeDesc data_format = TypeDesc::UINT8;
WriteContext ctx = imb_create_write_context("tga", ibuf, flags, false);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
file_spec.attribute("oiio:UnassociatedAlpha", 1);
file_spec.attribute("compression", (ibuf->foptions.flag & RAWTGA) ? "none" : "rle");
return {ctx, file_spec};
}
bool imb_save_tga(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_tga(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_tga(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_tga(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "oiio/openimageio_support.hh"
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
const char *imb_file_extensions_tiff[] = {".tif", ".tiff", ".tx", nullptr};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_tiff(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "tif");
}
ImBuf *imb_load_tiff(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{mem, size, "tif", IMB_FTYPE_TIF, flags};
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
if (ibuf) {
if (flag_is_set(flags, ImBufFlags::AlphaDetect)) {
if (spec.nchannels == 4 && spec.format == TypeDesc::UINT16) {
ibuf->flags |= ImBufFlags::AlphaPremul;
}
}
}
/* All TIFFs should be in default byte colorspace. */
r_colorspace.is_hdr_float = false;
return ibuf;
}
static std::tuple<WriteContext, ImageSpec> prepare_save_tiff(ImBuf *ibuf, ImBufFlags flags)
{
const bool is_16bit = ((ibuf->foptions.flag & TIF_16BIT) && ibuf->float_data());
const int file_channels = ibuf->color_mode_channels_get();
const TypeDesc data_format = is_16bit ? TypeDesc::UINT16 : TypeDesc::UINT8;
WriteContext ctx = imb_create_write_context("tif", ibuf, flags, is_16bit);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
if (is_16bit && file_channels == 4) {
file_spec.attribute("oiio:UnassociatedAlpha", 0);
}
else {
file_spec.attribute("oiio:UnassociatedAlpha", 1);
}
if (ibuf->foptions.flag & TIF_COMPRESS_DEFLATE) {
file_spec.attribute("compression", "zip");
}
else if (ibuf->foptions.flag & TIF_COMPRESS_LZW) {
file_spec.attribute("compression", "lzw");
}
else if (ibuf->foptions.flag & TIF_COMPRESS_PACKBITS) {
file_spec.attribute("compression", "packbits");
}
else if (ibuf->foptions.flag & TIF_COMPRESS_NONE) {
file_spec.attribute("compression", "none");
}
return {ctx, file_spec};
}
bool imb_save_tiff(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_tiff(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_tiff(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_tiff(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

View File

@@ -0,0 +1,175 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#ifdef _WIN32
# include <io.h>
#else
# include <unistd.h>
#endif
#include <fcntl.h>
#include <string>
#include <webp/decode.h>
#include "oiio/openimageio_support.hh"
#include "BLI_fileops.h"
#include "BLI_mmap.h"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "CLG_log.h"
namespace blender {
const char *imb_file_extensions_webp[] = {".webp", nullptr};
static CLG_LogRef LOG = {"image.webp"};
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
bool imb_is_a_webp(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "webp");
}
ImBuf *imb_loadwebp(const uchar *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace)
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{mem, size, "webp", IMB_FTYPE_WEBP, flags};
ImBuf *ibuf = imb_oiio_read(ctx, config, r_colorspace, spec);
r_colorspace.is_hdr_float = false;
return ibuf;
}
ImBuf *imb_load_filepath_thumbnail_webp(const char *filepath,
const ImBufFlags /*flags*/,
const size_t max_thumb_size,
ImFileColorSpace & /*r_colorspace*/,
size_t *r_width,
size_t *r_height)
{
const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
if (file == -1) {
return nullptr;
}
BLI_mmap_file *mmap_file = BLI_mmap_open(file);
close(file);
if (mmap_file == nullptr) {
return nullptr;
}
const uchar *data = static_cast<const uchar *>(BLI_mmap_get_pointer(mmap_file));
const size_t data_size = BLI_mmap_get_length(mmap_file);
WebPDecoderConfig config;
if (!data || !WebPInitDecoderConfig(&config) ||
WebPGetFeatures(data, data_size, &config.input) != VP8_STATUS_OK ||
BLI_mmap_any_io_error(mmap_file))
{
CLOG_ERROR(&LOG, "Invalid file");
BLI_mmap_free(mmap_file);
return nullptr;
}
/* Return full size of the image. */
*r_width = size_t(config.input.width);
*r_height = size_t(config.input.height);
const float scale = float(max_thumb_size) / std::max(config.input.width, config.input.height);
const int dest_w = std::max(int(config.input.width * scale), 1);
const int dest_h = std::max(int(config.input.height * scale), 1);
ImBuf *ibuf = IMB_allocImBuf(dest_w, dest_h, ImBufFlags::ByteData);
if (ibuf == nullptr) {
CLOG_ERROR(&LOG, "Failed to allocate image memory");
BLI_mmap_free(mmap_file);
return nullptr;
}
config.options.no_fancy_upsampling = 1;
config.options.use_scaling = 1;
config.options.scaled_width = dest_w;
config.options.scaled_height = dest_h;
config.options.bypass_filtering = 1;
config.options.use_threads = 0;
config.options.flip = 1;
config.output.is_external_memory = 1;
config.output.colorspace = MODE_RGBA;
config.output.u.RGBA.rgba = ibuf->byte_data_for_write();
config.output.u.RGBA.stride = 4 * ibuf->x;
config.output.u.RGBA.size = size_t(config.output.u.RGBA.stride) * size_t(ibuf->y);
if (WebPDecode(data, data_size, &config) != VP8_STATUS_OK || BLI_mmap_any_io_error(mmap_file)) {
CLOG_ERROR(&LOG, "Failed to decode image");
IMB_freeImBuf(ibuf);
BLI_mmap_free(mmap_file);
return nullptr;
}
/* Free the output buffer. */
WebPFreeDecBuffer(&config.output);
BLI_mmap_free(mmap_file);
return ibuf;
}
static std::tuple<WriteContext, ImageSpec> prepare_save_webp(ImBuf *ibuf, ImBufFlags flags)
{
int file_channels = ibuf->color_mode_channels_get();
/* WebP does not support 2-channel (gray + alpha) writes; promote to RGBA. */
if (file_channels == 2) {
file_channels = 4;
}
const TypeDesc data_format = TypeDesc::UINT8;
WriteContext ctx = imb_create_write_context("webp", ibuf, flags, false);
ImageSpec file_spec = imb_create_write_spec(ctx, file_channels, data_format);
file_spec.attribute("oiio:UnassociatedAlpha", 1);
/* A general quality/speed trade-off (0=fast, 6=slower-better). 4 matches historical value. */
file_spec.attribute("webp:method", 4);
if (ibuf->foptions.quality == 100.0f) {
/* Lossless compression. */
/* Use 70 to match historical value (see libwebp's LOSSLESS_DEFAULT_QUALITY). */
file_spec.attribute("compression", "lossless:70");
}
else {
/* Lossy compression. */
file_spec.attribute("compression",
std::string("webp:") + std::to_string(ibuf->foptions.quality));
}
return {ctx, file_spec};
}
bool imb_savewebp(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_webp(ibuf, flags);
return imb_oiio_write(ctx, filepath, file_spec);
}
Vector<uint8_t> imb_save_buffer_webp(ImBuf *ibuf, ImBufFlags flags)
{
const auto [ctx, file_spec] = prepare_save_webp(ibuf, flags);
return imb_oiio_write_buffer(ctx, file_spec);
}
} // namespace blender

View File

@@ -0,0 +1,64 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "BLI_math_vector.h"
#include "BLI_task.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Alpha-under
* \{ */
void IMB_alpha_under_color_float(float *rect_float, int x, int y, float backcol[3])
{
threading::parallel_for(IndexRange(int64_t(x) * y), 32 * 1024, [&](const IndexRange i_range) {
float *pix = rect_float + i_range.first() * 4;
for ([[maybe_unused]] const int i : i_range) {
const float mul = 1.0f - pix[3];
madd_v3_v3fl(pix, backcol, mul);
pix[3] = 1.0f;
pix += 4;
}
});
}
void IMB_alpha_under_color_byte(uchar *rect, int x, int y, const float backcol[3])
{
threading::parallel_for(IndexRange(int64_t(x) * y), 32 * 1024, [&](const IndexRange i_range) {
uchar *pix = rect + i_range.first() * 4;
for ([[maybe_unused]] const int i : i_range) {
if (pix[3] == 255) {
/* pass */
}
else if (pix[3] == 0) {
pix[0] = backcol[0] * 255;
pix[1] = backcol[1] * 255;
pix[2] = backcol[2] * 255;
}
else {
float alpha = pix[3] / 255.0;
float mul = 1.0f - alpha;
pix[0] = (pix[0] * alpha) + mul * backcol[0];
pix[1] = (pix[1] * alpha) + mul * backcol[1];
pix[2] = (pix[2] * alpha) + mul * backcol[2];
}
pix[3] = 255;
pix += 4;
}
});
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#pragma once
#include <cstdlib>
#include <sys/types.h>
#ifndef WIN32
# include <unistd.h>
#endif
#include <fcntl.h>
#include <sys/stat.h>
#ifndef WIN32
# include <sys/mman.h>
# define O_BINARY 0
#endif
#define IMB_DPI_DEFAULT 72.0

View File

@@ -0,0 +1,99 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <cstdlib>
#include <cstring>
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BKE_idprop.hh"
#include "DNA_ID.h" /* ID property definitions. */
#include "IMB_imbuf_types.hh"
#include "IMB_metadata.hh"
namespace blender {
void IMB_metadata_ensure(IDProperty **metadata)
{
if (*metadata != nullptr) {
return;
}
*metadata = bke::idprop::create_group("metadata").release();
}
void IMB_metadata_free(IDProperty *metadata)
{
if (metadata == nullptr) {
return;
}
IDP_FreeProperty(metadata);
}
bool IMB_metadata_get_field(const IDProperty *metadata,
const char *key,
char *value,
const size_t value_maxncpy)
{
if (metadata == nullptr) {
return false;
}
IDProperty *prop = IDP_GetPropertyFromGroup(metadata, key);
if (prop && prop->type == IDP_STRING) {
BLI_strncpy(value, IDP_string_get(prop), value_maxncpy);
return true;
}
return false;
}
void IMB_metadata_copy(ImBuf *ibuf_dst, const ImBuf *ibuf_src)
{
BLI_assert(ibuf_dst != ibuf_src);
if (ibuf_src->metadata) {
IMB_metadata_free(ibuf_dst->metadata);
ibuf_dst->metadata = IDP_CopyProperty(ibuf_src->metadata);
}
}
void IMB_metadata_set_field(IDProperty *metadata, const char *key, const char *value)
{
BLI_assert(metadata);
IDProperty *prop = IDP_GetPropertyFromGroup(metadata, key);
if (prop != nullptr && prop->type != IDP_STRING) {
IDP_FreeFromGroup(metadata, prop);
prop = nullptr;
}
if (prop) {
IDP_AssignString(prop, value);
}
else {
prop = bke::idprop::create(key, value).release();
IDP_AddToGroup(metadata, prop);
}
}
void IMB_metadata_foreach(const ImBuf *ibuf, IMBMetadataForeachCb callback, void *userdata)
{
if (ibuf->metadata == nullptr) {
return;
}
for (IDProperty &prop : ibuf->metadata->data.group) {
callback(prop.name, IDP_string_get(&prop), userdata);
}
}
} // namespace blender

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <cstddef>
#include "BLI_assert.h"
#include "BLI_mutex.hh"
#include "BLI_threads.h"
#include "GPU_context.hh"
#include "IMB_colormanagement_intern.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
namespace blender {
static gpu::GPUSecondaryContextData g_gpu_context;
static Mutex g_gpu_context_mutex;
void IMB_init()
{
imb_filetypes_init();
colormanagement_init();
}
void IMB_exit()
{
imb_filetypes_exit();
colormanagement_exit();
if (g_gpu_context.gpu_context) {
gpu::GPU_destroy_secondary_context(g_gpu_context);
}
}
void IMB_ensure_gpu_context()
{
BLI_assert(BLI_thread_is_main());
if (g_gpu_context.gpu_context) {
return;
}
g_gpu_context = gpu::GPU_create_secondary_context();
}
void IMB_activate_gpu_context()
{
BLI_assert(g_gpu_context.gpu_context);
g_gpu_context_mutex.lock();
gpu::GPU_activate_secondary_context(g_gpu_context);
}
void IMB_deactivate_gpu_context()
{
BLI_assert(g_gpu_context.gpu_context);
gpu::GPU_deactivate_secondary_context(g_gpu_context);
g_gpu_context_mutex.unlock();
}
} // namespace blender

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

View File

@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
..
../..
../../../../../intern/utfconv
)
set(INC_SYS
)
set(SRC
openexr_api.cpp
openexr_api.h
)
if(WITH_WEB)
set(SRC
openexr_api_stub.cc
openexr_api.h
)
endif()
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::clog
PRIVATE bf::dependencies::openexr
)
blender_add_lib(bf_imbuf_openexr "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup openexr
*/
#pragma once
namespace blender {
struct ImFileColorSpace;
extern const char *imb_file_extensions_openexr[];
void imb_initopenexr();
void imb_exitopenexr();
/**
* Test presence of OpenEXR file.
* \param mem: pointer to loaded OpenEXR bit-stream.
*/
bool imb_is_a_openexr(const unsigned char *mem, size_t size);
bool imb_save_openexr(ImBuf *ibuf, const char *filepath, ImBufFlags flags);
Vector<uint8_t> imb_save_buffer_openexr(ImBuf *ibuf, ImBufFlags flags);
ImBuf *imb_load_openexr(const unsigned char *mem,
size_t size,
ImBufFlags flags,
ImFileColorSpace &r_colorspace);
ImBuf *imb_load_filepath_thumbnail_openexr(const char *filepath,
ImBufFlags flags,
size_t max_thumb_size,
ImFileColorSpace &r_colorspace,
size_t *r_width,
size_t *r_height);
} // namespace blender

View File

@@ -0,0 +1,84 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "IMB_imbuf.hh"
#include "IMB_openexr.hh"
#include "openexr_api.h"
namespace blender {
const char *imb_file_extensions_openexr[] = {nullptr};
void imb_initopenexr() {}
void imb_exitopenexr() {}
bool imb_is_a_openexr(const unsigned char *, size_t)
{
return false;
}
bool imb_save_openexr(ImBuf *, const char *, ImBufFlags)
{
return false;
}
Vector<uint8_t> imb_save_buffer_openexr(ImBuf *, ImBufFlags)
{
return {};
}
ImBuf *imb_load_openexr(const unsigned char *, size_t, ImBufFlags, ImFileColorSpace &)
{
return nullptr;
}
ImBuf *imb_load_filepath_thumbnail_openexr(
const char *, ImBufFlags, size_t, ImFileColorSpace &, size_t *, size_t *)
{
return nullptr;
}
ExrHandle *IMB_exr_get_handle(bool)
{
return nullptr;
}
void IMB_exr_add_channels(
ExrHandle *, StringRefNull, StringRefNull, StringRefNull, StringRefNull, size_t, size_t, const float *, bool)
{
}
bool IMB_exr_begin_read(ExrHandle *, const char *, int *, int *, bool)
{
return false;
}
bool IMB_exr_begin_write(ExrHandle *, const char *, int, int, const double[2], int, int, const StampData *)
{
return false;
}
bool IMB_exr_set_channel(ExrHandle *, StringRefNull, int, int, float *)
{
return false;
}
void IMB_exr_read_channels(ExrHandle *) {}
void IMB_exr_write_channels(ExrHandle *) {}
void IMB_exr_multilayer_convert(ExrHandle *,
void *,
void *(*)(void *, const char *),
void *(*)(void *, const char *),
void (*)(void *, void *, const char *, float *, int, const char *, const char *))
{
}
void IMB_exr_close(ExrHandle *) {}
void IMB_exr_add_view(ExrHandle *, const char *) {}
bool IMB_exr_has_multilayer(ExrHandle *)
{
return false;
}
bool IMB_exr_get_ppm(ExrHandle *, double[2])
{
return false;
}
void IMB_exr_get_display_window(ExrHandle *, int display_size[2], int display_offset[2], int data_offset[2])
{
display_size[0] = display_size[1] = 0;
display_offset[0] = display_offset[1] = 0;
data_offset[0] = data_offset[1] = 0;
}
} // namespace blender

View File

@@ -0,0 +1,277 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#ifdef _WIN32
# include <io.h>
# include <stddef.h>
# include <sys/types.h>
#endif
#include <cstdlib>
#include "BLI_fileops.h"
#include "BLI_mmap.h"
#include "BLI_path_utils.hh" /* For assertions. */
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "CLG_log.h"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_metadata.hh"
#include "IMB_thumbs.hh"
#include "imbuf.hh"
#include "IMB_colormanagement.hh"
#include "IMB_colormanagement_intern.hh"
namespace blender {
static CLG_LogRef LOG = {"image.read"};
static void imb_handle_colorspace_and_alpha(ImBuf *ibuf,
const ImBufFlags flags,
const char *filepath,
const ImFileColorSpace &file_colorspace,
char r_colorspace[IM_MAX_SPACE])
{
/* Determine file colorspace. */
char new_colorspace[IM_MAX_SPACE];
if (r_colorspace && r_colorspace[0]) {
/* Existing configured colorspace has priority. */
STRNCPY_UTF8(new_colorspace, r_colorspace);
}
else if (file_colorspace.metadata_colorspace[0] &&
colormanage_colorspace_get_named(file_colorspace.metadata_colorspace))
{
/* Use colorspace from file metadata if provided. */
STRNCPY_UTF8(new_colorspace, file_colorspace.metadata_colorspace);
}
else {
/* The color-space from the file-path (not a file-path). */
const char *filepath_colorspace = (filepath) ?
IMB_colormanagement_space_from_filepath_rules(filepath) :
nullptr;
if (filepath_colorspace) {
/* Use colorspace from OpenColorIO file rules. */
STRNCPY_UTF8(new_colorspace, filepath_colorspace);
}
else {
/* Use float colorspace if the image may contain HDR colors, byte otherwise. */
const char *role_colorspace = IMB_colormanagement_role_colorspace_name_get(
file_colorspace.is_hdr_float ? COLOR_ROLE_DEFAULT_FLOAT : COLOR_ROLE_DEFAULT_BYTE);
STRNCPY_UTF8(new_colorspace, role_colorspace);
}
}
if (r_colorspace) {
BLI_strncpy_utf8(r_colorspace, new_colorspace, IM_MAX_SPACE);
}
if (r_colorspace) {
if (ibuf->byte_data() != nullptr && ibuf->float_data() == nullptr) {
/* byte buffer is never internally converted to some standard space,
* store pointer to its color space descriptor instead
*/
ibuf->byte_buffer.colorspace = colormanage_colorspace_get_named(new_colorspace);
}
}
bool is_data = (r_colorspace && IMB_colormanagement_space_name_is_data(new_colorspace));
ImBufFlags alpha_flags = flag_is_set(flags, ImBufFlags::AlphaDetect) ? ibuf->flags : flags;
if (is_data || flag_is_set(flags, ImBufFlags::AlphaChannelPacked)) {
/* Don't touch alpha. */
ibuf->flags |= ImBufFlags::AlphaChannelPacked;
}
else if (flag_is_set(flags, ImBufFlags::AlphaIgnore)) {
/* Make opaque. */
IMB_rectfill_alpha(ibuf, 1.0f);
ibuf->flags |= ImBufFlags::AlphaIgnore;
}
else {
if (flag_is_set(alpha_flags, ImBufFlags::AlphaPremul)) {
if (ibuf->byte_data()) {
IMB_unpremultiply_alpha(ibuf);
}
else {
/* pass, floats are expected to be premul */
}
}
else {
if (ibuf->float_data()) {
IMB_premultiply_alpha(ibuf);
}
else {
/* pass, bytes are expected to be straight */
}
}
}
if (flag_is_set(flags, ImBufFlags::NoColorspaceConvert)) {
if (ibuf->float_data() != nullptr) {
ibuf->float_buffer.colorspace = colormanage_colorspace_get_named(new_colorspace);
}
}
else {
colormanage_imbuf_make_linear(ibuf, new_colorspace, ColorManagedFileOutput::Image);
}
}
ImBuf *IMB_load_image_from_memory(const uchar *mem,
const size_t size,
const ImBufFlags flags,
const char *descr,
const char *filepath,
char r_colorspace[IM_MAX_SPACE])
{
ImBuf *ibuf;
const ImFileType *type;
if (mem == nullptr) {
CLOG_ERROR(&LOG, "%s: nullptr pointer", __func__);
return nullptr;
}
ImFileColorSpace file_colorspace;
for (type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (type->load) {
ibuf = type->load(mem, size, flags, file_colorspace);
if (ibuf) {
imb_handle_colorspace_and_alpha(ibuf, flags, filepath, file_colorspace, r_colorspace);
return ibuf;
}
}
}
if (!flag_is_set(flags, ImBufFlags::Test)) {
CLOG_ERROR(&LOG, "%s: unknown file-format (%s)", __func__, descr);
}
return nullptr;
}
ImBuf *IMB_load_image_from_file_descriptor(const int file,
const ImBufFlags flags,
const char *filepath,
char r_colorspace[IM_MAX_SPACE])
{
ImBuf *ibuf = nullptr;
if (file == -1) {
return nullptr;
}
BLI_mmap_file *mmap_file = BLI_mmap_open(file);
if (mmap_file == nullptr) {
CLOG_ERROR(&LOG, "%s: couldn't get mapping for \"%s\"", __func__, filepath);
return nullptr;
}
const uchar *mem = static_cast<const uchar *>(BLI_mmap_get_pointer(mmap_file));
const size_t size = BLI_mmap_get_length(mmap_file);
ibuf = IMB_load_image_from_memory(mem, size, flags, filepath, filepath, r_colorspace);
/* If we got an image but mmap encountered an error,
* free the image and return nullptr as it could be corrupted. */
if (ibuf != nullptr && BLI_mmap_any_io_error(mmap_file)) {
IMB_freeImBuf(ibuf);
ibuf = nullptr;
}
BLI_mmap_free(mmap_file);
return ibuf;
}
ImBuf *IMB_load_image_from_filepath(const char *filepath,
const ImBufFlags flags,
char r_colorspace[IM_MAX_SPACE])
{
ImBuf *ibuf;
int file;
BLI_assert(!BLI_path_is_rel(filepath));
file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
if (file == -1) {
return nullptr;
}
ibuf = IMB_load_image_from_file_descriptor(file, flags, filepath, r_colorspace);
if (ibuf) {
ibuf->filepath = filepath;
}
close(file);
return ibuf;
}
ImBuf *IMB_thumb_load_image(const char *filepath,
const size_t max_thumb_size,
char r_colorspace[IM_MAX_SPACE],
const IMBThumbLoadFlags load_flags)
{
const ImFileType *type = IMB_file_type_from_ftype(IMB_test_image_type(filepath));
if (type == nullptr) {
return nullptr;
}
ImBuf *ibuf = nullptr;
ImBufFlags flags = ImBufFlags::ByteData | ImBufFlags::Metadata;
/* Size of the original image. */
size_t width = 0;
size_t height = 0;
if (type->load_filepath_thumbnail) {
ImFileColorSpace file_colorspace;
ibuf = type->load_filepath_thumbnail(
filepath, flags, max_thumb_size, file_colorspace, &width, &height);
if (ibuf) {
imb_handle_colorspace_and_alpha(ibuf, flags, filepath, file_colorspace, r_colorspace);
}
}
else {
/* Skip images of other types if over 100MB. */
if (!flag_is_set(load_flags, IMBThumbLoadFlags::LoadLargeFiles)) {
const size_t file_size = BLI_file_size(filepath);
if (file_size != size_t(-1) && file_size > THUMB_SIZE_MAX) {
return nullptr;
}
}
ibuf = IMB_load_image_from_filepath(filepath, flags, r_colorspace);
if (ibuf) {
width = ibuf->x;
height = ibuf->y;
}
}
if (ibuf) {
if (width > 0 && height > 0) {
/* Save dimensions of original image into the thumbnail metadata. */
char cwidth[40];
char cheight[40];
SNPRINTF_UTF8(cwidth, "%zu", width);
SNPRINTF_UTF8(cheight, "%zu", height);
IMB_metadata_ensure(&ibuf->metadata);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Image::Width", cwidth);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Image::Height", cheight);
}
}
return ibuf;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,194 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
template<typename T>
static void rotate_pixels(const int degrees,
const int size_x,
const int size_y,
const T *src_pixels,
T *dst_pixels,
const int channels)
{
threading::parallel_for(IndexRange(size_y), 256, [&](const IndexRange y_range) {
const T *src_pixel = src_pixels + y_range.first() * size_x * channels;
if (degrees == 90) {
for (int y : y_range) {
for (int x = 0; x < size_x; x++, src_pixel += channels) {
memcpy(&dst_pixels[(y + ((size_x - x - 1) * size_y)) * channels],
src_pixel,
sizeof(T) * channels);
}
}
}
else if (degrees == 180) {
for (int y : y_range) {
for (int x = 0; x < size_x; x++, src_pixel += channels) {
memcpy(&dst_pixels[(((size_y - y - 1) * size_x) + (size_x - x - 1)) * channels],
src_pixel,
sizeof(T) * channels);
}
}
}
else if (degrees == 270) {
for (int y : y_range) {
for (int x = 0; x < size_x; x++, src_pixel += channels) {
memcpy(&dst_pixels[((size_y - y - 1) + (x * size_y)) * channels],
src_pixel,
sizeof(T) * channels);
}
}
}
});
}
bool IMB_rotate_orthogonal(ImBuf *ibuf, int degrees)
{
if (!ELEM(degrees, 90, 180, 270)) {
return false;
}
const int size_x = ibuf->x;
const int size_y = ibuf->y;
const ColorSpace *float_colorspace = ibuf->float_buffer.colorspace;
const ColorSpace *byte_colorspace = ibuf->byte_buffer.colorspace;
if (ELEM(degrees, 90, 270)) {
std::swap(ibuf->x, ibuf->y);
}
if (ibuf->float_data()) {
const int channels = ibuf->channels;
const float *src_pixels = ibuf->float_data();
float *dst_pixels = MEM_new_array_uninitialized<float>(
size_t(channels) * size_t(size_x) * size_t(size_y), __func__);
rotate_pixels<float>(degrees, size_x, size_y, src_pixels, dst_pixels, ibuf->channels);
ibuf->assign_float_data(dst_pixels);
ibuf->float_buffer.colorspace = float_colorspace;
if (ibuf->byte_data()) {
IMB_byte_from_float(ibuf);
}
}
else if (ibuf->byte_data()) {
const uchar *src_pixels = ibuf->byte_data();
uchar *dst_pixels = MEM_new_array_uninitialized<uchar>(4 * size_t(size_x) * size_t(size_y),
__func__);
rotate_pixels<uchar>(degrees, size_x, size_y, src_pixels, dst_pixels, 4);
ibuf->assign_byte_data(dst_pixels);
ibuf->byte_buffer.colorspace = byte_colorspace;
}
return true;
}
void IMB_flipy(ImBuf *ibuf)
{
size_t x_size, y_size;
if (ibuf == nullptr) {
return;
}
if (ibuf->byte_data()) {
uint *top, *bottom, *line;
x_size = ibuf->x;
y_size = ibuf->y;
const size_t stride = x_size * sizeof(int);
top = reinterpret_cast<uint *>(ibuf->byte_data_for_write());
bottom = top + ((y_size - 1) * x_size);
line = MEM_new_array_uninitialized<uint>(x_size, "linebuf");
y_size >>= 1;
for (; y_size > 0; y_size--) {
memcpy(line, top, stride);
memcpy(top, bottom, stride);
memcpy(bottom, line, stride);
bottom -= x_size;
top += x_size;
}
MEM_delete(line);
}
if (ibuf->float_data()) {
float *topf = nullptr, *bottomf = nullptr, *linef = nullptr;
x_size = ibuf->x;
y_size = ibuf->y;
const size_t stride = x_size * 4 * sizeof(float);
topf = ibuf->float_data_for_write();
bottomf = topf + 4 * ((y_size - 1) * x_size);
linef = MEM_new_array_uninitialized<float>(4 * x_size, "linebuf");
y_size >>= 1;
for (; y_size > 0; y_size--) {
memcpy(linef, topf, stride);
memcpy(topf, bottomf, stride);
memcpy(bottomf, linef, stride);
bottomf -= 4 * x_size;
topf += 4 * x_size;
}
MEM_delete(linef);
}
}
void IMB_flipx(ImBuf *ibuf)
{
int x, y, xr, xl, yi;
float px_f[4];
if (ibuf == nullptr) {
return;
}
x = ibuf->x;
y = ibuf->y;
if (ibuf->byte_data()) {
uint *rect = reinterpret_cast<uint *>(ibuf->byte_data_for_write());
for (yi = y - 1; yi >= 0; yi--) {
const size_t x_offset = size_t(x) * yi;
for (xr = x - 1, xl = 0; xr >= xl; xr--, xl++) {
std::swap(rect[x_offset + xr], rect[x_offset + xl]);
}
}
}
if (ibuf->float_data()) {
float *rect_float = ibuf->float_data_for_write();
for (yi = y - 1; yi >= 0; yi--) {
const size_t x_offset = size_t(x) * yi;
for (xr = x - 1, xl = 0; xr >= xl; xr--, xl++) {
memcpy(&px_f, &rect_float[(x_offset + xr) * 4], sizeof(float[4]));
memcpy(
&rect_float[(x_offset + xr) * 4], &rect_float[(x_offset + xl) * 4], sizeof(float[4]));
memcpy(&rect_float[(x_offset + xl) * 4], &px_f, sizeof(float[4]));
}
}
}
}
} // namespace blender

View File

@@ -0,0 +1,593 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "BLI_math_interp.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include "IMB_filter.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_metadata.hh"
#include "BLI_sys_types.h" /* for intptr_t support */
namespace blender {
static inline float4 load_pixel(const uchar4 *ptr)
{
return float4(ptr[0]);
}
static inline float4 load_pixel(const float *ptr)
{
return float4(ptr[0]);
}
static inline float4 load_pixel(const float2 *ptr)
{
return float4(ptr[0], 0.0f, 1.0f);
}
static inline float4 load_pixel(const float3 *ptr)
{
return float4(ptr[0], 1.0f);
}
static inline float4 load_pixel(const float4 *ptr)
{
return float4(ptr[0]);
}
static inline void store_pixel(float4 pix, uchar4 *ptr)
{
*ptr = uchar4(math::round(pix));
}
static inline void store_pixel(float4 pix, float *ptr)
{
*ptr = pix.x;
}
static inline void store_pixel(float4 pix, float2 *ptr)
{
memcpy(reinterpret_cast<void *>(ptr), &pix, sizeof(*ptr));
}
static inline void store_pixel(float4 pix, float3 *ptr)
{
memcpy(reinterpret_cast<void *>(ptr), &pix, sizeof(*ptr));
}
static inline void store_pixel(float4 pix, float4 *ptr)
{
*ptr = pix;
}
template<typename BufferT, typename Fn>
static void to_static_pixel_type(const BufferT *src_buffer,
const int channels,
BufferT *dst_buffer,
const Fn &fn)
{
if constexpr (std::is_same_v<BufferT, uchar>) {
fn(reinterpret_cast<const uchar4 *>(src_buffer), reinterpret_cast<uchar4 *>(dst_buffer));
}
else {
if (channels == 1) {
fn(src_buffer, dst_buffer);
}
else if (channels == 2) {
const float2 *src = reinterpret_cast<const float2 *>(src_buffer);
fn(src, reinterpret_cast<float2 *>(dst_buffer));
}
else if (channels == 3) {
const float3 *src = reinterpret_cast<const float3 *>(src_buffer);
fn(src, reinterpret_cast<float3 *>(dst_buffer));
}
else if (channels == 4) {
const float4 *src = reinterpret_cast<const float4 *>(src_buffer);
fn(src, reinterpret_cast<float4 *>(dst_buffer));
}
}
}
template<typename BufferT>
static void scale_down_x_func(const BufferT *src_buffer,
const int2 src_size,
const int channels,
BufferT *dst_buffer,
const int2 dst_size,
bool threaded)
{
const int newx = dst_size.x;
const int ibufx = src_size.x;
const int ibufy = src_size.y;
to_static_pixel_type(src_buffer, channels, dst_buffer, [&]<typename T>(const T *src, T *dst) {
const float add = (ibufx - 0.01f) / newx;
const float inv_add = 1.0f / add;
const int grain_size = threaded ? 32 : ibufy;
threading::parallel_for(IndexRange(ibufy), grain_size, [&](IndexRange range) {
for (const int y : range) {
const T *src_ptr = src + (int64_t(y) * ibufx);
T *dst_ptr = dst + (int64_t(y) * newx);
float sample = 0.0f;
float4 val(0.0f);
for (int x = 0; x < newx; x++) {
float4 nval = -val * sample;
sample += add;
while (sample >= 1.0f) {
sample -= 1.0f;
nval += load_pixel(src_ptr);
src_ptr++;
}
val = load_pixel(src_ptr);
src_ptr++;
float4 pix = (nval + sample * val) * inv_add;
store_pixel(pix, dst_ptr);
dst_ptr++;
sample -= 1.0f;
}
}
});
});
}
template<typename BufferT>
static void scale_down_y_func(const BufferT *src_buffer,
const int2 src_size,
const int channels,
BufferT *dst_buffer,
const int2 dst_size,
bool threaded)
{
const int newy = dst_size.y;
const int ibufx = src_size.x;
const int ibufy = src_size.y;
to_static_pixel_type(src_buffer, channels, dst_buffer, [&]<typename T>(const T *src, T *dst) {
const float add = (ibufy - 0.01f) / newy;
const float inv_add = 1.0f / add;
const int grain_size = threaded ? 32 : ibufx;
threading::parallel_for(IndexRange(ibufx), grain_size, [&](IndexRange range) {
for (const int x : range) {
const T *src_ptr = src + x;
T *dst_ptr = dst + x;
float sample = 0.0f;
float4 val(0.0f);
for (int y = 0; y < newy; y++) {
float4 nval = -val * sample;
sample += add;
while (sample >= 1.0f) {
sample -= 1.0f;
nval += load_pixel(src_ptr);
src_ptr += ibufx;
}
val = load_pixel(src_ptr);
src_ptr += ibufx;
float4 pix = (nval + sample * val) * inv_add;
store_pixel(pix, dst_ptr);
dst_ptr += ibufx;
sample -= 1.0f;
}
}
});
});
}
template<typename BufferT>
static void scale_up_x_func(const BufferT *src_buffer,
const int2 src_size,
const int channels,
BufferT *dst_buffer,
const int2 dst_size,
bool threaded)
{
const int newx = dst_size.x;
const int ibufx = src_size.x;
const int ibufy = src_size.y;
to_static_pixel_type(src_buffer, channels, dst_buffer, [&]<typename T>(const T *src, T *dst) {
const float add = (ibufx - 0.001f) / newx;
/* Special case: source is 1px wide (see #70356). */
if (UNLIKELY(ibufx == 1)) {
for (int y = ibufy; y > 0; y--) {
for (int x = newx; x > 0; x--) {
*dst = *src;
dst++;
}
src++;
}
}
else {
const int grain_size = threaded ? 32 : ibufy;
threading::parallel_for(IndexRange(ibufy), grain_size, [&](IndexRange range) {
for (const int y : range) {
float sample = -0.5f + add * 0.5f;
int counter = 0;
const T *src_ptr = src + (int64_t(y) * ibufx);
T *dst_ptr = dst + (int64_t(y) * newx);
float4 val = load_pixel(src_ptr);
float4 nval = load_pixel(src_ptr + 1);
float4 diff = nval - val;
if (ibufx > 2) {
src_ptr += 2;
counter += 2;
}
for (int x = 0; x < newx; x++) {
if (sample >= 1.0f) {
sample -= 1.0f;
val = nval;
nval = load_pixel(src_ptr);
diff = nval - val;
if (counter + 1 < ibufx) {
src_ptr++;
counter++;
}
}
float4 pix = val + math::max(sample, 0.0f) * diff;
store_pixel(pix, dst_ptr);
dst_ptr++;
sample += add;
}
}
});
}
});
}
template<typename BufferT>
static void scale_up_y_func(const BufferT *src_buffer,
const int2 src_size,
const int channels,
BufferT *dst_buffer,
const int2 dst_size,
bool threaded)
{
const int newy = dst_size.y;
const int ibufx = src_size.x;
const int ibufy = src_size.y;
to_static_pixel_type(src_buffer, channels, dst_buffer, [&]<typename T>(const T *src, T *dst) {
const float add = (ibufy - 0.001f) / newy;
/* Special case: source is 1px high (see #70356). */
if (UNLIKELY(ibufy == 1)) {
for (int y = newy; y > 0; y--) {
memcpy(reinterpret_cast<void *>(dst), src, sizeof(T) * ibufx);
dst += ibufx;
}
}
else {
const int grain_size = threaded ? 32 : ibufx;
threading::parallel_for(IndexRange(ibufx), grain_size, [&](IndexRange range) {
for (const int x : range) {
float sample = -0.5f + add * 0.5f;
int counter = 0;
const T *src_ptr = src + x;
T *dst_ptr = dst + x;
float4 val = load_pixel(src_ptr);
float4 nval = load_pixel(src_ptr + ibufx);
float4 diff = nval - val;
if (ibufy > 2) {
src_ptr += ibufx * 2;
counter += 2;
}
for (int y = 0; y < newy; y++) {
if (sample >= 1.0f) {
sample -= 1.0f;
val = nval;
nval = load_pixel(src_ptr);
diff = nval - val;
if (counter + 1 < ibufy) {
src_ptr += ibufx;
++counter;
}
}
float4 pix = val + math::max(sample, 0.0f) * diff;
store_pixel(pix, dst_ptr);
dst_ptr += ibufx;
sample += add;
}
}
});
}
});
}
template<typename BufferT>
static void imb_scale_box(const BufferT *src_buffer,
const int2 src_size,
const int channels,
BufferT *dst_buffer,
const int2 dst_size,
const bool threaded)
{
BufferT *tmp_buffer = MEM_new_array_uninitialized<BufferT>(
int64_t(channels) * dst_size.x * src_size.y, __func__);
if (dst_size.x < src_size.x) {
scale_down_x_func(
src_buffer, src_size, channels, tmp_buffer, int2(dst_size.x, src_size.y), threaded);
}
else {
scale_up_x_func(
src_buffer, src_size, channels, tmp_buffer, int2(dst_size.x, src_size.y), threaded);
}
if (dst_size.y < src_size.y) {
scale_down_y_func(
tmp_buffer, int2(dst_size.x, src_size.y), channels, dst_buffer, dst_size, threaded);
}
else {
scale_up_y_func(
tmp_buffer, int2(dst_size.x, src_size.y), channels, dst_buffer, dst_size, threaded);
}
MEM_delete(tmp_buffer);
}
void IMB_scale_box(const float *src_buffer,
const int2 src_size,
const int channels,
float *dst_buffer,
const int2 dst_size,
const bool threaded)
{
imb_scale_box(src_buffer, src_size, channels, dst_buffer, dst_size, threaded);
}
void IMB_scale_box(const uchar *src_buffer,
const int2 src_size,
const int channels,
uchar *dst_buffer,
const int2 dst_size,
const bool threaded)
{
imb_scale_box(src_buffer, src_size, channels, dst_buffer, dst_size, threaded);
}
template<typename T>
static void scale_nearest(
const T *src, T *dst, const int2 src_size, const int2 dst_size, IndexRange y_range)
{
const int ibufx = src_size.x;
const int ibufy = src_size.y;
const int newx = dst_size.x;
const int newy = dst_size.y;
/* Nearest sample scaling. Step through pixels in fixed point coordinates. */
constexpr int FRAC_BITS = 16;
int64_t stepx = ((int64_t(ibufx) << FRAC_BITS) + newx / 2) / newx;
int64_t stepy = ((int64_t(ibufy) << FRAC_BITS) + newy / 2) / newy;
int64_t posy = y_range.first() * stepy;
dst += y_range.first() * newx;
for (const int y : y_range) {
UNUSED_VARS(y);
const T *row = src + (posy >> FRAC_BITS) * ibufx;
int64_t posx = 0;
for (int x = 0; x < newx; x++, posx += stepx) {
*dst = row[posx >> FRAC_BITS];
dst++;
}
posy += stepy;
}
}
template<typename BufferT>
static void scale_nearest_func(const BufferT *src_buffer,
const int2 src_size,
const int channels,
BufferT *dst_buffer,
const int2 dst_size,
bool threaded)
{
const int grain_size = threaded ? 64 : dst_size.y;
threading::parallel_for(IndexRange(dst_size.y), grain_size, [&](IndexRange y_range) {
if constexpr (std::is_same_v<BufferT, uchar>) {
const uchar4 *src = reinterpret_cast<const uchar4 *>(src_buffer);
scale_nearest(src, reinterpret_cast<uchar4 *>(dst_buffer), src_size, dst_size, y_range);
}
else {
if (channels == 1) {
scale_nearest(src_buffer, dst_buffer, src_size, dst_size, y_range);
}
else if (channels == 2) {
const float2 *src = reinterpret_cast<const float2 *>(src_buffer);
scale_nearest(src, reinterpret_cast<float2 *>(dst_buffer), src_size, dst_size, y_range);
}
else if (channels == 3) {
const float3 *src = reinterpret_cast<const float3 *>(src_buffer);
scale_nearest(src, reinterpret_cast<float3 *>(dst_buffer), src_size, dst_size, y_range);
}
else if (channels == 4) {
const float4 *src = reinterpret_cast<const float4 *>(src_buffer);
scale_nearest(src, reinterpret_cast<float4 *>(dst_buffer), src_size, dst_size, y_range);
}
}
});
}
template<typename BufferT>
static void scale_bilinear(const BufferT *src_buffer,
const int2 src_size,
const int channels,
BufferT *dst_buffer,
const int2 dst_size,
bool threaded)
{
const int newx = dst_size.x;
const int newy = dst_size.y;
const int grain_size = threaded ? 32 : newy;
threading::parallel_for(IndexRange(newy), grain_size, [&](IndexRange y_range) {
float factor_x = float(src_size.x) / newx;
float factor_y = float(src_size.y) / newy;
for (const int y : y_range) {
float v = (float(y) + 0.5f) * factor_y - 0.5f;
for (int x = 0; x < newx; x++) {
float u = (float(x) + 0.5f) * factor_x - 0.5f;
int64_t offset = int64_t(y) * newx + x;
if constexpr (std::is_same_v<BufferT, uchar>) {
*reinterpret_cast<uchar4 *>(dst_buffer + offset * 4) = math::interpolate_bilinear_byte(
src_buffer, src_size.x, src_size.y, u, v);
}
else {
float *pixel = dst_buffer + channels * offset;
math::interpolate_bilinear_fl(src_buffer, pixel, src_size.x, src_size.y, channels, u, v);
}
}
}
});
}
bool IMB_scale(ImBuf *ibuf, const int2 new_size, IMBScaleFilter filter, bool threaded)
{
BLI_assert_msg(new_size.x > 0 && new_size.y > 0,
"Images must be at least 1 on both dimensions!");
if (ibuf == nullptr) {
return false;
}
const int2 src_size = int2(ibuf->x, ibuf->y);
if (src_size == new_size) {
return false;
}
const ColorSpace *float_colorspace = ibuf->float_buffer.colorspace;
const ColorSpace *byte_colorspace = ibuf->byte_buffer.colorspace;
switch (filter) {
case IMBScaleFilter::Nearest: {
if (const float *src = ibuf->float_data()) {
float *dst = MEM_new_array_uninitialized<float>(
size_t(ibuf->channels) * new_size.x * new_size.y, __func__);
scale_nearest_func(src, src_size, ibuf->channels, dst, new_size, threaded);
ibuf->assign_float_data(dst);
}
if (const uchar *src = ibuf->byte_data()) {
uchar *dst = MEM_new_array_uninitialized<uchar>(size_t(new_size.x) * new_size.y * 4,
__func__);
scale_nearest_func(src, src_size, 4, dst, new_size, threaded);
ibuf->assign_byte_data(dst);
}
break;
}
case IMBScaleFilter::Bilinear: {
if (const float *src = ibuf->float_data()) {
float *dst = MEM_new_array_uninitialized<float>(
size_t(ibuf->channels) * new_size.x * new_size.y, __func__);
scale_bilinear(src, src_size, ibuf->channels, dst, new_size, threaded);
ibuf->assign_float_data(dst);
}
if (const uchar *src = ibuf->byte_data()) {
uchar *dst = MEM_new_array_uninitialized<uchar>(size_t(new_size.x) * new_size.y * 4,
__func__);
scale_bilinear(src, src_size, 4, dst, new_size, threaded);
ibuf->assign_byte_data(dst);
}
break;
}
case IMBScaleFilter::Box: {
if (const float *src = ibuf->float_data()) {
float *dst = MEM_new_array_uninitialized<float>(
size_t(ibuf->channels) * new_size.x * new_size.y, __func__);
imb_scale_box(src, src_size, ibuf->channels, dst, new_size, threaded);
ibuf->assign_float_data(dst);
}
if (const uchar *src = ibuf->byte_data()) {
uchar *dst = MEM_new_array_uninitialized<uchar>(size_t(new_size.x) * new_size.y * 4,
__func__);
imb_scale_box(src, src_size, 4, dst, new_size, threaded);
ibuf->assign_byte_data(dst);
}
break;
}
}
ibuf->float_buffer.colorspace = float_colorspace;
ibuf->byte_buffer.colorspace = byte_colorspace;
ibuf->x = new_size.x;
ibuf->y = new_size.y;
return true;
}
ImBuf *IMB_scale_into_new(const ImBuf *ibuf,
const int2 new_size,
IMBScaleFilter filter,
bool threaded)
{
BLI_assert_msg(new_size.x > 0 && new_size.y > 0,
"Images must be at least 1 on both dimensions!");
if (ibuf == nullptr) {
return nullptr;
}
/* Size same as source: just copy source image. */
const int2 src_size = int2(ibuf->x, ibuf->y);
if (src_size == new_size) {
ImBuf *dst = IMB_dupImBuf(ibuf);
IMB_metadata_copy(dst, ibuf);
return dst;
}
/* Allocate destination buffers. */
ImBufFlags flags = ImBufFlags::UninitializedPixels;
if (ibuf->byte_data()) {
flags |= ImBufFlags::ByteData;
}
if (ibuf->float_data()) {
flags |= ImBufFlags::FloatData;
}
ImBuf *dst = IMB_allocImBuf(new_size.x, new_size.y, flags);
dst->color_mode = ibuf->color_mode;
dst->channels = ibuf->channels;
IMB_metadata_copy(dst, ibuf);
uchar *dst_byte = dst->byte_data_for_write();
float *dst_float = dst->float_data_for_write();
if (dst_byte == nullptr && dst_float == nullptr) {
IMB_freeImBuf(dst);
return nullptr;
}
switch (filter) {
case IMBScaleFilter::Nearest: {
if (const float *src = ibuf->float_data()) {
scale_nearest_func(src, src_size, ibuf->channels, dst_float, new_size, threaded);
}
if (const uchar *src = ibuf->byte_data()) {
scale_nearest_func(src, src_size, 4, dst_byte, new_size, threaded);
}
break;
}
case IMBScaleFilter::Bilinear: {
if (const float *src = ibuf->float_data()) {
scale_bilinear(src, src_size, ibuf->channels, dst_float, new_size, threaded);
}
if (const uchar *src = ibuf->byte_data()) {
scale_bilinear(src, src_size, 4, dst_byte, new_size, threaded);
}
break;
}
case IMBScaleFilter::Box: {
if (const float *src = ibuf->float_data()) {
imb_scale_box(src, src_size, ibuf->channels, dst_float, new_size, threaded);
}
if (const uchar *src = ibuf->byte_data()) {
imb_scale_box(src, src_size, 4, dst_byte, new_size, threaded);
}
break;
}
}
dst->byte_buffer.colorspace = ibuf->byte_buffer.colorspace;
dst->float_buffer.colorspace = ibuf->float_buffer.colorspace;
return dst;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,851 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <algorithm>
#include <cstdlib>
#include "MEM_guardedalloc.h"
#include "BKE_blendfile.hh"
#include "BLI_fileops.h"
#include "BLI_ghash.h"
#include "BLI_hash_md5.hh"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_system.h"
#include "BLI_tempfile.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
#include BLI_SYSTEM_PID_H
#include "DNA_space_types.h" /* For FILE_MAX_LIBEXTRA */
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_metadata.hh"
#include "IMB_thumbs.hh"
#include "MOV_read.hh"
#include <cctype>
#include <cstring>
#include <ctime>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef WIN32
/* Need to include windows.h so _WIN32_IE is defined. */
# include <windows.h>
# ifndef _WIN32_IE
/* Minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already. */
# define _WIN32_IE 0x0400
# endif
/* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
* because 'near' is disabled through BLI_windstuff */
# include "BLI_winstuff.h"
# include "utfconv.hh"
# include <direct.h> /* #chdir */
# include <shlobj.h>
#endif
#if defined(WIN32) || defined(__APPLE__)
/* pass */
#else
# define USE_FREEDESKTOP
#endif
/* '$HOME/.cache/thumbnails' or '$HOME/.thumbnails' */
#ifdef USE_FREEDESKTOP
# define THUMBNAILS "thumbnails"
#else
# define THUMBNAILS ".thumbnails"
#endif
#define URI_MAX (FILE_MAX * 3 + 8)
namespace blender {
static bool get_thumb_dir(char *dir, ThumbSize size)
{
char *s = dir;
const char *subdir;
#ifdef WIN32
wchar_t dir_16[MAX_PATH];
/* Yes, applications shouldn't store data there, but so does GIMP :). */
SHGetSpecialFolderPathW(0, dir_16, CSIDL_PROFILE, 0);
conv_utf_16_to_8(dir_16, dir, FILE_MAX);
s += strlen(dir);
#else
# if defined(USE_FREEDESKTOP)
const char *home_cache = BLI_getenv("XDG_CACHE_HOME");
const char *home = home_cache ? home_cache : BLI_dir_home();
# else
const char *home = BLI_dir_home();
# endif
if (!home) {
return false;
}
s += BLI_strncpy_rlen(s, home, FILE_MAX);
# ifdef USE_FREEDESKTOP
if (!home_cache) {
s += BLI_strncpy_rlen(s, "/.cache", FILE_MAX - (s - dir));
}
# endif
#endif
switch (size) {
case THB_NORMAL:
subdir = SEP_STR THUMBNAILS SEP_STR "normal" SEP_STR;
break;
case THB_LARGE:
subdir = SEP_STR THUMBNAILS SEP_STR "large" SEP_STR;
break;
case THB_FAIL:
subdir = SEP_STR THUMBNAILS SEP_STR "fail" SEP_STR "blender" SEP_STR;
break;
default:
return false; /* unknown size */
}
s += BLI_strncpy_rlen(s, subdir, FILE_MAX - (s - dir));
(void)s;
return true;
}
#undef THUMBNAILS
/* --- Begin of adapted code from glib. --- */
/* -------------------------------------------------------------------- */
/** \name Escape URI String
*
* The following code is adapted from function g_escape_uri_string from the gnome glib
* Source: http://svn.gnome.org/viewcvs/glib/trunk/glib/gconvert.c?view=markup
* released under the Gnu General Public License.
*
* \{ */
enum eUnsafeCharacterSet {
UNSAFE_ALL = 0x1, /* Escape all unsafe characters. */
UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+' */
UNSAFE_PATH = 0x8, /* Allows '/', '&', '=', ':', '@', '+', '$' and ',' */
UNSAFE_HOST = 0x10, /* Allows '/' and ':' and '@' */
UNSAFE_SLASHES = 0x20, /* Allows all characters except for '/' and '%' */
};
/* Don't lose comment alignment. */
/* clang-format off */
static const uchar acceptable[96] = {
/* A table of the ASCII chars from space (32) to DEL (127) */
/* ! " # $ % & ' ( ) * + , - . / */
0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C,
/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20,
/* @ A B C D E F G H I J K L M N O */
0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
/* P Q R S T U V W X Y Z [ \ ] ^ _ */
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F,
/* ` a b c d e f g h i j k l m n o */
0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
/* p q r s t u v w x y z { | } ~ DEL */
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20,
};
/* clang-format on */
static const char hex[17] = "0123456789abcdef";
/* NOTE: This escape function works on file: URIs, but if you want to
* escape something else, please read RFC-2396 */
static void escape_uri_string(const char *string,
char *escaped_string,
const int escaped_string_size,
const eUnsafeCharacterSet mask)
{
#define ACCEPTABLE(a) ((a) >= 32 && (a) < 128 && (acceptable[(a) - 32] & mask))
BLI_assert(escaped_string_size > 0);
/* Remove space for \0. */
int escaped_string_len = escaped_string_size - 1;
const char *p;
char *q;
int c;
for (q = escaped_string, p = string; (*p != '\0') && escaped_string_len; p++) {
c = uchar(*p);
if (!ACCEPTABLE(c)) {
if (escaped_string_len < 3) {
break;
}
*q++ = '%'; /* means hex coming */
*q++ = hex[c >> 4];
*q++ = hex[c & 15];
escaped_string_len -= 3;
}
else {
*q++ = *p;
escaped_string_len -= 1;
}
}
*q = '\0';
}
/** \} */
/* --- End of adapted code from glib. --- */
static bool thumbhash_from_path(const char * /*path*/, ThumbSource source, char *r_hash)
{
switch (source) {
case THB_SOURCE_FONT:
return IMB_thumb_load_font_get_hash(r_hash);
default:
r_hash[0] = '\0';
return false;
}
}
static bool uri_from_filepath(const char *path, char *uri)
{
char orig_uri[URI_MAX];
#ifdef WIN32
bool path_is_unc = BLI_path_is_unc(path);
char path_unc_normalized[FILE_MAX];
if (path_is_unc) {
STRNCPY(path_unc_normalized, path);
BLI_path_normalize_unc(path_unc_normalized, sizeof(path_unc_normalized));
path = path_unc_normalized;
/* Assign again because a normalized UNC path may resolve to a drive letter. */
path_is_unc = BLI_path_is_unc(path);
}
if (path_is_unc) {
/* Skip over the `\\` prefix, it's not needed for a URI. */
SNPRINTF(orig_uri, "file://%s", BLI_path_slash_skip(path));
}
else if (BLI_path_is_win32_drive(path)) {
SNPRINTF(orig_uri, "file:///%s", path);
/* Always use an uppercase drive/volume letter in the URI. */
orig_uri[8] = char(toupper(orig_uri[8]));
}
else {
/* Not a correct absolute path with a drive letter or UNC prefix. */
return false;
}
BLI_string_replace_char(orig_uri, '\\', '/');
#else
SNPRINTF(orig_uri, "file://%s", path);
#endif
escape_uri_string(orig_uri, uri, URI_MAX, UNSAFE_PATH);
return true;
}
static bool thumbpathname_from_uri(const char *uri,
char *r_path,
const int path_maxncpy,
char *r_name,
int name_maxncpy,
ThumbSize size)
{
char name_buff[40];
if (r_path && !r_name) {
r_name = name_buff;
name_maxncpy = sizeof(name_buff);
}
if (r_name) {
char hexdigest[33];
uchar digest[16];
BLI_hash_md5_buffer(uri, strlen(uri), digest);
hexdigest[0] = '\0';
BLI_snprintf(r_name, name_maxncpy, "%s.png", BLI_hash_md5_to_hexdigest(digest, hexdigest));
// printf("%s: '%s' --> '%s'\n", __func__, uri, r_name);
}
if (r_path) {
char tmppath[FILE_MAX];
if (get_thumb_dir(tmppath, size)) {
BLI_snprintf(r_path, path_maxncpy, "%s%s", tmppath, r_name);
// printf("%s: '%s' --> '%s'\n", __func__, uri, r_path);
return true;
}
}
return false;
}
static void thumbname_from_uri(const char *uri, char *thumb, const int thumb_maxncpy)
{
thumbpathname_from_uri(uri, nullptr, 0, thumb, thumb_maxncpy, THB_FAIL);
}
static bool thumbpath_from_uri(const char *uri, char *path, const int path_maxncpy, ThumbSize size)
{
return thumbpathname_from_uri(uri, path, path_maxncpy, nullptr, 0, size);
}
void IMB_thumb_makedirs()
{
char tpath[FILE_MAX];
#if 0 /* UNUSED */
if (get_thumb_dir(tpath, THB_NORMAL)) {
BLI_dir_create_recursive(tpath);
}
#endif
if (get_thumb_dir(tpath, THB_LARGE)) {
BLI_dir_create_recursive(tpath);
}
if (get_thumb_dir(tpath, THB_FAIL)) {
BLI_dir_create_recursive(tpath);
}
}
static bool thumb_cancel_requested(const ThumbCancellationToken *cancel_token)
{
return cancel_token && cancel_token->is_cancelled();
}
/* create thumbnail for file and returns new imbuf for thumbnail */
static ImBuf *thumb_create_ex(const char *file_path,
const char *uri,
const char *thumb,
const bool use_hash,
const char *hash,
const char *blen_group,
const char *blen_id,
ThumbSize size,
ThumbSource source,
ImBuf *img,
const ThumbCancellationToken *cancel_token = nullptr)
{
if (thumb_cancel_requested(cancel_token)) {
return nullptr;
}
/* Just in case these folders got deleted somehow. */
IMB_thumb_makedirs();
char desc[URI_MAX + 22];
char tpath[FILE_MAX];
char tdir[FILE_MAX];
char temp[FILE_MAX];
char mtime[40] = "0"; /* in case we can't stat the file */
short tsize = 128;
BLI_stat_t info;
switch (size) {
case THB_NORMAL:
tsize = PREVIEW_RENDER_DEFAULT_HEIGHT;
break;
case THB_LARGE:
tsize = PREVIEW_RENDER_LARGE_HEIGHT;
break;
case THB_FAIL:
tsize = 1;
break;
default:
return nullptr; /* unknown size */
}
if (get_thumb_dir(tdir, size)) {
SNPRINTF(tpath, "%s%s", tdir, thumb);
// thumb[8] = '\0'; /* shorten for `temp` name, not needed anymore */
SNPRINTF(temp, "%sblender_%d_%s.png", tdir, abs(getpid()), thumb);
if (BLI_path_ncmp(file_path, tdir, sizeof(tdir)) == 0) {
return nullptr;
}
if (size == THB_FAIL) {
img = IMB_allocImBuf(1, 1, ImBufFlags::ByteData | ImBufFlags::Metadata);
if (!img) {
return nullptr;
}
}
else {
if (ELEM(source, THB_SOURCE_IMAGE, THB_SOURCE_BLEND, THB_SOURCE_FONT, THB_SOURCE_OBJECT_IO))
{
/* only load if we didn't give an image */
if (img == nullptr) {
switch (source) {
case THB_SOURCE_IMAGE:
img = IMB_thumb_load_image(file_path, tsize, nullptr);
break;
case THB_SOURCE_BLEND:
img = IMB_thumb_load_blend(file_path, blen_group, blen_id);
break;
case THB_SOURCE_FONT:
img = IMB_thumb_load_font(file_path, tsize, tsize);
break;
case THB_SOURCE_OBJECT_IO: {
if (BLI_path_extension_check(file_path, ".svg")) {
img = IMB_thumb_load_image(file_path, tsize, nullptr);
}
break;
}
default:
BLI_assert_unreachable(); /* This should never happen */
}
}
if (img != nullptr) {
if (BLI_stat(file_path, &info) != -1) {
SNPRINTF_UTF8(mtime, "%ld", (long int)info.st_mtime);
}
}
}
else if (THB_SOURCE_MOVIE == source) {
MovieReader *anim = nullptr;
/* Image buffer is converted from float to byte and only the latter one is used, and the
* conversion process is aware of the float color-space. So it is possible to save some
* compute time by keeping the original color-space for movies. */
anim = MOV_open_file(file_path, ImBufFlags::Zero, 0, true, nullptr);
if (anim != nullptr) {
img = MOV_decode_frame(anim, 0, IMB_PROXY_NONE);
if (img == nullptr) {
// printf("not an anim; %s\n", file_path);
}
else {
IMB_freeImBuf(img);
img = MOV_decode_preview_frame(anim);
}
MOV_close(anim);
}
if (BLI_stat(file_path, &info) != -1) {
SNPRINTF_UTF8(mtime, "%ld", (long int)info.st_mtime);
}
}
if (!img) {
return nullptr;
}
if (thumb_cancel_requested(cancel_token)) {
IMB_freeImBuf(img);
return nullptr;
}
if (img->x > tsize || img->y > tsize) {
float scale = std::min(float(tsize) / float(img->x), float(tsize) / float(img->y));
/* Scaling down must never assign zero width/height, see: #89868. */
short ex = std::max(short(1), short(img->x * scale));
short ey = std::max(short(1), short(img->y * scale));
/* Save some time by only scaling byte buffer. */
if (img->float_data()) {
if (img->byte_data() == nullptr) {
IMB_byte_from_float(img);
}
IMB_free_float_pixels(img);
}
IMB_scale(img, ex, ey, IMBScaleFilter::Box, false);
}
}
if (thumb_cancel_requested(cancel_token)) {
IMB_freeImBuf(img);
return nullptr;
}
SNPRINTF_UTF8(desc, "Thumbnail for %s", uri);
IMB_metadata_ensure(&img->metadata);
IMB_metadata_set_field(img->metadata, "Software", "Blender");
IMB_metadata_set_field(img->metadata, "Thumb::URI", uri);
IMB_metadata_set_field(img->metadata, "Description", desc);
IMB_metadata_set_field(img->metadata, "Thumb::MTime", mtime);
if (use_hash) {
IMB_metadata_set_field(img->metadata, "X-Blender::Hash", hash);
}
img->ftype = IMB_FTYPE_PNG;
img->color_mode = ImColorMode::RGBA;
/* If we generated from a 16bit PNG e.g., we have a float rect, not a byte one - fix this. */
IMB_byte_from_float(img);
IMB_free_float_pixels(img);
if (IMB_save_image(img, temp, ImBufFlags::ByteData | ImBufFlags::Metadata)) {
#ifndef WIN32
chmod(temp, S_IRUSR | S_IWUSR);
#endif
// printf("%s saving thumb: '%s'\n", __func__, tpath);
BLI_rename_overwrite(temp, tpath);
}
}
return img;
}
static ImBuf *thumb_create_or_fail(const char *file_path,
const char *uri,
const char *thumb,
const bool use_hash,
const char *hash,
const char *blen_group,
const char *blen_id,
ThumbSize size,
ThumbSource source,
const ThumbCancellationToken *cancel_token)
{
ImBuf *img = thumb_create_ex(file_path,
uri,
thumb,
use_hash,
hash,
blen_group,
blen_id,
size,
source,
nullptr,
cancel_token);
if (!img && !thumb_cancel_requested(cancel_token)) {
/* thumb creation failed, write fail thumb */
img = thumb_create_ex(
file_path, uri, thumb, use_hash, hash, blen_group, blen_id, THB_FAIL, source, nullptr);
if (img) {
/* we don't need failed thumb anymore */
IMB_freeImBuf(img);
img = nullptr;
}
}
return img;
}
/**
* Do not generate thumbnails for 'temp' file paths (i.e. contained into system-defined temp
* directory).
*/
static bool skip_thumbnails_for_filepath(const char *filepath)
{
if (!BLI_path_extension_check(filepath, ".blend")) {
return false;
}
char temp_dir[FILE_MAX];
BLI_temp_directory_path_get(temp_dir, sizeof(temp_dir));
return BLI_path_contains(temp_dir, filepath);
}
ImBuf *IMB_thumb_create(const char *filepath, ThumbSize size, ThumbSource source, ImBuf *img)
{
if (source == THB_SOURCE_DIRECT) {
/* Not yet implemented (not needed currently). Could just directly write the image to the given
* `filepath`. */
BLI_assert_msg(source != THB_SOURCE_DIRECT,
"Writing thumbnails with direct source isn't implemented");
return nullptr;
}
if (skip_thumbnails_for_filepath(filepath)) {
return nullptr;
}
char uri[URI_MAX] = "";
char thumb_name[40];
if (!uri_from_filepath(filepath, uri)) {
return nullptr;
}
thumbname_from_uri(uri, thumb_name, sizeof(thumb_name));
return thumb_create_ex(
filepath, uri, thumb_name, false, THUMB_DEFAULT_HASH, nullptr, nullptr, size, source, img);
}
ImBuf *IMB_thumb_read(const char *file_or_lib_path, ThumbSize size)
{
char thumb[FILE_MAX];
char uri[URI_MAX];
ImBuf *img = nullptr;
if (!uri_from_filepath(file_or_lib_path, uri)) {
return nullptr;
}
if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
img = IMB_load_image_from_filepath(thumb, ImBufFlags::ByteData | ImBufFlags::Metadata);
}
return img;
}
void IMB_thumb_delete(const char *file_or_lib_path, ThumbSize size)
{
char thumb[FILE_MAX];
char uri[URI_MAX];
if (!uri_from_filepath(file_or_lib_path, uri)) {
return;
}
if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
if (BLI_path_ncmp(file_or_lib_path, thumb, sizeof(thumb)) == 0) {
return;
}
if (BLI_exists(thumb)) {
BLI_delete(thumb, false, false);
}
}
}
ImBuf *IMB_thumb_manage(const char *file_or_lib_path,
ThumbSize size,
ThumbSource source,
const ThumbCancellationToken *cancel_token)
{
if (thumb_cancel_requested(cancel_token)) {
return nullptr;
}
if (source == THB_SOURCE_DIRECT) {
const eFileAttributes file_attributes = BLI_file_attributes(file_or_lib_path);
/* Don't trigger download files from online drives. Maybe less of a problem for
* #THE_SOURCE_DIRECT, since what we request is the actual image itself. For other sources this
* may download a bunch of large files like videos or blends, just to extract a thumbnail. But
* for now, keep the API consistent and do not trigger download of such files. */
if (file_attributes & FILE_ATTR_OFFLINE) {
return nullptr;
}
ImBuf *thumb = IMB_load_image_from_filepath(file_or_lib_path,
ImBufFlags::ByteData | ImBufFlags::Metadata);
if (!thumb) {
return nullptr;
}
IMB_byte_from_float(thumb);
IMB_free_float_pixels(thumb);
return thumb;
}
if (skip_thumbnails_for_filepath(file_or_lib_path)) {
return nullptr;
}
char path_buff[FILE_MAX_LIBEXTRA];
char *blen_group = nullptr, *blen_id = nullptr;
/* Will be the actual path to the file, i.e. the same as #file_or_lib_path, or if that points
* into a .blend, the path of the .blend. */
const char *file_path = file_or_lib_path;
if (source == THB_SOURCE_BLEND) {
if (BKE_blendfile_library_path_explode(file_or_lib_path, path_buff, &blen_group, &blen_id)) {
if (blen_group) {
if (!blen_id) {
/* No preview for blen groups */
return nullptr;
}
file_path = path_buff; /* path needs to be a valid file! */
}
}
}
BLI_stat_t st;
if (BLI_stat(file_path, &st) == -1) {
return nullptr;
}
char uri[URI_MAX];
if (!uri_from_filepath(file_or_lib_path, uri)) {
return nullptr;
}
/* Don't access offline files, only use already existing thumbnails (don't recreate). */
const eFileAttributes file_attributes = BLI_file_attributes(file_path);
if (file_attributes & FILE_ATTR_OFFLINE) {
char thumb_path[FILE_MAX];
if (thumbpath_from_uri(uri, thumb_path, sizeof(thumb_path), size)) {
return IMB_load_image_from_filepath(thumb_path, ImBufFlags::ByteData | ImBufFlags::Metadata);
}
return nullptr;
}
char thumb_path[FILE_MAX];
if (thumbpath_from_uri(uri, thumb_path, sizeof(thumb_path), THB_FAIL)) {
/* failure thumb exists, don't try recreating */
if (BLI_exists(thumb_path)) {
/* clear out of date fail case (note for blen IDs we use blender file itself here) */
if (BLI_file_older(thumb_path, file_path)) {
BLI_delete(thumb_path, false, false);
}
else {
return nullptr;
}
}
}
ImBuf *img = nullptr;
char thumb_name[40];
if (thumbpathname_from_uri(
uri, thumb_path, sizeof(thumb_path), thumb_name, sizeof(thumb_name), size))
{
/* The requested path points to a generated thumbnail already (path into the thumbnail cache
* directory). Attempt to load that, there's nothing we can recreate. */
if (BLI_path_ncmp(file_or_lib_path, thumb_path, sizeof(thumb_path)) == 0) {
img = IMB_load_image_from_filepath(file_or_lib_path, ImBufFlags::ByteData);
}
else {
img = IMB_load_image_from_filepath(thumb_path, ImBufFlags::ByteData | ImBufFlags::Metadata);
if (img) {
bool regenerate = false;
char mtime[40];
char thumb_hash[33];
char thumb_hash_curr[33];
const bool use_hash = thumbhash_from_path(file_path, source, thumb_hash);
if (IMB_metadata_get_field(img->metadata, "Thumb::MTime", mtime, sizeof(mtime))) {
regenerate = (st.st_mtime != atol(mtime));
}
else {
/* illegal thumb, regenerate it! */
regenerate = true;
}
if (use_hash && !regenerate) {
if (IMB_metadata_get_field(
img->metadata, "X-Blender::Hash", thumb_hash_curr, sizeof(thumb_hash_curr)))
{
regenerate = !STREQ(thumb_hash, thumb_hash_curr);
}
else {
regenerate = true;
}
}
if (regenerate) {
/* recreate all thumbs */
IMB_freeImBuf(img);
img = nullptr;
IMB_thumb_delete(file_or_lib_path, THB_NORMAL);
IMB_thumb_delete(file_or_lib_path, THB_LARGE);
IMB_thumb_delete(file_or_lib_path, THB_FAIL);
img = thumb_create_or_fail(file_path,
uri,
thumb_name,
use_hash,
thumb_hash,
blen_group,
blen_id,
size,
source,
cancel_token);
}
}
else {
char thumb_hash[33];
const bool use_hash = thumbhash_from_path(file_path, source, thumb_hash);
img = thumb_create_or_fail(file_path,
uri,
thumb_name,
use_hash,
thumb_hash,
blen_group,
blen_id,
size,
source,
cancel_token);
}
}
}
/* Our imbuf **must** have a valid rect (i.e. 8-bits/channels)
* data, we rely on this in draw code.
* However, in some cases we may end loading 16bits PNGs, which generated float buffers.
* This should be taken care of in generation step, but add also a safeguard here! */
if (img) {
IMB_byte_from_float(img);
IMB_free_float_pixels(img);
}
return img;
}
/* ***** Threading ***** */
/* Thumbnail handling is not really threadsafe in itself.
* However, as long as we do not operate on the same file, we shall have no collision.
* So idea is to 'lock' a given source file path.
*/
struct IMBThumbLocks {
Set<std::string> locked_paths;
int lock_counter = 0;
ThreadCondition cond = {};
};
static IMBThumbLocks &get_thumb_locks()
{
static IMBThumbLocks thumb_locks{};
return thumb_locks;
}
void IMB_thumb_locks_acquire()
{
BLI_thread_lock(LOCK_IMAGE);
IMBThumbLocks &thumb_locks = get_thumb_locks();
if (thumb_locks.lock_counter == 0) {
BLI_condition_init(&thumb_locks.cond);
}
thumb_locks.lock_counter++;
BLI_assert(thumb_locks.lock_counter > 0);
BLI_thread_unlock(LOCK_IMAGE);
}
void IMB_thumb_locks_release()
{
IMBThumbLocks &thumb_locks = get_thumb_locks();
BLI_thread_lock(LOCK_IMAGE);
BLI_assert(thumb_locks.lock_counter > 0);
thumb_locks.lock_counter--;
if (thumb_locks.lock_counter == 0) {
thumb_locks.locked_paths.clear();
BLI_condition_end(&thumb_locks.cond);
}
BLI_thread_unlock(LOCK_IMAGE);
}
void IMB_thumb_path_lock(const char *path)
{
IMBThumbLocks &thumb_locks = get_thumb_locks();
BLI_thread_lock(LOCK_IMAGE);
BLI_assert(thumb_locks.lock_counter > 0);
while (!thumb_locks.locked_paths.add(path)) {
BLI_condition_wait_global_mutex(&thumb_locks.cond, LOCK_IMAGE);
}
BLI_thread_unlock(LOCK_IMAGE);
}
void IMB_thumb_path_unlock(const char *path)
{
IMBThumbLocks &thumb_locks = get_thumb_locks();
BLI_thread_lock(LOCK_IMAGE);
BLI_assert(thumb_locks.lock_counter > 0);
if (!thumb_locks.locked_paths.remove(path)) {
BLI_assert_unreachable();
}
BLI_condition_notify_all(&thumb_locks.cond);
BLI_thread_unlock(LOCK_IMAGE);
}
} // namespace blender

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <cstdlib>
#include <cstring>
#include "BLO_readfile.hh"
#include "BKE_idtype.hh"
#include "BKE_main.hh"
#include "BKE_preview_image.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_thumbs.hh"
#include "MEM_guardedalloc.h"
namespace blender {
/* NOTE: we should handle all previews for a same group at once, would avoid reopening
* `.blend` file for each and every ID. However, this adds some complexity,
* so keep it for later. */
static ImBuf *imb_thumb_load_from_blend_id(const char *blen_path,
const char *blen_group,
const char *blen_id)
{
ImBuf *ima = nullptr;
BlendFileReadReport bf_reports = {};
bf_reports.reports = nullptr;
BlendHandle *libfiledata = BLO_blendhandle_from_file(blen_path, &bf_reports);
if (libfiledata == nullptr) {
return nullptr;
}
int idcode = BKE_idtype_idcode_from_name(blen_group);
PreviewImage *preview = BLO_blendhandle_get_preview_for_id(libfiledata, idcode, blen_id);
BLO_blendhandle_close(libfiledata);
if (preview) {
ima = BKE_previewimg_to_imbuf(preview, ICON_SIZE_PREVIEW);
BKE_previewimg_free(&preview);
}
return ima;
}
static ImBuf *imb_thumb_load_from_blendfile(const char *blen_path)
{
BlendThumbnail *data = BLO_thumbnail_from_file(blen_path);
ImBuf *ima = BKE_main_thumbnail_to_imbuf(nullptr, data);
if (data) {
MEM_delete(data);
}
return ima;
}
ImBuf *IMB_thumb_load_blend(const char *blen_path, const char *blen_group, const char *blen_id)
{
if (blen_group && blen_id) {
return imb_thumb_load_from_blend_id(blen_path, blen_group, blen_id);
}
return imb_thumb_load_from_blendfile(blen_path);
}
} // namespace blender

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "BLI_hash_md5.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_thumbs.hh" /* own include. */
/* XXX, bad level call */
#include "../../blenfont/BLF_api.hh"
namespace blender {
/* Only change if we need to update the previews in the on-disk cache. */
#define FONT_THUMB_VERSION "1.0.1"
ImBuf *IMB_thumb_load_font(const char *filepath, uint x, uint y)
{
ImBuf *ibuf = IMB_allocImBuf(x, y, ImBufFlags::ByteData | ImBufFlags::Metadata);
/* fill with white and zero alpha */
const float col[4] = {1.0f, 1.0f, 1.0f, 0.0f};
IMB_rectfill(ibuf, col);
if (!BLF_thumb_preview(filepath, ibuf->byte_data_for_write(), ibuf->x, ibuf->y, ibuf->channels))
{
IMB_freeImBuf(ibuf);
ibuf = nullptr;
}
return ibuf;
}
bool IMB_thumb_load_font_get_hash(char *r_hash)
{
uchar digest[16];
BLI_hash_md5_buffer(FONT_THUMB_VERSION, sizeof(FONT_THUMB_VERSION), digest);
r_hash[0] = '\0';
BLI_hash_md5_to_hexdigest(digest, r_hash);
return true;
}
ImBuf *IMB_font_preview(const char *filepath,
uint width,
const float color[4],
const char *sample_text)
{
int font_id = (filepath[0] != '<') ? BLF_load(filepath) : 0;
if (font_id == -1) {
return nullptr;
}
const char default_sample[] = "ABCDabefg&0123";
const char *sample = sample_text ? sample_text : default_sample;
BLF_buffer_col(font_id, color);
BLF_size(font_id, 50.0f);
float name_w;
float name_h;
BLF_width_and_height(font_id, sample, strlen(sample), &name_w, &name_h);
const float scale = float(width) / name_w * 0.98f;
BLF_size(font_id, scale * 50.0f);
name_w *= scale;
name_h *= scale;
const int height = int(name_h * 1.8f);
ImBuf *ibuf = IMB_allocImBuf(width, height, ImBufFlags::ByteData);
/* fill with white and zero alpha */
const float col[4] = {1.0f, 1.0f, 1.0f, 0.0f};
IMB_rectfill(ibuf, col);
BLF_buffer(font_id, nullptr, ibuf->byte_data_for_write(), width, height, 4, nullptr);
BLF_position(font_id, 0.0f, height * 0.3f, 0.0f);
BLF_draw_buffer(font_id, sample, 1024);
BLF_buffer(font_id, nullptr, nullptr, 0, 0, 4, nullptr);
if (font_id != 0) {
BLF_unload_id(font_id);
}
return ibuf;
}
} // namespace blender

View File

@@ -0,0 +1,505 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <type_traits>
#include "BLI_math_color.h"
#include "BLI_math_interp.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include "BLI_task.hh"
#include "IMB_imbuf.hh"
#include "IMB_interp.hh"
namespace blender {
namespace imbuf::transform {
struct TransformContext {
const ImBuf *src;
ImBuf *dst;
eIMBTransformMode mode;
/* UV coordinates at the destination origin (0,0) in source image space. */
float2 start_uv;
/* Source UV step delta, when moving along one destination pixel in X axis. */
float2 add_x;
/* Source UV step delta, when moving along one destination pixel in Y axis. */
float2 add_y;
/* Source corners in destination pixel space, counter-clockwise. */
float2 src_corners[4];
IndexRange dst_region_x_range;
IndexRange dst_region_y_range;
/* Cropping region in source image pixel space. */
rctf src_crop;
void init(const float3x3 &transform_matrix, const bool has_source_crop)
{
start_uv = transform_matrix.location().xy();
add_x = transform_matrix.x_axis().xy();
add_y = transform_matrix.y_axis().xy();
init_destination_region(transform_matrix, has_source_crop);
}
private:
void init_destination_region(const float3x3 &transform_matrix, const bool has_source_crop)
{
if (!has_source_crop) {
dst_region_x_range = IndexRange(dst->x);
dst_region_y_range = IndexRange(dst->y);
return;
}
/* Transform the src_crop to the destination buffer with a margin. */
const int2 margin(2);
rcti rect;
BLI_rcti_init_minmax(&rect);
float3x3 inverse = math::invert(transform_matrix);
const int2 src_coords[4] = {int2(src_crop.xmin, src_crop.ymin),
int2(src_crop.xmax, src_crop.ymin),
int2(src_crop.xmax, src_crop.ymax),
int2(src_crop.xmin, src_crop.ymax)};
for (int i = 0; i < 4; i++) {
int2 src_co = src_coords[i];
float2 dst_co = math::transform_point(inverse, float2(src_co));
src_corners[i] = float2(dst_co.x, dst_co.y);
BLI_rcti_do_minmax_v(&rect, int2(dst_co) + margin);
BLI_rcti_do_minmax_v(&rect, int2(dst_co) - margin);
}
/* Clamp rect to fit inside the image buffer. */
rcti dest_rect;
BLI_rcti_init(&dest_rect, 0, dst->x, 0, dst->y);
BLI_rcti_isect(&rect, &dest_rect, &rect);
dst_region_x_range = IndexRange(rect.xmin, BLI_rcti_size_x(&rect));
dst_region_y_range = IndexRange(rect.ymin, BLI_rcti_size_y(&rect));
}
};
/* Crop uv-coordinates that are outside the user data src_crop rect. */
static bool should_discard(const TransformContext &ctx, const float2 &uv)
{
return uv.x < ctx.src_crop.xmin || uv.x >= ctx.src_crop.xmax || uv.y < ctx.src_crop.ymin ||
uv.y >= ctx.src_crop.ymax;
}
template<typename T> static T *init_pixel_pointer(ImBuf *image, int x, int y);
template<> uchar *init_pixel_pointer(ImBuf *image, int x, int y)
{
return image->byte_data_for_write() + (size_t(y) * image->x + x) * image->channels;
}
template<> float *init_pixel_pointer(ImBuf *image, int x, int y)
{
return image->float_data_for_write() + (size_t(y) * image->x + x) * image->channels;
}
static float wrap_uv(float value, int size)
{
int x = int(floorf(value));
if (UNLIKELY(x < 0 || x >= size)) {
x %= size;
if (x < 0) {
x += size;
}
}
return x;
}
/* Read a pixel from an image buffer, with filtering/wrapping parameters. */
template<eIMBInterpolationFilterMode Filter, typename T, int NumChannels, bool WrapUV>
static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
{
if constexpr (WrapUV) {
u = wrap_uv(u, source->x);
v = wrap_uv(v, source->y);
}
/* Bilinear/cubic interpolation functions use `floor(uv)` and `floor(uv)+1`
* texels. For proper mapping between pixel and texel spaces, need to
* subtract 0.5. */
if constexpr (Filter != IMB_FILTER_NEAREST) {
u -= 0.5f;
v -= 0.5f;
}
if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float> && NumChannels == 4) {
interpolate_bilinear_fl(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, uchar> && NumChannels == 4)
{
interpolate_nearest_border_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, uchar> && NumChannels == 4)
{
interpolate_bilinear_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float>) {
if constexpr (WrapUV) {
math::interpolate_bilinear_wrapmode_fl(source->float_data(),
r_sample,
source->x,
source->y,
NumChannels,
u,
v,
math::InterpWrapMode::Repeat,
math::InterpWrapMode::Repeat);
}
else {
math::interpolate_bilinear_fl(
source->float_data(), r_sample, source->x, source->y, NumChannels, u, v);
}
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, float>) {
math::interpolate_nearest_border_fl(
source->float_data(), r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, float>) {
math::interpolate_cubic_bspline_fl(
source->float_data(), r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, uchar> &&
NumChannels == 4)
{
interpolate_cubic_bspline_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, float>) {
math::interpolate_cubic_mitchell_fl(
source->float_data(), r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, uchar> &&
NumChannels == 4)
{
interpolate_cubic_mitchell_byte(source, r_sample, u, v);
}
else {
/* Unsupported sampler. */
BLI_assert_unreachable();
}
}
static void add_subsample(const float src[4], float dst[4])
{
add_v4_v4(dst, src);
}
static void add_subsample(const uchar src[4], float dst[4])
{
float premul[4];
straight_uchar_to_premul_float(premul, src);
add_v4_v4(dst, premul);
}
static void store_premul_float_sample(const float sample[4], float dst[4])
{
copy_v4_v4(dst, sample);
}
static void store_premul_float_sample(const float sample[4], uchar dst[4])
{
premul_float_to_straight_uchar(dst, sample);
}
template<int SrcChannels> static void store_sample(const uchar *sample, uchar *dst)
{
BLI_STATIC_ASSERT(SrcChannels == 4, "Unsigned chars always have 4 channels.");
copy_v4_v4_uchar(dst, sample);
}
template<int SrcChannels> static void store_sample(const float *sample, float *dst)
{
if constexpr (SrcChannels == 4) {
copy_v4_v4(dst, sample);
}
else if constexpr (SrcChannels == 3) {
copy_v4_fl4(dst, sample[0], sample[1], sample[2], 1.0f);
}
else if constexpr (SrcChannels == 2) {
copy_v4_fl4(dst, sample[0], sample[1], 0.0f, 1.0f);
}
else if constexpr (SrcChannels == 1) {
/* NOTE: single channel sample is stored as grayscale. */
copy_v4_fl4(dst, sample[0], sample[0], sample[0], 1.0f);
}
else {
BLI_assert_unreachable();
}
}
/* Process a block of destination image scanlines. */
template<eIMBInterpolationFilterMode Filter,
typename T,
int SrcChannels,
bool CropSource,
bool WrapUV>
static void process_scanlines(const TransformContext &ctx, IndexRange y_range)
{
if constexpr (Filter == IMB_FILTER_BOX) {
/* Multiple samples per pixel: accumulate them pre-multiplied,
* divide by sample count and write out (un-pre-multiplying if writing out
* to byte image).
*
* Do a box filter: for each destination pixel, accumulate XxY samples from source,
* based on scaling factors (length of X/Y pixel steps). Use at least 2 samples
* along each direction, so that in case of rotation the image gets
* some anti-aliasing. Use at most 100 samples along each direction,
* just as some way of clamping possible upper cost. Scaling something down by more
* than 100x should rarely if ever happen, worst case they will get some aliasing.
*/
float2 uv_start = ctx.start_uv;
int sub_count_x = int(math::clamp(roundf(math::length(ctx.add_x)), 2.0f, 100.0f));
int sub_count_y = int(math::clamp(roundf(math::length(ctx.add_y)), 2.0f, 100.0f));
const float inv_count = 1.0f / (sub_count_x * sub_count_y);
const float2 sub_step_x = ctx.add_x / sub_count_x;
const float2 sub_step_y = ctx.add_y / sub_count_y;
for (int yi : y_range) {
T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
float2 uv_row = uv_start + yi * ctx.add_y;
for (int xi : ctx.dst_region_x_range) {
const float2 uv = uv_row + xi * ctx.add_x;
float sample[4] = {};
for (int sub_y = 0; sub_y < sub_count_y; sub_y++) {
for (int sub_x = 0; sub_x < sub_count_x; sub_x++) {
float2 delta = (sub_x + 0.5f) * sub_step_x + (sub_y + 0.5f) * sub_step_y;
float2 sub_uv = uv + delta;
if (!CropSource || !should_discard(ctx, sub_uv)) {
T sub_sample[4];
sample_image<eIMBInterpolationFilterMode::IMB_FILTER_NEAREST,
T,
SrcChannels,
WrapUV>(ctx.src, sub_uv.x, sub_uv.y, sub_sample);
add_subsample(sub_sample, sample);
}
}
}
mul_v4_v4fl(sample, sample, inv_count);
store_premul_float_sample(sample, output);
output += 4;
}
}
}
else {
/* One sample per pixel.
* NOTE: sample at pixel center for proper filtering. */
float2 uv_start = ctx.start_uv + ctx.add_x * 0.5f + ctx.add_y * 0.5f;
for (int yi : y_range) {
T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
float2 uv_row = uv_start + yi * ctx.add_y;
for (int xi : ctx.dst_region_x_range) {
float2 uv = uv_row + xi * ctx.add_x;
if (!CropSource || !should_discard(ctx, uv)) {
T sample[4];
sample_image<Filter, T, SrcChannels, WrapUV>(ctx.src, uv.x, uv.y, sample);
store_sample<SrcChannels>(sample, output);
}
output += 4;
}
}
}
}
template<eIMBInterpolationFilterMode Filter, typename T, int SrcChannels>
static void transform_scanlines(const TransformContext &ctx, IndexRange y_range)
{
switch (ctx.mode) {
case IMB_TRANSFORM_MODE_REGULAR:
process_scanlines<Filter, T, SrcChannels, false, false>(ctx, y_range);
break;
case IMB_TRANSFORM_MODE_CROP_SRC:
process_scanlines<Filter, T, SrcChannels, true, false>(ctx, y_range);
break;
case IMB_TRANSFORM_MODE_WRAP_REPEAT:
process_scanlines<Filter, T, SrcChannels, false, true>(ctx, y_range);
break;
default:
BLI_assert_unreachable();
break;
}
}
template<eIMBInterpolationFilterMode Filter>
static void transform_scanlines_filter(const TransformContext &ctx, IndexRange y_range)
{
int channels = ctx.src->channels;
if (ctx.dst->float_data() && ctx.src->float_data()) {
/* Float pixels. */
if (channels == 4) {
transform_scanlines<Filter, float, 4>(ctx, y_range);
}
else if (channels == 3) {
transform_scanlines<Filter, float, 3>(ctx, y_range);
}
else if (channels == 2) {
transform_scanlines<Filter, float, 2>(ctx, y_range);
}
else if (channels == 1) {
transform_scanlines<Filter, float, 1>(ctx, y_range);
}
}
if (ctx.dst->byte_data() && ctx.src->byte_data()) {
/* Byte pixels. */
if (channels == 4) {
transform_scanlines<Filter, uchar, 4>(ctx, y_range);
}
}
}
static float calc_coverage(float2 pos, int2 ipos, float2 delta, bool is_steep)
{
/* Very approximate: just take difference from coordinate (x or y based on
* steepness) to the integer coordinate. Adjust based on directions
* of the edges. */
float cov;
if (is_steep) {
cov = fabsf(ipos.x - pos.x);
if (delta.y < 0) {
cov = 1.0f - cov;
}
}
else {
cov = fabsf(ipos.y - pos.y);
if (delta.x > 0) {
cov = 1.0f - cov;
}
}
cov = math::clamp(cov, 0.0f, 1.0f);
/* Resulting coverage is 0.5 .. 1.0 range, since we are only covering
* half of the pixels that should be AA'd (the other half is outside the
* quad and does not get rasterized). Square the coverage to get
* more range, and it looks a bit nicer that way. */
cov *= cov;
return cov;
}
static void edge_aa(const TransformContext &ctx)
{
/* Rasterize along outer source edges into the destination image,
* reducing alpha based on pixel distance to the edge at each pixel.
* This is very approximate and not 100% correct "analytical AA",
* but simple to do and better than nothing. */
for (int line_idx = 0; line_idx < 4; line_idx++) {
float2 ptA = ctx.src_corners[line_idx];
float2 ptB = ctx.src_corners[(line_idx + 1) & 3];
float2 delta = ptB - ptA;
float2 abs_delta = math::abs(delta);
float length = math::max(abs_delta.x, abs_delta.y);
if (length < 1) {
continue;
}
bool is_steep = length == abs_delta.y;
/* It is very common to have non-rotated strips; check if edge line is
* horizontal or vertical and would not alter the coverage and can
* be skipped. */
constexpr float NO_ROTATION = 1.0e-6f;
constexpr float NO_AA_CONTRIB = 1.0e-2f;
if (is_steep) {
if ((abs_delta.x < NO_ROTATION) && (fabsf(ptA.x - roundf(ptA.x)) < NO_AA_CONTRIB)) {
continue;
}
}
else {
if ((abs_delta.y < NO_ROTATION) && (fabsf(ptA.y - roundf(ptA.y)) < NO_AA_CONTRIB)) {
continue;
}
}
/* DDA line raster: step one pixel along the longer direction. */
delta /= length;
if (float *dst = ctx.dst->float_data_for_write()) {
for (int i = 0; i < length; i++) {
float2 pos = ptA + i * delta;
int2 ipos = int2(pos);
if (ipos.x >= 0 && ipos.x < ctx.dst->x && ipos.y >= 0 && ipos.y < ctx.dst->y) {
float cov = calc_coverage(pos, ipos, delta, is_steep);
size_t idx = (size_t(ipos.y) * ctx.dst->x + ipos.x) * 4;
dst[idx + 0] *= cov;
dst[idx + 1] *= cov;
dst[idx + 2] *= cov;
dst[idx + 3] *= cov;
}
}
}
if (uchar *dst = ctx.dst->byte_data_for_write()) {
for (int i = 0; i < length; i++) {
float2 pos = ptA + i * delta;
int2 ipos = int2(pos);
if (ipos.x >= 0 && ipos.x < ctx.dst->x && ipos.y >= 0 && ipos.y < ctx.dst->y) {
float cov = calc_coverage(pos, ipos, delta, is_steep);
size_t idx = (size_t(ipos.y) * ctx.dst->x + ipos.x) * 4;
dst[idx + 3] *= cov;
}
}
}
}
}
} // namespace imbuf::transform
using namespace blender::imbuf::transform;
void IMB_transform(const ImBuf *src,
ImBuf *dst,
const eIMBTransformMode mode,
const eIMBInterpolationFilterMode filter,
const float3x3 &transform_matrix,
const rctf *src_crop)
{
BLI_assert_msg(mode != IMB_TRANSFORM_MODE_CROP_SRC || src_crop != nullptr,
"No source crop rect given, but crop source is requested. Or source crop rect "
"was given, but crop source was not requested.");
BLI_assert_msg(dst->channels == 4, "Destination image must have 4 channels.");
TransformContext ctx;
ctx.src = src;
ctx.dst = dst;
ctx.mode = mode;
bool crop = mode == IMB_TRANSFORM_MODE_CROP_SRC;
if (crop) {
ctx.src_crop = *src_crop;
}
ctx.init(transform_matrix, crop);
threading::parallel_for(ctx.dst_region_y_range, 8, [&](IndexRange y_range) {
if (filter == IMB_FILTER_NEAREST) {
transform_scanlines_filter<IMB_FILTER_NEAREST>(ctx, y_range);
}
else if (filter == IMB_FILTER_BILINEAR) {
transform_scanlines_filter<IMB_FILTER_BILINEAR>(ctx, y_range);
}
else if (filter == IMB_FILTER_CUBIC_BSPLINE) {
transform_scanlines_filter<IMB_FILTER_CUBIC_BSPLINE>(ctx, y_range);
}
else if (filter == IMB_FILTER_CUBIC_MITCHELL) {
transform_scanlines_filter<IMB_FILTER_CUBIC_MITCHELL>(ctx, y_range);
}
else if (filter == IMB_FILTER_BOX) {
transform_scanlines_filter<IMB_FILTER_BOX>(ctx, y_range);
}
});
if (crop && (filter != IMB_FILTER_NEAREST)) {
edge_aa(ctx);
}
}
} // namespace blender

View File

@@ -0,0 +1,190 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#ifdef _WIN32
# include <io.h>
#endif
#include <cstdlib>
#include "BLI_fileops.h"
#include "BLI_path_utils.hh"
#ifdef _WIN32
# include "BLI_winstuff.h"
#endif
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "imbuf.hh"
#include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG = {"image.read"};
const char *imb_ext_image[] = {
/* #IMB_FTYPE_PNG */
".png",
/* #IMB_FTYPE_TGA */
".tga",
/* #IMB_FTYPE_BMP */
".bmp",
/* #IMB_FTYPE_JPG */
".jpg",
".jpeg",
/* #IMB_FTYPE_IRIS */
".sgi",
".rgb",
".rgba",
/* #IMB_FTYPE_TIF */
".tif",
".tiff",
/* A convention for naming tiled images at different resolutions (MIP-mapped),
* supported by various render engines texture caching systems.
* These are typically TIFF or EXR images. See the tool `maketx` from OpenImageIO. */
".tx",
/* #IMB_FTYPE_AVIF */
".avif",
#ifdef WITH_IMAGE_OPENJPEG
/* #IMB_FTYPE_JP2 */
".jp2",
".j2c",
#endif
/* #IMB_FTYPE_RADHDR */
".hdr",
/* #IMB_FTYPE_DDS */
".dds",
#ifdef WITH_IMAGE_CINEON
/* #IMB_FTYPE_DPX */
".dpx",
/* #IMB_FTYPE_CINEON */
".cin",
#endif
/* #IMB_FTYPE_EXR */
".exr",
/* #IMB_FTYPE_PSD */
".psd",
".pdd",
".psb",
#ifdef WITH_IMAGE_WEBP
/* #IMB_FTYPE_WEBP */
".webp",
#endif
nullptr,
};
const char *imb_ext_movie[] = {
".avi", ".flc", ".mov", ".movie", ".mp4", ".m4v", ".m2v", ".m2t", ".m2ts", ".mts",
".ts", ".mv", ".avs", ".wmv", ".ogv", ".ogg", ".r3d", ".dv", ".mpeg", ".mpg",
".mpg2", ".vob", ".mkv", ".flv", ".divx", ".xvid", ".mxf", ".webm", ".gif", nullptr,
};
/** Sort of wrong having audio extensions in imbuf. */
const char *imb_ext_audio[] = {
".wav",
".ogg",
".oga",
".mp3",
".mp2",
".ac3",
".aac",
".flac",
".wma",
".eac3",
".aif",
".aiff",
".m4a",
".mka",
".opus",
nullptr,
};
/* OIIO will validate the entire header of some files and DPX requires 2048 */
#define HEADER_SIZE 2048
static int64_t imb_test_image_read_header_from_filepath(const char *filepath,
uchar buf[HEADER_SIZE])
{
BLI_stat_t st;
int fp;
BLI_assert(!BLI_path_is_rel(filepath));
CLOG_TRACE(&LOG, "%s: loading %s", __func__, filepath);
if (BLI_stat(filepath, &st) == -1) {
return -1;
}
if (((st.st_mode) & S_IFMT) != S_IFREG) {
return -1;
}
if ((fp = BLI_open(filepath, O_BINARY | O_RDONLY, 0)) == -1) {
return -1;
}
const int64_t size = BLI_read(fp, buf, HEADER_SIZE);
close(fp);
return size;
}
eImbFileType IMB_test_image_type_from_memory(const uchar *buf, const size_t buf_size)
{
for (const ImFileType *type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (type->is_a != nullptr) {
if (type->is_a(buf, buf_size)) {
return type->filetype;
}
}
}
return IMB_FTYPE_NONE;
}
eImbFileType IMB_test_image_type(const char *filepath)
{
uchar buf[HEADER_SIZE];
const int64_t buf_size = imb_test_image_read_header_from_filepath(filepath, buf);
if (buf_size <= 0) {
return IMB_FTYPE_NONE;
}
return IMB_test_image_type_from_memory(buf, size_t(buf_size));
}
bool IMB_test_image_type_matches(const char *filepath, eImbFileType filetype)
{
uchar buf[HEADER_SIZE];
const int64_t buf_size = imb_test_image_read_header_from_filepath(filepath, buf);
if (buf_size <= 0) {
return false;
}
const ImFileType *type = IMB_file_type_from_ftype(filetype);
if (type != nullptr) {
/* Requesting to load a type that can't check its own header doesn't make sense.
* Keep the check for developers. */
BLI_assert(type->is_a != nullptr);
if (type->is_a != nullptr) {
return type->is_a(buf, size_t(buf_size));
}
}
return false;
}
#undef HEADER_SIZE
bool IMB_test_image(const char *filepath)
{
return (IMB_test_image_type(filepath) != IMB_FTYPE_NONE);
}
} // namespace blender

View File

@@ -0,0 +1,484 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include "CLG_log.h"
#include "GPU_capabilities.hh"
#include "GPU_texture.hh"
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender {
static CLG_LogRef LOG = {"image.gpu"};
/* gpu ibuf utils */
static bool imb_is_grayscale_texture_format_compatible(const ImBuf *ibuf)
{
if (ibuf->color_mode != ImColorMode::BW) {
return false;
}
if (ibuf->byte_data() && !ibuf->float_data()) {
if (IMB_colormanagement_space_is_scene_linear_srgb(ibuf->byte_buffer.colorspace) ||
IMB_colormanagement_space_is_scene_linear(ibuf->byte_buffer.colorspace))
{
/* Grey-scale byte buffers with these color transforms utilize float buffers under the hood
* and can therefore be optimized. */
return true;
}
/* TODO: Support gray-scale byte buffers.
* The challenge is that Blender always stores byte images as RGBA. */
return false;
}
/* Only #IMBuf's with color-space that do not modify the chrominance of the texture data relative
* to the scene color space can be uploaded as single channel textures. */
if (IMB_colormanagement_space_is_data(ibuf->float_buffer.colorspace) ||
IMB_colormanagement_space_is_scene_linear_srgb(ibuf->float_buffer.colorspace) ||
IMB_colormanagement_space_is_scene_linear(ibuf->float_buffer.colorspace))
{
return true;
}
return false;
}
static void imb_gpu_get_format(const ImBuf *ibuf,
bool high_bitdepth,
bool use_grayscale,
gpu::TextureFormat *r_texture_format)
{
const bool float_rect = (ibuf->float_data() != nullptr);
const bool is_grayscale = use_grayscale && imb_is_grayscale_texture_format_compatible(ibuf);
if (float_rect) {
/* Float. */
const bool use_high_bitdepth = (!(ibuf->foptions.flag & OPENEXR_HALF) && high_bitdepth);
*r_texture_format = is_grayscale ?
(use_high_bitdepth ? gpu::TextureFormat::SFLOAT_32 :
gpu::TextureFormat::SFLOAT_16) :
(use_high_bitdepth ? gpu::TextureFormat::SFLOAT_32_32_32_32 :
gpu::TextureFormat::SFLOAT_16_16_16_16);
}
else {
if (IMB_colormanagement_space_is_data(ibuf->byte_buffer.colorspace) ||
IMB_colormanagement_space_is_scene_linear(ibuf->byte_buffer.colorspace))
{
/* Non-color data or scene linear, just store buffer as is. */
*r_texture_format = (is_grayscale) ? gpu::TextureFormat::UNORM_8 :
gpu::TextureFormat::UNORM_8_8_8_8;
}
else if (IMB_colormanagement_space_is_scene_linear_srgb(ibuf->byte_buffer.colorspace)) {
/* scene linear + sRGB, store as byte texture that the GPU can decode directly. */
*r_texture_format = (is_grayscale) ? gpu::TextureFormat::SFLOAT_16 :
gpu::TextureFormat::SRGBA_8_8_8_8;
}
else {
/* Other colorspace, store as half float texture to avoid precision loss. */
*r_texture_format = (is_grayscale) ? gpu::TextureFormat::SFLOAT_16 :
gpu::TextureFormat::SFLOAT_16_16_16_16;
}
}
}
static const char *imb_gpu_get_swizzle(const ImBuf *ibuf)
{
return imb_is_grayscale_texture_format_compatible(ibuf) ? "rrra" : "rgba";
}
/* Return false if no suitable format was found. */
bool IMB_gpu_get_compressed_format(const ImBuf *ibuf, gpu::TextureFormat *r_texture_format)
{
if (ibuf->ftype != IMB_FTYPE_DDS) {
return false;
}
/* Compressed DDS files can really only express sRGB or data/linear. */
const bool use_srgb = (!IMB_colormanagement_space_is_data(ibuf->byte_buffer.colorspace) &&
!IMB_colormanagement_space_is_scene_linear(ibuf->byte_buffer.colorspace));
if (ibuf->foptions.flag & DDS_COMPRESSED_DXT1) {
*r_texture_format = use_srgb ? gpu::TextureFormat::SRGB_DXT1 : gpu::TextureFormat::SNORM_DXT1;
return true;
}
if (ibuf->foptions.flag & DDS_COMPRESSED_DXT3) {
*r_texture_format = use_srgb ? gpu::TextureFormat::SRGB_DXT3 : gpu::TextureFormat::SNORM_DXT3;
return true;
}
if (ibuf->foptions.flag & DDS_COMPRESSED_DXT5) {
*r_texture_format = use_srgb ? gpu::TextureFormat::SRGB_DXT5 : gpu::TextureFormat::SNORM_DXT5;
return true;
}
return false;
}
/**
* Apply colormanagement and scale buffer if needed.
* `*r_freedata` is set to true if the returned buffer need to be manually freed.
*/
static void *imb_gpu_get_data(ImBuf *ibuf,
const bool do_rescale,
const int rescale_size[2],
const bool store_premultiplied,
const bool allow_grayscale,
bool *r_freedata,
eGPUDataFormat *r_data_format)
{
bool is_float_rect = (ibuf->float_data() != nullptr);
const bool is_grayscale = allow_grayscale && imb_is_grayscale_texture_format_compatible(ibuf);
void *data_rect = (is_float_rect) ? static_cast<void *>(ibuf->float_data_for_write()) :
static_cast<void *>(ibuf->byte_data_for_write());
bool freedata = false;
if (is_float_rect) {
/* Float image is already in scene linear colorspace or non-color data by
* convention, no colorspace conversion needed. But we do require 4 channels
* currently. */
if (ibuf->channels != 4 || !store_premultiplied) {
data_rect = MEM_new_array_uninitialized<float>(4 * size_t(ibuf->x) * size_t(ibuf->y),
__func__);
*r_freedata = freedata = true;
if (data_rect == nullptr) {
return nullptr;
}
IMB_colormanagement_imbuf_to_float_texture(
static_cast<float *>(data_rect), 0, 0, ibuf->x, ibuf->y, ibuf, store_premultiplied);
}
}
else {
/* Byte image is in original colorspace from the file, and may need conversion.
*
* We must also convert to premultiplied for correct texture interpolation
* and consistency with float images. */
if (IMB_colormanagement_space_is_data(ibuf->byte_buffer.colorspace)) {
/* Non-color data, just store buffer as is. */
}
else if (IMB_colormanagement_space_is_scene_linear_srgb(ibuf->byte_buffer.colorspace) ||
IMB_colormanagement_space_is_scene_linear(ibuf->byte_buffer.colorspace))
{
/* scene linear + sRGB or scene linear, store as byte texture that the GPU can decode
* directly. */
data_rect = MEM_new_uninitialized((is_grayscale ? sizeof(float[4]) : sizeof(uchar[4])) *
IMB_get_pixel_count(ibuf),
__func__);
*r_freedata = freedata = true;
if (data_rect == nullptr) {
return nullptr;
}
/* Texture storage of images is defined by the alpha mode of the image. The
* downside of this is that there can be artifacts near alpha edges. However,
* this allows us to use sRGB texture formats and preserves color values in
* zero alpha areas, and appears generally closer to what game engines that we
* want to be compatible with do. */
if (is_grayscale) {
/* Convert to byte buffer to then pack as half floats reducing the buffer size by half. */
IMB_colormanagement_imbuf_to_float_texture(
static_cast<float *>(data_rect), 0, 0, ibuf->x, ibuf->y, ibuf, store_premultiplied);
is_float_rect = true;
}
else {
IMB_colormanagement_imbuf_to_byte_texture(
static_cast<uchar *>(data_rect), 0, 0, ibuf->x, ibuf->y, ibuf, store_premultiplied);
}
}
else {
/* Other colorspace, store as float texture to avoid precision loss. */
data_rect = MEM_new_array_uninitialized<float>(4 * size_t(ibuf->x) * size_t(ibuf->y),
__func__);
*r_freedata = freedata = true;
is_float_rect = true;
if (data_rect == nullptr) {
return nullptr;
}
/* Texture storage of images is defined by the alpha mode of the image. The
* downside of this is that there can be artifacts near alpha edges. However,
* this allows us to use sRGB texture formats and preserves color values in
* zero alpha areas, and appears generally closer to what game engines that we
* want to be compatible with do. */
IMB_colormanagement_imbuf_to_float_texture(
static_cast<float *>(data_rect), 0, 0, ibuf->x, ibuf->y, ibuf, store_premultiplied);
}
}
if (do_rescale) {
if (is_float_rect) {
float *new_rect = MEM_new_array_uninitialized<float>(
4 * size_t(rescale_size[0]) * size_t(rescale_size[1]), __func__);
IMB_scale_box(static_cast<float *>(data_rect),
int2(ibuf->x, ibuf->y),
4,
new_rect,
rescale_size,
true);
if (freedata) {
MEM_delete_void(data_rect);
}
data_rect = new_rect;
*r_freedata = freedata = true;
}
else {
uchar *new_rect = MEM_new_array_uninitialized<uchar>(
4 * size_t(rescale_size[0]) * size_t(rescale_size[1]), __func__);
IMB_scale_box(static_cast<uchar *>(data_rect),
int2(ibuf->x, ibuf->y),
4,
new_rect,
rescale_size,
true);
if (freedata) {
MEM_delete_void(data_rect);
}
data_rect = new_rect;
*r_freedata = freedata = true;
}
}
/* Pack first channel data manually at the start of the buffer. */
if (is_grayscale) {
void *src_rect = data_rect;
if (freedata == false) {
data_rect = MEM_new_uninitialized(
(is_float_rect ? sizeof(float) : sizeof(uchar)) * IMB_get_pixel_count(ibuf), __func__);
*r_freedata = freedata = true;
}
if (data_rect == nullptr) {
return nullptr;
}
size_t buffer_size = do_rescale ? size_t(rescale_size[0]) * size_t(rescale_size[1]) :
size_t(ibuf->x) * size_t(ibuf->y);
if (is_float_rect) {
for (size_t i = 0; i < buffer_size; i++) {
(static_cast<float *>(data_rect))[i] = (static_cast<float *>(src_rect))[i * 4];
}
}
else {
for (size_t i = 0; i < buffer_size; i++) {
(static_cast<uchar *>(data_rect))[i] = (static_cast<uchar *>(src_rect))[i * 4];
}
}
}
*r_data_format = (is_float_rect) ? GPU_DATA_FLOAT : GPU_DATA_UBYTE;
return data_rect;
}
gpu::Texture *IMB_touch_gpu_texture(const char *name,
ImBuf *ibuf,
int w,
int h,
int layers,
bool use_high_bitdepth,
bool use_grayscale)
{
gpu::TextureFormat tex_format;
imb_gpu_get_format(ibuf, use_high_bitdepth, use_grayscale, &tex_format);
gpu::Texture *tex;
if (layers > 0) {
tex = GPU_texture_create_2d_array(name,
w,
h,
layers,
9999,
tex_format,
GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_SHADER_WRITE,
nullptr);
}
else {
tex = GPU_texture_create_2d(name,
w,
h,
9999,
tex_format,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE,
nullptr);
}
GPU_texture_swizzle_set(tex, imb_gpu_get_swizzle(ibuf));
GPU_texture_anisotropic_filter(tex, true);
return tex;
}
void IMB_update_gpu_texture_sub(gpu::Texture *tex,
ImBuf *ibuf,
int x,
int y,
int z,
int w,
int h,
bool use_high_bitdepth,
bool use_grayscale,
bool use_premult)
{
const bool do_rescale = (ibuf->x != w || ibuf->y != h);
const int size[2] = {w, h};
gpu::TextureFormat tex_format;
imb_gpu_get_format(ibuf, use_high_bitdepth, use_grayscale, &tex_format);
bool freebuf = false;
eGPUDataFormat data_format;
void *data = imb_gpu_get_data(
ibuf, do_rescale, size, use_premult, use_grayscale, &freebuf, &data_format);
/* Update Texture. */
GPU_texture_update_sub(tex, data_format, data, x, y, z, w, h, 1);
if (freebuf) {
MEM_delete_void(data);
}
}
gpu::Texture *IMB_create_gpu_texture(
const char *name, ImBuf *ibuf, bool use_high_bitdepth, bool use_premult, const bool limit_size)
{
gpu::Texture *tex = nullptr;
int size[2] = {ibuf->x, ibuf->y};
if (limit_size) {
size[0] = GPU_texture_size_with_limit(ibuf->x);
size[1] = GPU_texture_size_with_limit(ibuf->y);
}
bool do_rescale = (ibuf->x != size[0]) || (ibuf->y != size[1]);
/* Correct the smaller size to maintain the original aspect ratio of the image. */
if (do_rescale && ibuf->x != ibuf->y) {
if (size[0] > size[1]) {
size[1] = int(ibuf->y * (float(size[0]) / ibuf->x));
}
else {
size[0] = int(ibuf->x * (float(size[1]) / ibuf->y));
}
}
if (ibuf->ftype == IMB_FTYPE_DDS) {
gpu::TextureFormat compressed_format;
if (!IMB_gpu_get_compressed_format(ibuf, &compressed_format)) {
CLOG_WARN(&LOG,
"DDS image '%s' is not in a supported GPU compression format",
ibuf->filepath.c_str());
}
else if (do_rescale) {
CLOG_WARN(
&LOG, "DDS image '%s' can't use compressed due to size limit", ibuf->filepath.c_str());
}
else if (!is_power_of_2_i(ibuf->x) || !is_power_of_2_i(ibuf->y)) {
/* We require POT DXT/S3TC texture sizes not because something in there
* intrinsically needs it, but because we flip them upside down at
* load time, and that (when mipmaps are involved) is only possible
* with POT height. */
CLOG_WARN(&LOG,
"DDS image '%s' can't use compressed due to non power of two size",
ibuf->filepath.c_str());
}
else {
int mip_count = 0;
uint8_t *compressed_data = imb_load_dds_compressed_data(
ibuf->filepath.c_str(), ibuf->x, ibuf->y, mip_count);
if (compressed_data != nullptr) {
tex = GPU_texture_create_compressed_2d(name,
ibuf->x,
ibuf->y,
mip_count,
compressed_format,
GPU_TEXTURE_USAGE_GENERAL,
compressed_data);
MEM_delete(compressed_data);
if (tex != nullptr) {
return tex;
}
CLOG_WARN(&LOG,
"DDS image '%s' failed to create compressed GPU texture",
ibuf->filepath.c_str());
}
else {
CLOG_WARN(&LOG, "DDS image '%s' failed to load data from file", ibuf->filepath.c_str());
}
}
/* Fallback to uncompressed texture. */
CLOG_WARN(&LOG, "DDS image '%s' falling back to uncompressed", ibuf->filepath.c_str());
}
gpu::TextureFormat tex_format;
imb_gpu_get_format(ibuf, use_high_bitdepth, true, &tex_format);
bool freebuf = false;
/* Create Texture. Specify read usage to allow both shader and host reads, the latter is needed
* by the GPU compositor. */
const eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE |
GPU_TEXTURE_USAGE_HOST_READ;
tex = GPU_texture_create_2d(name, UNPACK2(size), 9999, tex_format, usage, nullptr);
if (tex == nullptr) {
size[0] = max_ii(1, size[0] / 2);
size[1] = max_ii(1, size[1] / 2);
tex = GPU_texture_create_2d(name, UNPACK2(size), 9999, tex_format, usage, nullptr);
do_rescale = true;
}
BLI_assert(tex != nullptr);
eGPUDataFormat data_format;
void *data = imb_gpu_get_data(ibuf, do_rescale, size, use_premult, true, &freebuf, &data_format);
GPU_texture_update(tex, data_format, data);
GPU_texture_swizzle_set(tex, imb_gpu_get_swizzle(ibuf));
GPU_texture_anisotropic_filter(tex, true);
if (freebuf) {
MEM_delete_void(data);
}
return tex;
}
gpu::TextureFormat IMB_gpu_get_texture_format(const ImBuf *ibuf,
bool high_bitdepth,
bool use_grayscale)
{
gpu::TextureFormat gpu_texture_format;
imb_gpu_get_format(ibuf, high_bitdepth, use_grayscale, &gpu_texture_format);
return gpu_texture_format;
}
void IMB_gpu_clamp_half_float(ImBuf *image_buffer)
{
const float half_min = -65504;
const float half_max = 65504;
if (!image_buffer->float_data()) {
return;
}
float *rect_float = image_buffer->float_data_for_write();
int rect_float_len = image_buffer->x * image_buffer->y *
(image_buffer->channels == 0 ? 4 : image_buffer->channels);
for (int i = 0; i < rect_float_len; i++) {
rect_float[i] = clamp_f(rect_float[i], half_min, half_max);
}
}
} // namespace blender

View File

@@ -0,0 +1,84 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include <cerrno>
#include <cstdlib>
#include "BLI_path_utils.hh" /* For assertions. */
#include "IMB_colormanagement.hh"
#include "IMB_filetype.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG = {"image.write"};
static void ensure_byte_buffer(ImBuf *ibuf, const ImFileType *type)
{
/* If writing byte image from float buffer, create a byte buffer for writing.
*
* For color managed image writing, IMB_colormanagement_imbuf_for_write should
* have already created this byte buffer. This is a basic fallback for other
* cases where we do not have a specific desired output colorspace. */
if (!(type->flag & IM_FTYPE_FLOAT)) {
if (ibuf->byte_data() == nullptr && ibuf->float_data()) {
ibuf->byte_buffer.colorspace = colormanage_colorspace_get_roled(COLOR_ROLE_DEFAULT_BYTE);
IMB_byte_from_float(ibuf);
}
}
}
bool IMB_save_image(ImBuf *ibuf, const char *filepath, ImBufFlags flags)
{
errno = 0;
BLI_assert(!BLI_path_is_rel(filepath));
if (ibuf == nullptr) {
return false;
}
ibuf->flags = flags;
const ImFileType *type = IMB_file_type_from_ibuf(ibuf);
if (type == nullptr || type->save == nullptr) {
CLOG_ERROR(&LOG, "Couldn't save image to \"%s\"", filepath);
return false;
}
BLI_assert((type->capability_write & eImFileTypeCapability::File) !=
eImFileTypeCapability::Zero);
ensure_byte_buffer(ibuf, type);
return type->save(ibuf, filepath, flags);
}
Vector<uint8_t> IMB_save_image_to_buffer(ImBuf *ibuf, ImBufFlags flags)
{
if (ibuf == nullptr) {
return {};
}
ibuf->flags = flags;
const ImFileType *type = IMB_file_type_from_ibuf(ibuf);
if (type == nullptr || type->save_buffer == nullptr) {
CLOG_ERROR(&LOG, "Couldn't save image to buffer");
return {};
}
BLI_assert((type->capability_write & eImFileTypeCapability::Memory) !=
eImFileTypeCapability::Zero);
ensure_byte_buffer(ibuf, type);
return type->save_buffer(ibuf, flags);
}
} // namespace blender