Images: change alpha settings to support channel packing

This also replaces the Use Alpha setting. We now have these alpha modes:

* Straight: store RGB and alpha channels separately with alpha acting as a
  mask, also known as unassociated alpha.
* Premultiplied: transparent RGB pixels are multiplied by the alpha channel.
  The natural format for renders.
* Channel Packed: different images are packed in the RGB and alpha channels,
  and they should not influence each other. Channel packing is commonly used
  by game engines to save memory.
* None: ignore alpha channel from the file and make image fully opaque.

Cycles OSL does not correctly support Channel Packed and None yet, we are
missing fine control over the OpenImageIO texture cache to do that.

Fixes T53672
This commit is contained in:
Brecht Van Lommel 2019-05-18 20:52:20 +02:00
parent 3b23b5c638
commit 7aaa7aa9dd
21 changed files with 200 additions and 100 deletions

@ -292,7 +292,6 @@ static void create_mesh_volume_attribute(
VoxelAttribute *volume_data = attr->data_voxel(); VoxelAttribute *volume_data = attr->data_voxel();
ImageMetaData metadata; ImageMetaData metadata;
bool animated = false; bool animated = false;
bool use_alpha = true;
volume_data->manager = image_manager; volume_data->manager = image_manager;
volume_data->slot = image_manager->add_image(Attribute::standard_name(std), volume_data->slot = image_manager->add_image(Attribute::standard_name(std),
@ -301,7 +300,7 @@ static void create_mesh_volume_attribute(
frame, frame,
INTERPOLATION_LINEAR, INTERPOLATION_LINEAR,
EXTENSION_CLIP, EXTENSION_CLIP,
use_alpha, IMAGE_ALPHA_AUTO,
u_colorspace_raw, u_colorspace_raw,
metadata); metadata);
} }

@ -90,6 +90,12 @@ template<typename NodeType> static ExtensionType get_image_extension(NodeType &b
return (ExtensionType)validate_enum_value(value, EXTENSION_NUM_TYPES, EXTENSION_REPEAT); return (ExtensionType)validate_enum_value(value, EXTENSION_NUM_TYPES, EXTENSION_REPEAT);
} }
static ImageAlphaType get_image_alpha_type(BL::Image &b_image)
{
int value = b_image.alpha_mode();
return (ImageAlphaType)validate_enum_value(value, IMAGE_ALPHA_NUM_TYPES, IMAGE_ALPHA_AUTO);
}
/* Graph */ /* Graph */
static BL::NodeSocket get_node_output(BL::Node &b_node, const string &name) static BL::NodeSocket get_node_output(BL::Node &b_node, const string &name)
@ -655,7 +661,7 @@ static ShaderNode *add_node(Scene *scene,
image->colorspace = get_enum_identifier(colorspace_ptr, "name"); image->colorspace = get_enum_identifier(colorspace_ptr, "name");
image->animated = b_image_node.image_user().use_auto_refresh(); image->animated = b_image_node.image_user().use_auto_refresh();
image->use_alpha = b_image.use_alpha(); image->alpha_type = get_image_alpha_type(b_image);
/* TODO: restore */ /* TODO: restore */
/* TODO(sergey): Does not work properly when we change builtin type. */ /* TODO(sergey): Does not work properly when we change builtin type. */
@ -703,7 +709,7 @@ static ShaderNode *add_node(Scene *scene,
env->colorspace = get_enum_identifier(colorspace_ptr, "name"); env->colorspace = get_enum_identifier(colorspace_ptr, "name");
env->animated = b_env_node.image_user().use_auto_refresh(); env->animated = b_env_node.image_user().use_auto_refresh();
env->use_alpha = b_image.use_alpha(); env->alpha_type = get_image_alpha_type(b_image);
/* TODO: restore */ /* TODO: restore */
/* TODO(sergey): Does not work properly when we change builtin type. */ /* TODO(sergey): Does not work properly when we change builtin type. */
@ -868,7 +874,7 @@ static ShaderNode *add_node(Scene *scene,
point_density->builtin_data, point_density->builtin_data,
point_density->interpolation, point_density->interpolation,
EXTENSION_CLIP, EXTENSION_CLIP,
true, IMAGE_ALPHA_AUTO,
u_colorspace_raw); u_colorspace_raw);
} }
node = point_density; node = point_density;

