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