AlbumShaper  1.0a3
histogramInterface.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 <qapplication.h>
13 #include <qpainter.h>
14 #include <qpixmap.h>
15 #include <qimage.h>
16 #include <q3accel.h>
17 #include <qcursor.h>
18 //Added by qt3to4:
19 #include <QPaintEvent>
20 #include <QResizeEvent>
21 #include <QMouseEvent>
22 #include <QDesktopWidget>
23 
24 //Projectwide includes
25 #include "histogramInterface.h"
26 #include "histogramEditor.h"
27 #include "../../backend/tools/imageTools.h"
28 #include "../cursors.h"
29 
30 //a mouse press within DRAG_THRESHOLD will move boundaries of selection
31 //if mouse press is not within DRAG_THRESHOLD a new selection will be started
32 //and both click and drag points will be reset
33 #define DRAG_THRESHOLD 5
34 
35 #define COLOR_BAR_MARGIN 2
36 #define COLOR_BAR_BORDER 2
37 #define COLOR_BAR_HEIGHT 6
38 #define HISTOGRAM_HEIGHT ( height() - COLOR_BAR_BORDER - 2*COLOR_BAR_MARGIN - COLOR_BAR_HEIGHT )
39 
40 //==============================================
42  QWidget *parent, const char* name ) :
43  QWidget (parent, name, Qt::WNoAutoErase)
44 {
45  //set default mode to adjusted image
47 
48  //record original image width and height
49  getImageSize( imageFilename, origImageSize );
50 
51  //construct histogram for color and luminosity channels
52  //resize image to current screen size for faster
53  //scaling during resize events
54  QRect screenSize = qApp->desktop()->availableGeometry();
55  QImage image;
56  scaleImage( imageFilename, image, screenSize.width()/4, screenSize.height()/4 );
57 
58  int i;
59  for(i=0; i<256; i++)
60  {
61  redVals[i] = 0;
62  greenVals[i] = 0;
63  blueVals[i] = 0;
64  grayVals[i] = 0;
65  }
66  int x, y;
67  QRgb* rgb;
68  uchar* scanLine;
69  for( y=0; y<image.height(); y++)
70  {
71  scanLine = image.scanLine(y);
72  for( x=0; x<image.width(); x++)
73  {
74  rgb = ((QRgb*)scanLine+x);
75  redVals[ qRed(*rgb) ]++;
76  greenVals[ qGreen(*rgb) ]++;
77  blueVals[ qBlue(*rgb) ]++;
78  grayVals[ qGray(*rgb) ]++;
79  } //x
80  } //y
81 
82  //find max r,g,b, and gray counts
83  maxRcount = 0;
84  maxGcount = 0;
85  maxBcount = 0;
86  maxGRAYcount = 0;
87  for(i=0; i<256; i++)
88  {
89  if(redVals[i] > maxRcount) maxRcount = redVals[i];
90  if(greenVals[i] > maxGcount) maxGcount = greenVals[i];
91  if(blueVals[i] > maxBcount) maxBcount = blueVals[i];
93  }
94  //----
95  //by default mouse drags have no effect
98 
99  //watch mouse movements in order to drag selection
100  setMouseTracking(true);
101 
102  //accept focus when clicked on
103  setFocusPolicy( Qt::ClickFocus );
104 
106  Q3Accel *keyAccel = new Q3Accel( this );
107  keyAccel->connectItem( keyAccel->insertItem( Qt::CTRL + Qt::Key_A),
108  this, SLOT(selectAll()) );
109 
110  //default cursor is cross hair indication regions can be selected
111  setCursor( getCursor(CROSS_CURSOR) );
112 
113  //by default entire range is selected for all channels
114  resetBoundaries();
115 }
116 //==============================================
118 //==============================================
119 void HistogramInterface::resizeEvent( QResizeEvent * )
120 {
121  repaint(false);
122 }
123 //==============================================
124 void HistogramInterface::getSelectedRange( int &left, int &right )
125 {
127  {
128  left = QMIN( lumClick, lumDrag );
129  right = QMAX( lumClick, lumDrag );
130  }
131  else if(displayedChannel == RED)
132  {
133  left = QMIN( redClick, redDrag );
134  right = QMAX( redClick, redDrag );
135  }
136  else if(displayedChannel == GREEN)
137  {
138  left = QMIN( greenClick, greenDrag );
139  right = QMAX( greenClick, greenDrag );
140  }
141  else if(displayedChannel == BLUE)
142  {
143  left = QMIN( blueClick, blueDrag );
144  right = QMAX( blueClick, blueDrag );
145  }
146  else
147  { left = 0; right = 0; }
148 }
149 //==============================================
150 double HistogramInterface::displayToIndex( int coordinate )
151 {
152  return (255.0*coordinate) / ( width()-1 );
153 }
154 //==============================================
156 {
157  return (index* (width()-1) ) / 255;
158 }
159 //==============================================
160 void HistogramInterface::paintEvent(QPaintEvent *e)
161 {
162  //create buffer to draw in
163  QPixmap buffer( size() );
164  buffer.fill( Qt::white );
165 
166  //create a painter pointing to the buffer
167  QPainter bufferPainter( &buffer );
168 
169  //turn off clipping to make painting operations faster
170  bufferPainter.setClipping(false);
171 
172  //initialize buffer with background brush
173  bufferPainter.fillRect( buffer.rect(), backgroundBrush() );
174 
175  //get handle on histogram data, get max count, set default draw color, and find
176  //left and right boundaries of current selection
177  QColor color = Qt::black;
178  int* data = grayVals;
179  int maxCount = maxGRAYcount;
180 
181  if(displayedChannel == RED) { data = redVals; color = Qt::red; maxCount = maxRcount; }
182  else if(displayedChannel == GREEN) { data = greenVals; color = Qt::green; maxCount = maxGcount; }
183  else if(displayedChannel == BLUE) { data = blueVals; color = Qt::blue; maxCount = maxBcount; }
184 
185  int indexLeft, indexRight;
186  getSelectedRange(indexLeft,indexRight);
187  int displayLeft = indexToDisplay ( indexLeft );
188  int displayRight = indexToDisplay ( indexRight );
189 
190  int histogramHeight = HISTOGRAM_HEIGHT;
191 
192  //iterate over each pixel column
193  int x;
194  for(x=0; x<width(); x++)
195  {
196  double index = displayToIndex( x );
197  int indexL = (int)index;
198  double scaleR = index - indexL;
199 
200  int h = 0;
201  if(indexL < 255)
202  {
203  h = (int) ((1-scaleR)*data[indexL] + scaleR*data[indexL+1]);
204  }
205  else
206  {
207  h = data[255];
208  }
209 
210  //scale count so that the maxCount maps to the maximum height
211  double scaledH = (histogramHeight*h)/maxCount;
212  h = (int) scaledH;
213  //round up values between 0 and 1 so show data is there
214  if( h == 0 && scaledH > h) h++;
215 
216  if(h > 0)
217  {
218  //use a gray color outside selected range
219  QColor usedColor = color;
220  if(x < displayLeft || x > displayRight) { usedColor = Qt::gray; }
221 
222  bufferPainter.fillRect( QRect(x, histogramHeight - h,
223  1, h),
224  QBrush(usedColor) );
225  }
226 
227  //if this is left or right boundary of selection and entire range not selected then
228  //draw a vertical black line to make it stand out more
229  if( (x == displayLeft || x == displayLeft+1 ||
230  x == displayRight || x == displayRight-1) )
231  {
232  bufferPainter.drawLine( x, 0, x, histogramHeight-1 );
233  }
234  }
235  //----
236  //paint color bar key below
237 
238  //first a black border
239  bufferPainter.fillRect( QRect(0, histogramHeight + COLOR_BAR_MARGIN,
241  QBrush(Qt::black) );
242 
243  //next the color gradient
244  QColor scaledColor;
245  for(x=COLOR_BAR_BORDER; x < width()-COLOR_BAR_BORDER; x++)
246  {
247  int index;
248  if(x <= displayLeft )
249  index = 0;
250  else if(x >= displayRight)
251  index = 255;
252  else
253  index = (int) (255.0*(x-displayLeft))/(displayRight - displayLeft);
254 
255  int r = color.red();
256  int g = color.green();
257  int b = color.blue();
258 
259  if( r != 0) r = index;
260  if( g != 0) g = index;
261  if( b != 0) b = index;
262 
263  //black color was used when adjusting luminance, scale to white instead (since black is 0)
264  if( color == Qt::black )
265  { r = g = b = index; }
266 
267  scaledColor.setRgb( r,g,b );
268  bufferPainter.fillRect( QRect(x, histogramHeight + COLOR_BAR_MARGIN + COLOR_BAR_BORDER,
269  1, COLOR_BAR_HEIGHT),
270  QBrush(scaledColor) );
271  }
272 
273  //end painter
274  bufferPainter.end();
275 
276  //blit buffer to screen
277  bitBlt( this,
278  e->rect().x(), e->rect().y(),
279  &buffer,
280  e->rect().x(), e->rect().y(),
281  e->rect().width(), e->rect().height() );
282 }
283 //==============================================
285 {
286  //set mode and repaint
287  displayedChannel = channel;
288  repaint(false);
289 }
290 //==============================================
292 {
293  return QSize( 256,100 + COLOR_BAR_MARGIN + 2*COLOR_BAR_BORDER + COLOR_BAR_HEIGHT );
294 }
295 //==============================================
297 {
298  //compute index from mouse position
299  int index = (int) displayToIndex( p.x() );
300 
301  //get left and right to check for clicks near current boundaries
302  int left, right;
303  getSelectedRange( left, right );
304 
305  //check if within threshold of left or right boundaries
306  return ( (index < left + 1 + DRAG_THRESHOLD &&
307  index > left - DRAG_THRESHOLD) ||
308  (index < right + DRAG_THRESHOLD &&
309  index > right - 1 - DRAG_THRESHOLD) );
310 }
311 //==============================================
313 {
314  //begin drag mode!
315  dragMode = DRAG;
316 
317  //compute index from mouse position
318  int index = (int) displayToIndex( e->pos().x() );
319 
320  //get left and right to check for clicks near current boundaries
321  int left, right;
322  getSelectedRange( left, right );
323 
324  //get click and drag handles
325  int *click, *drag;
327  {
328  click = &lumClick; drag = &lumDrag;
329  }
330  else if(displayedChannel == RED)
331  {
332  click = &redClick; drag = &redDrag;
333  }
334  else if(displayedChannel == GREEN)
335  {
336  click = &greenClick; drag = &greenDrag;
337  }
338  else
339  {
340  click = &blueClick; drag = &blueDrag;
341  }
342 
343  //if within threshold of left then start dragging that side
344  if( index < left + DRAG_THRESHOLD &&
345  index > left - DRAG_THRESHOLD )
346  {
347  *click = right;
348  *drag = left;
349  return;
350  }
351  //if within threshold of left then start dragging that side
352  if( index < right + DRAG_THRESHOLD &&
353  index > right - DRAG_THRESHOLD )
354  {
355  *click = left;
356  *drag = right;
357  return;
358  }
359  //else begin new drag
360  else
361  {
362  *click = index;
363  *drag = index;
364  repaint(false);
365 
366  //emit selectection changed signal
367  int left, right;
368  getSelectedRange( left, right );
369  emit selectedRangeChanged();
370  }
371 }
372 //==============================================
374 {
375  //if not dragging a selection then update mouse cursor as appropriate
376  if(dragMode == NO_EFFECT)
377  {
378  if( nearBoundary(e->pos()) && currentMouseShape == NO_EFFECT )
379  {
381  setCursor( getCursor(MOVE_HOR_CURSOR) );
382  }
383  else if( !nearBoundary(e->pos()) && currentMouseShape == DRAG )
384  {
386  setCursor( getCursor(CROSS_CURSOR) );
387  }
388 
389  return;
390  }
391 
392  //compute index in 0-255 range from mouse coordinates
393  int x = QMAX( QMIN( e->pos().x(), width()-1 ), 0 );
394  int index = (int) displayToIndex( x );
395 
396  //reset boundary
397  if(displayedChannel == LUMINOSITY) { lumDrag = index; }
398  else if(displayedChannel == RED) { redDrag = index; }
399  else if(displayedChannel == GREEN) { greenDrag = index; }
400  else if(displayedChannel == BLUE) { blueDrag = index; }
401 
402  //repaint
403  repaint(false);
404 
405  //emit selectection changed signal
406  int left, right;
407  getSelectedRange( left, right );
408  emit selectedRangeChanged();
409 }
410 //==============================================
412 {
413  //set mouse drags to no longer have any effect on boundary
415 
416  //update mouse cursor if necessary
417  if( !nearBoundary(e->pos()) && currentMouseShape == DRAG )
418  {
420  setCursor( getCursor(CROSS_CURSOR) );
421  }
422 }
423 //==============================================
425 {
426  //reset boundary
427  if(displayedChannel == LUMINOSITY) { lumClick = 0, lumDrag = 255; }
428  else if(displayedChannel == RED) { redClick = 0; redDrag = 255; }
429  else if(displayedChannel == GREEN) { greenClick = 0; greenDrag = 255; }
430  else if(displayedChannel == BLUE) { blueClick = 0; blueDrag = 255; }
431  repaint(false);
432 
433  //emit selectection changed signal
434  int left, right;
435  getSelectedRange( left, right );
436  emit selectedRangeChanged();
437 }
438 //==============================================
439 void HistogramInterface::getHistBoundaries(int &lumLeft, int &lumRight,
440  int &redLeft, int &redRight,
441  int &greenLeft, int &greenRight,
442  int &blueLeft, int &blueRight)
443 {
444  lumLeft = QMIN( lumClick, lumDrag );
445  lumRight = QMAX( lumClick, lumDrag );
446 
447  redLeft = QMIN( redClick, redDrag );
448  redRight = QMAX( redClick, redDrag );
449 
450  greenLeft = QMIN( greenClick, greenDrag );
451  greenRight = QMAX( greenClick, greenDrag );
452 
453  blueLeft = QMIN( blueClick, blueDrag );
454  blueRight = QMAX( blueClick, blueDrag );
455 }
456 //==============================================
458 {
460  lumDrag = redDrag = greenDrag = blueDrag = 255;
461  repaint(false);
462  emit selectedRangeChanged();
463 }
464 //==============================================
465 
466 
467 
468 
int redVals[256]
color and luminosity histograms
void resizeEvent(QResizeEvent *)
void paintEvent(QPaintEvent *e)
HistogramInterface(QString imageFilename, QWidget *parent=0, const char *name=0)
Creates layout.
#define COLOR_BAR_MARGIN
int maxRcount
max r,g,b, and gray counts
long b
Definition: jpegInternal.h:125
#define COLOR_BAR_BORDER
#define DRAG_THRESHOLD
~HistogramInterface()
Deletes objects.
void setDisplayChannel(DISPLAYED_CHANNEL channel)
Sets currently displayed channel.
#define COLOR_BAR_HEIGHT
QSize origImageSize
original image dimensions, needed for painting
#define HISTOGRAM_HEIGHT
int width
Definition: blur.cpp:79
double displayToIndex(int val)
convert screen coordinate to index in 0-255 range
DISPLAYED_CHANNEL
chanel histogram displays
bool scaleImage(QString fileIn, QString fileOut, int newWidth, int newHeight)
Scale image and save copy to disk.
Definition: imageTools.cpp:157
void selectedRangeChanged()
void mousePressEvent(QMouseEvent *e)
bool getImageSize(const char *filename, QSize &size)
Get image dimensions.
Definition: imageTools.cpp:192
int indexToDisplay(int val)
converts index in 0-255 ranges to screen coordinate
const QCursor & getCursor(CUSTOM_CURSOR_TYPE type)
Definition: cursors.cpp:52
float * buffer
Definition: blur.cpp:80
virtual QSize minimumSizeHint() const
int lumClick
left and right bounds for each channel
void getSelectedRange(int &left, int &right)
this utility function finds currently selected range
void mouseReleaseEvent(QMouseEvent *)
DRAG_MODE dragMode
effect of mouse drags
bool nearBoundary(QPoint p)
determines if mouse is near boundary
DISPLAYED_CHANNEL displayedChannel
Currently displayed channel.
void getHistBoundaries(int &lumLeft, int &lumRight, int &redLeft, int &redRight, int &greenLeft, int &greenRight, int &blueLeft, int &blueRight)
returns histogram boundaries
void resetBoundaries()
resets all boundaries
void mouseMoveEvent(QMouseEvent *e)
DRAG_MODE currentMouseShape
current mouse shape.