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