@ -299,12 +299,12 @@ static bool image_equals(ImageManager::Image *image,
void *builtin_data, void *builtin_data,
InterpolationType interpolation, InterpolationType interpolation,
ExtensionType extension, ExtensionType extension,
bool use_alpha, ImageAlphaType alpha_type,
ustring colorspace) ustring colorspace)
{ {
return image->filename == filename && image->builtin_data == builtin_data && return image->filename == filename && image->builtin_data == builtin_data &&
image->interpolation == interpolation && image->extension == extension && image->interpolation == interpolation && image->extension == extension &&
image->use_alpha == use_alpha && image->colorspace == colorspace; image->alpha_type == alpha_type && image->colorspace == colorspace;
} }
int ImageManager::add_image(const string &filename, int ImageManager::add_image(const string &filename,
@ -313,7 +313,7 @@ int ImageManager::add_image(const string &filename,
float frame, float frame,
InterpolationType interpolation, InterpolationType interpolation,
ExtensionType extension, ExtensionType extension,
bool use_alpha, ImageAlphaType alpha_type,
ustring colorspace, ustring colorspace,
ImageMetaData &metadata) ImageMetaData &metadata)
{ {
@ -338,14 +338,15 @@ int ImageManager::add_image(const string &filename,
/* Fnd existing image. */ /* Fnd existing image. */
for (slot = 0; slot < images[type].size(); slot++) { for (slot = 0; slot < images[type].size(); slot++) {
img = images[type][slot]; img = images[type][slot];
if (img && image_equals( if (img &&
img, filename, builtin_data, interpolation, extension, use_alpha, colorspace)) { image_equals(
img, filename, builtin_data, interpolation, extension, alpha_type, colorspace)) {
if (img->frame != frame) { if (img->frame != frame) {
img->frame = frame; img->frame = frame;
img->need_load = true; img->need_load = true;
} }
if (img->use_alpha != use_alpha) { if (img->alpha_type != alpha_type) {
img->use_alpha = use_alpha; img->alpha_type = alpha_type;
img->need_load = true; img->need_load = true;
} }
if (img->colorspace != colorspace) { if (img->colorspace != colorspace) {
@ -399,7 +400,7 @@ int ImageManager::add_image(const string &filename,
img->interpolation = interpolation; img->interpolation = interpolation;
img->extension = extension; img->extension = extension;
img->users = 1; img->users = 1;
img->use_alpha = use_alpha; img->alpha_type = alpha_type;
img->colorspace = colorspace; img->colorspace = colorspace;
img->mem = NULL; img->mem = NULL;
@ -445,7 +446,7 @@ void ImageManager::remove_image(const string &filename,
void *builtin_data, void *builtin_data,
InterpolationType interpolation, InterpolationType interpolation,
ExtensionType extension, ExtensionType extension,
bool use_alpha, ImageAlphaType alpha_type,
ustring colorspace) ustring colorspace)
{ {
size_t slot; size_t slot;
@ -457,7 +458,7 @@ void ImageManager::remove_image(const string &filename,
builtin_data, builtin_data,
interpolation, interpolation,
extension, extension,
use_alpha, alpha_type,
colorspace)) { colorspace)) {
remove_image(type_index_to_flattened_slot(slot, (ImageDataType)type)); remove_image(type_index_to_flattened_slot(slot, (ImageDataType)type));
return; return;
@ -474,7 +475,7 @@ void ImageManager::tag_reload_image(const string &filename,
void *builtin_data, void *builtin_data,
InterpolationType interpolation, InterpolationType interpolation,
ExtensionType extension, ExtensionType extension,
bool use_alpha, ImageAlphaType alpha_type,
ustring colorspace) ustring colorspace)
{ {
for (size_t type = 0; type < IMAGE_DATA_NUM_TYPES; type++) { for (size_t type = 0; type < IMAGE_DATA_NUM_TYPES; type++) {
@ -484,7 +485,7 @@ void ImageManager::tag_reload_image(const string &filename,
builtin_data, builtin_data,
interpolation, interpolation,
extension, extension,
use_alpha, alpha_type,
colorspace)) { colorspace)) {
images[type][slot]->need_load = true; images[type][slot]->need_load = true;
break; break;
@ -516,7 +517,8 @@ bool ImageManager::file_load_image_generic(Image *img, unique_ptr<ImageInput> *i
/* For typical RGBA images we let OIIO convert to associated alpha, /* For typical RGBA images we let OIIO convert to associated alpha,
* but some types we want to leave the RGB channels untouched. */ * but some types we want to leave the RGB channels untouched. */
const bool associate_alpha = !(ColorSpaceManager::colorspace_is_data(img->colorspace) || const bool associate_alpha = !(ColorSpaceManager::colorspace_is_data(img->colorspace) ||
img->use_alpha == false); img->alpha_type == IMAGE_ALPHA_IGNORE ||
img->alpha_type == IMAGE_ALPHA_CHANNEL_PACKED);
if (!associate_alpha) { if (!associate_alpha) {
config.attribute("oiio:UnassociatedAlpha", 1); config.attribute("oiio:UnassociatedAlpha", 1);
@ -692,7 +694,7 @@ bool ImageManager::file_load_image(Image *img,
} }
/* Disable alpha if requested by the user. */ /* Disable alpha if requested by the user. */
if (img->use_alpha == false) { if (img->alpha_type == IMAGE_ALPHA_IGNORE) {
for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) { for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) {
pixels[i * 4 + 3] = one; pixels[i * 4 + 3] = one;
} }

@ -83,7 +83,7 @@ class ImageManager {
float frame, float frame,
InterpolationType interpolation, InterpolationType interpolation,
ExtensionType extension, ExtensionType extension,
bool use_alpha, ImageAlphaType alpha_type,
ustring colorspace, ustring colorspace,
ImageMetaData &metadata); ImageMetaData &metadata);
void add_image_user(int flat_slot); void add_image_user(int flat_slot);
@ -92,13 +92,13 @@ class ImageManager {
void *builtin_data, void *builtin_data,
InterpolationType interpolation, InterpolationType interpolation,
ExtensionType extension, ExtensionType extension,
bool use_alpha, ImageAlphaType alpha_type,
ustring colorspace); ustring colorspace);
void tag_reload_image(const string &filename, void tag_reload_image(const string &filename,
void *builtin_data, void *builtin_data,
InterpolationType interpolation, InterpolationType interpolation,
ExtensionType extension, ExtensionType extension,
bool use_alpha, ImageAlphaType alpha_type,
ustring colorspace); ustring colorspace);
bool get_image_metadata(const string &filename, bool get_image_metadata(const string &filename,
void *builtin_data, void *builtin_data,
@ -147,7 +147,7 @@ class ImageManager {
ImageMetaData metadata; ImageMetaData metadata;
ustring colorspace; ustring colorspace;
bool use_alpha; ImageAlphaType alpha_type;
bool need_load; bool need_load;
bool animated; bool animated;
float frame; float frame;

@ -210,7 +210,13 @@ NODE_DEFINE(ImageTextureNode)
SOCKET_STRING(filename, "Filename", ustring()); SOCKET_STRING(filename, "Filename", ustring());
SOCKET_STRING(colorspace, "Colorspace", u_colorspace_auto); SOCKET_STRING(colorspace, "Colorspace", u_colorspace_auto);
SOCKET_BOOLEAN(use_alpha, "Use Alpha", true); static NodeEnum alpha_type_enum;
alpha_type_enum.insert("auto", IMAGE_ALPHA_AUTO);
alpha_type_enum.insert("unassociated", IMAGE_ALPHA_UNASSOCIATED);
alpha_type_enum.insert("associated", IMAGE_ALPHA_ASSOCIATED);
alpha_type_enum.insert("channel_packed", IMAGE_ALPHA_CHANNEL_PACKED);
alpha_type_enum.insert("ignore", IMAGE_ALPHA_IGNORE);
SOCKET_ENUM(alpha_type, "Alpha Type", alpha_type_enum, IMAGE_ALPHA_AUTO);
static NodeEnum interpolation_enum; static NodeEnum interpolation_enum;
interpolation_enum.insert("closest", INTERPOLATION_CLOSEST); interpolation_enum.insert("closest", INTERPOLATION_CLOSEST);
@ -257,7 +263,7 @@ ImageTextureNode::~ImageTextureNode()
{ {
if (image_manager) { if (image_manager) {
image_manager->remove_image( image_manager->remove_image(
filename.string(), builtin_data, interpolation, extension, use_alpha, colorspace); filename.string(), builtin_data, interpolation, extension, alpha_type, colorspace);
} }
} }
@ -300,12 +306,12 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
0, 0,
interpolation, interpolation,
extension, extension,
use_alpha, alpha_type,
colorspace, colorspace,
metadata); metadata);
is_float = metadata.is_float; is_float = metadata.is_float;
compress_as_srgb = metadata.compress_as_srgb; compress_as_srgb = metadata.compress_as_srgb;
colorspace = metadata.colorspace; known_colorspace = metadata.colorspace;
} }
if (slot != -1) { if (slot != -1) {
@ -317,7 +323,8 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
} }
if (!alpha_out->links.empty()) { if (!alpha_out->links.empty()) {
const bool unassociate_alpha = !(ColorSpaceManager::colorspace_is_data(colorspace) || const bool unassociate_alpha = !(ColorSpaceManager::colorspace_is_data(colorspace) ||
use_alpha == false); alpha_type == IMAGE_ALPHA_CHANNEL_PACKED ||
alpha_type == IMAGE_ALPHA_IGNORE);
if (unassociate_alpha) { if (unassociate_alpha) {
flags |= NODE_IMAGE_ALPHA_UNASSOCIATE; flags |= NODE_IMAGE_ALPHA_UNASSOCIATE;
@ -378,29 +385,30 @@ void ImageTextureNode::compile(OSLCompiler &compiler)
0, 0,
interpolation, interpolation,
extension, extension,
use_alpha, alpha_type,
colorspace, colorspace,
metadata); metadata);
} }
is_float = metadata.is_float; is_float = metadata.is_float;
compress_as_srgb = metadata.compress_as_srgb; compress_as_srgb = metadata.compress_as_srgb;
colorspace = metadata.colorspace; known_colorspace = metadata.colorspace;
} }
if (slot == -1) { if (slot == -1) {
compiler.parameter_texture("filename", filename, colorspace); compiler.parameter_texture("filename", filename, known_colorspace);
} }
else { else {
compiler.parameter_texture("filename", slot); compiler.parameter_texture("filename", slot);
} }
const bool unassociate_alpha = !(ColorSpaceManager::colorspace_is_data(colorspace) || const bool unassociate_alpha = !(ColorSpaceManager::colorspace_is_data(colorspace) ||
use_alpha == false); alpha_type == IMAGE_ALPHA_CHANNEL_PACKED ||
alpha_type == IMAGE_ALPHA_IGNORE);
compiler.parameter(this, "projection"); compiler.parameter(this, "projection");
compiler.parameter(this, "projection_blend"); compiler.parameter(this, "projection_blend");
compiler.parameter("convert_from_srgb", compress_as_srgb); compiler.parameter("compress_as_srgb", compress_as_srgb);
compiler.parameter("ignore_alpha", !use_alpha); compiler.parameter("ignore_alpha", alpha_type == IMAGE_ALPHA_IGNORE);
compiler.parameter("unassociate_alpha", !alpha_out->links.empty() && unassociate_alpha); compiler.parameter("unassociate_alpha", !alpha_out->links.empty() && unassociate_alpha);
compiler.parameter("is_float", is_float); compiler.parameter("is_float", is_float);
compiler.parameter(this, "interpolation"); compiler.parameter(this, "interpolation");
@ -420,7 +428,13 @@ NODE_DEFINE(EnvironmentTextureNode)
SOCKET_STRING(filename, "Filename", ustring()); SOCKET_STRING(filename, "Filename", ustring());
SOCKET_STRING(colorspace, "Colorspace", u_colorspace_auto); SOCKET_STRING(colorspace, "Colorspace", u_colorspace_auto);
SOCKET_BOOLEAN(use_alpha, "Use Alpha", true); static NodeEnum alpha_type_enum;
alpha_type_enum.insert("auto", IMAGE_ALPHA_AUTO);
alpha_type_enum.insert("unassociated", IMAGE_ALPHA_UNASSOCIATED);
alpha_type_enum.insert("associated", IMAGE_ALPHA_ASSOCIATED);
alpha_type_enum.insert("channel_packed", IMAGE_ALPHA_CHANNEL_PACKED);
alpha_type_enum.insert("ignore", IMAGE_ALPHA_IGNORE);
SOCKET_ENUM(alpha_type, "Alpha Type", alpha_type_enum, IMAGE_ALPHA_AUTO);
static NodeEnum interpolation_enum; static NodeEnum interpolation_enum;
interpolation_enum.insert("closest", INTERPOLATION_CLOSEST); interpolation_enum.insert("closest", INTERPOLATION_CLOSEST);
@ -457,7 +471,7 @@ EnvironmentTextureNode::~EnvironmentTextureNode()
{ {
if (image_manager) { if (image_manager) {
image_manager->remove_image( image_manager->remove_image(
filename.string(), builtin_data, interpolation, EXTENSION_REPEAT, use_alpha, colorspace); filename.string(), builtin_data, interpolation, EXTENSION_REPEAT, alpha_type, colorspace);
} }
} }
@ -498,12 +512,12 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler)
0, 0,
interpolation, interpolation,
EXTENSION_REPEAT, EXTENSION_REPEAT,
use_alpha, alpha_type,
colorspace, colorspace,
metadata); metadata);
is_float = metadata.is_float; is_float = metadata.is_float;
compress_as_srgb = metadata.compress_as_srgb; compress_as_srgb = metadata.compress_as_srgb;
colorspace = metadata.colorspace; known_colorspace = metadata.colorspace;
} }
if (slot != -1) { if (slot != -1) {
@ -558,17 +572,17 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler)
0, 0,
interpolation, interpolation,
EXTENSION_REPEAT, EXTENSION_REPEAT,
use_alpha, alpha_type,
colorspace, colorspace,
metadata); metadata);
} }
is_float = metadata.is_float; is_float = metadata.is_float;
compress_as_srgb = metadata.compress_as_srgb; compress_as_srgb = metadata.compress_as_srgb;
colorspace = metadata.colorspace; known_colorspace = metadata.colorspace;
} }
if (slot == -1) { if (slot == -1) {
compiler.parameter_texture("filename", filename, colorspace); compiler.parameter_texture("filename", filename, known_colorspace);
} }
else { else {
compiler.parameter_texture("filename", slot); compiler.parameter_texture("filename", slot);
@ -576,8 +590,8 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler)
compiler.parameter(this, "projection"); compiler.parameter(this, "projection");
compiler.parameter(this, "interpolation"); compiler.parameter(this, "interpolation");
compiler.parameter("convert_from_srgb", compress_as_srgb); compiler.parameter("compress_as_srgb", compress_as_srgb);
compiler.parameter("ignore_alpha", !use_alpha); compiler.parameter("ignore_alpha", alpha_type == IMAGE_ALPHA_IGNORE);
compiler.parameter("is_float", is_float); compiler.parameter("is_float", is_float);
compiler.add(this, "node_environment_texture"); compiler.add(this, "node_environment_texture");
} }
@ -1490,8 +1504,12 @@ PointDensityTextureNode::PointDensityTextureNode() : ShaderNode(node_type)
PointDensityTextureNode::~PointDensityTextureNode() PointDensityTextureNode::~PointDensityTextureNode()
{ {
if (image_manager) { if (image_manager) {
image_manager->remove_image( image_manager->remove_image(filename.string(),
filename.string(), builtin_data, interpolation, EXTENSION_CLIP, true, ustring()); builtin_data,
interpolation,
EXTENSION_CLIP,
IMAGE_ALPHA_AUTO,
ustring());
} }
} }
@ -1524,7 +1542,7 @@ void PointDensityTextureNode::add_image()
0, 0,
interpolation, interpolation,
EXTENSION_CLIP, EXTENSION_CLIP,
true, IMAGE_ALPHA_AUTO,
u_colorspace_raw, u_colorspace_raw,
metadata); metadata);
} }

