Fix painting UI theme colors as they are specified in sRGB color space

Fixes a regression found after merging #5414:
https://github.com/aseprite/aseprite/pull/5414#issuecomment-3286339563

We always expected a sRGB color in ui::Graphics API and we can specify
a color in another color space using the ui::Paint version of its
member functions.

Several functions related to color spaces are now using a ui::Display
to receive the specific display where we're going to paint, instead of
using os::System::instance()->defaultWindow()->colorSpace().
This commit is contained in:
David Capello 2025-09-15 15:49:09 -03:00
parent 5d5f3ec234
commit c444b566e1
15 changed files with 91 additions and 63 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -15,6 +15,7 @@
#include "app/ui/editor/editor.h"
#include "os/system.h"
#include "os/window.h"
#include "ui/display.h"
namespace app {
@ -29,17 +30,15 @@ void initialize_color_spaces(Preferences& pref)
pref.color.manage.AfterChange.connect([](bool manage) { g_manage = manage; });
}
os::ColorSpaceRef get_screen_color_space()
os::ColorSpaceRef get_current_color_space(ui::Display* display, Doc* doc)
{
return os::System::instance()->defaultWindow()->colorSpace();
}
os::ColorSpaceRef get_current_color_space()
{
if (auto* editor = Editor::activeEditor())
return editor->document()->osColorSpace();
else
return get_screen_color_space();
if (!doc) {
if (auto* editor = Editor::activeEditor())
doc = editor->document();
}
if (doc)
return doc->osColorSpace();
return display->colorSpace();
}
gfx::ColorSpaceRef get_working_rgb_space_from_preferences()
@ -62,11 +61,11 @@ gfx::ColorSpaceRef get_working_rgb_space_from_preferences()
//////////////////////////////////////////////////////////////////////
// Color conversion
ConvertCS::ConvertCS()
ConvertCS::ConvertCS(ui::Display* display, Doc* doc)
{
if (g_manage) {
auto srcCS = get_current_color_space();
auto dstCS = get_screen_color_space();
auto srcCS = get_current_color_space(display, doc);
auto dstCS = display->colorSpace();
if (srcCS && dstCS)
m_conversion = os::System::instance()->convertBetweenColorSpace(srcCS, dstCS);
}
@ -95,9 +94,9 @@ gfx::Color ConvertCS::operator()(const gfx::Color c)
}
}
ConvertCS convert_from_current_to_screen_color_space()
ConvertCS convert_from_current_to_display_color_space(ui::Display* display)
{
return ConvertCS();
return ConvertCS(display);
}
ConvertCS convert_from_custom_to_srgb(const os::ColorSpaceRef& from)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2018-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -16,21 +16,25 @@ namespace doc {
class Sprite;
}
namespace ui {
class Display;
}
namespace app {
class Doc;
class Preferences;
void initialize_color_spaces(Preferences& pref);
os::ColorSpaceRef get_screen_color_space();
// Returns the color space of the current document.
os::ColorSpaceRef get_current_color_space();
os::ColorSpaceRef get_current_color_space(ui::Display* display, Doc* doc = nullptr);
gfx::ColorSpaceRef get_working_rgb_space_from_preferences();
class ConvertCS {
public:
ConvertCS();
ConvertCS() = delete;
ConvertCS(ui::Display* display, Doc* doc = nullptr);
ConvertCS(const os::ColorSpaceRef& srcCS, const os::ColorSpaceRef& dstCS);
ConvertCS(ConvertCS&&);
ConvertCS& operator=(const ConvertCS&) = delete;
@ -40,7 +44,7 @@ private:
os::Ref<os::ColorSpaceConversion> m_conversion;
};
ConvertCS convert_from_current_to_screen_color_space();
ConvertCS convert_from_current_to_display_color_space(ui::Display* display);
ConvertCS convert_from_custom_to_srgb(const os::ColorSpaceRef& from);
} // namespace app

View File

