AlbumShaper  1.0a3
selectionInterface.cpp
Go to the documentation of this file.
1 //==============================================
2 // copyright : (C) 2003-2005 by Will Stokes
3 //==============================================
4 // This program is free software; you can redistribute it
5 // and/or modify it under the terms of the GNU General
6 // Public License as published by the Free Software
7 // Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //==============================================
10 
11 //Systemwide includes
12 #include <qsize.h>
13 #include <qapplication.h>
14 #include <qpainter.h>
15 #include <qevent.h>
16 #include <qpoint.h>
17 #include <q3accel.h>
18 #include <qcursor.h>
19 //Added by qt3to4:
20 #include <QPaintEvent>
21 #include <QResizeEvent>
22 #include <QPixmap>
23 #include <QMouseEvent>
24 #include <QKeyEvent>
25 #include <QDesktopWidget>
26 
27 //Projectwide includes
28 #include "selectionInterface.h"
29 #include "../cursors.h"
30 #include "../../config.h"
31 #include "../../backend/tools/imageTools.h"
32 #include "../window.h"
33 #include "../../configuration/configuration.h"
34 
35 #define MIN(x,y) ((x) < (y) ? (x) : (y))
36 #define MAX(x,y) ((x) > (y) ? (x) : (y))
37 
38 //a mouse press within DRAG_THRESHOLD will move corners or sides
39 //if mouse press is not within DRAG_THRESHOLD of any corner or side of
40 //current selection a new selection will be started and both press and drag points will be reset
41 #define DRAG_THRESHOLD 8
42 
43 //a mouse press within MOVE_THRESHOLD of the center along both x and y axis will
44 //move the entier selection
45 #define MOVE_THRESHOLD 150
46 
47 //minimum size of a valid selection
48 #define MINIMUM_SELECTION_SIZE 3
49 
50 //==============================================
51 SelectionInterface::SelectionInterface(QWidget *parent, const char* name ) : QWidget(parent,name)
52 {
53  //avoid flicker when repainting
54  setWindowFlags(Qt::WNoAutoErase);
55 
56  //set default selection off screen
57  mousePressPoint = QPoint(-1,-1);
58  mouseDragPoint = QPoint(-1,-1);
59 
60  //by default mouse movement does not effect the current selection
63 
64  Q3Accel *keyAccel = new Q3Accel( this );
65  keyAccel->connectItem( keyAccel->insertItem( Qt::CTRL + Qt::Key_A),
66  this, SLOT(selectAll()) );
67  keyAccel->connectItem( keyAccel->insertItem( Qt::CTRL + Qt::SHIFT + Qt::Key_A ),
68  this, SLOT(selectNone()) );
69 
70  //watch mouse movements in order to change mouse cursor
71  setMouseTracking(true);
72 
73  //accept focus when clicked on
74  setFocusPolicy( Qt::ClickFocus );
75 
76  //initialize cached mouse position to be offscreen by default
77  cachedMousePosition = QPoint(-1,-1);
78 
79  //no crop size by default
80  cropMaxDimen = -1.0;
81 }
82 //==============================================
84 //==============================================
85 void SelectionInterface::setPhoto(QString imageFilename, bool resetSelection)
86 {
87  //store original image filename
88  origImageFilename = imageFilename;
89 
90  //store original image dimensions
92 
93  //resize image to current screen size for faster
94  //scaling during resize events
95  QRect screenSize = qApp->desktop()->availableGeometry();
96  scaleImage( origImageFilename, fullScreenImage, screenSize.width(), screenSize.height() );
97 
98  //construct display images
100 
101  //reset selection area to nothing
102  if(resetSelection)
103  {
104  mousePressPoint = QPoint(-1,-1);
105  mouseDragPoint = QPoint(-1,-1);
106  cropMaxDimen = -1.0;
107  SHIFT_Pressed = false;
108  CTRL_Pressed = false;
109  emit selectionChanged();
110  }
111 
112  //repaint widget
113  //if we are resetting the selection then aspect ratio may have changed
114  //so a full repaint (with erase) is necessary
115  repaint(resetSelection);
116 }
117 //==============================================
118 void SelectionInterface::resizeEvent( QResizeEvent * )
119 {
120  //if image has yet to be set return
121  if( fullScreenImage.isNull() ) return;
122 
123  //construct new images for painting
125 }
126 //==============================================
128 {
129  //rescale image to fit on screen
130  scaledImage = fullScreenImage.scaled( width(), height(), Qt::KeepAspectRatio );
131 
132  //construct an unselected scaled image
134  int x, y;
135  QRgb* rgb;
136  uchar* scanLine;
137  for( y=0; y<unselectedScaledImage.height(); y++)
138  {
139  //iterate over each selected pixel in scanline
140  scanLine = unselectedScaledImage.scanLine(y);
141  for( x=0; x<unselectedScaledImage.width(); x++)
142  {
143  //compress dynamic range to 25% of original
144  rgb = ((QRgb*)scanLine+x);
145 
146  double r = ((double)qRed(*rgb) )/255.0;
147  double g = ((double)qGreen(*rgb) )/255.0;
148  double b = ((double)qBlue(*rgb) )/255.0;
149 
150  //convert to hsv
151  double h,s,v;
152  RGBtoHSV(r,g,b,&h,&s,&v);
153 
154  //scale and clamp v
155  v*=0.25;
156 
157  //convert adjusted color back to rgb colorspace and clamp
158  HSVtoRGB( &r,&g,&b, h,s,v);
159  int rp = (int) MIN( MAX((r*255), 0), 255 );
160  int gp = (int) MIN( MAX((g*255), 0), 255 );
161  int bp = (int) MIN( MAX((b*255), 0), 255 );
162 
163  //set adjusted color value
164  *rgb = qRgb(rp,gp,bp);
165  }
166  }
167 
168 
169 }
170 //==============================================
171 void SelectionInterface::paintEvent(QPaintEvent *e)
172 {
173  //if no scaled image just return
174  if(scaledImage.isNull()) { return; }
175 
176  //create buffer to draw in
177  QRect rct = rect();
178  rct.moveBy(-x(), -y());
179  QPixmap buffer( size() );
180 
181  //create a painter pointing to the buffer
182  QPainter bufferPainter( &buffer );
183 
184  //turn off clipping to make painting operations faster
185  bufferPainter.setClipping(false);
186 
187  //initialize buffer with background brush
188  bufferPainter.fillRect( buffer.rect(), backgroundBrush() );
189 
190  //paint the image
191  int xOffset = (width() - scaledImage.width()) / 2;
192  int yOffset = (height() - scaledImage.height()) / 2;
193 
194  //if in draw line mode paint the image normall and then the currently selected line
195  //if the two points are set
197  {
198  //paint entire image normally
199  bufferPainter.drawImage( QPoint(xOffset, yOffset), scaledImage );
200 
201  //if the first and 2nd points have been set draw the line as well
202  if( mousePressPoint.x() != -1 &&
203  mouseDragPoint.x() != -1 )
204  {
205  //get points in display space
206  QPoint p1, p2;
209 
210  //setup painter to use green color
211  QPen pen;
212  pen.setStyle( Qt::SolidLine );
213  pen.setCapStyle( Qt::RoundCap );
214  pen.setWidth( 2 );
215  pen.setColor( Qt::green );
216  bufferPainter.setPen( pen);
217 
218  //draw line
219  bufferPainter.drawLine( p1.x(), p1.y(), p2.x(), p2.y() );
220  }
221  }
222  //else we're in normal selection mode
223  else
224  {
225  //if selected region is empty paint entire image in color
226  if( mousePressPoint.x() == -1 ||
227  (
229  (
230  mousePressPoint.x() - mouseDragPoint.x() == 0 ||
231  mousePressPoint.y() - mouseDragPoint.y() == 0
232  )
233  ))
234  {
235  bufferPainter.drawImage( QPoint(xOffset, yOffset), scaledImage );
236  }
237  //otherwise paint using a two-pass approach,
238  //first in monochrome and then the selected region in color
239  else
240  {
241  //first paint using unselected coloring
242  bufferPainter.drawImage( QPoint(xOffset, yOffset), unselectedScaledImage );
243 
244  //construct topLeft and bottomRight points to make logic easier
245  QPoint topLeft( MIN(mousePressPoint.x(), mouseDragPoint.x()),
246  MIN(mousePressPoint.y(), mouseDragPoint.y()) );
247  QPoint bottomRight( MAX(mousePressPoint.x(), mouseDragPoint.x()),
248  MAX(mousePressPoint.y(), mouseDragPoint.y()) );
249 
250  //convert coordinates from original image space to display space
251  topLeft = ConvertImageToDisplayCoordinate( topLeft );
252  bottomRight = ConvertImageToDisplayCoordinate( bottomRight );
253 
254  //now paint selected region in color
255  bufferPainter.drawImage( topLeft.x(),
256  topLeft.y(),
257  scaledImage,
258  topLeft.x()-xOffset, topLeft.y()-yOffset,
259  bottomRight.x() - topLeft.x(),
260  bottomRight.y() - topLeft.y() );
261 
262  //determine color for painting selection rectangle and dimensions
263  //use gray/white if cropping to a custom size (and thus real world dimensions
264  //are not known). If the crop size is known (eg 5"x7") use green if above
265  //resolution is great enough, otherwise use red to indicate that cropping to
266  //the specified size will result in pixelation during printing
267  QPen pen;
268  QColor selectionColor = Qt::gray;
269  QColor textColor = Qt::white;
270  int selectedWidth = QABS( mouseDragPoint.x() - mousePressPoint.x() ) + 1;
271  int selectedHeight = QABS( mouseDragPoint.y() - mousePressPoint.y() ) + 1;
272  if(selectedWidth == 1) selectedWidth = 0;
273  if(selectedHeight == 1) selectedHeight = 0;
274 
275  int DPI = -1;
276  int minDPI = -1;
277  if( cropMaxDimen != -1.0 )
278  {
279  //compute DPI that will be used in dominant direction
280  int maxDimen = QMAX( selectedWidth, selectedHeight );
281  DPI = (int) (maxDimen / cropMaxDimen);
282 
283  //if DPI is below minimum use red color for selection rectangle to indicate pixalation may occur
284  minDPI = ((Window*)qApp->mainWidget())->getConfig()->getInt( "misc", "minDPI" );
285  if( DPI < minDPI )
286  { selectionColor = QColor( 200, 0, 0 ); }
287  //otherwise use green to signal cropping to this size is safe
288  else { selectionColor = QColor( 0, 200, 0 ); }
289  }
290  pen.setStyle( Qt::SolidLine );
291  pen.setWidth( 2 );
292  bufferPainter.setPen( pen);
293 
294  //paint selection size in lower right corner of selected area
295  QString selectionText;
296  if( cropMaxDimen != -1.0 &&
297  DPI < minDPI )
298  selectionText = QString("%1 x %2 (DPI: %3!)").arg(selectedWidth).arg(selectedHeight).arg(DPI);
299  else
300  selectionText = QString("%1 x %2").arg(selectedWidth).arg(selectedHeight);
301 
302  QFontMetrics fm( this->font() );
303  int stringWidth = fm.width(selectionText);
304  int stringHeight = fm.ascent();
305 
306  int textX = 0;
307  int textY = 0;
308  const int TEXT_MARGIN = 4;
309  if( mouseDragPoint.x() > mousePressPoint.x() )
310  {
311  textX = MIN( xOffset + unselectedScaledImage.width() - TEXT_MARGIN - stringWidth,
312  bottomRight.x() + TEXT_MARGIN );
313  }
314  else
315  {
316  textX = MAX( xOffset + TEXT_MARGIN,
317  topLeft.x() - TEXT_MARGIN - stringWidth );
318  }
319 
320  if( mouseDragPoint.y() > mousePressPoint.y() )
321  {
322  textY = MIN( yOffset + unselectedScaledImage.height() - TEXT_MARGIN,
323  bottomRight.y() + TEXT_MARGIN + stringHeight);
324  }
325  else
326  {
327  textY = MAX( yOffset + TEXT_MARGIN + stringHeight,
328  topLeft.y() - TEXT_MARGIN );
329  }
330 
331  //paint a thin outline around the selection
332  pen.setColor( selectionColor );
333  bufferPainter.setPen( pen);
334  QRect selection( topLeft, bottomRight );
335  bufferPainter.drawRect(selection);
336 
337  //paint image under selected dimension using selected coloring to help make it more visible
338  bufferPainter.drawImage( textX, textY-stringHeight,
340  textX - xOffset, textY-stringHeight-yOffset,
341  stringWidth, fm.height() );
342 
343 
344  //paint selected dimensions
345  pen.setColor( textColor );
346  bufferPainter.setPen( pen);
347  bufferPainter.drawText( textX, textY, selectionText );
348  }
349  }
350 
351  bufferPainter.end();
352 
353  //blit buffer to screen
354  bitBlt( this,
355  e->rect().x(), e->rect().y(),
356  &buffer,
357  e->rect().x(), e->rect().y(),
358  e->rect().width(), e->rect().height() );
359 }
360 //==============================================
362 {
363  //If SHIFT_Pressed is set then scaling a selection
364  if(SHIFT_Pressed) { return SCALE_SELECTION; }
365 
366  //construct topLeft and bottomRight points to make logic easier
367  QPoint topLeft( MIN(mousePressPoint.x(), mouseDragPoint.x()),
368  MIN(mousePressPoint.y(), mouseDragPoint.y()) );
369  QPoint bottomRight( MAX(mousePressPoint.x(), mouseDragPoint.x()),
370  MAX(mousePressPoint.y(), mouseDragPoint.y()) );
371 
372  //convert selelection bounary to display space since thresholds are all in display space
373  topLeft = ConvertImageToDisplayCoordinate( topLeft );
374  bottomRight = ConvertImageToDisplayCoordinate( bottomRight );
375 
376  //compute selection center
377  QPoint center( (topLeft.x() + bottomRight.x()) / 2,
378  (topLeft.y() + bottomRight.y()) / 2);
379 
380  //if pointer is not over image no effect will occur when clicking
381  int xOffset = (width() - scaledImage.width() ) / 2;
382  int yOffset = (height() - scaledImage.height() ) / 2;
383  if(p.x() < xOffset || p.x() >= scaledImage.width() + xOffset ||
384  p.y() < yOffset || p.y() >= scaledImage.height() + yOffset )
385  { return NO_EFFECT; }
386 
387  //if either there is no selection or the entire image is selected, new
388  //mouse clicks are interpreted as being for selecting new regions
389  if( mousePressPoint.x() == -1 ||
390  ( bottomRight.x() - topLeft.x() == origImageSize.width() - 1 &&
391  bottomRight.y() - topLeft.y() == origImageSize.height() - 1 )
392  )
393  { return DRAW_SELECTION; }
394  //move entire selection
395  //mouse is within MOVE_THRESH of center and outside
396  //DRAG_THRESH of any side
397  else if( p.x() >= MAX( center.x() - MOVE_THRESHOLD, topLeft.x() ) &&
398  p.x() <= MIN( center.x() + MOVE_THRESHOLD, bottomRight.x() ) &&
399  p.y() >= MAX( center.y() - MOVE_THRESHOLD, topLeft.y() ) &&
400  p.y() <= MIN( center.y() + MOVE_THRESHOLD, bottomRight.y() ) &&
401  p.x() > topLeft.x() + DRAG_THRESHOLD &&
402  p.x() < bottomRight.x() - DRAG_THRESHOLD &&
403  p.y() > topLeft.y() + DRAG_THRESHOLD &&
404  p.y() < bottomRight.y() - DRAG_THRESHOLD )
405 { return MOVE_SELECTION; }
406  //drag top left
407  else if( QABS(topLeft.x() - p.x()) <= DRAG_THRESHOLD &&
408  QABS(topLeft.y() - p.y()) <= DRAG_THRESHOLD )
409  { return MOVE_TOP_LEFT_CORNER; }
410  //drag top right
411  else if( QABS(bottomRight.x() - p.x()) <= DRAG_THRESHOLD &&
412  QABS(topLeft.y() - p.y()) <= DRAG_THRESHOLD )
413  { return MOVE_TOP_RIGHT_CORNER; }
414  //drag bottom left
415  else if( QABS(topLeft.x() - p.x()) <= DRAG_THRESHOLD &&
416  QABS(bottomRight.y() - p.y()) <= DRAG_THRESHOLD )
417  { return MOVE_BOTTOM_LEFT_CORNER; }
418  //drag bottom right
419  else if( QABS(bottomRight.x() - p.x()) <= DRAG_THRESHOLD &&
420  QABS(bottomRight.y() - p.y()) <= DRAG_THRESHOLD )
421  { return MOVE_BOTTOM_RIGHT_CORNER; }
422  //drag left
423  else if( QABS(topLeft.x() - p.x()) <= DRAG_THRESHOLD &&
424  p.y() >= topLeft.y() &&
425  p.y() <= bottomRight.y() )
426  { return MOVE_LEFT_SIDE; }
427  //drag right
428  else if( QABS(bottomRight.x() - p.x()) <= DRAG_THRESHOLD &&
429  p.y() >= topLeft.y() &&
430  p.y() <= bottomRight.y() )
431  { return MOVE_RIGHT_SIDE; }
432  //drag top
433  else if( QABS(topLeft.y() - p.y()) <= DRAG_THRESHOLD &&
434  p.x() >= topLeft.x() &&
435  p.x() <= bottomRight.x() )
436  { return MOVE_TOP_SIDE; }
437  //drag bottom
438  else if( QABS(bottomRight.y() - p.y()) <= DRAG_THRESHOLD &&
439  p.x() >= topLeft.x() &&
440  p.x() <= bottomRight.x() )
441  { return MOVE_BOTTOM_SIDE; }
442  //else new selection
443  else { return DRAW_SELECTION; }
444 }
445 //==============================================
447 {
448  //If CTRL pressed then emit ctrlClick event
449  if( CTRL_Pressed)
450  {
451  emit ctrlClick();
452  return;
453  }
454 
455  //get mouse position in original image coordinates
456  QPoint p = ConvertDisplayToImageCoordinate( e->pos() );
457 
458  //in draw line mode a mouse press has not immediate effect,
459  //mouse release is how a point is finally selected
460  if( currentDragMode == DRAW_LINE )
461  { return; }
462 
463  //construct topLeft and bottomRight points to make logic easier
464  QPoint topLeft( MIN(mousePressPoint.x(), mouseDragPoint.x()),
465  MIN(mousePressPoint.y(), mouseDragPoint.y()) );
466  QPoint bottomRight( MAX(mousePressPoint.x(), mouseDragPoint.x()),
467  MAX(mousePressPoint.y(), mouseDragPoint.y()) );
468 
469  //determine action mouse click will cause
471 
472  switch(currentDragMode)
473  {
474  case DRAW_SELECTION:
475  mousePressPoint = p;
476  mouseDragPoint = p;
477  cropMaxDimen = -1.0;
478  emit aspectRatioChanged();
479  break;
480  case MOVE_SELECTION:
481  //nothing done until mouse moves
482  break;
486  break;
490  break;
492  mousePressPoint = QPoint( topLeft.x(), bottomRight.y() );
493  mouseDragPoint = QPoint( bottomRight.x(), topLeft.y() );
494  break;
496  mousePressPoint = QPoint( bottomRight.x(), topLeft.y() );
497  mouseDragPoint = QPoint( topLeft.x(), bottomRight.y() );
498  break;
499  case MOVE_LEFT_SIDE:
501  mouseDragPoint = topLeft;
502  break;
503  case MOVE_RIGHT_SIDE:
505  mouseDragPoint = bottomRight;
506  break;
507  case MOVE_TOP_SIDE:
509  mouseDragPoint = topLeft;
510  break;
511  case MOVE_BOTTOM_SIDE:
513  mouseDragPoint = bottomRight;
514  break;
515  //no effect
516  default:
517  return;
518  }
519 
520  //repaint the selection
521  repaint(false);
522  emit selectionChanged();
523 }
524 //==============================================
526 {
527  //if resizing a selection always use size all cursor
528  if(SHIFT_Pressed)
529  {
530  setCursor( getCursor(SCALE_SELECTION_CURSOR) );
531  return;
532  }
533 
534  //if ctrl pressed show rotate selection cursor
535  if(CTRL_Pressed)
536  {
537  setCursor( getCursor(ROTATE_CURSOR) );
538  return;
539  }
540 
541  //if selecting a line the cursor is always the same
542  if( currentDragMode == DRAW_LINE )
543  {
544  setCursor( getCursor(TARGET_CURSOR) );
545  return;
546  }
547 
548  //update mouse cursor based on coordinates
550  switch(currentMouseShape)
551  {
552  case DRAW_SELECTION:
553  setCursor( getCursor(CROSS_CURSOR) ); break;
554  case MOVE_SELECTION:
555  setCursor( getCursor(MOVE_SELECTION_CURSOR) ); break;
558  setCursor( getCursor(MOVE_TL_CURSOR) ); break;
561  setCursor( getCursor(MOVE_TR_CURSOR) ); break;
562  case MOVE_LEFT_SIDE:
563  case MOVE_RIGHT_SIDE:
564  setCursor( getCursor(MOVE_HOR_CURSOR) ); break;
565  case MOVE_TOP_SIDE:
566  case MOVE_BOTTOM_SIDE:
567  setCursor( getCursor(MOVE_VERT_CURSOR) ); break;
568  break;
569  default:
570  setCursor( Qt::ArrowCursor );
571  break;
572  }
573 }
574 //==============================================
576 {
577  //construct topLeft and bottomRight points to make logic easier
578  QPoint topLeft( MIN(mousePressPoint.x(), mouseDragPoint.x()),
579  MIN(mousePressPoint.y(), mouseDragPoint.y()) );
580  QPoint bottomRight( MAX(mousePressPoint.x(), mouseDragPoint.x()),
581  MAX(mousePressPoint.y(), mouseDragPoint.y()) );
582 
583  //compute new selection dimension
584  int curSelectionWidth = bottomRight.x() - topLeft.x() + 1;
585  int curSelectionHeight = bottomRight.y()- topLeft.y() + 1;
586  int newSelectionWidth = -1;
587  int newSelectionHeight = -1;
588 
589  //adjust in dimension of greater resolution
590  if( curSelectionWidth > curSelectionHeight )
591  {
592  newSelectionWidth = MIN( curSelectionWidth + delta, origImageSize.width() );
593  newSelectionHeight = (newSelectionWidth * cachedSelectionSize.height()) / cachedSelectionSize.width();
594  }
595  else
596  {
597  newSelectionHeight = MIN( curSelectionHeight + delta, origImageSize.height() );
598  newSelectionWidth = (newSelectionHeight * cachedSelectionSize.width()) / cachedSelectionSize.height();
599  }
600 
601  //if selection width or height already maxed out then ignore attempt to make selection bigger
602  if(delta > 0 &&
603  ( curSelectionWidth == origImageSize.width() ||
604  curSelectionHeight == origImageSize.height() ) )
605  {
606  return false;
607  }
608 
609  //again prevent from growing too big, now along y-axis
610  if(newSelectionHeight > origImageSize.height())
611  {
612  newSelectionHeight = origImageSize.height();
613  newSelectionWidth = (newSelectionHeight * cachedSelectionSize.width()) / cachedSelectionSize.height();
614  }
615 
616  //prevent selection becoming empty
617  if(newSelectionWidth <= 0 || newSelectionHeight <= 0)
618  {
619  newSelectionWidth = curSelectionWidth;
620  newSelectionHeight = curSelectionHeight;
621  }
622 
623  //center new selection over current selection , move if necessary
624  topLeft = QPoint(QMAX( cachedSelctionCenter.x() - newSelectionWidth/2, 0 ),
625  QMAX( cachedSelctionCenter.y() - newSelectionHeight/2, 0 ));
626  bottomRight = QPoint( topLeft.x() + newSelectionWidth - 1,
627  topLeft.y() + newSelectionHeight - 1 );
628 
629  //too far right
630  if(bottomRight.x() > origImageSize.width() - 1 )
631  {
632  int diff = bottomRight.x() - (origImageSize.width() - 1);
633  topLeft.setX( topLeft.x() - diff );
634  bottomRight.setX( bottomRight.x() - diff );
635 
636  //recompute center
637  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
638  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
639  }
640 
641  //too far down
642  if(bottomRight.y() > origImageSize.height() - 1 )
643  {
644  int diff = bottomRight.y() - (origImageSize.height() - 1);
645  topLeft.setY( topLeft.y() - diff );
646  bottomRight.setY( bottomRight.y() - diff );
647 
648  //recompute center
649  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
650  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
651  }
652 
653  //set new selection
656 
657  //success
658  return true;
659 }
660 //==============================================
662 {
663  //if the SHIFT_Pressed state variable is true but SHIFT is now not pressed
664  //reset bool and mouse cursor!
665  if( SHIFT_Pressed && !(e->state() & Qt::ShiftModifier) )
666  {
667  SHIFT_Pressed = false;
669  }
670 
671  //if the CTRL_Pressed state variable is true but CTRL is now not pressed
672  //reset bool and mouse cursor!
673  if( CTRL_Pressed && !(e->state() & Qt::ControlModifier) )
674  {
675  CTRL_Pressed = false;
677  }
678 
679  //if mouse not pressed update mouse cursor if
680  //mode will change if user presses mouse button
682  {
683  //only update the mouse cursor shape if it will change
684  if( mouseActionByPosition( e->pos() ) != currentMouseShape )
685  updateCursorShape( e->pos() );
686 
688  }
689  //if currently in draw line mode update the selected line
690  else if(currentDragMode == DRAW_LINE)
691  {
692  //if the 1st point hasn't been set yet do nothing
693  if( mousePressPoint.x() == -1 )
694  return;
695 
696  //1st point set, set 2nd point to current mouse position and redraw
698 
699  //repaint the line
700  repaint(false);
701  }
702  //update selection rectangle or selected line
703  else
704  {
705  //get mouse position in original image coordinates
706  QPoint p = ConvertDisplayToImageCoordinate( cropSelectedPoint(e->pos()) );
707 
708  //construct topLeft and bottomRight points to make logic easier
709  QPoint topLeft( MIN(mousePressPoint.x(), mouseDragPoint.x()),
710  MIN(mousePressPoint.y(), mouseDragPoint.y()) );
711  QPoint bottomRight( MAX(mousePressPoint.x(), mouseDragPoint.x()),
712  MAX(mousePressPoint.y(), mouseDragPoint.y()) );
713 
714  //if SHIFT pressed and both selction dimensions non-empty then scale selection
715  if(SHIFT_Pressed &&
716  bottomRight.x() > topLeft.x() &&
717  bottomRight.y() > topLeft.y() )
718  {
719  //update width, prevent from growing beyond image boundaries
720  int delta = p.x() - cachedMousePosition.x();
721  if( !scaleSelection( delta ) )
722  {
723  //cache the current mouse position before returning without change
725  return;
726  }
727  }
728  //if draging the entire selection, update x and y coordinates for
729  //both mousePress and mouseDrag points and update the dragSelectionPoint
730  else if( currentDragMode == MOVE_SELECTION )
731  {
732  //compute offset
733  QPoint offset( p.x() - cachedMousePosition.x(),
734  p.y() - cachedMousePosition.y() );
735 
736  //construct topLeft and bottomRight points to make logic easier
737  QPoint topLeft( MIN(mousePressPoint.x(), mouseDragPoint.x()),
738  MIN(mousePressPoint.y(), mouseDragPoint.y()) );
739  QPoint bottomRight( MAX(mousePressPoint.x(), mouseDragPoint.x()),
740  MAX(mousePressPoint.y(), mouseDragPoint.y()) );
741 
742  //clip offset so selected area is always within image boundaries
743 
744  //left
745  if(offset.x() < 0 && topLeft.x() + offset.x() < 0 )
746  offset.setX( -topLeft.x() );
747 
748  //right
749  if(offset.x() > 0 && bottomRight.x() + offset.x() >= origImageSize.width() )
750  offset.setX( origImageSize.width() - 1 - bottomRight.x() );
751 
752  //top
753  if(offset.y() < 0 && topLeft.y() + offset.y() < 0 )
754  offset.setY( -topLeft.y() );
755 
756  //bottom
757  if(offset.y() > 0 && bottomRight.y() + offset.y() >= origImageSize.height() )
758  offset.setY( origImageSize.height() - 1 - bottomRight.y() );
759 
760  //update press and drag points
761  mousePressPoint+= offset;
762  mouseDragPoint+= offset;
763 
764  //recompute selection center
765  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
766  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
767  }
768  //if moving any corner or drawing the original selection,
769  //update x and y coords of drag point
770  else if( currentDragMode == DRAW_SELECTION ||
775  {
776  mouseDragPoint.setX( p.x() );
777  mouseDragPoint.setY( p.y() );
778  cropMaxDimen = -1.0;
779 
780  //cache selection size and center, necessary for computing next size
781  cachedSelectionSize = QSize( QABS( mouseDragPoint.x() - mousePressPoint.x() )+1,
782  QABS( mouseDragPoint.y() - mousePressPoint.y() )+1);
783  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
784  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
785 
786 
787  emit aspectRatioChanged();
788  }
789  //else if moving the left or right sides just update the x coordinate of
790  //the drag point
791  else if( currentDragMode == MOVE_LEFT_SIDE ||
793  {
794  mouseDragPoint.setX( p.x() );
795  cropMaxDimen = -1.0;
796  emit aspectRatioChanged();
797 
798  //cache selection size and center, necessary for computing next size
799  cachedSelectionSize = QSize( QABS( mouseDragPoint.x() - mousePressPoint.x() )+1,
800  QABS( mouseDragPoint.y() - mousePressPoint.y() )+1);
801  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
802  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
803  }
804  //else if moving the top or bottom sides just update the x coordinate of
805  //the drag point
806  else if( currentDragMode == MOVE_TOP_SIDE ||
808  {
809  mouseDragPoint.setY( p.y() );
810  cropMaxDimen = -1.0;
811  emit aspectRatioChanged();
812 
813  //cache selection size and center, necessary for computing next size
814  cachedSelectionSize = QSize( QABS( mouseDragPoint.x() - mousePressPoint.x() )+1,
815  QABS( mouseDragPoint.y() - mousePressPoint.y() )+1);
816  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
817  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
818 
819  }
820 
821  //cache current mouse position
823 
824  //repaint the selection
825  repaint(false);
826  emit selectionChanged();
827 
828  //only update the mouse cursor shape if it will change
829  if( mouseActionByPosition( e->pos() ) != currentMouseShape )
830  updateCursorShape( e->pos() );
831  }
832 }
833 //==============================================
835 {
836  //if in draw line mode
837  if( currentDragMode == DRAW_LINE )
838  {
839  //get mouse location in image space
840  QPoint p = ConvertDisplayToImageCoordinate( e->pos() );
841 
842  //if first point has not been set set it now
843  if( mousePressPoint.x() == -1 )
844  {
845  mousePressPoint = p;
846  repaint(false);
847  }
848  //else first point has been set then we're setting the 2nd point, emit the selected line
849  else
850  {
851  //get two points on line
852  QPoint p1 = mousePressPoint;
853  QPoint p2 = p;
854 
855  //reset mode, press and drag points so there is no selection
857  selectNone();
859 
860  //emit the selected line
861  emit lineSelected( p1, p2 );
862  }
863  return;
864  }
865 
866  //disable move moving from having an effect on the selection
868 
869  //construct topLeft and bottomRight points to make logic easier
870  QPoint topLeft( MIN(mousePressPoint.x(), mouseDragPoint.x()),
871  MIN(mousePressPoint.y(), mouseDragPoint.y()) );
872  QPoint bottomRight( MAX(mousePressPoint.x(), mouseDragPoint.x()),
873  MAX(mousePressPoint.y(), mouseDragPoint.y()) );
874 
875  //convert selection region to display space to check for tiny selections to ignore
876  topLeft = ConvertImageToDisplayCoordinate( topLeft );
877  bottomRight = ConvertImageToDisplayCoordinate( bottomRight );
878 
879  //if selection is very small reset to select nothing
880  if( bottomRight.x() - topLeft.x() + 1 < MINIMUM_SELECTION_SIZE ||
881  bottomRight.y() - topLeft.y() + 1 < MINIMUM_SELECTION_SIZE ) { selectNone(); }
882 }
883 //==============================================
885 {
886  QPoint newPoint = p;
887 
888  //remove display offset
889  int xOffset = (width() - scaledImage.width() ) / 2;
890  int yOffset = (height() - scaledImage.height() ) / 2;
891  newPoint.setX( newPoint.x() - xOffset );
892  newPoint.setY( newPoint.y() - yOffset );
893 
894  //if coordinate is at max set new value explicitly to avoid roundoff error,
895  //otherwise scale to full image dimensions
896  if(newPoint.x() == scaledImage.width() - 1)
897  newPoint.setX( origImageSize.width() - 1);
898  else
899  newPoint.setX( (int) (0.5 + ((double)(newPoint.x() * (origImageSize.width()-1))) / (scaledImage.width()-1) ) );
900 
901  if(newPoint.y() == scaledImage.height() - 1)
902  newPoint.setY( origImageSize.height() - 1);
903  else
904  newPoint.setY( (int) (0.5 + ((double)(newPoint.y() * (origImageSize.height()-1))) / (scaledImage.height()-1) ) );
905 
906  //return point in image coordinates
907  return newPoint;
908 }
909 //==============================================
911 {
912  QPoint newPoint = p;
913 
914  //if coordinate is at max set new value explicitly to avoid roundoff error,
915  //otherwise scale to full image dimensions
916  if(newPoint.x() == origImageSize.width() - 1)
917  newPoint.setX( scaledImage.width() - 1);
918  else
919  newPoint.setX( (newPoint.x() * (scaledImage.width()-1)) / (origImageSize.width()-1) );
920 
921  if(newPoint.y() == origImageSize.height() - 1)
922  newPoint.setY( scaledImage.height() - 1);
923  else
924  newPoint.setY( (newPoint.y() * (scaledImage.height()-1)) / (origImageSize.height()-1) );
925 
926  //add display offset
927  int xOffset = (width() - scaledImage.width() ) / 2;
928  int yOffset = (height() - scaledImage.height() ) / 2;
929  newPoint.setX( newPoint.x() + xOffset );
930  newPoint.setY( newPoint.y() + yOffset );
931 
932  //return point in image coordinates
933  return newPoint;
934 }
935 //==============================================
937 {
938  int xOffset = (width() - scaledImage.width() ) / 2;
939  int yOffset = (height() - scaledImage.height() ) / 2;
940 
941  QPoint croppedPoint;
942  croppedPoint.setX( MIN( MAX(xOffset, p.x()), xOffset + scaledImage.width() - 1 ) );
943  croppedPoint.setY( MIN( MAX(yOffset, p.y()), yOffset + scaledImage.height() - 1 ) );
944  return croppedPoint;
945 }
946 //==============================================
948 {
949  return (
950  mousePressPoint.x() == -1 ||
951  mouseDragPoint.x() - mousePressPoint.x() == 0 ||
952  mouseDragPoint.y() - mousePressPoint.y() == 0
953  );
954 }
955 //==============================================
957 {
958  mousePressPoint.setX( 0 );
959  mousePressPoint.setY( 0 );
960  mouseDragPoint.setX( origImageSize.width() - 1 );
961  mouseDragPoint.setY( origImageSize.height() - 1);
962 
963  //cache selection size and center, necessary for computing next size
964  cachedSelectionSize = QSize( QABS( mouseDragPoint.x() - mousePressPoint.x() )+1,
965  QABS( mouseDragPoint.y() - mousePressPoint.y() )+1);
966  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
967  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
968 
969  cropMaxDimen = -1.0;
970  repaint(false);
971  emit selectionChanged();
972  emit aspectRatioChanged();
973 }
974 //==============================================
976 {
977  mousePressPoint = QPoint(-1,-1);
978  mouseDragPoint = QPoint(-1,-1);
979  cropMaxDimen = -1.0;
980  repaint(false);
981  emit selectionChanged();
982  emit aspectRatioChanged();
983 }
984 //==============================================
986  double cropMaxDimen)
987 {
990 
991  this->cropMaxDimen = cropMaxDimen;
992 
993  //cache selection size and center, necessary for computing next size
994  cachedSelectionSize = QSize( QABS( mouseDragPoint.x() - mousePressPoint.x() )+1,
995  QABS( mouseDragPoint.y() - mousePressPoint.y() )+1);
996  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
997  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
998 
999  repaint(false);
1000  emit selectionChanged();
1001 }
1002 //==============================================
1004 {
1005  //if none selected just return immediately
1006  if(mousePressPoint.x() == -1)
1007  {
1008  topLeft.setX(-1); topLeft.setY(-1);
1009  bottomRight.setX(-1); bottomRight.setY(-1);
1010  return;
1011  }
1012 
1013  //set coordinates based on raw selection
1014  topLeft.setX( MIN(mousePressPoint.x(), mouseDragPoint.x()) );
1015  topLeft.setY( MIN(mousePressPoint.y(), mouseDragPoint.y()) );
1016  bottomRight.setX( MAX( mousePressPoint.x(), mouseDragPoint.x()) );
1017  bottomRight.setY( MAX( mousePressPoint.y(), mouseDragPoint.y()) );
1018 }
1019 //==============================================
1021 {
1022  width = scaledImage.width();
1023  height = scaledImage.height();
1024 }
1025 //==============================================
1027 {
1028  //if currently drawing a line, allow the user
1029  //to escape the current acction by hiting escape, all other
1030  //key presses are ignored
1031  if( currentDragMode == DRAW_LINE )
1032  {
1033  if(e->key() == Qt::Key_Escape )
1034  {
1035  //reset mode, press and drag points so there is no selection, and mouse cursor
1037  selectNone();
1039 
1040  //emit an invalid line
1041  emit lineSelected( QPoint(-1,-1), QPoint(-1,-1) );
1042  }
1043 
1044  return;
1045  }
1046 
1047  //if user pressed SHIFT button and selection non-empty
1048  //then note state change,
1049  //this will effect resizing selections during drag events
1050  if(e->key() == Qt::Key_Shift && !selectionEmpty() )
1051  {
1052  SHIFT_Pressed = true;
1054  return;
1055  }
1056 
1057  //if user pressed CTRL button and selection non-empty
1058  //then note state change,
1059  //this will effect mouse clicks by rotating the current selection
1060  if(e->key() == Qt::Key_Control && !selectionEmpty() && !SHIFT_Pressed )
1061  {
1062  CTRL_Pressed = true;
1064  return;
1065  }
1066 
1067  //ignore keypress if selection is empty
1068  if(mousePressPoint.x() == -1 ||
1069  mousePressPoint.x() - mouseDragPoint.x() == 0 ||
1070  mousePressPoint.y() - mouseDragPoint.y() == 0)
1071  {
1072  e->ignore();
1073  return;
1074  }
1075 
1076  //ignore keypresses while mouse button is down since
1077  //intended behavious is unclear
1078  if( currentDragMode != NO_EFFECT )
1079  {
1080  e->ignore();
1081  return;
1082  }
1083 
1084  //-------
1085  //if += / -_ keys are pressed scale selection
1086  if( e->key() == Qt::Key_Plus ||
1087  e->key() == Qt::Key_Equal ||
1088  e->key() == Qt::Key_Minus ||
1089  e->key() == Qt::Key_Underscore )
1090  {
1091  int delta = 2*QMAX( origImageSize.width(), scaledImage.width() ) / scaledImage.width();
1092 
1093  delta = 1;
1094 
1095  //negate delta if decreasing size
1096  if( e->key() == Qt::Key_Minus ||
1097  e->key() == Qt::Key_Underscore )
1098  delta = -delta;
1099 
1100  if( scaleSelection( delta ) )
1101  {
1102  //repaint the selection
1103  repaint(false);
1104  emit selectionChanged();
1105  }
1106  return;
1107  }
1108  //-------
1109 
1110  //find topleft and bottom right of current selection
1111  QPoint topLeft, bottomRight;
1112  topLeft.setX( MIN(mousePressPoint.x(), mouseDragPoint.x()) );
1113  topLeft.setY( MIN(mousePressPoint.y(), mouseDragPoint.y()) );
1114  bottomRight.setX( MAX( mousePressPoint.x(), mouseDragPoint.x()) );
1115  bottomRight.setY( MAX( mousePressPoint.y(), mouseDragPoint.y()) );
1116 
1117  //compute the number of pixels in the image correspond to one display pixel
1118  //this is the unit by which the selection will be moved
1119  int moveBy = 0;
1120  if( e->key() == Qt::Key_Left ||
1121  e->key() == Qt::Key_Right )
1122  {
1123  moveBy = QMAX( origImageSize.width(), scaledImage.width() ) / scaledImage.width();
1124  }
1125  else
1126  {
1127  moveBy = QMAX( origImageSize.height(), scaledImage.height() ) / scaledImage.height();
1128  }
1129 
1130  //boundary checked moved by value. must be decalred outside switch statement below
1131  int dx = 0;
1132  int dy = 0;
1133  switch( e->key() )
1134  {
1135  case Qt::Key_Left:
1136  dx = QMAX( topLeft.x() - moveBy, 0) - topLeft.x();
1137  break;
1138  case Qt::Key_Right:
1139  dx = MIN( bottomRight.x() + moveBy, origImageSize.width() - 1) - bottomRight.x();
1140  break;
1141  case Qt::Key_Up:
1142  dy = MAX( topLeft.y() - moveBy, 0) - topLeft.y();
1143  break;
1144  case Qt::Key_Down:
1145  dy = MIN( bottomRight.y() + moveBy, origImageSize.height() - 1) - bottomRight.y();
1146  break;
1147  default:
1148  e->ignore();
1149  return;
1150  }
1151 
1152  //shift by dx
1153  mousePressPoint.setX( mousePressPoint.x() + dx );
1154  mouseDragPoint.setX( mouseDragPoint.x() + dx );
1155 
1156  //shift by dy
1157  mousePressPoint.setY( mousePressPoint.y() + dy );
1158  mouseDragPoint.setY( mouseDragPoint.y() + dy );
1159 
1160  //recompute center
1161  cachedSelctionCenter = QPoint( ( mouseDragPoint.x() + mousePressPoint.x() )/2,
1162  ( mouseDragPoint.y() + mousePressPoint.y() )/2 );
1163 
1164  //only bother with repaint and updating cursor shape if shift actually occured
1165  if(dx != 0 || dy != 0)
1166  {
1167  repaint(false);
1169  }
1170 }
1171 //==============================================
1173 {
1174  //if user released SHIFT button then note state change,
1175  //this will effect resizing selections during drag events
1176  if(e->key() == Qt::Key_Shift)
1177  {
1178  SHIFT_Pressed = false;
1180  }
1181  //if user released CTRL button then note state change,
1182  //this will effect mouse clicks
1183  else if(e->key() == Qt::Key_Control)
1184  {
1185  CTRL_Pressed = false;
1187  }
1188  //unhandled key press: pass up the object tree
1189  else { e->ignore(); }
1190 }
1191 //==============================================
1193 {
1194  //set the current mode
1196 
1197  //during draw line mode ignore control and shift keys, they are only useful for adjusting
1198  //selections, not lines
1199  SHIFT_Pressed = false;
1200  CTRL_Pressed = false;
1201 
1202  //reset 1st point
1203  mousePressPoint = QPoint( -1, -1 );
1204  mouseDragPoint = QPoint( -1, -1 );
1205 
1206  //repaint and reset the mouse cursor
1208  repaint(false);
1209 }
1210 //==============================================
1211 
QPoint bottomRight
QSize origImageSize
original image dimensions
void mousePressEvent(QMouseEvent *e)
void updateCursorShape(QPoint p)
update mouse cursor based on position over widget and selected region
QPoint topLeft
void keyPressEvent(QKeyEvent *e)
QString origImageFilename
original image filename
void HSVtoRGB(double *r, double *g, double *b, double h, double s, double v)
Convert a HSV color triplet to RGB.
Definition: imageTools.cpp:264
#define TEXT_MARGIN
~SelectionInterface()
Deletes objects.
Top level widget, encapsulates the title widget, the layout widget, and the toolbar widget...
Definition: window.h:39
void selectionChanged()
emitted when the user changed the selected region
#define MOVE_THRESHOLD
long b
Definition: jpegInternal.h:125
void constructDisplayImages()
construct scaled image and unselected images for drawing purposes
QPoint mousePressPoint
first corner of selection, where mouse first clicked
DRAG_MODE
current drag mode, effect of mouse movement on selected range
double cropMaxDimen
Current crop max dimension (in inches)
QPoint mouseDragPoint
second corner of selection, where mouse moved to
void keyReleaseEvent(QKeyEvent *e)
void enterDrawLineMode()
enter draw line mode - used for tilt correction
bool SHIFT_Pressed
state of SHIFT button, effects if mouse drags adjust or scale the current selection ...
void setPhoto(QString imageFilename, bool resetSelection=true)
Updates displayed photo.
DRAG_MODE currentDragMode
method of dragging out, either new selection or resizing selection corner or side ...
int width
Definition: blur.cpp:79
QPoint cropSelectedPoint(QPoint p)
crops a selected point to within the photo
void selectAll()
selects all of the image
QPoint ConvertDisplayToImageCoordinate(QPoint p)
converts a point from display coordinates to original image coordinates
void mouseReleaseEvent(QMouseEvent *)
void paintEvent(QPaintEvent *e)
void getSelection(QPoint &topLeft, QPoint &bottomRight)
Returns the current selected coordinates (actual slideshow image space, aka not including buffered wh...
#define MAX(x, y)
bool CTRL_Pressed
state of CTRL button, effects if mouse clicks rotate current selection
QPoint cachedSelctionCenter
cache the aspect ratio when pressing the control buttion, this helps aleviate numerical error that bu...
bool scaleImage(QString fileIn, QString fileOut, int newWidth, int newHeight)
Scale image and save copy to disk.
Definition: imageTools.cpp:157
void getDisplaySize(int &width, int &height)
returns the current photo display size (in screen pixels)
void setSelection(QPoint topLeft, QPoint bottomRight, double cropMaxDimen=-1.0)
Sets the current selection cropMaxDimen specifies the idealized dimension in dominant direction in in...
bool getImageSize(const char *filename, QSize &size)
Get image dimensions.
Definition: imageTools.cpp:192
const QCursor & getCursor(CUSTOM_CURSOR_TYPE type)
Definition: cursors.cpp:52
#define MIN(x, y)
DRAG_MODE currentMouseShape
current mouse shape.
void aspectRatioChanged()
emitted when the user changed the aspect ratio of the selected region
#define MINIMUM_SELECTION_SIZE
QPoint cachedMousePosition
cached mouse position, used to scale or drag around selection area
void resizeEvent(QResizeEvent *)
float * buffer
Definition: blur.cpp:80
void RGBtoHSV(double r, double g, double b, double *h, double *s, double *v)
Convert a RGB color triplet to HSV.
Definition: imageTools.cpp:231
void selectNone()
selects none of the image
void ctrlClick()
emitted when a user CTRL-clicks a selection indicating the selection needs to be rotated intelligentl...
void lineSelected(QPoint p1, QPoint p2)
emitted once line has been selected, on or the other points will be set to -1,-1 if the user escaped ...
bool scaleSelection(int delta)
increase/decrease selection while maintaining aspect ratio by changing selected width by delta ...
QImage unselectedScaledImage
Grayscale version of scaled image, used for drawing non-selected regions.
void mouseMoveEvent(QMouseEvent *e)
QPoint ConvertImageToDisplayCoordinate(QPoint p)
converts a point from original image coordinates to display coordinates
bool selectionEmpty()
returns true if selection is empty
DRAG_MODE mouseActionByPosition(QPoint p)
determine action based on mouse position
SelectionInterface(QWidget *parent=0, const char *name=0)
Creates layout.
QImage scaledImage
Scaled image used for display purposes.
QImage fullScreenImage
Full screen version of image.
int height
Definition: blur.cpp:79
#define DRAG_THRESHOLD