Engauge Digitizer  2
 All Classes Files Functions Variables Enumerations Enumerator Friends Pages
DigitizeStateColorPicker.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 "CmdMediator.h"
8 #include "CmdSettingsColorFilter.h"
9 #include "ColorFilter.h"
10 #include "ColorFilterHistogram.h"
11 #include "DigitizeStateContext.h"
12 #include "DigitizeStateColorPicker.h"
13 #include "DocumentModelColorFilter.h"
14 #include "EngaugeAssert.h"
15 #include "Logger.h"
16 #include "MainWindow.h"
17 #include <QBitmap>
18 #include <QGraphicsPixmapItem>
19 #include <QGraphicsScene>
20 #include <QImage>
21 #include <QMessageBox>
22 
25 {
26 }
27 
28 DigitizeStateColorPicker::~DigitizeStateColorPicker ()
29 {
30 }
31 
33 {
35 }
36 
38  DigitizeState previousState)
39 {
40  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::begin";
41 
42  setCursor(cmdMediator);
43  context().setDragMode(QGraphicsView::NoDrag);
44 
45  // Save current state stuff so it can be restored afterwards
46  m_previousDigitizeState = previousState;
47  m_previousBackground = context().mainWindow().selectOriginal(BACKGROUND_IMAGE_ORIGINAL); // Only makes sense to have original image with all its colors
48 
50 }
51 
52 bool DigitizeStateColorPicker::computeFilterFromPixel (CmdMediator *cmdMediator,
53  const QPointF &posScreen,
54  const QString &curveName,
55  DocumentModelColorFilter &modelColorFilterAfter)
56 {
57  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::computeFilterFromPixel";
58 
59  bool rtn = false;
60 
61  // Filter for background color now, and then later, once filter mode is set, processing of image
62  ColorFilter filter;
63  QImage image = cmdMediator->document().pixmap().toImage();
64  QRgb rgbBackground = filter.marginColor(&image);
65 
66  // Adjust screen position so truncation gives round-up behavior
67  QPointF posScreenPlusHalf = posScreen - QPointF (0.5, 0.5);
68 
69  QColor pixel;
70  rtn = findNearestNonBackgroundPixel (cmdMediator,
71  image,
72  posScreenPlusHalf,
73  rgbBackground,
74  pixel);
75  if (rtn) {
76 
77  // The choice of which filter mode to use is determined, currently, by the selected pixel. This
78  // could be maybe made smarter by looking at other pixels, or even the entire image
79  int r = qRed (pixel.rgb());
80  int g = qGreen (pixel.rgb());
81  int b = qBlue (pixel.rgb());
82  if (r == g && g == b) {
83 
84  // Pixel is gray scale, so we use intensity
85  modelColorFilterAfter.setColorFilterMode (curveName,
86  COLOR_FILTER_MODE_INTENSITY);
87 
88  } else {
89 
90  // Pixel is not gray scale, so we use hue
91  modelColorFilterAfter.setColorFilterMode (curveName,
92  COLOR_FILTER_MODE_HUE);
93 
94  }
95 
96  // Generate histogram
97  double *histogramBins = new double [ColorFilterHistogram::HISTOGRAM_BINS ()];
98 
99  ColorFilterHistogram filterHistogram;
100  int maxBinCount;
101  filterHistogram.generate (filter,
102  histogramBins,
103  modelColorFilterAfter.colorFilterMode (curveName),
104  image,
105  maxBinCount);
106 
107  // Bin for pixel
108  int pixelBin = filterHistogram.binFromPixel(filter,
109  modelColorFilterAfter.colorFilterMode (curveName),
110  pixel,
111  rgbBackground);
112 
113  // Identify the entire width of the peak that the selected pixel belongs to. Go in both directions until the count
114  // hits zero or goes up
115  int lowerBin = pixelBin, upperBin = pixelBin;
116  while ((lowerBin > 0) &&
117  (histogramBins [lowerBin - 1] <= histogramBins [lowerBin]) &&
118  (histogramBins [lowerBin] > 0)) {
119  --lowerBin;
120  }
121  while ((upperBin < ColorFilterHistogram::HISTOGRAM_BINS () - 1) &&
122  (histogramBins [upperBin + 1] <= histogramBins [upperBin]) &&
123  (histogramBins [upperBin] > 0)) {
124  ++upperBin;
125  }
126 
127  // Compute and save values from bin numbers
128  int lowerValue = filterHistogram.valueFromBin(filter,
129  modelColorFilterAfter.colorFilterMode (curveName),
130  lowerBin);
131  int upperValue = filterHistogram.valueFromBin(filter,
132  modelColorFilterAfter.colorFilterMode (curveName),
133  upperBin);
134 
135  saveLowerValueUpperValue (modelColorFilterAfter,
136  curveName,
137  lowerValue,
138  upperValue);
139 
140  delete [] histogramBins;
141 
142  } else {
143 
144  QMessageBox::warning (0,
145  QObject::tr ("Color Picker"),
146  QObject::tr ("Sorry, but the color picker point must be near a non-background pixel. Please try again."));
147 
148  }
149 
150  return rtn;
151 }
152 
153 QCursor DigitizeStateColorPicker::cursor(CmdMediator * /* cmdMediator */) const
154 {
155  // Hot point is at the point of the eye dropper
156  const int HOT_X_IN_BITMAP = 8;
157  const int HOT_Y_IN_BITMAP = 24;
158  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::cursor";
159 
160  QBitmap bitmap (":/engauge/img/cursor_eyedropper.xpm");
161  QBitmap bitmapMask (":/engauge/img/cursor_eyedropper_mask.xpm");
162  return QCursor (bitmap,
163  bitmapMask,
164  HOT_X_IN_BITMAP,
165  HOT_Y_IN_BITMAP);
166 }
167 
169 {
170  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::end";
171 
172  // Restore original background. The state transition was triggered earlier by either the user selecting
173  // a valid point, or by user clicking on another digitize state button
174  context().mainWindow().selectOriginal(m_previousBackground);
175 }
176 
177 bool DigitizeStateColorPicker::findNearestNonBackgroundPixel (CmdMediator *cmdMediator,
178  const QImage &image,
179  const QPointF &posScreenPlusHalf,
180  const QRgb &rgbBackground,
181  QColor &pixel)
182 {
183  QPoint pos = posScreenPlusHalf.toPoint ();
184 
185  int maxRadiusForSearch = cmdMediator->document().modelGeneral().cursorSize();
186 
187  // Starting at pos, search in ever-widening squares for a non-background pixel
188  for (int radius = 0; radius < maxRadiusForSearch; radius++) {
189 
190  for (int xOffset = -radius; xOffset <= radius; xOffset++) {
191  for (int yOffset = -radius; yOffset <= radius; yOffset++) {
192 
193  // Top side
194  pixel = image.pixel (pos.x () + xOffset, pos.y () - radius);
195  if (pixel != rgbBackground) {
196  return true;
197  }
198 
199  // Bottom side
200  pixel = image.pixel (pos.x () + xOffset, pos.y () + radius);
201  if (pixel != rgbBackground) {
202  return true;
203  }
204 
205  // Left side
206  pixel = image.pixel (pos.x () - radius, pos.y () - yOffset);
207  if (pixel != rgbBackground) {
208  return true;
209  }
210 
211  // Right side
212  pixel = image.pixel (pos.x () + radius, pos.y () + yOffset);
213  if (pixel != rgbBackground) {
214  return true;
215  }
216  }
217  }
218  }
219 
220  return false;
221 }
222 
224 {
225  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleCurveChange";
226 }
227 
229  Qt::Key key,
230  bool /* atLeastOneSelectedItem */)
231 {
232  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleKeyPress"
233  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
234 }
235 
237  QPointF /* posScreen */)
238 {
239 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseMove";
240 }
241 
243  QPointF /* posScreen */)
244 {
245  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMousePress";
246 }
247 
249  QPointF posScreen)
250 {
251  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseRelease";
252 
253  DocumentModelColorFilter modelColorFilterBefore = cmdMediator->document().modelColorFilter();
254  DocumentModelColorFilter modelColorFilterAfter = cmdMediator->document().modelColorFilter();
255  if (computeFilterFromPixel (cmdMediator,
256  posScreen,
257  context().mainWindow().selectedGraphCurve(),
258  modelColorFilterAfter)) {
259 
260  // Trigger a state transition. The background restoration will be handled by the end method
261  context().requestDelayedStateTransition(m_previousDigitizeState);
262 
263  // Create command to change segment filter
264  QUndoCommand *cmd = new CmdSettingsColorFilter (context ().mainWindow(),
265  cmdMediator->document (),
266  modelColorFilterBefore,
267  modelColorFilterAfter);
268  context().appendNewCmd(cmdMediator,
269  cmd);
270  }
271 }
272 
273 void DigitizeStateColorPicker::saveLowerValueUpperValue (DocumentModelColorFilter &modelColorFilterAfter,
274  const QString &curveName,
275  double lowerValue,
276  double upperValue)
277 {
278  switch (modelColorFilterAfter.colorFilterMode (curveName)) {
279  case COLOR_FILTER_MODE_FOREGROUND:
280  modelColorFilterAfter.setForegroundLow(curveName,
281  lowerValue);
282  modelColorFilterAfter.setForegroundHigh(curveName,
283  upperValue);
284  break;
285 
286  case COLOR_FILTER_MODE_HUE:
287  modelColorFilterAfter.setHueLow(curveName,
288  lowerValue);
289  modelColorFilterAfter.setHueHigh(curveName,
290  upperValue);
291  break;
292 
293  case COLOR_FILTER_MODE_INTENSITY:
294  modelColorFilterAfter.setIntensityLow(curveName,
295  lowerValue);
296  modelColorFilterAfter.setIntensityHigh(curveName,
297  upperValue);
298  break;
299 
300  case COLOR_FILTER_MODE_SATURATION:
301  modelColorFilterAfter.setSaturationLow(curveName,
302  lowerValue);
303  modelColorFilterAfter.setSaturationHigh(curveName,
304  upperValue);
305  break;
306 
307  case COLOR_FILTER_MODE_VALUE:
308  modelColorFilterAfter.setValueLow(curveName,
309  lowerValue);
310  modelColorFilterAfter.setValueHigh(curveName,
311  upperValue);
312  break;
313 
314  default:
315  ENGAUGE_ASSERT (false);
316  }
317 }
318 
320 {
321  return "DigitizeStateColorPicker";
322 }
323 
325  const DocumentModelDigitizeCurve & /*modelDigitizeCurve */)
326 {
327  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelDigitizeCurve";
328 }
329 
331 {
332  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelSegments";
333 }
void requestDelayedStateTransition(DigitizeState digitizeState)
Initiate state transition to be performed later, when DigitizeState is off the stack.
void generate(const ColorFilter &filter, double histogramBins[], ColorFilterMode colorFilterMode, const QImage &image, int &maxBinCount) const
Generate the histogram.
virtual void handleMouseMove(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse move. This is part of an experiment to see if augmenting the cursor in Point Match mod...
virtual void handleMousePress(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse press that was intercepted earlier.
virtual void updateModelDigitizeCurve(CmdMediator *cmdMediator, const DocumentModelDigitizeCurve &modelDigitizeCurve)
Update the digitize curve settings.
DocumentModelColorFilter modelColorFilter() const
Get method for DocumentModelColorFilter.
Definition: Document.cpp:639
void setColorFilterMode(const QString &curveName, ColorFilterMode colorFilterMode)
Set method for filter mode.
void setDragMode(QGraphicsView::DragMode dragMode)
Set QGraphicsView drag mode (in m_view). Called from DigitizeStateAbstractBase subclasses.
virtual void handleKeyPress(CmdMediator *cmdMediator, Qt::Key key, bool atLeastOneSelectedItem)
Handle a key press that was intercepted earlier.
DigitizeStateColorPicker(DigitizeStateContext &context)
Single constructor.
virtual QString state() const
State name for debugging.
void updateViewsOfSettings(const QString &activeCurve)
Update curve-specific view of settings. Private version gets active curve name from DigitizeStateCont...
QString selectedGraphCurve() const
Curve name that is currently selected in m_cmbCurve.
int cursorSize() const
Get method for effective cursor size.
QPixmap pixmap() const
Return the image that is being digitized.
Definition: Document.cpp:723
virtual void updateModelSegments(const DocumentModelSegments &modelSegments)
Update the segments given the new settings.
Document & document()
Provide the Document to commands, primarily for undo/redo processing.
Definition: CmdMediator.cpp:72
virtual QString activeCurve() const
Name of the active Curve. This can include AXIS_CURVE_NAME.
Class for filtering image to remove unimportant information.
Definition: ColorFilter.h:20
BackgroundImage selectOriginal(BackgroundImage backgroundImage)
Make original background visible, for DigitizeStateColorPicker.
void setValueLow(const QString &curveName, int valueLow)
Set method for value low.
virtual void begin(CmdMediator *cmdMediator, DigitizeState previousState)
Method that is called at the exact moment a state is entered.
DigitizeStateContext & context()
Reference to the DigitizeStateContext that contains all the DigitizeStateAbstractBase subclasses...
MainWindow & mainWindow()
Reference to the MainWindow, without const.
void setForegroundLow(const QString &curveName, int foregroundLow)
Set method for foreground lower bound.
void setHueLow(const QString &curveName, int hueLow)
Set method for hue lower bound.
Model for DlgSettingsDigitizeCurve and CmdSettingsDigitizeCurve.
void setIntensityLow(const QString &curveName, int intensityLow)
Set method for intensity lower bound.
void setForegroundHigh(const QString &curveName, int foregroundHigh)
Set method for foreground higher bound.
Model for DlgSettingsColorFilter and CmdSettingsColorFilter.
void setCursor(CmdMediator *cmdMediator)
Update the cursor according to the current state.
void setIntensityHigh(const QString &curveName, int intensityHigh)
Set method for intensity higher bound.
Container for all DigitizeStateAbstractBase subclasses. This functions as the context class in a stan...
void appendNewCmd(CmdMediator *cmdMediator, QUndoCommand *cmd)
Append just-created QUndoCommand to command stack. This is called from DigitizeStateAbstractBase subc...
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
int binFromPixel(const ColorFilter &filter, ColorFilterMode colorFilterMode, const QColor &pixel, const QRgb &rgbBackground) const
Compute histogram bin number from pixel according to filter.
ColorFilterMode colorFilterMode(const QString &curveName) const
Get method for filter mode.
Command for DlgSettingsColorFilter.
void setSaturationLow(const QString &curveName, int saturationLow)
Set method for saturation low.
int valueFromBin(const ColorFilter &filter, ColorFilterMode colorFilterMode, int bin)
Inverse of binFromPixel.
void setSaturationHigh(const QString &curveName, int saturationHigh)
Set method for saturation high.
virtual void end()
Method that is called at the exact moment a state is exited. Typically called just before begin for t...
void setHueHigh(const QString &curveName, int hueHigh)
Set method for hue higher bound.
virtual QCursor cursor(CmdMediator *cmdMediator) const
Returns the state-specific cursor shape.
virtual void handleCurveChange(CmdMediator *cmdMediator)
Handle the selection of a new curve. At a minimum, DigitizeStateSegment will generate a new set of Se...
Command queue stack.
Definition: CmdMediator.h:23
Model for DlgSettingsSegments and CmdSettingsSegments.
void setValueHigh(const QString &curveName, int valueHigh)
Set method for value high.
virtual void handleMouseRelease(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse release that was intercepted earlier.
Base class for all digitizing states. This serves as an interface to DigitizeStateContext.
Class that generates a histogram according to the current filter.
static int HISTOGRAM_BINS()
Number of histogram bins.
DocumentModelGeneral modelGeneral() const
Get method for DocumentModelGeneral.
Definition: Document.cpp:674