Add more precision to HSV <-> RGB conversion (fix #961)

This patch fixes problems using the eyedropper tool in HSB mode. If we
use "int" precision for HSB values, the resulting RGB color could be
different from the original one.
This commit is contained in:
David Capello 2016-02-12 13:01:32 -03:00
parent 19f8aad2ef
commit 3f47c23cd8
5 changed files with 92 additions and 88 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -46,7 +46,7 @@ Color Color::fromRgb(int r, int g, int b, int a)
}
// static
Color Color::fromHsv(int h, int s, int v, int a)
Color Color::fromHsv(double h, double s, double v, int a)
{
Color color(Color::HsvType);
color.m_value.hsv.h = h;
@ -124,24 +124,25 @@ Color Color::fromString(const std::string& str)
if (str.find("rgb{") == 0 ||
str.find("hsv{") == 0 ||
str.find("gray{") == 0) {
int c = 0, table[4] = { 0, 0, 0, 255 };
int c = 0;
double table[4] = { 0.0, 0.0, 0.0, 255.0 };
std::string::size_type i = str.find_first_of('{')+1, j;
while ((j = str.find_first_of(",}", i)) != std::string::npos) {
std::string element = str.substr(i, j - i);
if (c < 4)
table[c++] = std::strtol(element.c_str(), NULL, 10);
table[c++] = std::strtod(element.c_str(), NULL);
if (c >= 4)
break;
i = j+1;
}
if (str[0] == 'r')
color = Color::fromRgb(table[0], table[1], table[2], table[3]);
color = Color::fromRgb(table[0], table[1], table[2], int(table[3]));
else if (str[0] == 'h')
color = Color::fromHsv(table[0], table[1], table[2], table[3]);
color = Color::fromHsv(table[0], table[1], table[2], int(table[3]));
else if (str[0] == 'g')
color = Color::fromGray(table[0], c >= 2 ? table[1]: 255);
color = Color::fromGray(table[0], (c >= 2 ? int(table[1]): 255));
}
else if (str.find("index{") == 0) {
color = Color::fromIndex(std::strtol(str.c_str()+6, NULL, 10));
@ -171,6 +172,8 @@ std::string Color::toString() const
case Color::HsvType:
result << "hsv{"
<< std::setprecision(2)
<< std::fixed
<< m_value.hsv.h << ","
<< m_value.hsv.s << ","
<< m_value.hsv.v << ","
@ -225,9 +228,9 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
}
else {
result << "HSB "
<< m_value.hsv.h << "\xc2\xb0 "
<< m_value.hsv.s << " "
<< m_value.hsv.v;
<< int(m_value.hsv.h) << "\xc2\xb0 "
<< int(m_value.hsv.s) << "% "
<< int(m_value.hsv.v) << "%";
if (pixelFormat == IMAGE_INDEXED)
result << " Index " << color_utils::color_for_image(*this, pixelFormat);
@ -290,9 +293,9 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
result << "Gry-" << getGray();
}
else {
result << m_value.hsv.h << "\xc2\xb0"
<< m_value.hsv.s << ","
<< m_value.hsv.v;
result << int(m_value.hsv.h) << "\xc2\xb0"
<< int(m_value.hsv.s) << ","
<< int(m_value.hsv.v);
}
break;
@ -332,10 +335,10 @@ bool Color::operator==(const Color& other) const
case Color::HsvType:
return
m_value.hsv.h == other.m_value.hsv.h &&
m_value.hsv.s == other.m_value.hsv.s &&
m_value.hsv.v == other.m_value.hsv.v &&
m_value.hsv.a == other.m_value.hsv.a;
(std::fabs(m_value.hsv.h - other.m_value.hsv.h) < 0.001) &&
(std::fabs(m_value.hsv.s - other.m_value.hsv.s) < 0.001) &&
(std::fabs(m_value.hsv.v - other.m_value.hsv.v) < 0.001) &&
(m_value.hsv.a == other.m_value.hsv.a);
case Color::GrayType:
return
@ -379,8 +382,8 @@ int Color::getRed() const
case Color::HsvType:
return Rgb(Hsv(m_value.hsv.h,
double(m_value.hsv.s) / 100.0,
double(m_value.hsv.v) / 100.0)).red();
m_value.hsv.s / 100.0,
m_value.hsv.v / 100.0)).red();
case Color::GrayType:
return m_value.gray.g;
@ -411,8 +414,8 @@ int Color::getGreen() const
case Color::HsvType:
return Rgb(Hsv(m_value.hsv.h,
double(m_value.hsv.s) / 100.0,
double(m_value.hsv.v) / 100.0)).green();
m_value.hsv.s / 100.0,
m_value.hsv.v / 100.0)).green();
case Color::GrayType:
return m_value.gray.g;
@ -443,8 +446,8 @@ int Color::getBlue() const
case Color::HsvType:
return Rgb(Hsv(m_value.hsv.h,
double(m_value.hsv.s) / 100.0,
double(m_value.hsv.v) / 100.0)).blue();
m_value.hsv.s / 100.0,
m_value.hsv.v / 100.0)).blue();
case Color::GrayType:
return m_value.gray.g;
@ -463,23 +466,23 @@ int Color::getBlue() const
return -1;
}
int Color::getHue() const
double Color::getHue() const
{
switch (getType()) {
case Color::MaskType:
return 0;
return 0.0;
case Color::RgbType:
return Hsv(Rgb(m_value.rgb.r,
m_value.rgb.g,
m_value.rgb.b)).hueInt();
m_value.rgb.b)).hue();
case Color::HsvType:
return m_value.hsv.h;
case Color::GrayType:
return 0;
return 0.0;
case Color::IndexType: {
int i = m_value.index;
@ -487,19 +490,19 @@ int Color::getHue() const
uint32_t c = get_current_palette()->getEntry(i);
return Hsv(Rgb(rgba_getr(c),
rgba_getg(c),
rgba_getb(c))).hueInt();
rgba_getb(c))).hue();
}
else
return 0;
return 0.0;
}
}
ASSERT(false);
return -1;
return -1.0;
}
int Color::getSaturation() const
double Color::getSaturation() const
{
switch (getType()) {
@ -509,7 +512,7 @@ int Color::getSaturation() const
case Color::RgbType:
return Hsv(Rgb(m_value.rgb.r,
m_value.rgb.g,
m_value.rgb.b)).saturationInt();
m_value.rgb.b)).saturation() * 100.0;
case Color::HsvType:
return m_value.hsv.s;
@ -523,35 +526,35 @@ int Color::getSaturation() const
uint32_t c = get_current_palette()->getEntry(i);
return Hsv(Rgb(rgba_getr(c),
rgba_getg(c),
rgba_getb(c))).saturationInt();
rgba_getb(c))).saturation() * 100.0;
}
else
return 0;
return 0.0;
}
}
ASSERT(false);
return -1;
return -1.0;
}
int Color::getValue() const
double Color::getValue() const
{
switch (getType()) {
case Color::MaskType:
return 0;
return 0.0;
case Color::RgbType:
return Hsv(Rgb(m_value.rgb.r,
m_value.rgb.g,
m_value.rgb.b)).valueInt();
m_value.rgb.b)).value() * 100.0;
case Color::HsvType:
return m_value.hsv.v;
case Color::GrayType:
return 100 * m_value.gray.g / 255;
return 100.0 * m_value.gray.g / 255.0;
case Color::IndexType: {
int i = m_value.index;
@ -559,16 +562,16 @@ int Color::getValue() const
uint32_t c = get_current_palette()->getEntry(i);
return Hsv(Rgb(rgba_getr(c),
rgba_getg(c),
rgba_getb(c))).valueInt();
rgba_getb(c))).value() * 100.0;
}
else
return 0;
return 0.0;
}
}
ASSERT(false);
return -1;
return -1.0;
}
int Color::getGray() const
@ -579,12 +582,12 @@ int Color::getGray() const
return 0;
case Color::RgbType:
return 255 * Hsv(Rgb(m_value.rgb.r,
m_value.rgb.g,
m_value.rgb.b)).valueInt() / 100;
return int(255.0 * Hsv(Rgb(m_value.rgb.r,
m_value.rgb.g,
m_value.rgb.b)).value() / 100.0);
case Color::HsvType:
return 255 * m_value.hsv.v / 100;
return int(255.0 * m_value.hsv.v / 100.0);
case Color::GrayType:
return m_value.gray.g;
@ -593,9 +596,9 @@ int Color::getGray() const
int i = m_value.index;
if (i >= 0 && i < get_current_palette()->size()) {
uint32_t c = get_current_palette()->getEntry(i);
return 255 * Hsv(Rgb(rgba_getr(c),
rgba_getg(c),
rgba_getb(c))).valueInt() / 100;
return int(255.0 * Hsv(Rgb(rgba_getr(c),
rgba_getg(c),
rgba_getb(c))).value() / 100.0);
}
else
return 0;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -43,7 +43,7 @@ namespace app {
static Color fromMask();
static Color fromRgb(int r, int g, int b, int a = 255);
static Color fromHsv(int h, int s, int v, int a = 255); // h=[0,360], s=[0,100], v=[0,100]
static Color fromHsv(double h, double s, double v, int a = 255); // h=[0,360], s=[0,100], v=[0,100]
static Color fromGray(int g, int a = 255);
static Color fromIndex(int index);
@ -69,9 +69,9 @@ namespace app {
int getRed() const;
int getGreen() const;
int getBlue() const;
int getHue() const;
int getSaturation() const;
int getValue() const;
double getHue() const;
double getSaturation() const;
double getValue() const;
int getGray() const;
int getIndex() const;
int getAlpha() const;
@ -88,7 +88,8 @@ namespace app {
int r, g, b, a;
} rgb;
struct {
int h, s, v, a;
double h, s, v;
int a;
} hsv;
struct {
int g, a;

View File

@ -283,9 +283,9 @@ HsvSliders::HsvSliders()
void HsvSliders::onSetColor(const app::Color& color)
{
setAbsSliderValue(0, color.getHue());
setAbsSliderValue(1, color.getSaturation());
setAbsSliderValue(2, color.getValue());
setAbsSliderValue(0, int(color.getHue()));
setAbsSliderValue(1, int(color.getSaturation()));
setAbsSliderValue(2, int(color.getValue()));
setAbsSliderValue(3, color.getAlpha());
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -60,14 +60,14 @@ app::Color ColorSpectrum::pickColor(const gfx::Point& pos) const
umax = MAX(1, rc.h-1);
}
int hue = 360 * u / umax;
int sat = (v < vmid ? 100 * v / vmid : 100);
int val = (v < vmid ? 100 : 100-(100 * (v-vmid) / vmid));
double hue = 360.0 * u / umax;
double sat = (v < vmid ? 100.0 * v / vmid : 100.0);
double val = (v < vmid ? 100.0 : 100.0-(100.0 * (v-vmid) / vmid));
return app::Color::fromHsv(
MID(0, hue, 360),
MID(0, sat, 100),
MID(0, val, 100));
MID(0.0, hue, 360.0),
MID(0.0, sat, 100.0),
MID(0.0, val, 100.0));
}
void ColorSpectrum::selectColor(const app::Color& color)
@ -116,32 +116,32 @@ void ColorSpectrum::onPaint(ui::PaintEvent& ev)
umax = MAX(1, rc.h-1);
}
int hue = 360 * u / umax;
int sat = (v < vmid ? 100 * v / vmid : 100);
int val = (v < vmid ? 100 : 100-(100 * (v-vmid) / vmid));
double hue = 360.0 * u / umax;
double sat = (v < vmid ? 100.0 * v / vmid : 100.0);
double val = (v < vmid ? 100.0 : 100.0-(100.0 * (v-vmid) / vmid));
gfx::Color color = color_utils::color_for_ui(
app::Color::fromHsv(
MID(0, hue, 360),
MID(0, sat, 100),
MID(0, val, 100)));
MID(0.0, hue, 360.0),
MID(0.0, sat, 100.0),
MID(0.0, val, 100.0)));
g->putPixel(color, rc.x+x, rc.y+y);
}
}
if (m_color.getType() != app::Color::MaskType) {
int hue = m_color.getHue();
int sat = m_color.getSaturation();
int val = m_color.getValue();
int lit = (200 - sat) * val / 200;
gfx::Point pos(rc.x + hue * rc.w / 360,
rc.y + rc.h - (lit * rc.h / 100));
double hue = m_color.getHue();
double sat = m_color.getSaturation();
double val = m_color.getValue();
double lit = (200.0 - sat) * val / 200.0;
gfx::Point pos(rc.x + int(hue * rc.w / 360.0),
rc.y + rc.h - int(lit * rc.h / 100.0));
she::Surface* icon = theme->parts.colorWheelIndicator()->bitmap(0);
g->drawColoredRgbaSurface(
icon,
lit > 50 ? gfx::rgba(0, 0, 0): gfx::rgba(255, 255, 255),
lit > 50.0 ? gfx::rgba(0, 0, 0): gfx::rgba(255, 255, 255),
pos.x-icon->width()/2,
pos.y-icon->height()/2);
}