@ -100,10 +100,10 @@ class ImageTextureNode : public ImageSlotTextureNode {
} }
/* Parameters. */ /* Parameters. */
bool use_alpha;
ustring filename; ustring filename;
void *builtin_data; void *builtin_data;
ustring colorspace; ustring colorspace;
ImageAlphaType alpha_type;
NodeImageProjection projection; NodeImageProjection projection;
InterpolationType interpolation; InterpolationType interpolation;
ExtensionType extension; ExtensionType extension;
@ -141,10 +141,10 @@ class EnvironmentTextureNode : public ImageSlotTextureNode {
} }
/* Parameters. */ /* Parameters. */
bool use_alpha;
ustring filename; ustring filename;
void *builtin_data; void *builtin_data;
ustring colorspace; ustring colorspace;
ImageAlphaType alpha_type;
NodeEnvironmentProjection projection; NodeEnvironmentProjection projection;
InterpolationType interpolation; InterpolationType interpolation;
bool animated; bool animated;

@ -59,6 +59,18 @@ typedef enum ImageDataType {
IMAGE_DATA_NUM_TYPES IMAGE_DATA_NUM_TYPES
} ImageDataType; } ImageDataType;
/* Alpha types
* How to treat alpha in images. */
typedef enum ImageAlphaType {
IMAGE_ALPHA_UNASSOCIATED = 0,
IMAGE_ALPHA_ASSOCIATED = 1,
IMAGE_ALPHA_CHANNEL_PACKED = 2,
IMAGE_ALPHA_IGNORE = 3,
IMAGE_ALPHA_AUTO = 4,
IMAGE_ALPHA_NUM_TYPES,
} ImageAlphaType;
#define IMAGE_DATA_TYPE_SHIFT 3 #define IMAGE_DATA_TYPE_SHIFT 3
#define IMAGE_DATA_TYPE_MASK 0x7 #define IMAGE_DATA_TYPE_MASK 0x7

