Use shaders for ColorWheel selector

This patch includes a fix for the RYB color wheel (the blue was not in
the correct hue angle).
This commit is contained in:
David Capello 2022-05-27 19:57:37 -03:00
parent b561a6fbfb
commit 23b269a6bc
4 changed files with 153 additions and 49 deletions

View File

@ -471,6 +471,7 @@ void ColorSelector::onPaint(ui::PaintEvent& ev)
SkRuntimeShaderBuilder builder1(m_mainEffect);
builder1.uniform("iRes") = SkV3{float(rc2.w), float(rc2.h), 0.0f};
builder1.uniform("iColor") = appColor_to_SkV4(m_color);
setShaderMainAreaParams(builder1);
p.setShader(builder1.makeShader());
if (isSRGB)
@ -677,6 +678,6 @@ sk_sp<SkRuntimeEffect> ColorSelector::buildEffect(const char* code)
return result.effect;
}
}
#endif
#endif // SK_ENABLE_SKSL
} // namespace app

View File

@ -70,6 +70,9 @@ namespace app {
virtual const char* getMainAreaShader() { return nullptr; }
virtual const char* getBottomBarShader() { return nullptr; }
#if SK_ENABLE_SKSL
virtual void setShaderMainAreaParams(SkRuntimeShaderBuilder& builder) { }
#endif
virtual app::Color getMainAreaColor(const int u, const int umax,
const int v, const int vmax) = 0;
virtual app::Color getBottomBarColor(const int u, const int umax) = 0;

View File

@ -15,9 +15,9 @@
#include "app/pref/preferences.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h"
#include "app/util/shader_helpers.h"
#include "base/clamp.h"
#include "base/pi.h"
#include "filters/color_curve.h"
#include "os/surface.h"
#include "ui/graphics.h"
#include "ui/menu.h"
@ -67,6 +67,117 @@ ColorWheel::ColorWheel()
initTheme();
}
const char* ColorWheel::getMainAreaShader()
{
#if SK_ENABLE_SKSL
// TODO create one shader for each wheel mode (RGB, RYB, normal)
if (m_mainShader.empty()) {
m_mainShader += "uniform half3 iRes;"
"uniform half4 iColor;"
"uniform half4 iBack;"
"uniform int iDiscrete;"
"uniform int iMode;";
m_mainShader += kRGB_to_HSV_sksl;
m_mainShader += kHSV_to_RGB_sksl;
m_mainShader += R"(
const half PI = 3.1415;
half rybhue_to_rgbhue(half h) {
if (h >= 0 && h < 120) return h / 2; // from red to yellow
else if (h < 180) return (h-60.0); // from yellow to green
else if (h < 240) return 120 + 2*(h-180); // from green to blue
else return h; // from blue to red (same hue)
}
half4 main(vec2 fragcoord) {
vec2 res = vec2(min(iRes.x, iRes.y), min(iRes.x, iRes.y));
vec2 d = (fragcoord.xy-iRes.xy/2) / res.xy;
half r = length(d);
if (r <= 0.5) {
half a = atan(-d.y, d.x);
half hue = (floor(180.0 * a / PI)
+ 180 // To avoid [-180,0) range
+ 180 + 30 // To locate green at 12 o'clock
);
hue = mod(hue, 360); // To leave hue in [0,360) range
if (iDiscrete != 0) {
hue += 15.0;
hue = floor(hue / 30.0);
hue *= 30.0;
}
if (iMode == 1) { // RYB color wheel
hue = rybhue_to_rgbhue(hue);
}
hue /= 360.0;
if (iMode == 2) { // Normal map mode
float di = 0.5 * r / 0.5;
half3 rgb = half3(0.5+di*cos(a), 0.5+di*sin(a), 1.0-di);
return half4(
clamp(rgb.x, 0, 1),
clamp(rgb.y, 0, 1),
clamp(rgb.z, 0.5, 1), 1);
}
half sat = r / 0.5;
if (iDiscrete != 0) {
sat *= 120.0;
sat = floor(sat / 20.0);
sat *= 20.0;
sat /= 100.0;
sat = clamp(sat, 0.0, 1.0);
}
vec3 hsv = rgb_to_hsv(iColor.rgb);
return hsv_to_rgb(vec3(hue, sat, iColor.w > 0 ? hsv.z: 1.0)).rgb1;
}
else {
if (iMode == 2) // Normal map mode
return half4(0.5, 0.5, 1, 1);
return iBack;
}
}
)";
}
return m_mainShader.c_str();
#else
return nullptr;
#endif
}
const char* ColorWheel::getBottomBarShader()
{
#if SK_ENABLE_SKSL
if (m_bottomShader.empty()) {
m_bottomShader += "uniform half3 iRes;"
"uniform half4 iColor;";
m_bottomShader += kRGB_to_HSV_sksl;
m_bottomShader += kHSV_to_RGB_sksl;
// TODO should we display the hue bar with the current sat/value?
m_bottomShader += R"(
half4 main(vec2 fragcoord) {
half v = (fragcoord.x / iRes.x);
half3 hsv = rgb_to_hsv(iColor.rgb);
return hsv_to_rgb(half3(hsv.x, hsv.y, v)).rgb1;
}
)";
}
return m_bottomShader.c_str();
#else
return nullptr;
#endif
}
#if SK_ENABLE_SKSL
void ColorWheel::setShaderMainAreaParams(SkRuntimeShaderBuilder& builder)
{
builder.uniform("iBack") = gfxColor_to_SkV4(m_bgColor);
builder.uniform("iDiscrete") = (m_discrete ? 1: 0);
builder.uniform("iMode") = int(m_colorModel);
}
#endif
app::Color ColorWheel::getMainAreaColor(const int _u, const int umax,
const int _v, const int vmax)
{
@ -89,7 +200,7 @@ app::Color ColorWheel::getMainAreaColor(const int _u, const int umax,
boxsize, boxsize).contains(pos)) {
m_harmonyPicked = true;
color = app::Color::fromHsv(convertHueAngle(int(color.getHsvHue()), 1),
color = app::Color::fromHsv(convertHueAngle(color.getHsvHue(), 1),
color.getHsvSaturation(),
color.getHsvValue(),
m_color.getAlpha());
@ -212,7 +323,7 @@ void ColorWheel::onPaintMainArea(ui::Graphics* g, const gfx::Rect& rc)
double angle = color.getHsvHue()-30.0;
double dist = color.getHsvSaturation();
color = app::Color::fromHsv(convertHueAngle(int(color.getHsvHue()), 1),
color = app::Color::fromHsv(convertHueAngle(color.getHsvHue(), 1),
color.getHsvSaturation(),
color.getHsvValue());
@ -347,7 +458,7 @@ app::Color ColorWheel::getColorInHarmony(int j) const
{
int i = base::clamp((int)m_harmony, 0, (int)Harmony::LAST);
j = base::clamp(j, 0, harmonies[i].n-1);
double hue = convertHueAngle(int(m_color.getHsvHue()), -1) + harmonies[i].hues[j];
double hue = convertHueAngle(m_color.getHsvHue(), -1) + harmonies[i].hues[j];
double sat = m_color.getHsvSaturation() * harmonies[i].sats[j] / 100.0;
return app::Color::fromHsv(std::fmod(hue, 360),
base::clamp(sat, 0.0, 1.0),
@ -421,51 +532,33 @@ void ColorWheel::onOptions()
menu.showPopup(gfx::Point(rc.x+rc.w, rc.y));
}
int ColorWheel::convertHueAngle(int hue, int dir) const
float ColorWheel::convertHueAngle(float h, int dir) const
{
switch (m_colorModel) {
case ColorModel::RGB:
return hue;
case ColorModel::RYB: {
static std::vector<int> map1;
static std::vector<int> map2;
if (map2.empty()) {
filters::ColorCurve curve1(filters::ColorCurve::Linear);
curve1.addPoint(gfx::Point(0, 0));
curve1.addPoint(gfx::Point(60, 35));
curve1.addPoint(gfx::Point(122, 60));
curve1.addPoint(gfx::Point(165, 120));
curve1.addPoint(gfx::Point(218, 180));
curve1.addPoint(gfx::Point(275, 240));
curve1.addPoint(gfx::Point(330, 300));
curve1.addPoint(gfx::Point(360, 360));
filters::ColorCurve curve2(filters::ColorCurve::Linear);
for (const auto& pt : curve1)
curve2.addPoint(gfx::Point(pt.y, pt.x));
map1.resize(360);
map2.resize(360);
curve1.getValues(0, 359, map1);
curve2.getValues(0, 359, map2);
}
if (hue < 0)
hue += 360;
hue %= 360;
ASSERT(hue >= 0 && hue < 360);
if (dir > 0)
return map1[hue];
else if (dir < 0)
return map2[hue];
if (m_colorModel == ColorModel::RYB) {
if (dir == 1) {
// rybhue_to_rgbhue() maps:
// [0,120) -> [0,60)
// [120,180) -> [60,120)
// [180,240) -> [120,240)
// [240,360] -> [240,360]
if (h >= 0 && h < 120) return h / 2; // from red to yellow
else if (h < 180) return (h-60); // from yellow to green
else if (h < 240) return 120 + 2*(h-180); // from green to blue
else return h; // from blue to red (same hue)
}
else {
// rgbhue_to_rybhue()
// [0,60) -> [0,120)
// [60,120) -> [120,180)
// [120,240) -> [180,240)
// [240,360] -> [240,360]
if (h >= 0 && h < 60) return 2 * h; // from red to yellow
else if (h < 120) return 60 + h; // from yellow to green
else if (h < 240) return 180 + (h-120)/2; // from green to blue
else return h; // from blue to red (same hue)
}
}
return hue;
return h;
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2021-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -43,9 +43,14 @@ namespace app {
void setHarmony(Harmony harmony);
protected:
const char* getMainAreaShader() override;
const char* getBottomBarShader() override;
app::Color getMainAreaColor(const int u, const int umax,
const int v, const int vmax) override;
app::Color getBottomBarColor(const int u, const int umax) override;
#if SK_ENABLE_SKSL
void setShaderMainAreaParams(SkRuntimeShaderBuilder& builder) override;
#endif
void onPaintMainArea(ui::Graphics* g, const gfx::Rect& rc) override;
void onPaintBottomBar(ui::Graphics* g, const gfx::Rect& rc) override;
void onPaintSurfaceInBgThread(os::Surface* s,
@ -65,8 +70,10 @@ namespace app {
// Converts an hue angle from HSV <-> current color model hue.
// With dir == +1, the angle is from the color model and it's converted to HSV hue.
// With dir == -1, the angle came from HSV and is converted to the current color model.
int convertHueAngle(int angle, int dir) const;
float convertHueAngle(float angle, int dir) const;
std::string m_mainShader;
std::string m_bottomShader;
gfx::Rect m_wheelBounds;
gfx::Color m_bgColor;
double m_wheelRadius;