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