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 [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  } else {
132 
133  QMessageBox::warning (0,
134  "Color Picker",
135  "Sorry, but the color picker point must be near a non-background pixel. Please try again.");
136 
137  }
138 
139  return rtn;
140 }
141 
143 {
144  // Hot point is at the point of the eye dropper
145  const int HOT_X_IN_BITMAP = 8;
146  const int HOT_Y_IN_BITMAP = 24;
147  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::cursor";
148 
149  QBitmap bitmap (":/engauge/img/cursor_eyedropper.xpm");
150  QBitmap bitmapMask (":/engauge/img/cursor_eyedropper_mask.xpm");
151  return QCursor (bitmap,
152  bitmapMask,
153  HOT_X_IN_BITMAP,
154  HOT_Y_IN_BITMAP);
155 }
156 
158 {
159  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::end";
160 
161  // Restore original background. The state transition was triggered earlier by either the user selecting
162  // a valid point, or by user clicking on another digitize state button
163  context().mainWindow().selectOriginal(m_previousBackground);
164 }
165 
166 bool DigitizeStateColorPicker::findNearestNonBackgroundPixel (const QImage &image,
167  const QPointF &posScreenPlusHalf,
168  const QRgb &rgbBackground,
169  QColor &pixel)
170 {
171  QPoint pos = posScreenPlusHalf.toPoint ();
172 
173  int maxRadiusForSearch = context().cmdMediator().document().modelCommon().cursorSize();
174 
175  // Starting at pos, search in ever-widening squares for a non-background pixel
176  for (int radius = 0; radius < maxRadiusForSearch; radius++) {
177 
178  for (int xOffset = -radius; xOffset <= radius; xOffset++) {
179  for (int yOffset = -radius; yOffset <= radius; yOffset++) {
180 
181  // Top side
182  pixel = image.pixel (pos.x () + xOffset, pos.y () - radius);
183  if (pixel != rgbBackground) {
184  return true;
185  }
186 
187  // Bottom side
188  pixel = image.pixel (pos.x () + xOffset, pos.y () + radius);
189  if (pixel != rgbBackground) {
190  return true;
191  }
192 
193  // Left side
194  pixel = image.pixel (pos.x () - radius, pos.y () - yOffset);
195  if (pixel != rgbBackground) {
196  return true;
197  }
198 
199  // Right side
200  pixel = image.pixel (pos.x () + radius, pos.y () + yOffset);
201  if (pixel != rgbBackground) {
202  return true;
203  }
204  }
205  }
206  }
207 
208  return false;
209 }
210 
212 {
213  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleCurveChange";
214 }
215 
217  bool /* atLeastOneSelectedItem */)
218 {
219  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleKeyPress"
220  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
221 }
222 
223 void DigitizeStateColorPicker::handleMouseMove (QPointF /* posScreen */)
224 {
225 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseMove";
226 }
227 
228 void DigitizeStateColorPicker::handleMousePress (QPointF /* posScreen */)
229 {
230  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMousePress";
231 }
232 
234 {
235  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::handleMouseRelease";
236 
237  DocumentModelColorFilter modelColorFilterBefore = context().cmdMediator().document().modelColorFilter();
238  DocumentModelColorFilter modelColorFilterAfter = context().cmdMediator().document().modelColorFilter();
239  if (computeFilterFromPixel (posScreen,
240  context().mainWindow().selectedGraphCurve(),
241  modelColorFilterAfter)) {
242 
243  // Trigger a state transition. The background restoration will be handled by the end method
244  context().requestDelayedStateTransition(m_previousDigitizeState);
245 
246  // Create command to change segment filter
247  QUndoCommand *cmd = new CmdSettingsColorFilter (context ().mainWindow(),
248  context ().cmdMediator ().document (),
249  modelColorFilterBefore,
250  modelColorFilterAfter);
251  context().appendNewCmd(cmd);
252  }
253 }
254 
255 void DigitizeStateColorPicker::saveLowerValueUpperValue (DocumentModelColorFilter &modelColorFilterAfter,
256  const QString &curveName,
257  double lowerValue,
258  double upperValue)
259 {
260  switch (modelColorFilterAfter.colorFilterMode (curveName)) {
261  case COLOR_FILTER_MODE_FOREGROUND:
262  modelColorFilterAfter.setForegroundLow(curveName,
263  lowerValue);
264  modelColorFilterAfter.setForegroundHigh(curveName,
265  upperValue);
266  break;
267 
268  case COLOR_FILTER_MODE_HUE:
269  modelColorFilterAfter.setHueLow(curveName,
270  lowerValue);
271  modelColorFilterAfter.setHueHigh(curveName,
272  upperValue);
273  break;
274 
275  case COLOR_FILTER_MODE_INTENSITY:
276  modelColorFilterAfter.setIntensityLow(curveName,
277  lowerValue);
278  modelColorFilterAfter.setIntensityHigh(curveName,
279  upperValue);
280  break;
281 
282  case COLOR_FILTER_MODE_SATURATION:
283  modelColorFilterAfter.setSaturationLow(curveName,
284  lowerValue);
285  modelColorFilterAfter.setSaturationHigh(curveName,
286  upperValue);
287  break;
288 
289  case COLOR_FILTER_MODE_VALUE:
290  modelColorFilterAfter.setValueLow(curveName,
291  lowerValue);
292  modelColorFilterAfter.setValueHigh(curveName,
293  upperValue);
294  break;
295 
296  default:
297  ENGAUGE_ASSERT (false);
298  }
299 }
300 
302 {
303  return "DigitizeStateColorPicker";
304 }
305 
307 {
308  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelDigitizeCurve";
309 }
310 
312 {
313  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStateColorPicker::updateModelSegments";
314 }
DocumentModelCommon modelCommon() const
Get method for DocumentModelCommon.
Definition: Document.cpp:442
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:434
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:511
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.