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::canPaste (const Transformation & /* transformation */,
53  const QSize & /* viewSize */) const
54 {
55  return false;
56 }
57 
58 bool DigitizeStateColorPicker::computeFilterFromPixel (CmdMediator *cmdMediator,
59  const QPointF &posScreen,
60  const QString &curveName,
61  DocumentModelColorFilter &modelColorFilterAfter)
62 {
63  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::computeFilterFromPixel";
64 
65  bool rtn = false;
66 
67  // Filter for background color now, and then later, once filter mode is set, processing of image
68  ColorFilter filter;
69  QImage image = cmdMediator->document().pixmap().toImage();
70  QRgb rgbBackground = filter.marginColor(&image);
71 
72  // Adjust screen position so truncation gives round-up behavior
73  QPointF posScreenPlusHalf = posScreen - QPointF (0.5, 0.5);
74 
75  QColor pixel;
76  rtn = findNearestNonBackgroundPixel (cmdMediator,
77  image,
78  posScreenPlusHalf,
79  rgbBackground,
80  pixel);
81  if (rtn) {
82 
83  // The choice of which filter mode to use is determined, currently, by the selected pixel. This
84  // could be maybe made smarter by looking at other pixels, or even the entire image
85  int r = qRed (pixel.rgb());
86  int g = qGreen (pixel.rgb());
87  int b = qBlue (pixel.rgb());
88  if (r == g && g == b) {
89 
90  // Pixel is gray scale, so we use intensity
91  modelColorFilterAfter.setColorFilterMode (curveName,
92  COLOR_FILTER_MODE_INTENSITY);
93 
94  } else {
95 
96  // Pixel is not gray scale, so we use hue
97  modelColorFilterAfter.setColorFilterMode (curveName,
98  COLOR_FILTER_MODE_HUE);
99 
100  }
101 
102  // Generate histogram
103  double *histogramBins = new double [ColorFilterHistogram::HISTOGRAM_BINS ()];
104 
105  ColorFilterHistogram filterHistogram;
106  int maxBinCount;
107  filterHistogram.generate (filter,
108  histogramBins,
109  modelColorFilterAfter.colorFilterMode (curveName),
110  image,
111  maxBinCount);
112 
113  // Bin for pixel
114  int pixelBin = filterHistogram.binFromPixel(filter,
115  modelColorFilterAfter.colorFilterMode (curveName),
116  pixel,
117  rgbBackground);
118 
119  // Identify the entire width of the peak that the selected pixel belongs to. Go in both directions until the count
120  // hits zero or goes up
121  int lowerBin = pixelBin, upperBin = pixelBin;
122  while ((lowerBin > 0) &&
123  (histogramBins [lowerBin - 1] <= histogramBins [lowerBin]) &&
124  (histogramBins [lowerBin] > 0)) {
125  --lowerBin;
126  }
127  while ((upperBin < ColorFilterHistogram::HISTOGRAM_BINS () - 1) &&
128  (histogramBins [upperBin + 1] <= histogramBins [upperBin]) &&
129  (histogramBins [upperBin] > 0)) {
130  ++upperBin;
131  }
132 
133  // Compute and save values from bin numbers
134  int lowerValue = filterHistogram.valueFromBin(filter,
135  modelColorFilterAfter.colorFilterMode (curveName),
136  lowerBin);
137  int upperValue = filterHistogram.valueFromBin(filter,
138  modelColorFilterAfter.colorFilterMode (curveName),
139  upperBin);
140 
141  saveLowerValueUpperValue (modelColorFilterAfter,
142  curveName,
143  lowerValue,
144  upperValue);
145 
146  delete [] histogramBins;
147 
148  } else {
149 
150  QMessageBox::warning (0,
151  QObject::tr ("Color Picker"),
152  QObject::tr ("Sorry, but the color picker point must be near a non-background pixel. Please try again."));
153 
154  }
155 
156  return rtn;
157 }
158 
159 QCursor DigitizeStateColorPicker::cursor(CmdMediator * /* cmdMediator */) const
160 {
161  // Hot point is at the point of the eye dropper
162  const int HOT_X_IN_BITMAP = 8;
163  const int HOT_Y_IN_BITMAP = 24;
164  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::cursor";
165 
166  QBitmap bitmap (":/engauge/img/cursor_eyedropper.xpm");
167  QBitmap bitmapMask (":/engauge/img/cursor_eyedropper_mask.xpm");
168  return QCursor (bitmap,
169  bitmapMask,
170  HOT_X_IN_BITMAP,
171  HOT_Y_IN_BITMAP);
172 }
173 
175 {
176  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::end";
177 
178  // Restore original background. The state transition was triggered earlier by either the user selecting
179  // a valid point, or by user clicking on another digitize state button
180  context().mainWindow().selectOriginal(m_previousBackground);
181 }
182 
183 bool DigitizeStateColorPicker::findNearestNonBackgroundPixel (CmdMediator *cmdMediator,
184  const QImage &image,
185  const QPointF &posScreenPlusHalf,
186  const QRgb &rgbBackground,
187  QColor &pixel)
188 {
189  QPoint pos = posScreenPlusHalf.toPoint ();
190 
191  int maxRadiusForSearch = cmdMediator->document().modelGeneral().cursorSize();
192 
193  // Starting at pos, search in ever-widening squares for a non-background pixel
194  for (int radius = 0; radius < maxRadiusForSearch; radius++) {
195 
196  for (int xOffset = -radius; xOffset <= radius; xOffset++) {
197  for (int yOffset = -radius; yOffset <= radius; yOffset++) {
198 
199  // Top side
200  pixel = image.pixel (pos.x () + xOffset, pos.y () - radius);
201  if (pixel != rgbBackground) {
202  return true;
203  }
204 
205  // Bottom side
206  pixel = image.pixel (pos.x () + xOffset, pos.y () + radius);
207  if (pixel != rgbBackground) {
208  return true;
209  }
210 
211  // Left side
212  pixel = image.pixel (pos.x () - radius, pos.y () - yOffset);
213  if (pixel != rgbBackground) {
214  return true;
215  }
216 
217  // Right side
218  pixel = image.pixel (pos.x () + radius, pos.y () + yOffset);
219  if (pixel != rgbBackground) {
220  return true;
221  }
222  }
223  }
224  }
225 
226  return false;
227 }
228 
230  const QString &pointIdentifier)
231 {
232  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleContextMenuEventAxis "
233  << " point=" << pointIdentifier.toLatin1 ().data ();
234 }
235 
237  const QStringList &pointIdentifiers)
238 {
239  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker ::handleContextMenuEventGraph "
240  << "points=" << pointIdentifiers.join(",").toLatin1 ().data ();
241 }
242 
244 {
245  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleCurveChange";
246 }
247 
249  Qt::Key key,
250  bool /* atLeastOneSelectedItem */)
251 {
252  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleKeyPress"
253  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
254 }
255 
257  QPointF /* posScreen */)
258 {
259 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseMove";
260 }
261 
263  QPointF /* posScreen */)
264 {
265  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMousePress";
266 }
267 
269  QPointF posScreen)
270 {
271  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseRelease";
272 
273  DocumentModelColorFilter modelColorFilterBefore = cmdMediator->document().modelColorFilter();
274  DocumentModelColorFilter modelColorFilterAfter = cmdMediator->document().modelColorFilter();
275  if (computeFilterFromPixel (cmdMediator,
276  posScreen,
277  context().mainWindow().selectedGraphCurve(),
278  modelColorFilterAfter)) {
279 
280  // Trigger a state transition. The background restoration will be handled by the end method
281  context().requestDelayedStateTransition(m_previousDigitizeState);
282 
283  // Create command to change segment filter
284  QUndoCommand *cmd = new CmdSettingsColorFilter (context ().mainWindow(),
285  cmdMediator->document (),
286  modelColorFilterBefore,
287  modelColorFilterAfter);
288  context().appendNewCmd(cmdMediator,
289  cmd);
290  }
291 }
292 
293 void DigitizeStateColorPicker::saveLowerValueUpperValue (DocumentModelColorFilter &modelColorFilterAfter,
294  const QString &curveName,
295  double lowerValue,
296  double upperValue)
297 {
298  switch (modelColorFilterAfter.colorFilterMode (curveName)) {
299  case COLOR_FILTER_MODE_FOREGROUND:
300  modelColorFilterAfter.setForegroundLow(curveName,
301  lowerValue);
302  modelColorFilterAfter.setForegroundHigh(curveName,
303  upperValue);
304  break;
305 
306  case COLOR_FILTER_MODE_HUE:
307  modelColorFilterAfter.setHueLow(curveName,
308  lowerValue);
309  modelColorFilterAfter.setHueHigh(curveName,
310  upperValue);
311  break;
312 
313  case COLOR_FILTER_MODE_INTENSITY:
314  modelColorFilterAfter.setIntensityLow(curveName,
315  lowerValue);
316  modelColorFilterAfter.setIntensityHigh(curveName,
317  upperValue);
318  break;
319 
320  case COLOR_FILTER_MODE_SATURATION:
321  modelColorFilterAfter.setSaturationLow(curveName,
322  lowerValue);
323  modelColorFilterAfter.setSaturationHigh(curveName,
324  upperValue);
325  break;
326 
327  case COLOR_FILTER_MODE_VALUE:
328  modelColorFilterAfter.setValueLow(curveName,
329  lowerValue);
330  modelColorFilterAfter.setValueHigh(curveName,
331  upperValue);
332  break;
333 
334  default:
335  ENGAUGE_ASSERT (false);
336  }
337 }
338 
340 {
341  return "DigitizeStateColorPicker";
342 }
343 
345 {
346  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateAfterPointAddition";
347 }
348 
350  const DocumentModelDigitizeCurve & /*modelDigitizeCurve */)
351 {
352  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelDigitizeCurve";
353 }
354 
356 {
357  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelSegments";
358 }
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 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:717
DocumentModelColorFilter modelColorFilter() const
Get method for DocumentModelColorFilter.
Definition: Document.cpp:682
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.
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.
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:811
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.