@ -30,6 +30,7 @@
#include "os/surface.h"
#include "os/system.h"
#include "ui/intern.h"
#include "ui/paint.h"
#include "ui/system.h"
#include "ui/theme.h"
@ -118,9 +119,6 @@ void draw_color(ui::Graphics* g,
app::Color color = _color;
const int alpha = color.getAlpha();
// Color space conversion
auto convertColor = convert_from_current_to_screen_color_space();
if (alpha < 255) {
if (rc.w == rc.h)
draw_checkered_grid(g, rc, gfx::Size(rc.w / 2, rc.h / 2));
@ -133,11 +131,15 @@ void draw_color(ui::Graphics* g,
color = app::Color::fromGray(color.getGray(), color.getAlpha());
}
// The color is in the current sprite color space.
ui::Paint paint;
paint.color(color_utils::color_for_ui(color), get_current_color_space(g->display()).get());
if (color.getType() == app::Color::IndexType) {
int index = color.getIndex();
if (index >= 0 && index < get_current_palette()->size()) {
g->fillRect(convertColor(color_utils::color_for_ui(color)), rc);
g->drawRect(rc, paint);
}
else {
g->fillRect(gfx::rgba(0, 0, 0), rc);
@ -147,7 +149,7 @@ void draw_color(ui::Graphics* g,
}
}
else {
g->fillRect(convertColor(color_utils::color_for_ui(color)), rc);
g->drawRect(rc, paint);
}
}
}
@ -219,7 +221,8 @@ void draw_tile(ui::Graphics* g, const Rect& rc, const Site& site, doc::tile_t ti
int w = tileImage->width();
int h = tileImage->height();
os::SurfaceRef surface = os::System::instance()->makeRgbaSurface(w, h, get_current_color_space());
os::SurfaceRef surface =
os::System::instance()->makeRgbaSurface(w, h, get_current_color_space(g->display()));
convert_image_to_surface(tileImage.get(), get_current_palette(), surface.get(), 0, 0, 0, 0, w, h);
ui::Paint paint;

View File

@ -190,7 +190,9 @@ void Canvas::onResize(ui::ResizeEvent& ev)
}
if (!m_surface || m_surface->width() != w || m_surface->height() != h) {
m_surface = system->makeSurface(w, h, get_current_color_space());
ui::Display* display = this->display();
os::ColorSpaceRef cs = (display ? display->colorSpace() : nullptr);
m_surface = system->makeSurface(w, h, cs);
callPaint();
}
}

View File

