Engauge Digitizer  2
DigitizeStateColorPicker.cpp
Go to the documentation of this file.
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"
9 #include "ColorFilter.h"
10 #include "ColorFilterHistogram.h"
11 #include "DigitizeStateContext.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 <qmath.h>
22 #include <QMessageBox>
23 
26 {
27 }
28 
30 {
31 }
32 
34 {
36 }
37 
39  DigitizeState previousState)
40 {
41  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::begin";
42 
43  setCursor(cmdMediator);
44  context().setDragMode(QGraphicsView::NoDrag);
45 
46  // Save current state stuff so it can be restored afterwards
47  m_previousDigitizeState = previousState;
48  m_previousBackground = context().mainWindow().selectOriginal(BACKGROUND_IMAGE_ORIGINAL); // Only makes sense to have original image with all its colors
49 
51 }
52 
53 bool DigitizeStateColorPicker::canPaste (const Transformation & /* transformation */,
54  const QSize & /* viewSize */) const
55 {
56  return false;
57 }
58 
59 bool DigitizeStateColorPicker::computeFilterFromPixel (CmdMediator *cmdMediator,
60  const QPointF &posScreen,
61  const QString &curveName,
62  DocumentModelColorFilter &modelColorFilterAfter)
63 {
64  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::computeFilterFromPixel";
65 
66  bool rtn = false;
67 
68  // Filter for background color now, and then later, once filter mode is set, processing of image
69  ColorFilter filter;
70  QImage image = cmdMediator->document().pixmap().toImage();
71  QRgb rgbBackground = filter.marginColor(&image);
72 
73  // Adjust screen position so truncation gives round-up behavior
74  QPointF posScreenPlusHalf = posScreen - QPointF (0.5, 0.5);
75 
76  QColor pixel;
77  rtn = findNearestNonBackgroundPixel (cmdMediator,
78  image,
79  posScreenPlusHalf,
80  rgbBackground,
81  pixel);
82  if (rtn) {
83 
84  // The choice of which filter mode to use is determined, currently, by the selected pixel. This
85  // could be maybe made smarter by looking at other pixels, or even the entire image
86  int r = qRed (pixel.rgb());
87  int g = qGreen (pixel.rgb());
88  int b = qBlue (pixel.rgb());
89  if (r == g && g == b) {
90 
91  // Pixel is gray scale, so we use intensity
92  modelColorFilterAfter.setColorFilterMode (curveName,
94 
95  } else {
96 
97  // Pixel is not gray scale, so we use hue
98  modelColorFilterAfter.setColorFilterMode (curveName,
100 
101  }
102 
103  // Generate histogram
104  double *histogramBins = new double [unsigned (ColorFilterHistogram::HISTOGRAM_BINS ())];
105 
106  ColorFilterHistogram filterHistogram;
107  int maxBinCount;
108  filterHistogram.generate (filter,
109  histogramBins,
110  modelColorFilterAfter.colorFilterMode (curveName),
111  image,
112  maxBinCount);
113 
114  // Bin for pixel
115  int pixelBin = filterHistogram.binFromPixel(filter,
116  modelColorFilterAfter.colorFilterMode (curveName),
117  pixel,
118  rgbBackground);
119 
120  // Identify the entire width of the peak that the selected pixel belongs to. Go in both directions until the count
121  // hits zero or goes up
122  int lowerBin = pixelBin, upperBin = pixelBin;
123  while ((lowerBin > 0) &&
124  (histogramBins [lowerBin - 1] <= histogramBins [lowerBin]) &&
125  (histogramBins [lowerBin] > 0)) {
126  --lowerBin;
127  }
128  while ((upperBin < ColorFilterHistogram::HISTOGRAM_BINS () - 1) &&
129  (histogramBins [upperBin + 1] <= histogramBins [upperBin]) &&
130  (histogramBins [upperBin] > 0)) {
131  ++upperBin;
132  }
133 
134  // Compute and save values from bin numbers
135  int lowerValue = filterHistogram.valueFromBin(filter,
136  modelColorFilterAfter.colorFilterMode (curveName),
137  lowerBin);
138  int upperValue = filterHistogram.valueFromBin(filter,
139  modelColorFilterAfter.colorFilterMode (curveName),
140  upperBin);
141 
142  saveLowerValueUpperValue (modelColorFilterAfter,
143  curveName,
144  lowerValue,
145  upperValue);
146 
147  delete [] histogramBins;
148 
149  } else {
150 
151  QMessageBox::warning (nullptr,
152  QObject::tr ("Color Picker"),
153  QObject::tr ("Sorry, but the color picker point must be near a non-background pixel. Please try again."));
154 
155  }
156 
157  return rtn;
158 }
159 
160 QCursor DigitizeStateColorPicker::cursor(CmdMediator * /* cmdMediator */) const
161 {
162  // Hot point is at the point of the eye dropper
163  const int HOT_X_IN_BITMAP = 8;
164  const int HOT_Y_IN_BITMAP = 24;
165  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::cursor";
166 
167  QBitmap bitmap (":/engauge/img/cursor_eyedropper.xpm");
168  QBitmap bitmapMask (":/engauge/img/cursor_eyedropper_mask.xpm");
169  return QCursor (bitmap,
170  bitmapMask,
171  HOT_X_IN_BITMAP,
172  HOT_Y_IN_BITMAP);
173 }
174 
176 {
177  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::end";
178 
179  // Restore original background. The state transition was triggered earlier by either the user selecting
180  // a valid point, or by user clicking on another digitize state button
181  context().mainWindow().selectOriginal(m_previousBackground);
182 }
183 
184 bool DigitizeStateColorPicker::findNearestNonBackgroundPixel (CmdMediator *cmdMediator,
185  const QImage &image,
186  const QPointF &posScreenPlusHalf,
187  const QRgb &rgbBackground,
188  QColor &pixel)
189 {
190  QPoint pos = posScreenPlusHalf.toPoint ();
191 
192  int maxRadiusForSearch = cmdMediator->document().modelGeneral().cursorSize();
193 
194  // Starting at pos, search in ever-widening squares for a non-background pixel
195  for (int radius = 0; radius < maxRadiusForSearch; radius++) {
196 
197  for (int xOffset = -radius; xOffset <= radius; xOffset++) {
198  for (int yOffset = -radius; yOffset <= radius; yOffset++) {
199 
200  // Top side
201  pixel = image.pixel (pos.x () + xOffset, pos.y () - radius);
202  if (pixel != rgbBackground) {
203  return true;
204  }
205 
206  // Bottom side
207  pixel = image.pixel (pos.x () + xOffset, pos.y () + radius);
208  if (pixel != rgbBackground) {
209  return true;
210  }
211 
212  // Left side
213  pixel = image.pixel (pos.x () - radius, pos.y () - yOffset);
214  if (pixel != rgbBackground) {
215  return true;
216  }
217 
218  // Right side
219  pixel = image.pixel (pos.x () + radius, pos.y () + yOffset);
220  if (pixel != rgbBackground) {
221  return true;
222  }
223  }
224  }
225  }
226 
227  return false;
228 }
229 
231  const QString &pointIdentifier)
232 {
233  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleContextMenuEventAxis "
234  << " point=" << pointIdentifier.toLatin1 ().data ();
235 }
236 
238  const QStringList &pointIdentifiers)
239 {
240  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker ::handleContextMenuEventGraph "
241  << "points=" << pointIdentifiers.join(",").toLatin1 ().data ();
242 }
243 
245 {
246  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleCurveChange";
247 }
248 
250  Qt::Key key,
251  bool /* atLeastOneSelectedItem */)
252 {
253  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleKeyPress"
254  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
255 }
256 
258  QPointF /* posScreen */)
259 {
260 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseMove";
261 }
262 
264  QPointF /* posScreen */)
265 {
266  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMousePress";
267 }
268 
270  QPointF posScreen)
271 {
272  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseRelease";
273 
274  DocumentModelColorFilter modelColorFilterBefore = cmdMediator->document().modelColorFilter();
275  DocumentModelColorFilter modelColorFilterAfter = cmdMediator->document().modelColorFilter();
276  if (computeFilterFromPixel (cmdMediator,
277  posScreen,
278  context().mainWindow().selectedGraphCurve(),
279  modelColorFilterAfter)) {
280 
281  // Trigger a state transition. The background restoration will be handled by the end method
282  context().requestDelayedStateTransition(m_previousDigitizeState);
283 
284  // Create command to change segment filter
285  QUndoCommand *cmd = new CmdSettingsColorFilter (context ().mainWindow(),
286  cmdMediator->document (),
287  modelColorFilterBefore,
288  modelColorFilterAfter);
289  context().appendNewCmd(cmdMediator,
290  cmd);
291  }
292 }
293 
294 void DigitizeStateColorPicker::saveLowerValueUpperValue (DocumentModelColorFilter &modelColorFilterAfter,
295  const QString &curveName,
296  double lowerValueIn,
297  double upperValueIn)
298 {
299  int lowerValue = qFloor (lowerValueIn);
300  int upperValue = qFloor (upperValueIn);
301 
302  switch (modelColorFilterAfter.colorFilterMode (curveName)) {
304  modelColorFilterAfter.setForegroundLow(curveName,
305  lowerValue);
306  modelColorFilterAfter.setForegroundHigh(curveName,
307  upperValue);
308  break;
309 
311  modelColorFilterAfter.setHueLow(curveName,
312  lowerValue);
313  modelColorFilterAfter.setHueHigh(curveName,
314  upperValue);
315  break;
316 
318  modelColorFilterAfter.setIntensityLow(curveName,
319  lowerValue);
320  modelColorFilterAfter.setIntensityHigh(curveName,
321  upperValue);
322  break;
323 
325  modelColorFilterAfter.setSaturationLow(curveName,
326  lowerValue);
327  modelColorFilterAfter.setSaturationHigh(curveName,
328  upperValue);
329  break;
330 
332  modelColorFilterAfter.setValueLow(curveName,
333  lowerValue);
334  modelColorFilterAfter.setValueHigh(curveName,
335  upperValue);
336  break;
337 
338  default:
339  ENGAUGE_ASSERT (false);
340  }
341 }
342 
344 {
345  return "DigitizeStateColorPicker";
346 }
347 
349 {
350  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateAfterPointAddition";
351 }
352 
354  const DocumentModelDigitizeCurve & /*modelDigitizeCurve */)
355 {
356  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelDigitizeCurve";
357 }
358 
360 {
361  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelSegments";
362 }
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:78
virtual void handleKeyPress(CmdMediator *cmdMediator, Qt::Key key, bool atLeastOneSelectedItem)
Handle a key press that was intercepted earlier.
DigitizeStateColorPicker(DigitizeStateContext &context)
Single constructor.
virtual bool canPaste(const Transformation &transformation, const QSize &viewSize) const
Return true if there is good data in the clipboard for pasting, and that is compatible with the curre...
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:723
DocumentModelColorFilter modelColorFilter() const
Get method for DocumentModelColorFilter.
Definition: Document.cpp:688
#define LOG4CPP_INFO_S(logger)
Definition: convenience.h:18
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.
DigitizeState
Set of possible states of Digitize toolbar.
Model for DlgSettingsDigitizeCurve and CmdSettingsDigitizeCurve.
void setIntensityLow(const QString &curveName, int intensityLow)
Set method for intensity lower bound.
Affine transformation between screen and graph coordinates, based on digitized axis points...
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.
log4cpp::Category * mainCat
Definition: Logger.cpp:14
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:817
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.
#define ENGAUGE_ASSERT(cond)
Drop in replacement for Q_ASSERT if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS) define ENGAUGE...
Definition: EngaugeAssert.h:20
#define LOG4CPP_DEBUG_S(logger)
Definition: convenience.h:20