@ -27,7 +27,7 @@
* \note Use #STRINGIFY() rather than defining with quotes. * \note Use #STRINGIFY() rather than defining with quotes.
*/ */
#define BLENDER_VERSION 280 #define BLENDER_VERSION 280
#define BLENDER_SUBVERSION 69 #define BLENDER_SUBVERSION 70
/** Several breakages with 280, e.g. collections vs layers. */ /** Several breakages with 280, e.g. collections vs layers. */
#define BLENDER_MINVERSION 280 #define BLENDER_MINVERSION 280
#define BLENDER_MINSUBVERSION 0 #define BLENDER_MINSUBVERSION 0

@ -502,6 +502,12 @@ static void image_init_color_management(Image *ima)
if (ibuf->flags & IB_alphamode_premul) { if (ibuf->flags & IB_alphamode_premul) {
ima->alpha_mode = IMA_ALPHA_PREMUL; ima->alpha_mode = IMA_ALPHA_PREMUL;
} }
else if (ibuf->flags & IB_alphamode_channel_packed) {
ima->alpha_mode = IMA_ALPHA_CHANNEL_PACKED;
}
else if (ibuf->flags & IB_alphamode_ignore) {
ima->alpha_mode = IMA_ALPHA_IGNORE;
}
else { else {
ima->alpha_mode = IMA_ALPHA_STRAIGHT; ima->alpha_mode = IMA_ALPHA_STRAIGHT;
} }
@ -3592,16 +3598,18 @@ static void image_initialize_after_load(Image *ima, ImBuf *UNUSED(ibuf))
static int imbuf_alpha_flags_for_image(Image *ima) static int imbuf_alpha_flags_for_image(Image *ima)
{ {
int flag = 0; switch (ima->alpha_mode) {
case IMA_ALPHA_STRAIGHT:
if (ima->flag & IMA_IGNORE_ALPHA) { return 0;
flag |= IB_ignore_alpha; case IMA_ALPHA_PREMUL:
} return IB_alphamode_premul;
else if (ima->alpha_mode == IMA_ALPHA_PREMUL) { case IMA_ALPHA_CHANNEL_PACKED:
flag |= IB_alphamode_premul; return IB_alphamode_channel_packed;
case IMA_ALPHA_IGNORE:
return IB_alphamode_ignore;
} }
return flag; return 0;
} }
/* the number of files will vary according to the stereo format */ /* the number of files will vary according to the stereo format */

