Engauge Digitizer 2
ColorFilter.cpp
1/******************************************************************************************************
2 * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3 * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4 * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5 ******************************************************************************************************/
6
7#include "ColorConstants.h"
8#include "ColorFilter.h"
9#include "EngaugeAssert.h"
10#include "mmsubs.h"
11#include <QDebug>
12#include <qmath.h>
13#include <QImage>
14
16{
17}
18
20 QRgb rgb2) const
21{
22 const long MASK = 0xf0f0f0f0;
23 return (rgb1 & MASK) == (rgb2 & MASK);
24}
25
26void ColorFilter::filterImage (const QImage &imageOriginal,
27 QImage &imageFiltered,
28 ColorFilterMode colorFilterMode,
29 double low,
30 double high,
31 QRgb rgbBackground)
32{
33 ENGAUGE_ASSERT (imageOriginal.width () == imageFiltered.width());
34 ENGAUGE_ASSERT (imageOriginal.height() == imageFiltered.height());
35 ENGAUGE_ASSERT (imageFiltered.format () == QImage::Format_RGB32);
36
37 for (int x = 0; x < imageOriginal.width(); x++) {
38 for (int y = 0; y < imageOriginal.height (); y++) {
39
40 QColor pixel = imageOriginal.pixel (x, y);
41 bool isOn = false;
42 if (pixel.rgb() != rgbBackground) {
43
44 isOn = pixelUnfilteredIsOn (colorFilterMode,
45 pixel,
46 rgbBackground,
47 low,
48 high);
49 }
50
51 imageFiltered.setPixel (x, y, (isOn ?
52 QColor (Qt::black).rgb () :
53 QColor (Qt::white).rgb ()));
54 }
55 }
56}
57
58QRgb ColorFilter::marginColor(const QImage *image) const
59{
60 // Add unique colors to colors list
61 ColorList colorCounts;
62 for (int x = 0; x < image->width (); x++) {
63 mergePixelIntoColorCounts (image->pixel (x, 0), colorCounts);
64 mergePixelIntoColorCounts (image->pixel (x, image->height () - 1), colorCounts);
65 }
66 for (int y = 0; y < image->height (); y++) {
67 mergePixelIntoColorCounts (image->pixel (0, y), colorCounts);
68 mergePixelIntoColorCounts (image->pixel (image->width () - 1, y), colorCounts);
69 }
70
71 // Margin color is the most frequent color
72 ColorFilterEntry entryMax;
73 entryMax.count = 0;
74 for (ColorList::const_iterator itr = colorCounts.begin (); itr != colorCounts.end (); itr++) {
75 if ((*itr).count > entryMax.count) {
76 entryMax = *itr;
77 }
78 }
79
80 return entryMax.color.rgb();
81}
82
83void ColorFilter::mergePixelIntoColorCounts (QRgb pixel,
84 ColorList &colorCounts) const
85{
86 ColorFilterEntry entry;
87 entry.color = pixel;
88 entry.count = 0;
89
90 // Look for previous entry
91 bool found = false;
92 for (ColorList::iterator itr = colorCounts.begin (); itr != colorCounts.end (); itr++) {
93 if (colorCompare (entry.color.rgb(),
94 (*itr).color.rgb())) {
95 found = true;
96 ++(*itr).count;
97 break;
98 }
99 }
100
101 if (!found) {
102 colorCounts.append (entry);
103 }
104}
105
106bool ColorFilter::pixelFilteredIsOn (const QImage &image,
107 int x,
108 int y) const
109{
110 bool rtn = false;
111
112 if ((0 <= x) &&
113 (0 <= y) &&
114 (x < image.width()) &&
115 (y < image.height())) {
116
117 // Pixel is on if it is closer to black than white in gray scale. This test must be performed
118 // on little endian and big endian systems, with or without alpha bits (which are typically high bits);
119 const int BLACK_WHITE_THRESHOLD = 255 / 2; // Put threshold in middle of range
120 int gray = qGray (pixelRGB (image, x, y));
121 rtn = (gray < BLACK_WHITE_THRESHOLD);
122
123 }
124
125 return rtn;
126}
127
128bool ColorFilter::pixelUnfilteredIsOn (ColorFilterMode colorFilterMode,
129 const QColor &pixel,
130 QRgb rgbBackground,
131 double low0To1,
132 double high0To1) const
133{
134 bool rtn = false;
135
136 double s = pixelToZeroToOneOrMinusOne (colorFilterMode,
137 pixel,
138 rgbBackground);
139 if (s >= 0.0) {
140 if (low0To1 <= high0To1) {
141
142 // Single valid range
143 rtn = (low0To1 <= s) && (s <= high0To1);
144
145 } else {
146
147 // Two ranges
148 rtn = (s <= high0To1) || (low0To1 <= s);
149
150 }
151 }
152
153 return rtn;
154}
155
156double ColorFilter::pixelToZeroToOneOrMinusOne (ColorFilterMode colorFilterMode,
157 const QColor &pixel,
158 QRgb rgbBackground) const
159{
160 double s = 0.0;
161
162 switch (colorFilterMode) {
163 case COLOR_FILTER_MODE_FOREGROUND:
164 {
165 double distance = qSqrt (pow ((double) pixel.red() - qRed (rgbBackground), 2) +
166 pow ((double) pixel.green() - qGreen (rgbBackground), 2) +
167 pow ((double) pixel.blue() - qBlue (rgbBackground), 2));
168 s = distance / qSqrt (255.0 * 255.0 + 255.0 * 255.0 + 255.0 * 255.0);
169 }
170 break;
171
172 case COLOR_FILTER_MODE_HUE:
173 {
174 s = pixel.hueF();
175 if (s < 0) {
176 // Color is achromatic (r=g=b) so it has no hue
177 }
178 }
179 break;
180
181 case COLOR_FILTER_MODE_INTENSITY:
182 {
183 double distance = qSqrt (pow ((double) pixel.red(), 2) +
184 pow ((double) pixel.green(), 2) +
185 pow ((double) pixel.blue(), 2));
186 s = distance / qSqrt (255.0 * 255.0 + 255.0 * 255.0 + 255.0 * 255.0);
187 }
188 break;
189
190 case COLOR_FILTER_MODE_SATURATION:
191 s = pixel.saturationF();
192 break;
193
194 case COLOR_FILTER_MODE_VALUE:
195 s = pixel.valueF();
196 break;
197
198 default:
199 ENGAUGE_ASSERT (false);
200 }
201
202 return s;
203}
204
205int ColorFilter::zeroToOneToValue (ColorFilterMode colorFilterMode,
206 double s) const
207{
208 int value = 0;
209
210 switch (colorFilterMode) {
211 case COLOR_FILTER_MODE_FOREGROUND:
212 {
213 value = FOREGROUND_MIN + s * (FOREGROUND_MAX - FOREGROUND_MIN);
214 }
215 break;
216
217 case COLOR_FILTER_MODE_HUE:
218 {
219 value = HUE_MIN + s * (HUE_MAX - HUE_MIN);
220 }
221 break;
222
223 case COLOR_FILTER_MODE_INTENSITY:
224 {
225 value = INTENSITY_MIN + s * (INTENSITY_MAX - INTENSITY_MIN);
226 }
227 break;
228
229 case COLOR_FILTER_MODE_SATURATION:
230 {
231 value = SATURATION_MIN + s * (SATURATION_MAX - SATURATION_MIN);
232 }
233 break;
234
235 case COLOR_FILTER_MODE_VALUE:
236 {
237 value = VALUE_MIN + s * (VALUE_MAX - VALUE_MIN);
238 }
239 break;
240
241 default:
242 ENGAUGE_ASSERT (false);
243 }
244
245 return value;
246}
QRgb marginColor(const QImage *image) const
Identify the margin color of the image, which is defined as the most common color in the four margins...
Definition: ColorFilter.cpp:58
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered pixel is on.
void filterImage(const QImage &imageOriginal, QImage &imageFiltered, ColorFilterMode colorFilterMode, double low, double high, QRgb rgbBackground)
Filter the original image according to the specified filtering parameters.
Definition: ColorFilter.cpp:26
int zeroToOneToValue(ColorFilterMode colorFilterMode, double s) const
Inverse of pixelToZeroToOneOrMinusOne.
bool pixelUnfilteredIsOn(ColorFilterMode colorFilterMode, const QColor &pixel, QRgb rgbBackground, double low0To1, double high0To1) const
Return true if specified unfiltered pixel is on.
double pixelToZeroToOneOrMinusOne(ColorFilterMode colorFilterMode, const QColor &pixel, QRgb rgbBackground) const
Return pixel converted according to the current filter parameter, normalized to zero to one.
ColorFilter()
Single constructor.
Definition: ColorFilter.cpp:15
bool colorCompare(QRgb rgb1, QRgb rgb2) const
See if the two color values are close enough to be considered to be the same.
Definition: ColorFilter.cpp:19
Helper class so ColorFilter class can compute the background color.
QColor color
Unique color entry.
unsigned int count
Number of times this color has appeared.