@ -22,7 +22,8 @@
namespace app { namespace thumb {
os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel,
os::SurfaceRef get_cel_thumbnail(ui::Display* display,
const doc::Cel* cel,
const bool scaleUpToFit,
const gfx::Size& fitInSize)
{
@ -57,7 +58,7 @@ os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel,
if (os::SurfaceRef thumbnail = os::System::instance()->makeRgbaSurface(
thumbnailImage->width(),
thumbnailImage->height(),
get_current_color_space())) {
get_current_color_space(display))) {
convert_image_to_surface(thumbnailImage.get(),
palette,
thumbnail.get(),

View File

@ -11,6 +11,7 @@
#include "gfx/size.h"
#include "os/surface.h"
#include "ui/display.h"
namespace doc {
class Cel;
@ -22,7 +23,8 @@ class Surface;
namespace app { namespace thumb {
os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel,
os::SurfaceRef get_cel_thumbnail(ui::Display* display,
const doc::Cel* cel,
const bool scaleUpToFit,
const gfx::Size& fitInSize);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
@ -107,11 +107,11 @@ public:
}
}
os::Surface* getCanvas(int w, int h, gfx::Color bgColor)
os::Surface* getCanvas(Display* display, int w, int h, gfx::Color bgColor)
{
assert_ui_thread();
auto activeCS = get_current_color_space();
auto activeCS = get_current_color_space(display);
if (!m_canvas || m_canvas->width() != w || m_canvas->height() != h ||
m_canvas->colorSpace() != activeCS) {
@ -432,9 +432,9 @@ void ColorSelector::onPaint(ui::PaintEvent& ev)
SkCanvas* canvas;
bool isSRGB;
// TODO compare both color spaces
if ((!get_current_color_space() || get_current_color_space()->isSRGB()) &&
(!g->getInternalSurface()->colorSpace() ||
g->getInternalSurface()->colorSpace()->isSRGB())) {
auto displayCs = get_current_color_space(display());
auto gCs = g->getInternalSurface()->colorSpace();
if ((!displayCs || displayCs->isSRGB()) && (!gCs || gCs->isSRGB())) {
// We can render directly in the ui::Graphics surface
canvas = &static_cast<os::SkiaSurface*>(g->getInternalSurface())->canvas();
isSRGB = true;
@ -442,7 +442,7 @@ void ColorSelector::onPaint(ui::PaintEvent& ev)
else {
// We'll paint in the ColorSelector::Painter canvas, and so we
// can convert color spaces.
painterSurface = painter.getCanvas(rc.w, rc.h, theme->colors.workspace());
painterSurface = painter.getCanvas(display(), rc.w, rc.h, theme->colors.workspace());
canvas = &static_cast<os::SkiaSurface*>(painterSurface)->canvas();
isSRGB = false;
}
@ -497,7 +497,7 @@ void ColorSelector::onPaint(ui::PaintEvent& ev)
else
#endif // SK_ENABLE_SKSL
{
painterSurface = painter.getCanvas(rc.w, rc.h, theme->colors.workspace());
painterSurface = painter.getCanvas(display(), rc.w, rc.h, theme->colors.workspace());
}
if (painterSurface)

View File

@ -58,9 +58,7 @@ public:
return;
}
// Color space conversion
auto convertColor = convert_from_current_to_screen_color_space();
Paint paint;
gfx::Color color = gfx::ColorNone;
int w = std::max(rc.w - 1, 1);
@ -110,7 +108,11 @@ public:
color = color_utils::color_for_ui(app::Color::fromGray(255 * x / w));
break;
}
g->drawVLine(convertColor(color), rc.x + x, rc.y, rc.h);
// Color space conversion
paint.color(color, get_current_color_space(slider->display()).get());
g->drawVLine(rc.x + x, rc.y, rc.h, paint);
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "app/ui/color_wheel.h"
#include "app/color_spaces.h"
#include "app/color_utils.h"
#include "app/i18n/strings.h"
#include "app/pref/preferences.h"
@ -358,6 +359,9 @@ void ColorWheel::onPaintMainArea(ui::Graphics* g, const gfx::Rect& rc)
int n = getHarmonies();
int boxsize = std::min(rc.w / 10, rc.h / 10);
ui::Paint paint;
auto cs = get_current_color_space(g->display());
for (int i = 0; i < n; ++i) {
app::Color color = getColorInHarmony(i);
double angle = color.getHsvHue() - 30.0;
@ -374,9 +378,10 @@ void ColorWheel::onPaintMainArea(ui::Graphics* g, const gfx::Rect& rc)
paintColorIndicator(g, pos, color.getHsvValue() < 0.5);
g->fillRect(
gfx::rgba(color.getRed(), color.getGreen(), color.getBlue(), 255),
gfx::Rect(rc.x + rc.w - (n - i) * boxsize, rc.y + rc.h - boxsize, boxsize, boxsize));
paint.color(gfx::rgba(color.getRed(), color.getGreen(), color.getBlue(), 255), cs.get());
g->drawRect(
gfx::Rect(rc.x + rc.w - (n - i) * boxsize, rc.y + rc.h - boxsize, boxsize, boxsize),
paint);
}
}
}

View File

@ -309,7 +309,7 @@ public:
int w = tileImage->width();
int h = tileImage->height();
os::SurfaceRef surface =
os::System::instance()->makeRgbaSurface(w, h, get_current_color_space());
os::System::instance()->makeRgbaSurface(w, h, get_current_color_space(g->display()));
convert_image_to_surface(tileImage.get(),
get_current_palette(),
surface.get(),

View File

@ -970,8 +970,10 @@ void Tabs::createFloatingUILayer(Tab* tab)
ASSERT(!m_floatingUILayer);
ui::Display* display = this->display();
os::SurfaceRef surface =
os::System::instance()->makeRgbaSurface(tab->width, m_tabsHeight, get_current_color_space());
os::SurfaceRef surface = os::System::instance()->makeRgbaSurface(
tab->width,
m_tabsHeight,
get_current_color_space(display));
// Fill the surface with pink color
{

View File

@ -2526,7 +2526,7 @@ void Timeline::drawCel(ui::Graphics* g,
if (!thumb_bounds.isEmpty()) {
if (os::SurfaceRef surface =
thumb::get_cel_thumbnail(cel, m_scaleUpToFit, thumb_bounds.size())) {
thumb::get_cel_thumbnail(g->display(), cel, m_scaleUpToFit, thumb_bounds.size())) {
const int t = std::clamp(thumb_bounds.w / 8, 4, 16);
draw_checkered_grid(g, thumb_bounds, gfx::Size(t, t), docPref());
@ -2618,7 +2618,8 @@ void Timeline::drawCelOverlay(ui::Graphics* g)
return;
gfx::Rect rc = m_sprite->bounds().fitIn(gfx::Rect(m_thumbnailsOverlayBounds).shrink(1));
if (os::SurfaceRef surface = thumb::get_cel_thumbnail(cel, m_scaleUpToFit, rc.size())) {
if (os::SurfaceRef surface =
thumb::get_cel_thumbnail(g->display(), cel, m_scaleUpToFit, rc.size())) {
draw_checkered_grid(g, rc, gfx::Size(8, 8) * ui::guiscale(), docPref());
g->drawRgbaSurface(surface.get(),

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -31,6 +31,7 @@ public:
Display* parentDisplay() { return m_parentDisplay; }
os::Window* nativeWindow() const { return m_nativeWindow.get(); }
os::SurfaceRef nativeSurface() const;
os::ColorSpaceRef colorSpace() const { return m_nativeWindow->colorSpace(); }
UILayers layers() { return m_layers; }
UILayerRef backLayer() { return m_layers.front(); }

View File

@ -155,7 +155,7 @@ void Graphics::drawHLine(gfx::Color color, int x, int y, int w)
os::SurfaceLock lock(m_surface.get());
os::Paint paint;
paint.color(color, colorSpace());
paint.color(color);
m_surface->drawRect(gfx::Rect(m_dx + x, m_dy + y, w, 1), paint);
}
@ -173,7 +173,7 @@ void Graphics::drawVLine(gfx::Color color, int x, int y, int h)
os::SurfaceLock lock(m_surface.get());
os::Paint paint;
paint.color(color, colorSpace());
paint.color(color);
m_surface->drawRect(gfx::Rect(m_dx + x, m_dy + y, 1, h), paint);
}
@ -185,7 +185,7 @@ void Graphics::drawLine(gfx::Color color, const gfx::Point& _a, const gfx::Point
os::SurfaceLock lock(m_surface.get());
os::Paint paint;
paint.color(color, colorSpace());
paint.color(color);
m_surface->drawLine(a, b, paint);
}
@ -242,7 +242,7 @@ void Graphics::drawRect(gfx::Color color, const gfx::Rect& rcOrig)
os::SurfaceLock lock(m_surface.get());
os::Paint paint;
paint.color(color, colorSpace());
paint.color(color);
paint.style(os::Paint::Stroke);
m_surface->drawRect(rc, paint);
}
@ -255,7 +255,7 @@ void Graphics::fillRect(gfx::Color color, const gfx::Rect& rcOrig)
os::SurfaceLock lock(m_surface.get());
os::Paint paint;
paint.color(color, colorSpace());
paint.color(color);
paint.style(os::Paint::Fill);
m_surface->drawRect(rc, paint);
}
@ -444,11 +444,11 @@ void Graphics::drawUIText(const std::string& str,
Paint paint;
if (gfx::geta(bg) > 0) { // Paint background
paint.color(bg, colorSpace());
paint.color(bg);
paint.style(os::Paint::Fill);
drawRect(gfx::RectF(textBlob->bounds()).offset(pt), paint);
}
paint.color(fg, colorSpace());
paint.color(fg);
drawTextBlob(textBlob, gfx::PointF(pt), paint);
@ -603,10 +603,10 @@ gfx::Size Graphics::doUIStringAlgorithm(const std::string& str,
Paint paint;
paint.style(os::Paint::Fill);
if (!gfx::is_transparent(bg)) {
paint.color(bg, colorSpace());
paint.color(bg);
drawRect(gfx::RectF(xout, pt.y, rc.w, lineSize.h), paint);
}
paint.color(fg, colorSpace());
paint.color(fg);
float baselineDelta = -metrics.ascent - lineBlob->baseline();
drawTextBlob(lineBlob, gfx::PointF(xout, pt.y + baselineDelta), paint);

View File

@ -39,6 +39,11 @@ namespace ui {
class Display;
// Class to render a widget in the screen.
//
// The gfx::Color parameter is a color in the sRGB color space
// (e.g. used to paint theme elements on widgets). If you want to
// paint a color from other color space, use the Paint version of each
// function.
class Graphics {
public:
Graphics(Display* display, const os::SurfaceRef& surface, int dx, int dy);
@ -47,6 +52,7 @@ public:
int width() const;
int height() const;
Display* display() const { return m_display; }
os::Surface* getInternalSurface() { return m_surface.get(); }
os::ColorSpace* colorSpace() { return m_surface->colorSpace().get(); }