@ -1808,6 +1808,7 @@ void blo_do_versions_260(FileData *fd, Library *UNUSED(lib), Main *bmain)
Image *image = blo_do_versions_newlibadr(fd, tex->id.lib, tex->ima); Image *image = blo_do_versions_newlibadr(fd, tex->id.lib, tex->ima);
if (image && (image->flag & IMA_DO_PREMUL) == 0) { if (image && (image->flag & IMA_DO_PREMUL) == 0) {
const int IMA_IGNORE_ALPHA = (1 << 12);
image->flag |= IMA_IGNORE_ALPHA; image->flag |= IMA_IGNORE_ALPHA;
} }
} }

@ -3450,6 +3450,17 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
} }
} }
if (!MAIN_VERSION_ATLEAST(bmain, 280, 70)) {
/* New image alpha modes. */
LISTBASE_FOREACH (Image *, image, &bmain->images) {
const int IMA_IGNORE_ALPHA = (1 << 12);
if (image->flag & IMA_IGNORE_ALPHA) {
image->alpha_mode = IMA_ALPHA_IGNORE;
image->flag &= ~IMA_IGNORE_ALPHA;
}
}
}
{ {
/* Versioning code until next subversion bump goes here. */ /* Versioning code until next subversion bump goes here. */
} }

