Engauge Digitizer  2
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  const QString &pointIdentifier)
225 {
226  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleContextMenuEventAxis "
227  << " point=" << pointIdentifier.toLatin1 ().data ();
228 }
229 
231  const QStringList &pointIdentifiers)
232 {
233  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker ::handleContextMenuEventGraph "
234  << "points=" << pointIdentifiers.join(",").toLatin1 ().data ();
235 }
236 
238 {
239  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleCurveChange";
240 }
241 
243  Qt::Key key,
244  bool /* atLeastOneSelectedItem */)
245 {
246  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleKeyPress"
247  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
248 }
249 
251  QPointF /* posScreen */)
252 {
253 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseMove";
254 }
255 
257  QPointF /* posScreen */)
258 {
259  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMousePress";
260 }
261 
263  QPointF posScreen)
264 {
265  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseRelease";
266 
267  DocumentModelColorFilter modelColorFilterBefore = cmdMediator->document().modelColorFilter();
268  DocumentModelColorFilter modelColorFilterAfter = cmdMediator->document().modelColorFilter();
269  if (computeFilterFromPixel (cmdMediator,
270  posScreen,
271  context().mainWindow().selectedGraphCurve(),
272  modelColorFilterAfter)) {
273 
274  // Trigger a state transition. The background restoration will be handled by the end method
275  context().requestDelayedStateTransition(m_previousDigitizeState);
276 
277  // Create command to change segment filter
278  QUndoCommand *cmd = new CmdSettingsColorFilter (context ().mainWindow(),
279  cmdMediator->document (),
280  modelColorFilterBefore,
281  modelColorFilterAfter);
282  context().appendNewCmd(cmdMediator,
283  cmd);
284  }
285 }
286 
287 void DigitizeStateColorPicker::saveLowerValueUpperValue (DocumentModelColorFilter &modelColorFilterAfter,
288  const QString &curveName,
289  double lowerValue,
290  double upperValue)
291 {
292  switch (modelColorFilterAfter.colorFilterMode (curveName)) {
293  case COLOR_FILTER_MODE_FOREGROUND:
294  modelColorFilterAfter.setForegroundLow(curveName,
295  lowerValue);
296  modelColorFilterAfter.setForegroundHigh(curveName,
297  upperValue);
298  break;
299 
300  case COLOR_FILTER_MODE_HUE:
301  modelColorFilterAfter.setHueLow(curveName,
302  lowerValue);
303  modelColorFilterAfter.setHueHigh(curveName,
304  upperValue);
305  break;
306 
307  case COLOR_FILTER_MODE_INTENSITY:
308  modelColorFilterAfter.setIntensityLow(curveName,
309  lowerValue);
310  modelColorFilterAfter.setIntensityHigh(curveName,
311  upperValue);
312  break;
313 
314  case COLOR_FILTER_MODE_SATURATION:
315  modelColorFilterAfter.setSaturationLow(curveName,
316  lowerValue);
317  modelColorFilterAfter.setSaturationHigh(curveName,
318  upperValue);
319  break;
320 
321  case COLOR_FILTER_MODE_VALUE:
322  modelColorFilterAfter.setValueLow(curveName,
323  lowerValue);
324  modelColorFilterAfter.setValueHigh(curveName,
325  upperValue);
326  break;
327 
328  default:
329  ENGAUGE_ASSERT (false);
330  }
331 }
332 
334 {
335  return "DigitizeStateColorPicker";
336 }
337 
339 {
340  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateAfterPointAddition";
341 }
342 
344  const DocumentModelDigitizeCurve & /*modelDigitizeCurve */)
345 {
346  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelDigitizeCurve";
347 }
348 
350 {
351  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelSegments";
352 }
virtual void updateAfterPointAddition()
Update graphics attributes after possible new points. This is useful for highlight opacity...
void requestDelayedStateTransition(DigitizeState digitizeState)
Initiate state transition to be performed later, when DigitizeState is off the stack.
virtual QString activeCurve() const
Name of the active Curve. This can include AXIS_CURVE_NAME.
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.
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.
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
virtual void handleKeyPress(CmdMediator *cmdMediator, Qt::Key key, bool atLeastOneSelectedItem)
Handle a key press that was intercepted earlier.
DigitizeStateColorPicker(DigitizeStateContext &context)
Single constructor.
virtual void handleContextMenuEventAxis(CmdMediator *cmdMediator, const QString &pointIdentifier)
Handle a right click, on an axis point, that was intercepted earlier.
void updateViewsOfSettings(const QString &activeCurve)
Update curve-specific view of settings. Private version gets active curve name from DigitizeStateCont...
DocumentModelGeneral modelGeneral() const
Get method for DocumentModelGeneral.
Definition: Document.cpp:693
DocumentModelColorFilter modelColorFilter() const
Get method for DocumentModelColorFilter.
Definition: Document.cpp:658
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
Class for filtering image to remove unimportant information.
Definition: ColorFilter.h:20
ColorFilterMode colorFilterMode(const QString &curveName) const
Get method for filter mode.
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...
void generate(const ColorFilter &filter, double histogramBins [], ColorFilterMode colorFilterMode, const QImage &image, int &maxBinCount) const
Generate the histogram.
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.
int binFromPixel(const ColorFilter &filter, ColorFilterMode colorFilterMode, const QColor &pixel, const QRgb &rgbBackground) const
Compute histogram bin number from pixel according to filter.
void setCursor(CmdMediator *cmdMediator)
Update the cursor according to the current state.
int cursorSize() const
Get method for effective cursor size.
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...
virtual QCursor cursor(CmdMediator *cmdMediator) const
Returns the state-specific cursor shape.
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 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.
virtual QString state() const
State name for debugging.
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.
QPixmap pixmap() const
Return the image that is being digitized.
Definition: Document.cpp:742
virtual void handleContextMenuEventGraph(CmdMediator *cmdMediator, const QStringList &pointIdentifiers)
Handle a right click, on a graph point, that was intercepted earlier.
Class that generates a histogram according to the current filter.
QString selectedGraphCurve() const
Curve name that is currently selected in m_cmbCurve.
static int HISTOGRAM_BINS()
Number of histogram bins.