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 
19 bool ColorFilter::colorCompare (QRgb rgb1,
20  QRgb rgb2) const
21 {
22  const long MASK = 0xf0f0f0f0;
23  return (rgb1 & MASK) == (rgb2 & MASK);
24 }
25 
26 void 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 
58 QRgb 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 
83 void 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 
106 bool 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 
128 bool 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 
156 double 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 
205 int 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 }
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:58
ColorFilter()
Single constructor.
Definition: ColorFilter.cpp:15
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:19
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:26