Engauge Digitizer 2
Loading...
Searching...
No Matches
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
18
20 QRgb rgb2) const
21{
22 const long MASK = 0xf0f0f0f0;
23 return (rgb1 & MASK) == (rgb2 & MASK);
24}
25
26void 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
58QRgb 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
83void 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
106bool 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
128bool 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
156double 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
205int 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}
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...
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered 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.
int zeroToOneToValue(ColorFilterMode colorFilterMode, double s) const
Inverse of pixelToZeroToOneOrMinusOne.
bool pixelUnfilteredIsOn(ColorFilterMode colorFilterMode, const QColor &pixel, QRgb rgbBackground, double low0To1, double high0To1) const
Return true if specified unfiltered 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.
ColorFilter()
Single constructor.
bool colorCompare(QRgb rgb1, QRgb rgb2) const
See if the two color values are close enough to be considered to be the same.
Helper class so ColorFilter class can compute the background color.
QColor color
Unique color entry.
unsigned int count
Number of times this color has appeared.