@ -42,6 +42,7 @@
#include "RE_pipeline.h" #include "RE_pipeline.h"
#include "IMB_colormanagement.h"
#include "IMB_imbuf.h" #include "IMB_imbuf.h"
#include "IMB_imbuf_types.h" #include "IMB_imbuf_types.h"
@ -995,10 +996,11 @@ void uiTemplateImage(uiLayout *layout,
if (has_alpha) { if (has_alpha) {
col = uiLayoutColumn(layout, false); col = uiLayoutColumn(layout, false);
uiItemR(col, &imaptr, "use_alpha", 0, NULL, ICON_NONE); uiItemR(col, &imaptr, "alpha_mode", 0, IFACE_("Alpha"), ICON_NONE);
row = uiLayoutRow(col, false);
uiLayoutSetActive(row, RNA_boolean_get(&imaptr, "use_alpha")); /* Alpha mode has no effect for non-color data. */
uiItemR(row, &imaptr, "alpha_mode", 0, IFACE_("Alpha"), ICON_NONE); bool is_data = IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name);
uiLayoutSetActive(col, !is_data);
} }
if (ima->source == IMA_SRC_MOVIE) { if (ima->source == IMA_SRC_MOVIE) {

@ -476,6 +476,9 @@ int imb_get_anim_type(const char *name);
*/ */
bool IMB_isfloat(struct ImBuf *ibuf); bool IMB_isfloat(struct ImBuf *ibuf);
/* Do byte/float and colorspace conversions need to take alpha into account? */
bool IMB_alpha_affects_rgb(const struct ImBuf *ibuf);
/* create char buffer, color corrected if necessary, for ImBufs that lack one */ /* create char buffer, color corrected if necessary, for ImBufs that lack one */
void IMB_rect_from_float(struct ImBuf *ibuf); void IMB_rect_from_float(struct ImBuf *ibuf);
void IMB_float_from_rect(struct ImBuf *ibuf); void IMB_float_from_rect(struct ImBuf *ibuf);

@ -285,10 +285,12 @@ enum {
IB_alphamode_premul = 1 << 12, IB_alphamode_premul = 1 << 12,
/** if this flag is set, alpha mode would be guessed from file */ /** if this flag is set, alpha mode would be guessed from file */
IB_alphamode_detect = 1 << 13, IB_alphamode_detect = 1 << 13,
/* alpha channel is unrelated to RGB and should not affect it */
IB_alphamode_channel_packed = 1 << 14,
/** ignore alpha on load and substitute it with 1.0f */ /** ignore alpha on load and substitute it with 1.0f */
IB_ignore_alpha = 1 << 14, IB_alphamode_ignore = 1 << 15,
IB_thumbnail = 1 << 15, IB_thumbnail = 1 << 16,
IB_multiview = 1 << 16, IB_multiview = 1 << 17,
}; };
/** \} */ /** \} */

@ -1057,13 +1057,19 @@ void colormanage_imbuf_make_linear(ImBuf *ibuf, const char *from_colorspace)
if (ibuf->rect_float) { if (ibuf->rect_float) {
const char *to_colorspace = global_role_scene_linear; const char *to_colorspace = global_role_scene_linear;
const bool predivide = IMB_alpha_affects_rgb(ibuf);
if (ibuf->rect) { if (ibuf->rect) {
imb_freerectImBuf(ibuf); imb_freerectImBuf(ibuf);
} }
IMB_colormanagement_transform( IMB_colormanagement_transform(ibuf->rect_float,
ibuf->rect_float, ibuf->x, ibuf->y, ibuf->channels, from_colorspace, to_colorspace, true); ibuf->x,
ibuf->y,
ibuf->channels,
from_colorspace,
to_colorspace,
predivide);
} }
} }
@ -1405,6 +1411,7 @@ typedef struct DisplayBufferThread {
int channels; int channels;
float dither; float dither;
bool is_data; bool is_data;
bool predivide;
const char *byte_colorspace; const char *byte_colorspace;
const char *float_colorspace; const char *float_colorspace;
@ -1469,6 +1476,7 @@ static void display_buffer_init_handle(void *handle_v,
handle->channels = channels; handle->channels = channels;
handle->dither = dither; handle->dither = dither;
handle->is_data = is_data; handle->is_data = is_data;
handle->predivide = IMB_alpha_affects_rgb(ibuf);
handle->byte_colorspace = init_data->byte_colorspace; handle->byte_colorspace = init_data->byte_colorspace;
handle->float_colorspace = init_data->float_colorspace; handle->float_colorspace = init_data->float_colorspace;
@ -1486,6 +1494,7 @@ static void display_buffer_apply_get_linear_buffer(DisplayBufferThread *handle,
bool is_data = handle->is_data; bool is_data = handle->is_data;
bool is_data_display = handle->cm_processor->is_data_result; bool is_data_display = handle->cm_processor->is_data_result;
bool predivide = handle->predivide;
if (!handle->buffer) { if (!handle->buffer) {
unsigned char *byte_buffer = handle->byte_buffer; unsigned char *byte_buffer = handle->byte_buffer;
@ -1534,7 +1543,7 @@ static void display_buffer_apply_get_linear_buffer(DisplayBufferThread *handle,
if (!is_data && !is_data_display) { if (!is_data && !is_data_display) {
IMB_colormanagement_transform( IMB_colormanagement_transform(
linear_buffer, width, height, channels, from_colorspace, to_colorspace, true); linear_buffer, width, height, channels, from_colorspace, to_colorspace, predivide);
} }
*is_straight_alpha = false; *is_straight_alpha = false;
@ -1590,13 +1599,13 @@ static void *do_display_buffer_apply_thread(void *handle_v)
} }
} }
else { else {
bool is_straight_alpha, predivide; bool is_straight_alpha;
float *linear_buffer = MEM_mallocN(((size_t)channels) * width * height * sizeof(float), float *linear_buffer = MEM_mallocN(((size_t)channels) * width * height * sizeof(float),
"color conversion linear buffer"); "color conversion linear buffer");
display_buffer_apply_get_linear_buffer(handle, height, linear_buffer, &is_straight_alpha); display_buffer_apply_get_linear_buffer(handle, height, linear_buffer, &is_straight_alpha);
predivide = is_straight_alpha == false; bool predivide = handle->predivide && (is_straight_alpha == false);
if (is_data) { if (is_data) {
/* special case for data buffers - no color space conversions, /* special case for data buffers - no color space conversions,
@ -2178,6 +2187,7 @@ void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *out_buffer,
/* TODO(brecht): make this multithreaded, or at least process in batches. */ /* TODO(brecht): make this multithreaded, or at least process in batches. */
const unsigned char *in_buffer = (unsigned char *)ibuf->rect; const unsigned char *in_buffer = (unsigned char *)ibuf->rect;
const bool use_premultiply = IMB_alpha_affects_rgb(ibuf);
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
const size_t in_offset = (offset_y + y) * ibuf->x + offset_x; const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
@ -2192,11 +2202,13 @@ void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *out_buffer,
rgba_uchar_to_float(pixel, in); rgba_uchar_to_float(pixel, in);
OCIO_processorApplyRGB(processor, pixel); OCIO_processorApplyRGB(processor, pixel);
linearrgb_to_srgb_v3_v3(pixel, pixel); linearrgb_to_srgb_v3_v3(pixel, pixel);
mul_v3_fl(pixel, pixel[3]); if (use_premultiply) {
mul_v3_fl(pixel, pixel[3]);
}
rgba_float_to_uchar(out, pixel); rgba_float_to_uchar(out, pixel);
} }
} }
else { else if (use_premultiply) {
/* Premultiply only. */ /* Premultiply only. */
for (int x = 0; x < width; x++, in += 4, out += 4) { for (int x = 0; x < width; x++, in += 4, out += 4) {
out[0] = (in[0] * in[3]) >> 8; out[0] = (in[0] * in[3]) >> 8;
@ -2205,6 +2217,15 @@ void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *out_buffer,
out[3] = in[3]; out[3] = in[3];
} }
} }
else {
/* Copy only. */
for (int x = 0; x < width; x++, in += 4, out += 4) {
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
out[3] = in[3];
}
}
} }
} }

