Color management: add "Color Unpremultiply" option for images and render settings.

For premultiplied alpha images, this makes any color space conversion for the image
or render output work on color without alpha multiplied in.

This is typically useful to avoid fringing when the image was or will be composited
over a light background. If the image will be composited over a black background on
the other hand, leaving this option off will give correct results.

In an ideal world, there should never be any color space conversion on images with
alpha, since it's undefined what to do then, but in practice it's useful to have
this option.

Patch by Troy Sobotka, with changes by me.
This commit is contained in:
Brecht Van Lommel 2011-12-30 14:17:11 +00:00
parent 424e09a2ce
commit d7d856a23d
17 changed files with 90 additions and 61 deletions

@ -316,6 +316,9 @@ class RENDER_PT_shading(RenderButtonsPanel, Panel):
col = split.column()
col.prop(rd, "use_raytrace", text="Ray Tracing")
col.prop(rd, "use_color_management")
sub = col.row()
sub.active = rd.use_color_management == True
sub.prop(rd, "use_color_unpremultiply")
col.prop(rd, "alpha_mode", text="Alpha")

@ -285,6 +285,10 @@ static void image_assign_ibuf(Image *ima, ImBuf *ibuf, int index, int frame)
break;
ibuf->index= index;
if(ima->flag & IMA_CM_PREDIVIDE)
ibuf->flags |= IB_cm_predivide;
else
ibuf->flags &= ~IB_cm_predivide;
/* this function accepts link==NULL */
BLI_insertlinkbefore(&ima->ibufs, link, ibuf);
@ -2304,9 +2308,17 @@ static ImBuf *image_get_render_result(Image *ima, ImageUser *iuser, void **lock_
/* since its possible to access the buffer from the image directly, set the profile [#25073] */
ibuf->profile= (iuser->scene->r.color_mgt_flag & R_COLOR_MANAGEMENT) ? IB_PROFILE_LINEAR_RGB : IB_PROFILE_NONE;
ibuf->dither= dither;
if(iuser->scene->r.color_mgt_flag & R_COLOR_MANAGEMENT_PREDIVIDE) {
ibuf->flags |= IB_cm_predivide;
ima->flag |= IMA_CM_PREDIVIDE;
}
else {
ibuf->flags &= ~IB_cm_predivide;
ima->flag &= ~IMA_CM_PREDIVIDE;
}
ima->ok= IMA_OK_LOADED;
return ibuf;

@ -136,13 +136,8 @@ void glaDrawPixelsSafe (float x, float y, int img_w, int img_h, int row_w, int
* is expected to be in RGBA byte or float format, and the
* modelview and projection matrices are assumed to define a
* 1-to-1 mapping to screen space.
* @param gamma_correct Optionally gamma correct float sources to sRGB for display
*/
/* only for float rects, converts to 32 bits and draws */
void glaDrawPixelsSafe_to32(float fx, float fy, int img_w, int img_h, int row_w, float *rectf, int gamma_correct);
void glaDrawPixelsTex (float x, float y, int img_w, int img_h, int format, void *rect);
void glaDrawPixelsTexScaled(float x, float y, int img_w, int img_h, int format, void *rect, float scaleX, float scaleY);

@ -138,11 +138,14 @@ void image_buffer_rect_update(Scene *scene, RenderResult *rr, ImBuf *ibuf, volat
rectf+= 4*(rr->rectx*ymin + xmin);
rectc= (unsigned char*)(ibuf->rect + ibuf->x*rymin + rxmin);
if(scene && (scene->r.color_mgt_flag & R_COLOR_MANAGEMENT))
if(scene && (scene->r.color_mgt_flag & R_COLOR_MANAGEMENT)) {
profile_from= IB_PROFILE_LINEAR_RGB;
else
predivide= (scene->r.color_mgt_flag & R_COLOR_MANAGEMENT_PREDIVIDE);
}
else {
profile_from= IB_PROFILE_SRGB;
predivide= 0;
predivide= 0;
}
IMB_buffer_byte_from_float(rectc, rectf,
4, ibuf->dither, IB_PROFILE_SRGB, profile_from, predivide,

@ -460,12 +460,15 @@ static int ed_preview_draw_rect(ScrArea *sa, Scene *sce, ID *id, int split, int
Render *re;
RenderResult rres;
char name[32];
int do_gamma_correct=0;
int do_gamma_correct=0, do_predivide=0;
int offx=0, newx= rect->xmax-rect->xmin, newy= rect->ymax-rect->ymin;
if (id && GS(id->name) != ID_TE) {
/* exception: don't color manage texture previews - show the raw values */
if (sce) do_gamma_correct = sce->r.color_mgt_flag & R_COLOR_MANAGEMENT;
if (sce) {
do_gamma_correct = sce->r.color_mgt_flag & R_COLOR_MANAGEMENT;
do_predivide = sce->r.color_mgt_flag & R_COLOR_MANAGEMENT_PREDIVIDE;
}
}
if(!split || first) sprintf(name, "Preview %p", (void *)sa);
@ -488,10 +491,28 @@ static int ed_preview_draw_rect(ScrArea *sa, Scene *sce, ID *id, int split, int
if(rres.rectf) {
if(ABS(rres.rectx-newx)<2 && ABS(rres.recty-newy)<2) {
newrect->xmax= MAX2(newrect->xmax, rect->xmin + rres.rectx + offx);
newrect->ymax= MAX2(newrect->ymax, rect->ymin + rres.recty);
glaDrawPixelsSafe_to32(rect->xmin+offx, rect->ymin, rres.rectx, rres.recty, rres.rectx, rres.rectf, do_gamma_correct);
if(rres.rectx && rres.recty) {
/* temporary conversion to byte for drawing */
float fx= rect->xmin + offx;
float fy= rect->ymin;
int profile_from= (do_gamma_correct)? IB_PROFILE_LINEAR_RGB: IB_PROFILE_SRGB;
int dither= 0;
unsigned char *rect_byte;
rect_byte= MEM_mallocN(rres.rectx*rres.recty*sizeof(int), "ed_preview_draw_rect");
IMB_buffer_byte_from_float(rect_byte, rres.rectf,
4, dither, IB_PROFILE_SRGB, profile_from, do_predivide,
rres.rectx, rres.recty, rres.rectx, rres.rectx);
glaDrawPixelsSafe(fx, fy, rres.rectx, rres.recty, rres.rectx, GL_RGBA, GL_UNSIGNED_BYTE, rect_byte);
MEM_freeN(rect_byte);
}
RE_ReleaseResultImage(re);
return 1;

@ -45,9 +45,6 @@
#include "BIF_gl.h"
#include "BIF_glutil.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#ifndef GL_CLAMP_TO_EDGE
#define GL_CLAMP_TO_EDGE 0x812F
#endif
@ -562,27 +559,6 @@ void glaDrawPixelsTex(float x, float y, int img_w, int img_h, int format, void *
glaDrawPixelsTexScaled(x, y, img_w, img_h, format, rect, 1.0f, 1.0f);
}
/* row_w is unused but kept for completeness */
void glaDrawPixelsSafe_to32(float fx, float fy, int img_w, int img_h, int UNUSED(row_w), float *rectf, int do_gamma_correct)
{
unsigned char *rect32;
int profile_from= (do_gamma_correct)? IB_PROFILE_LINEAR_RGB: IB_PROFILE_SRGB;
int predivide= 0;
/* copy imgw-imgh to a temporal 32 bits rect */
if(img_w<1 || img_h<1) return;
rect32= MEM_mallocN(img_w*img_h*sizeof(int), "temp 32 bits");
IMB_buffer_byte_from_float(rect32, rectf,
4, 0, IB_PROFILE_SRGB, profile_from, predivide,
img_w, img_h, img_w, img_w);
glaDrawPixelsSafe(fx, fy, img_w, img_h, img_w, GL_RGBA, GL_UNSIGNED_BYTE, rect32);
MEM_freeN(rect32);
}
void glaDrawPixelsSafe(float x, float y, int img_w, int img_h, int row_w, int format, int type, void *rect)
{
float xzoom= glaGetOneFloat(GL_ZOOM_X);

@ -750,7 +750,9 @@ void uiTemplateImage(uiLayout *layout, bContext *C, PointerRNA *ptr, const char
uiLayoutSetActive(row, RNA_boolean_get(&imaptr, "use_fields"));
uiItemR(row, &imaptr, "field_order", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
uiItemR(split, &imaptr, "use_premultiply", 0, NULL, ICON_NONE);
row= uiLayoutRow(layout, 0);
uiItemR(row, &imaptr, "use_premultiply", 0, NULL, ICON_NONE);
uiItemR(row, &imaptr, "use_color_unpremultiply", 0, NULL, ICON_NONE);
}
}

@ -248,12 +248,10 @@ static void node_area_listener(ScrArea *sa, wmNotifier *wmn)
case NC_IMAGE:
if (wmn->action == NA_EDITED) {
if(type==NTREE_COMPOSIT) {
Scene *scene= wmn->window->screen->scene;
/* note that nodeUpdateID is already called by BKE_image_signal() on all
* scenes so really this is just to know if the images is used in the compo else
* painting on images could become very slow when the compositor is open. */
if(nodeUpdateID(scene->nodetree, wmn->reference))
if(nodeUpdateID(snode->nodetree, wmn->reference))
ED_area_tag_refresh(sa);
}
}

@ -158,6 +158,7 @@ typedef struct ImBuf {
#define IB_tiles (1 << 10)
#define IB_tilecache (1 << 11)
#define IB_premul (1 << 12)
#define IB_cm_predivide (1 << 13)
/*
* The bit flag is stored in the ImBuf.ftype variable.

@ -453,7 +453,8 @@ void IMB_buffer_byte_from_byte(uchar *rect_to, const uchar *rect_from,
void IMB_rect_from_float(struct ImBuf *ibuf)
{
int predivide= 0, profile_from;
int predivide= (ibuf->flags & IB_cm_predivide);
int profile_from;
/* verify we have a float buffer */
if(ibuf->rect_float==NULL)
@ -485,7 +486,8 @@ void IMB_partial_rect_from_float(struct ImBuf *ibuf, float *buffer, int x, int y
{
float *rect_float;
uchar *rect_byte;
int predivide= 0, profile_from;
int predivide= (ibuf->flags & IB_cm_predivide);
int profile_from;
/* verify we have a float buffer */
if(ibuf->rect_float==NULL || buffer==NULL)
@ -521,7 +523,8 @@ void IMB_partial_rect_from_float(struct ImBuf *ibuf, float *buffer, int x, int y
void IMB_float_from_rect(struct ImBuf *ibuf)
{
int predivide= 0, profile_from;
int predivide= (ibuf->flags & IB_cm_predivide);
int profile_from;
/* verify if we byte and float buffers */
if(ibuf->rect==NULL)
@ -546,7 +549,7 @@ void IMB_float_from_rect(struct ImBuf *ibuf)
/* no profile conversion */
void IMB_float_from_rect_simple(struct ImBuf *ibuf)
{
int predivide= 0;
int predivide= (ibuf->flags & IB_cm_predivide);
if(ibuf->rect_float==NULL)
imb_addrectfloatImBuf(ibuf);
@ -558,7 +561,8 @@ void IMB_float_from_rect_simple(struct ImBuf *ibuf)
void IMB_convert_profile(struct ImBuf *ibuf, int profile)
{
int predivide= 0, profile_from, profile_to;
int predivide= (ibuf->flags & IB_cm_predivide);
int profile_from, profile_to;
if(ibuf->profile == profile)
return;
@ -599,7 +603,8 @@ void IMB_convert_profile(struct ImBuf *ibuf, int profile)
* if the return */
float *IMB_float_profile_ensure(struct ImBuf *ibuf, int profile, int *alloc)
{
int predivide= 0, profile_from, profile_to;
int predivide= (ibuf->flags & IB_cm_predivide);
int profile_from, profile_to;
/* determine profiles */
if(ibuf->profile == IB_PROFILE_NONE)

@ -112,14 +112,14 @@ typedef struct Image {
/* **************** IMAGE ********************* */
/* Image.flag */
#define IMA_FIELDS 1
#define IMA_STD_FIELD 2
#define IMA_DO_PREMUL 4
#define IMA_REFLECT 16
#define IMA_NOCOLLECT 32
#define IMA_DEPRECATED 64
#define IMA_OLD_PREMUL 128
#define IMA_FIELDS 1
#define IMA_STD_FIELD 2
#define IMA_DO_PREMUL 4
#define IMA_REFLECT 16
#define IMA_NOCOLLECT 32
#define IMA_DEPRECATED 64
#define IMA_OLD_PREMUL 128
#define IMA_CM_PREDIVIDE 256
/* Image.tpageflag */
#define IMA_TILES 1

@ -1100,7 +1100,8 @@ typedef struct Scene {
#define R_ALPHAKEY 2
/* color_mgt_flag */
#define R_COLOR_MANAGEMENT 1
#define R_COLOR_MANAGEMENT (1 << 0)
#define R_COLOR_MANAGEMENT_PREDIVIDE (1 << 1)
/* subimtype, flag options for imtype */
#define R_OPENEXR_HALF 1 /*deprecated*/

@ -40,6 +40,7 @@
#include "BKE_image.h"
#include "WM_types.h"
#include "WM_api.h"
static EnumPropertyItem image_source_items[]= {
{IMA_SRC_FILE, "FILE", 0, "Single Image", "Single image file"},
@ -110,6 +111,7 @@ static void rna_Image_reload_update(Main *UNUSED(bmain), Scene *UNUSED(scene), P
{
Image *ima= ptr->id.data;
BKE_image_signal(ima, NULL, IMA_SIGNAL_RELOAD);
WM_main_add_notifier(NC_IMAGE|NA_EDITED, &ima->id);
DAG_id_tag_update(&ima->id, 0);
}
@ -476,6 +478,11 @@ static void rna_def_image(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Premultiply", "Convert RGB from key alpha to premultiplied alpha");
RNA_def_property_update(prop, NC_IMAGE|ND_DISPLAY, "rna_Image_reload_update");
prop= RNA_def_property(srna, "use_color_unpremultiply", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", IMA_CM_PREDIVIDE);
RNA_def_property_ui_text(prop, "Color Unpremultiply", "For premultiplied alpha images, do color space conversion on colors without alpha, to avoid fringing for images with light backgrounds");
RNA_def_property_update(prop, NC_IMAGE|ND_DISPLAY, "rna_Image_reload_update");
prop= RNA_def_property(srna, "is_dirty", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop, "rna_Image_dirty_get", NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

@ -3229,6 +3229,10 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Color Management", "Use linear workflow - gamma corrected imaging pipeline");
RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, "rna_RenderSettings_color_management_update");
prop= RNA_def_property(srna, "use_color_unpremultiply", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "color_mgt_flag", R_COLOR_MANAGEMENT_PREDIVIDE);
RNA_def_property_ui_text(prop, "Color Unpremultipy", "For premultiplied alpha render output, do color space conversion on colors without alpha, to avoid fringing on light backgrounds");
RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL);
prop= RNA_def_property(srna, "use_file_extension", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "scemode", R_EXTENSION);

@ -607,7 +607,7 @@ void generate_preview(void *data, bNode *node, CompBuf *stackbuf)
bNodePreview *preview= node->preview;
int xsize, ysize;
int profile_from= (rd->color_mgt_flag & R_COLOR_MANAGEMENT)? IB_PROFILE_LINEAR_RGB: IB_PROFILE_SRGB;
int predivide= 0;
int predivide= (rd->color_mgt_flag & R_COLOR_MANAGEMENT_PREDIVIDE);
int dither= 0;
unsigned char *rect;

@ -62,7 +62,7 @@ static bNodeSocketTemplate cmp_node_rlayers_out[]= {
float *node_composit_get_float_buffer(RenderData *rd, ImBuf *ibuf, int *alloc)
{
float *rect;
int predivide= 0;
int predivide= (ibuf->flags & IB_cm_predivide);
*alloc= FALSE;

@ -1154,7 +1154,7 @@ void RE_ResultGet32(Render *re, unsigned int *rect)
}
else if(rres.rectf) {
int profile_from= (re->r.color_mgt_flag & R_COLOR_MANAGEMENT)? IB_PROFILE_LINEAR_RGB: IB_PROFILE_SRGB;
int predivide= 0;
int predivide= (re->r.color_mgt_flag & R_COLOR_MANAGEMENT_PREDIVIDE);
int dither= 0;
IMB_buffer_byte_from_float((unsigned char*)rect, rres.rectf,
@ -2556,7 +2556,7 @@ static void do_render_seq(Render * re)
* render engine delivers */
int profile_to= (re->r.color_mgt_flag & R_COLOR_MANAGEMENT)? IB_PROFILE_LINEAR_RGB: IB_PROFILE_SRGB;
int profile_from= (ibuf->profile == IB_PROFILE_LINEAR_RGB)? IB_PROFILE_LINEAR_RGB: IB_PROFILE_SRGB;
int predivide= 0;
int predivide= (re->r.color_mgt_flag & R_COLOR_MANAGEMENT_PREDIVIDE);
if (!rr->rectf)
rr->rectf= MEM_mallocN(4*sizeof(float)*rr->rectx*rr->recty, "render_seq rectf");
@ -2995,7 +2995,8 @@ static int do_write_image_or_movie(Render *re, Main *bmain, Scene *scene, bMovie
}
}
else {
ImBuf *ibuf= IMB_allocImBuf(rres.rectx, rres.recty, scene->r.im_format.planes, 0);
int flags = (scene->r.color_mgt_flag & R_COLOR_MANAGEMENT_PREDIVIDE)? IB_cm_predivide: 0;
ImBuf *ibuf= IMB_allocImBuf(rres.rectx, rres.recty, scene->r.im_format.planes, flags);
/* if not exists, BKE_write_ibuf makes one */
ibuf->rect= (unsigned int *)rres.rect32;