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  // Ignore false positive cmake compiler warning about -Wreturn-stack-address in next line (bug #26396)
178  const ColorFilterStrategyAbstractBase *strategy = m_strategies [colorFilterMode];
179  return strategy->pixelToZeroToOne (pixel,
180  rgbBackground);
181 
182  } else {
183 
184  ENGAUGE_ASSERT (false);
185  return 0.0;
186 
187  }
188 }
189 
190 int ColorFilter::zeroToOneToValue (ColorFilterMode colorFilterMode,
191  double s) const
192 {
193  if (m_strategies.contains (colorFilterMode)) {
194 
195  const ColorFilterStrategyAbstractBase *strategy = m_strategies [colorFilterMode];
196  return strategy->zeroToOneToValue (s);
197 
198  } else {
199 
200  ENGAUGE_ASSERT (false);
201  return 0;
202 
203  }
204 }
Leaf class for foreground strategy for ColorFilter.
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
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
virtual int zeroToOneToValue(double s) const =0
Return the low value normalized to 0 to 1.
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered 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...
int zeroToOneToValue(ColorFilterMode colorFilterMode, double s) const
Inverse of pixelToZeroToOneOrMinusOne.
QColor color
Unique color entry.
Base class for strategy pattern whose subclasses process the different color filter settings modes (o...
ColorFilter()
Single constructor.
Definition: ColorFilter.cpp:20
unsigned int count
Number of times this color has appeared.
Leaf class for value strategy for ColorFilter.
Leaf class for intensity strategy for ColorFilter.
Leaf class for hue strategy for ColorFilter.
virtual double pixelToZeroToOne(const QColor &pixel, QRgb rgbBackground) const =0
Return a normalized value of 0 to 1 given input pixel.
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