Add wxImage Change{Saturation,Brightness,HSV,Lightness}()

Add more functions for manipulating wxImage colours.

Show using them in the same and add new unit tests for them.

This is a squashed commit of branch 'tomay3000/image-lightness'.

Closes https://github.com/wxWidgets/wxWidgets/pull/2310
This commit is contained in:
Tomay
2021-05-01 14:03:04 +02:00
committed by Vadim Zeitlin
parent 3217a4e8a2
commit 0e39566b3a
20 changed files with 626 additions and 133 deletions

View File

@@ -1827,119 +1827,89 @@ void wxImage::Replace( unsigned char r1, unsigned char g1, unsigned char b1,
}
}
wxImage wxImage::ConvertToGreyscale(void) const
wxImage wxImage::ConvertToGreyscale() const
{
return ConvertToGreyscale(0.299, 0.587, 0.114);
}
// red, green and blue are doubles in the range [0.0..1.0], they are used
// internally by DoMakeGrey() only.
class WeightValue
{
public:
WeightValue(double r = 0.0, double g = 0.0, double b = 0.0)
: red(r), green(g), blue(b) {}
double red;
double green;
double blue;
};
// TODO-C++11: Replace with a lambda function.
void DoMakeGrey(wxImage *image, unsigned char *rgb, WeightValue weight)
{
if ( !image->HasMask() || rgb[0] != image->GetMaskRed() ||
rgb[1] != image->GetMaskGreen() || rgb[2] != image->GetMaskBlue() )
wxColour::MakeGrey(rgb, rgb + 1, rgb + 2, weight.red, weight.green, weight.blue);
}
wxImage wxImage::ConvertToGreyscale(double weight_r, double weight_g, double weight_b) const
{
wxImage image;
wxCHECK_MSG(IsOk(), image, "invalid image");
const int w = M_IMGDATA->m_width;
const int h = M_IMGDATA->m_height;
size_t size = size_t(w) * h;
image.Create(w, h, false);
const unsigned char* alpha = M_IMGDATA->m_alpha;
if (alpha)
{
image.SetAlpha();
memcpy(image.GetAlpha(), alpha, size);
}
const unsigned char mask_r = M_IMGDATA->m_maskRed;
const unsigned char mask_g = M_IMGDATA->m_maskGreen;
const unsigned char mask_b = M_IMGDATA->m_maskBlue;
const bool hasMask = M_IMGDATA->m_hasMask;
if (hasMask)
image.SetMaskColour(mask_r, mask_g, mask_b);
const unsigned char* src = M_IMGDATA->m_data;
unsigned char* dst = image.GetData();
while (size--)
{
unsigned char r = *src++;
unsigned char g = *src++;
unsigned char b = *src++;
if (!hasMask || r != mask_r || g != mask_g || b != mask_b)
wxColour::MakeGrey(&r, &g, &b, weight_r, weight_g, weight_b);
*dst++ = r;
*dst++ = g;
*dst++ = b;
}
wxImage image = *this;
image.ApplyToAllPixels(&DoMakeGrey, WeightValue(weight_r, weight_g, weight_b));
return image;
}
wxImage wxImage::ConvertToMono( unsigned char r, unsigned char g, unsigned char b ) const
// TODO-C++11: Replace with a lambda function.
void DoMakeMono(wxImage *WXUNUSED(image), unsigned char *rgb, wxImage::RGBValue rgbValue)
{
wxImage image;
const bool on = (rgb[0] == rgbValue.red) && (rgb[1] == rgbValue.green) && (rgb[2] == rgbValue.blue);
wxColour::MakeMono(rgb, rgb + 1, rgb + 2, on);
}
wxCHECK_MSG( IsOk(), image, wxT("invalid image") );
wxImage wxImage::ConvertToMono(unsigned char r, unsigned char g, unsigned char b) const
{
wxImage image = *this;
image.Create( M_IMGDATA->m_width, M_IMGDATA->m_height, false );
unsigned char *data = image.GetData();
wxCHECK_MSG( data, image, wxT("unable to create image") );
if (M_IMGDATA->m_hasMask)
if ( image.HasMask() )
{
if (M_IMGDATA->m_maskRed == r && M_IMGDATA->m_maskGreen == g &&
M_IMGDATA->m_maskBlue == b)
image.SetMaskColour( 255, 255, 255 );
if ( image.GetMaskRed() == r && image.GetMaskGreen() == g && image.GetMaskBlue() == b)
image.SetMaskColour(255, 255, 255);
else
image.SetMaskColour( 0, 0, 0 );
}
long size = M_IMGDATA->m_height * M_IMGDATA->m_width;
unsigned char *srcd = M_IMGDATA->m_data;
unsigned char *tard = image.GetData();
for ( long i = 0; i < size; i++, srcd += 3, tard += 3 )
{
bool on = (srcd[0] == r) && (srcd[1] == g) && (srcd[2] == b);
wxColourBase::MakeMono(tard + 0, tard + 1, tard + 2, on);
image.SetMaskColour(0, 0, 0);
}
image.ApplyToAllPixels(&DoMakeMono, RGBValue(r, g, b));
return image;
}
// TODO-C++11: Replace with a lambda function.
void DoMakeDisabled(wxImage *image, unsigned char *rgb, unsigned char brightness)
{
if ( !image->HasMask() || rgb[0] != image->GetMaskRed() ||
rgb[1] != image->GetMaskGreen() || rgb[2] != image->GetMaskBlue() )
wxColour::MakeDisabled(rgb, rgb + 1, rgb + 2, brightness);
}
wxImage wxImage::ConvertToDisabled(unsigned char brightness) const
{
wxImage image;
wxCHECK_MSG(IsOk(), image, "invalid image");
wxImage image = *this;
image.ApplyToAllPixels(&DoMakeDisabled, brightness);
return image;
}
const int w = M_IMGDATA->m_width;
const int h = M_IMGDATA->m_height;
size_t size = size_t(w) * h;
image.Create(w, h, false);
const unsigned char* alpha = M_IMGDATA->m_alpha;
if (alpha)
{
image.SetAlpha();
memcpy(image.GetAlpha(), alpha, size);
}
const unsigned char mask_r = M_IMGDATA->m_maskRed;
const unsigned char mask_g = M_IMGDATA->m_maskGreen;
const unsigned char mask_b = M_IMGDATA->m_maskBlue;
const bool hasMask = M_IMGDATA->m_hasMask;
if (hasMask)
image.SetMaskColour(mask_r, mask_g, mask_b);
// TODO-C++11: Replace with a lambda function.
void DoChangeLightness(wxImage *image, unsigned char *rgb, int alpha)
{
if ( !image->HasMask() || rgb[0] != image->GetMaskRed() ||
rgb[1] != image->GetMaskGreen() || rgb[2] != image->GetMaskBlue() )
wxColour::ChangeLightness(rgb, rgb + 1, rgb + 2, alpha);
}
const unsigned char* src = M_IMGDATA->m_data;
unsigned char* dst = image.GetData();
while (size--)
{
unsigned char r = *src++;
unsigned char g = *src++;
unsigned char b = *src++;
if (!hasMask || r != mask_r || g != mask_g || b != mask_b)
wxColour::MakeDisabled(&r, &g, &b, brightness);
*dst++ = r;
*dst++ = g;
*dst++ = b;
}
wxImage wxImage::ChangeLightness(int alpha) const
{
wxASSERT(alpha >= 0 && alpha <= 200);
wxImage image = *this;
image.ApplyToAllPixels(&DoChangeLightness, alpha);
return image;
}
@@ -3323,45 +3293,126 @@ wxImage::RGBValue wxImage::HSVtoRGB(const HSVValue& hsv)
(unsigned char)wxRound(blue * 255.0));
}
/*
* Rotates the hue of each pixel of the image. angle is a double in the range
* -1.0..1.0 where -1.0 is -360 degrees and 1.0 is 360 degrees
*/
// TODO-C++11: Replace with a lambda function.
void DoRotateHue(wxImage *WXUNUSED(image), unsigned char *rgb, double angle)
{
wxImage::RGBValue rgbValue(rgb[0], rgb[1], rgb[2]);
wxImage::HSVValue hsvValue = wxImage::RGBtoHSV(rgbValue);
hsvValue.hue = hsvValue.hue + angle;
if (hsvValue.hue > 1.0)
hsvValue.hue = hsvValue.hue - 1.0;
else if (hsvValue.hue < 0.0)
hsvValue.hue = hsvValue.hue + 1.0;
rgbValue = wxImage::HSVtoRGB(hsvValue);
rgb[0] = rgbValue.red;
rgb[1] = rgbValue.green;
rgb[2] = rgbValue.blue;
}
// Rotates the hue of each pixel in the image by angle, which is a double in the
// range [-1.0..+1.0], where -1.0 corresponds to -360 degrees and +1.0 corresponds
// to +360 degrees.
void wxImage::RotateHue(double angle)
{
AllocExclusive();
if ( wxIsNullDouble(angle) )
return;
unsigned long count;
wxImage::HSVValue hsv;
wxImage::RGBValue rgb;
wxASSERT(angle >= -1.0 && angle <= 1.0);
ApplyToAllPixels(&DoRotateHue, angle);
}
wxASSERT (angle >= -1.0 && angle <= 1.0);
count = M_IMGDATA->m_width * M_IMGDATA->m_height;
if ( count > 0 && !wxIsNullDouble(angle) )
{
unsigned char* srcBytePtr;
unsigned char* dstBytePtr;
srcBytePtr = M_IMGDATA->m_data;
dstBytePtr = srcBytePtr;
do
{
rgb.red = *srcBytePtr++;
rgb.green = *srcBytePtr++;
rgb.blue = *srcBytePtr++;
hsv = RGBtoHSV(rgb);
// TODO-C++11: Replace with a lambda function.
void DoChangeSaturation(wxImage *WXUNUSED(image), unsigned char *rgb, double factor)
{
wxImage::RGBValue rgbValue(rgb[0], rgb[1], rgb[2]);
wxImage::HSVValue hsvValue = wxImage::RGBtoHSV(rgbValue);
hsv.hue = hsv.hue + angle;
if (hsv.hue > 1.0)
hsv.hue = hsv.hue - 1.0;
else if (hsv.hue < 0.0)
hsv.hue = hsv.hue + 1.0;
hsvValue.saturation += hsvValue.saturation * factor;
rgb = HSVtoRGB(hsv);
*dstBytePtr++ = rgb.red;
*dstBytePtr++ = rgb.green;
*dstBytePtr++ = rgb.blue;
} while (--count != 0);
}
if (hsvValue.saturation > 1.0)
hsvValue.saturation = 1.0;
else if (hsvValue.saturation < 0.0)
hsvValue.saturation = 0.0;
rgbValue = wxImage::HSVtoRGB(hsvValue);
rgb[0] = rgbValue.red;
rgb[1] = rgbValue.green;
rgb[2] = rgbValue.blue;
}
// Changes the saturation of each pixel in the image. factor is a double in the
// range [-1.0..+1.0], where -1.0 corresponds to -100 percent and +1.0 corresponds
// to +100 percent.
void wxImage::ChangeSaturation(double factor)
{
if ( wxIsNullDouble(factor) )
return;
wxASSERT(factor >= -1.0 && factor <= 1.0);
ApplyToAllPixels(&DoChangeSaturation, factor);
}
// TODO-C++11: Replace with a lambda function.
void DoChangeBrightness(wxImage *WXUNUSED(image), unsigned char *rgb, double factor)
{
wxImage::RGBValue rgbValue(rgb[0], rgb[1], rgb[2]);
wxImage::HSVValue hsvValue = wxImage::RGBtoHSV(rgbValue);
hsvValue.value += hsvValue.value * factor;
if (hsvValue.value > 1.0)
hsvValue.value = 1.0;
else if (hsvValue.value < 0.0)
hsvValue.value = 0.0;
rgbValue = wxImage::HSVtoRGB(hsvValue);
rgb[0] = rgbValue.red;
rgb[1] = rgbValue.green;
rgb[2] = rgbValue.blue;
}
// Changes the brightness (value) of each pixel in the image. factor is a double
// in the range [-1.0..+1.0], where -1.0 corresponds to -100 percent and +1.0
// corresponds to +100 percent.
void wxImage::ChangeBrightness(double factor)
{
if ( wxIsNullDouble(factor) )
return;
wxASSERT(factor >= -1.0 && factor <= 1.0);
ApplyToAllPixels(&DoChangeBrightness, factor);
}
// TODO-C++11: Replace with a lambda function.
void DoChangeHSV(wxImage *image, unsigned char *rgb, wxImage::HSVValue hsvValue)
{
if ( !wxIsNullDouble(hsvValue.hue) )
DoRotateHue(image, rgb, hsvValue.hue);
if ( !wxIsNullDouble(hsvValue.saturation) )
DoChangeSaturation(image, rgb, hsvValue.saturation);
if ( !wxIsNullDouble(hsvValue.value) )
DoChangeBrightness(image, rgb, hsvValue.value);
}
// Changes the hue, the saturation and the brightness (value) of each pixel in
// the image. angleH is a double in the range [-1.0..+1.0], where -1.0 corresponds
// to -360 degrees and +1.0 corresponds to +360 degrees, factorS is a double in
// the range [-1.0..+1.0], where -1.0 corresponds to -100 percent and +1.0
// corresponds to +100 percent and factorV is a double in the range [-1.0..+1.0],
// where -1.0 corresponds to -100 percent and +1.0 corresponds to +100 percent.
void wxImage::ChangeHSV(double angleH, double factorS, double factorV)
{
if ( wxIsNullDouble(angleH) && wxIsNullDouble(factorS) && wxIsNullDouble(factorV) )
return;
wxASSERT(angleH >= -1.0 && angleH <= 1.0 && factorS >= -1.0 &&
factorS <= 1.0 && factorV >= -1.0 && factorV <= 1.0);
ApplyToAllPixels(&DoChangeHSV, HSVValue(angleH, factorS, factorV));
}
//-----------------------------------------------------------------------------
@@ -3857,9 +3908,20 @@ wxImage wxImage::Rotate(double angle,
return rotated;
}
// Helper function used internally by wxImage class only.
template <typename T>
void wxImage::ApplyToAllPixels(void (*filter)(wxImage *, unsigned char *, T), T value)
{
AllocExclusive();
const size_t size = GetWidth() * GetHeight();
unsigned char *data = GetData();
for ( size_t i = 0; i < size; i++, data += 3 )
{
(*filter)(this, data, value);
}
}
// A module to allow wxImage initialization/cleanup
// without calling these functions from app.cpp or from