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