View File

@ -132,7 +132,7 @@ app::Color ColorWheel::pickColor(const gfx::Point& pos) const
boxsize, boxsize).contains(pos)) {
m_harmonyPicked = true;
color = app::Color::fromHsv(convertHueAngle(color.getHue(), 1),
color = app::Color::fromHsv(convertHueAngle(int(color.getHue()), 1),
color.getSaturation(),
color.getValue());
return color;
@ -186,10 +186,10 @@ app::Color ColorWheel::getColorInHarmony(int j) const
{
int i = MID(0, (int)m_harmony, (int)Harmony::LAST);
j = MID(0, j, harmonies[i].n-1);
int hue = convertHueAngle(m_mainColor.getHue(), -1) + harmonies[i].hues[j];
int sat = m_mainColor.getSaturation() * harmonies[i].sats[j] / 100;
return app::Color::fromHsv(hue % 360,
MID(0, sat, 100),
double hue = convertHueAngle(int(m_mainColor.getHue()), -1) + harmonies[i].hues[j];
double sat = m_mainColor.getSaturation() * harmonies[i].sats[j] / 100.0;
return app::Color::fromHsv(std::fmod(hue, 360),
MID(0.0, sat, 100.0),
m_mainColor.getValue());
}
@ -253,17 +253,17 @@ void ColorWheel::onPaint(ui::PaintEvent& ev)
for (int i=0; i<n; ++i) {
app::Color color = getColorInHarmony(i);
int angle = color.getHue()-30;
int dist = color.getSaturation();
double angle = color.getHue()-30.0;
double dist = color.getSaturation();
color = app::Color::fromHsv(convertHueAngle(color.getHue(), 1),
color = app::Color::fromHsv(convertHueAngle(int(color.getHue()), 1),
color.getSaturation(),
color.getValue());
gfx::Point pos =
m_wheelBounds.center() +
gfx::Point(int(+std::cos(PI*angle/180)*double(m_wheelRadius)*dist/100.0),
int(-std::sin(PI*angle/180)*double(m_wheelRadius)*dist/100.0));
gfx::Point(int(+std::cos(PI*angle/180.0)*double(m_wheelRadius)*dist/100.0),
int(-std::sin(PI*angle/180.0)*double(m_wheelRadius)*dist/100.0));
she::Surface* icon = theme->parts.colorWheelIndicator()->bitmap(0);
g->drawRgbaSurface(icon,