Fix #143474: Color Space inconsistent between 2D and 3D texture paint
The short description of the root of the problem is that 2D texture paint properly handled non-color paint while 3D painting did not (for both painting and color sampling). This change makes it so 3D texture paint avoids color space conversion of brush color (which is assumed to be in display space) to scene linear when painting on images set to Non-Color. On the design level this change moves code closer to the following design: - Brush color is specified in display space. - Float buffers are in scene linear space (or a non-color). - When painting on color float buffers brush color gets linearized - When painting on non-color float buffers brush color is used as-is. For painting on non-color images it means: values from brush color are used as-is, without any color space conversion. The change makes 3D painting behave the same as 2D painting. There are a couple of tricky parts remaining. Relatively small thing is that the color displayed on the brush color swatch is not what it will be after making a stroke. This is because brush colors are used as-is, image is not color managed (and is drawn as-is on a surface which is assumed to be in scene linear), and the brush color swatch is color managed. Generally it means the swatch color is darker than what the stroke on non-color image would look like. The bigger topic is color sampling that read pixel value from frame buffer. This change makes it so sampling color in such cases (by either using eyedropper from the color swatch popup, or by using Shift-X sampling operator outside of painting object/texture) will set brush color in a way that making stroke will result in the same color as it was on the screen. When painting on non-color images it means the brush color will be sampled darker than when painting on a color textures. This introduces some context dependency to the way how sampling works which might be undesirable. Pull Request: https://projects.blender.org/blender/blender/pulls/143642
This commit is contained in:
committed by
Sergey Sharybin
parent
a55e0b9516
commit
cc6a6fe893
@@ -88,6 +88,63 @@ static void eyedropper_draw_cb(const wmWindow * /*window*/, void *arg)
|
||||
eyedropper_draw_cursor_text_region(eye->cb_win_event_xy, eye->sample_text);
|
||||
}
|
||||
|
||||
/* A heuristic to check whether the current eyedropper destination property is used for non-color
|
||||
* painting. If so, the eyedropper will ignore the PROP_COLOR_GAMMA nature of the property and
|
||||
* not convert linear colors to display space.
|
||||
*
|
||||
* The current logic is targeting texture painting, both 2D and 3D. It assumes that invoking the
|
||||
* operator from 3D viewport means 3D painting, and invoking from image editor means 2D painting.
|
||||
*
|
||||
* For the 3D painting the function checks whether active object is in texture paint mode, and if
|
||||
* so checks the active image (via material slot, or the explicitly specified image) to have
|
||||
* non-color (data) colorspace.
|
||||
*
|
||||
* For the 2D painting it checks the active image editor's image colorspace.
|
||||
*
|
||||
* Since brush color could be re-used from multiple spaces the check is not fully reliable: it is
|
||||
* possible to invoke sampling from one editor and do stroke in other editor. There is no easy way
|
||||
* of dealing with this, and it is unlikely to be a common configuration. */
|
||||
static bool is_data_destination(const bContext *C, const Eyedropper *eye)
|
||||
{
|
||||
if (eye->ptr.type != &RNA_Brush) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const View3D *v3d = CTX_wm_view3d(C);
|
||||
if (v3d) {
|
||||
/*const*/ Object *object = CTX_data_active_object(C);
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
if ((object->mode & OB_MODE_TEXTURE_PAINT) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
const ImagePaintSettings &settings = scene->toolsettings->imapaint;
|
||||
Image *image = nullptr;
|
||||
if (settings.mode == IMAGEPAINT_MODE_MATERIAL) {
|
||||
Material *material = BKE_object_material_get(object, object->actcol);
|
||||
if (material && material->texpaintslot) {
|
||||
image = material->texpaintslot[material->paint_active_slot].ima;
|
||||
}
|
||||
}
|
||||
else if (settings.mode == IMAGEPAINT_MODE_IMAGE) {
|
||||
image = settings.canvas;
|
||||
}
|
||||
|
||||
return image && IMB_colormanagement_space_name_is_data(image->colorspace_settings.name);
|
||||
}
|
||||
|
||||
const SpaceImage *space_image = CTX_wm_space_image(C);
|
||||
if (space_image) {
|
||||
return space_image->image &&
|
||||
IMB_colormanagement_space_name_is_data(space_image->image->colorspace_settings.name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool eyedropper_init(bContext *C, wmOperator *op)
|
||||
{
|
||||
Eyedropper *eye = MEM_new<Eyedropper>(__func__);
|
||||
@@ -137,7 +194,7 @@ static bool eyedropper_init(bContext *C, wmOperator *op)
|
||||
eye->draw_handle_sample_text = WM_draw_cb_activate(eye->cb_win, eyedropper_draw_cb, eye);
|
||||
}
|
||||
|
||||
if (prop_subtype != PROP_COLOR) {
|
||||
if (prop_subtype != PROP_COLOR && !is_data_destination(C, eye)) {
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
const char *display_device;
|
||||
|
||||
|
||||
@@ -5749,6 +5749,17 @@ static bool project_paint_op(void *state, const float lastpos[2], const float po
|
||||
return touch_any;
|
||||
}
|
||||
|
||||
static bool has_data_projection_paint_image(const ProjPaintState &ps)
|
||||
{
|
||||
for (int i = 0; i < ps.image_tot; i++) {
|
||||
const ImBuf *ibuf = ps.projImages[i].ibuf;
|
||||
if (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void paint_proj_stroke_ps(const bContext * /*C*/,
|
||||
void *ps_handle_p,
|
||||
const float prev_pos[2],
|
||||
@@ -5782,7 +5793,12 @@ static void paint_proj_stroke_ps(const bContext * /*C*/,
|
||||
pressure,
|
||||
nullptr,
|
||||
ps->paint_color);
|
||||
srgb_to_linearrgb_v3_v3(ps->paint_color_linear, ps->paint_color);
|
||||
if (has_data_projection_paint_image(*ps)) {
|
||||
copy_v3_v3(ps->paint_color_linear, ps->paint_color);
|
||||
}
|
||||
else {
|
||||
srgb_to_linearrgb_v3_v3(ps->paint_color_linear, ps->paint_color);
|
||||
}
|
||||
}
|
||||
else if (ps->brush_type == IMAGE_PAINT_BRUSH_TYPE_MASK) {
|
||||
ps->stencil_value = brush->weight;
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "paint_intern.hh"
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -180,6 +182,25 @@ static void paint_sample_color(
|
||||
SpaceImage *sima = CTX_wm_space_image(C);
|
||||
const View3D *v3d = CTX_wm_view3d(C);
|
||||
|
||||
bool is_data = false;
|
||||
|
||||
if (v3d) {
|
||||
const ImagePaintSettings *imapaint = &scene->toolsettings->imapaint;
|
||||
if (imapaint->mode == IMAGEPAINT_MODE_MATERIAL) {
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *object = BKE_view_layer_active_object_get(view_layer);
|
||||
const Material *material = BKE_object_material_get(object, object->actcol);
|
||||
if (material && material->texpaintslot) {
|
||||
const Image *image = material->texpaintslot[material->paint_active_slot].ima;
|
||||
is_data = image && IMB_colormanagement_space_name_is_data(image->colorspace_settings.name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const Image *image = imapaint->canvas;
|
||||
is_data = image && IMB_colormanagement_space_name_is_data(image->colorspace_settings.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (v3d && texpaint_proj) {
|
||||
/* first try getting a color directly from the mesh faces if possible */
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
@@ -257,10 +278,17 @@ static void paint_sample_color(
|
||||
rgba_f = math::clamp(rgba_f, 0.0f, 1.0f);
|
||||
straight_to_premul_v4(rgba_f);
|
||||
if (use_palette) {
|
||||
linearrgb_to_srgb_v3_v3(color->rgb, rgba_f);
|
||||
if (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) {
|
||||
copy_v3_v3(color->rgb, rgba_f);
|
||||
}
|
||||
else {
|
||||
linearrgb_to_srgb_v3_v3(color->rgb, rgba_f);
|
||||
}
|
||||
}
|
||||
else {
|
||||
linearrgb_to_srgb_v3_v3(rgba_f, rgba_f);
|
||||
if ((ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) == 0) {
|
||||
linearrgb_to_srgb_v3_v3(rgba_f, rgba_f);
|
||||
}
|
||||
BKE_brush_color_set(paint, br, rgba_f);
|
||||
}
|
||||
}
|
||||
@@ -291,7 +319,6 @@ static void paint_sample_color(
|
||||
/* Sample from the active image buffer. The sampled color is in
|
||||
* Linear Scene Reference Space. */
|
||||
float rgba_f[3];
|
||||
bool is_data;
|
||||
if (ED_space_image_color_sample(sima, region, blender::int2(x, y), rgba_f, &is_data)) {
|
||||
if (!is_data) {
|
||||
linearrgb_to_srgb_v3_v3(rgba_f, rgba_f);
|
||||
@@ -314,6 +341,24 @@ static void paint_sample_color(
|
||||
CTX_wm_window(C),
|
||||
blender::int2(x + region->winrct.xmin, y + region->winrct.ymin),
|
||||
rgb_fl);
|
||||
|
||||
/* The sampled color is in display space, which is what it is supposed to be when painting on
|
||||
* an image with known colorspace. When painting on non-color/data textures convert display to
|
||||
* scene linear so that painting with the new color will produce the same color after the
|
||||
* texture comes via rendering/color management. */
|
||||
if (is_data) {
|
||||
/* Note that the logic for the image sampling above uses hardcoded linear<->srgb conversion,
|
||||
* as well does the do_projectpaint_draw(). For the consistency use hardcoded conversion here
|
||||
* as well.
|
||||
*
|
||||
* Ideally it should become something like:
|
||||
* const ColorManagedDisplay *display = IMB_colormanagement_display_get_named(
|
||||
* scene->display_settings.display_device);
|
||||
* IMB_colormanagement_display_to_scene_linear_v3(rgb_fl, display);
|
||||
*/
|
||||
srgb_to_linearrgb_v3_v3(rgb_fl, rgb_fl);
|
||||
}
|
||||
|
||||
if (use_palette) {
|
||||
copy_v3_v3(color->rgb, rgb_fl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user