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 "ColorFilterStrategyForeground.h"
10 #include "ColorFilterStrategyHue.h"
11 #include "ColorFilterStrategyIntensity.h"
12 #include "ColorFilterStrategySaturation.h"
13 #include "ColorFilterStrategyValue.h"
14 #include "EngaugeAssert.h"
15 #include "mmsubs.h"
16 #include <QDebug>
17 #include <qmath.h>
18 #include <QImage>
19 
21 {
22  createStrategies ();
23 }
24 
25 bool ColorFilter::colorCompare (QRgb rgb1,
26  QRgb rgb2) const
27 {
28  const long MASK = 0xf0f0f0f0;
29  return (rgb1 & MASK) == (rgb2 & MASK);
30 }
31 
32 void ColorFilter::createStrategies ()
33 {
34  m_strategies [COLOR_FILTER_MODE_FOREGROUND] = new ColorFilterStrategyForeground ();
35  m_strategies [COLOR_FILTER_MODE_HUE ] = new ColorFilterStrategyHue ();
36  m_strategies [COLOR_FILTER_MODE_INTENSITY ] = new ColorFilterStrategyIntensity ();
37  m_strategies [COLOR_FILTER_MODE_SATURATION] = new ColorFilterStrategySaturation ();
38  m_strategies [COLOR_FILTER_MODE_VALUE ] = new ColorFilterStrategyValue ();
39 }
40 
41 void ColorFilter::filterImage (const QImage &imageOriginal,
42  QImage &imageFiltered,
43  ColorFilterMode colorFilterMode,
44  double low,
45  double high,
46  QRgb rgbBackground)
47 {
48  ENGAUGE_ASSERT (imageOriginal.width () == imageFiltered.width());
49  ENGAUGE_ASSERT (imageOriginal.height() == imageFiltered.height());
50  ENGAUGE_ASSERT (imageFiltered.format () == QImage::Format_RGB32);
51 
52  for (int x = 0; x < imageOriginal.width(); x++) {
53  for (int y = 0; y < imageOriginal.height (); y++) {
54 
55  QColor pixel = imageOriginal.pixel (x, y);
56  bool isOn = false;
57  if (pixel.rgb() != rgbBackground) {
58 
59  isOn = pixelUnfilteredIsOn (colorFilterMode,
60  pixel,
61  rgbBackground,
62  low,
63  high);
64  }
65 
66  imageFiltered.setPixel (x, y, (isOn ?
67  QColor (Qt::black).rgb () :
68  QColor (Qt::white).rgb ()));
69  }
70  }
71 }
72 
73 QRgb ColorFilter::marginColor(const QImage *image) const
74 {
75  // Add unique colors to colors list
76  ColorList colorCounts;
77  for (int x = 0; x < image->width (); x++) {
78  mergePixelIntoColorCounts (image->pixel (x, 0), colorCounts);
79  mergePixelIntoColorCounts (image->pixel (x, image->height () - 1), colorCounts);
80  }
81  for (int y = 0; y < image->height (); y++) {
82  mergePixelIntoColorCounts (image->pixel (0, y), colorCounts);
83  mergePixelIntoColorCounts (image->pixel (image->width () - 1, y), colorCounts);
84  }
85 
86  // Margin color is the most frequent color
87  ColorFilterEntry entryMax;
88  entryMax.count = 0;
89  for (ColorList::const_iterator itr = colorCounts.begin (); itr != colorCounts.end (); itr++) {
90  if ((*itr).count > entryMax.count) {
91  entryMax = *itr;
92  }
93  }
94 
95  return entryMax.color.rgb();
96 }
97 
98 void ColorFilter::mergePixelIntoColorCounts (QRgb pixel,
99  ColorList &colorCounts) const
100 {
101  ColorFilterEntry entry;
102  entry.color = pixel;
103  entry.count = 0;
104 
105  // Look for previous entry
106  bool found = false;
107  for (ColorList::iterator itr = colorCounts.begin (); itr != colorCounts.end (); itr++) {
108  if (colorCompare (entry.color.rgb(),
109  (*itr).color.rgb())) {
110  found = true;
111  ++(*itr).count;
112  break;
113  }
114  }
115 
116  if (!found) {
117  colorCounts.append (entry);
118  }
119 }
120 
121 bool ColorFilter::pixelFilteredIsOn (const QImage &image,
122  int x,
123  int y) const
124 {
125  bool rtn = false;
126 
127  if ((0 <= x) &&
128  (0 <= y) &&
129  (x < image.width()) &&
130  (y < image.height())) {
131 
132  // Pixel is on if it is closer to black than white in gray scale. This test must be performed
133  // on little endian and big endian systems, with or without alpha bits (which are typically high bits);
134  const int BLACK_WHITE_THRESHOLD = 255 / 2; // Put threshold in middle of range
135  int gray = qGray (pixelRGB (image, x, y));
136  rtn = (gray < BLACK_WHITE_THRESHOLD);
137 
138  }
139 
140  return rtn;
141 }
142 
143 bool ColorFilter::pixelUnfilteredIsOn (ColorFilterMode colorFilterMode,
144  const QColor &pixel,
145  QRgb rgbBackground,
146  double low0To1,
147  double high0To1) const
148 {
149  bool rtn = false;
150 
151  double s = pixelToZeroToOneOrMinusOne (colorFilterMode,
152  pixel,
153  rgbBackground);
154  if (s >= 0.0) {
155  if (low0To1 <= high0To1) {
156 
157  // Single valid range
158  rtn = (low0To1 <= s) && (s <= high0To1);
159 
160  } else {
161 
162  // Two ranges
163  rtn = (s <= high0To1) || (low0To1 <= s);
164 
165  }
166  }
167 
168  return rtn;
169 }
170 
171 double ColorFilter::pixelToZeroToOneOrMinusOne (ColorFilterMode colorFilterMode,
172  const QColor &pixel,
173  QRgb rgbBackground) const
174 {
175  if (m_strategies.contains (colorFilterMode)) {
176 
177  return m_strategies [colorFilterMode]->pixelToZeroToOne (pixel,
178  rgbBackground);
179 
180  } else {
181 
182  ENGAUGE_ASSERT (false);
183  return 0.0;
184 
185  }
186 }
187 
188 int ColorFilter::zeroToOneToValue (ColorFilterMode colorFilterMode,
189  double s) const
190 {
191  if (m_strategies.contains (colorFilterMode)) {
192 
193  return m_strategies [colorFilterMode]->zeroToOneToValue (s);
194 
195  } else {
196 
197  ENGAUGE_ASSERT (false);
198  return 0;
199 
200  }
201 }
Leaf class for foreground strategy for ColorFilter.
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:73
ColorFilter()
Single constructor.
Definition: ColorFilter.cpp:20
unsigned int count
Number of times this color has appeared.
Leaf class for value strategy for ColorFilter.
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:25
Leaf class for intensity strategy for ColorFilter.
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered pixel is on.
Leaf class for hue strategy for ColorFilter.
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.
Leaf class for saturation strategy for ColorFilter.
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:41