@ -96,6 +96,12 @@ MINLINE void float_to_byte_dither_v4(
b[3] = unit_float_to_uchar_clamp(f[3]); b[3] = unit_float_to_uchar_clamp(f[3]);
} }
/* Test if colorspace conversions of pixels in buffer need to take into account alpha. */
bool IMB_alpha_affects_rgb(const ImBuf *ibuf)
{
return (ibuf->flags & IB_alphamode_channel_packed) == 0;
}
/* float to byte pixels, output 4-channel RGBA */ /* float to byte pixels, output 4-channel RGBA */
void IMB_buffer_byte_from_float(uchar *rect_to, void IMB_buffer_byte_from_float(uchar *rect_to,
const float *rect_from, const float *rect_from,
@ -728,16 +734,19 @@ void IMB_rect_from_float(ImBuf *ibuf)
buffer = MEM_dupallocN(ibuf->rect_float); buffer = MEM_dupallocN(ibuf->rect_float);
/* first make float buffer in byte space */ /* first make float buffer in byte space */
const bool predivide = IMB_alpha_affects_rgb(ibuf);
IMB_colormanagement_transform(buffer, IMB_colormanagement_transform(buffer,
ibuf->x, ibuf->x,
ibuf->y, ibuf->y,
ibuf->channels, ibuf->channels,
from_colorspace, from_colorspace,
ibuf->rect_colorspace->name, ibuf->rect_colorspace->name,
true); predivide);
/* convert from float's premul alpha to byte's straight alpha */ /* convert from float's premul alpha to byte's straight alpha */
IMB_unpremultiply_rect_float(buffer, ibuf->channels, ibuf->x, ibuf->y); if (IMB_alpha_affects_rgb(ibuf)) {
IMB_unpremultiply_rect_float(buffer, ibuf->channels, ibuf->x, ibuf->y);
}
/* convert float to byte */ /* convert float to byte */
IMB_buffer_byte_from_float((unsigned char *)ibuf->rect, IMB_buffer_byte_from_float((unsigned char *)ibuf->rect,
@ -802,7 +811,9 @@ void IMB_float_from_rect(ImBuf *ibuf)
rect_float, ibuf->x, ibuf->y, ibuf->channels, ibuf->rect_colorspace, false); rect_float, ibuf->x, ibuf->y, ibuf->channels, ibuf->rect_colorspace, false);
/* byte buffer is straight alpha, float should always be premul */ /* byte buffer is straight alpha, float should always be premul */
IMB_premultiply_rect_float(rect_float, ibuf->channels, ibuf->x, ibuf->y); if (IMB_alpha_affects_rgb(ibuf)) {
IMB_premultiply_rect_float(rect_float, ibuf->channels, ibuf->x, ibuf->y);
}
if (ibuf->rect_float == NULL) { if (ibuf->rect_float == NULL) {
ibuf->rect_float = rect_float; ibuf->rect_float = rect_float;

@ -61,20 +61,16 @@ static void imb_handle_alpha(ImBuf *ibuf,
} }
bool is_data = (colorspace && IMB_colormanagement_space_name_is_data(colorspace)); bool is_data = (colorspace && IMB_colormanagement_space_name_is_data(colorspace));
int alpha_flags; int alpha_flags = (flags & IB_alphamode_detect) ? ibuf->flags : flags;
if (flags & IB_alphamode_detect) { if (is_data || (flags & IB_alphamode_channel_packed)) {
alpha_flags = ibuf->flags & IB_alphamode_premul;
}
else {
alpha_flags = flags & IB_alphamode_premul;
}
if (is_data) {
/* Don't touch alpha. */ /* Don't touch alpha. */
ibuf->flags |= IB_alphamode_channel_packed;
} }
else if (flags & IB_ignore_alpha) { else if (flags & IB_alphamode_ignore) {
/* Make opaque. */
IMB_rectfill_alpha(ibuf, 1.0f); IMB_rectfill_alpha(ibuf, 1.0f);
ibuf->flags |= IB_alphamode_ignore;
} }
else { else {
if (alpha_flags & IB_alphamode_premul) { if (alpha_flags & IB_alphamode_premul) {

@ -178,7 +178,7 @@ enum {
/** For image user, but these flags are mixed. */ /** For image user, but these flags are mixed. */
IMA_USER_FRAME_IN_RANGE = (1 << 10), IMA_USER_FRAME_IN_RANGE = (1 << 10),
IMA_VIEW_AS_RENDER = (1 << 11), IMA_VIEW_AS_RENDER = (1 << 11),
IMA_IGNORE_ALPHA = (1 << 12), IMA_FLAG_UNUSED_12 = (1 << 12), /* cleared */
IMA_DEINTERLACE = (1 << 13), IMA_DEINTERLACE = (1 << 13),
IMA_USE_VIEWS = (1 << 14), IMA_USE_VIEWS = (1 << 14),
IMA_FLAG_UNUSED_15 = (1 << 15), /* cleared */ IMA_FLAG_UNUSED_15 = (1 << 15), /* cleared */
@ -233,6 +233,8 @@ enum {
enum { enum {
IMA_ALPHA_STRAIGHT = 0, IMA_ALPHA_STRAIGHT = 0,
IMA_ALPHA_PREMUL = 1, IMA_ALPHA_PREMUL = 1,
IMA_ALPHA_CHANNEL_PACKED = 2,
IMA_ALPHA_IGNORE = 3,
}; };
#endif #endif

@ -680,12 +680,26 @@ static void rna_def_image(BlenderRNA *brna)
"STRAIGHT", "STRAIGHT",
0, 0,
"Straight", "Straight",
"Transparent RGB and alpha pixels are unmodified"}, "Store RGB and alpha channels separately with alpha acting as a mask, also known as "
"unassociated alpha. Commonly used by image editing applications and file formats like "
"PNG"},
{IMA_ALPHA_PREMUL, {IMA_ALPHA_PREMUL,
"PREMUL", "PREMUL",
0, 0,
"Premultiplied", "Premultiplied",
"Transparent RGB pixels are multiplied by the alpha channel"}, "Store RGB channels with alpha multipled in, also known as associated alpha. The natural "
"format for renders and used by file formats like OpenEXR"},
{IMA_ALPHA_CHANNEL_PACKED,
"CHANNEL_PACKED",
0,
"Channel Packed",
"Different images are packed in the RGB and alpha channels, and they should not "
"affect each other. Channel packing is commonly used by game engines to save memory"},
{IMA_ALPHA_IGNORE,
"NONE",
0,
"None",
"Ignore alpha channel from the file and make image fully opaque"},
{0, NULL, 0, NULL, NULL}, {0, NULL, 0, NULL, NULL},
}; };
@ -744,15 +758,6 @@ static void rna_def_image(BlenderRNA *brna)
"Apply render part of display transformation when displaying this image on the screen"); "Apply render part of display transformation when displaying this image on the screen");
RNA_def_property_update(prop, NC_IMAGE | ND_DISPLAY, NULL); RNA_def_property_update(prop, NC_IMAGE | ND_DISPLAY, NULL);
prop = RNA_def_property(srna, "use_alpha", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_STATIC);
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", IMA_IGNORE_ALPHA);
RNA_def_property_ui_text(
prop,
"Use Alpha",
"Use the alpha channel information from the image or make image fully opaque");
RNA_def_property_update(prop, NC_IMAGE | ND_DISPLAY, "rna_Image_colormanage_update");
prop = RNA_def_property(srna, "use_deinterlace", PROP_BOOLEAN, PROP_NONE); prop = RNA_def_property(srna, "use_deinterlace", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_STATIC); RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_STATIC);
RNA_def_property_boolean_sdna(prop, NULL, "flag", IMA_DEINTERLACE); RNA_def_property_boolean_sdna(prop, NULL, "flag", IMA_DEINTERLACE);

@ -184,7 +184,8 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
* that if we blend the color with a transparent shader using alpha as * that if we blend the color with a transparent shader using alpha as
* a factor, we don't multiply alpha into the color twice. */ * a factor, we don't multiply alpha into the color twice. */
if (out[1].hasoutput && if (out[1].hasoutput &&
!IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name)) { !(ELEM(ima->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED) ||
IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name))) {
GPU_link(mat, "tex_color_alpha_unpremultiply", out[0].link, &out[0].link); GPU_link(mat, "tex_color_alpha_unpremultiply", out[0].link, &out[0].link);
} }
else { else {

@ -233,7 +233,7 @@ int imagewrap(Tex *tex,
/* keep this before interpolation [#29761] */ /* keep this before interpolation [#29761] */
if (ima) { if (ima) {
if ((tex->imaflag & TEX_USEALPHA) && (ima->flag & IMA_IGNORE_ALPHA) == 0) { if ((tex->imaflag & TEX_USEALPHA) && (ima->alpha_mode != IMA_ALPHA_IGNORE)) {
if ((tex->imaflag & TEX_CALCALPHA) == 0) { if ((tex->imaflag & TEX_CALCALPHA) == 0) {
texres->talpha = true; texres->talpha = true;
} }
@ -1056,7 +1056,7 @@ static int imagewraposa_aniso(Tex *tex,
image_mipmap_test(tex, ibuf); image_mipmap_test(tex, ibuf);
if (ima) { if (ima) {
if ((tex->imaflag & TEX_USEALPHA) && (ima->flag & IMA_IGNORE_ALPHA) == 0) { if ((tex->imaflag & TEX_USEALPHA) && (ima->alpha_mode != IMA_ALPHA_IGNORE)) {
if ((tex->imaflag & TEX_CALCALPHA) == 0) { if ((tex->imaflag & TEX_CALCALPHA) == 0) {
texres->talpha = 1; texres->talpha = 1;
} }
@ -1512,7 +1512,7 @@ int imagewraposa(Tex *tex,
image_mipmap_test(tex, ibuf); image_mipmap_test(tex, ibuf);
if (ima) { if (ima) {
if ((tex->imaflag & TEX_USEALPHA) && (ima->flag & IMA_IGNORE_ALPHA) == 0) { if ((tex->imaflag & TEX_USEALPHA) && (ima->alpha_mode != IMA_ALPHA_IGNORE)) {
if ((tex->imaflag & TEX_CALCALPHA) == 0) { if ((tex->imaflag & TEX_CALCALPHA) == 0) {
texres->talpha = true; texres->talpha = true;
} }