QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsrasterhistogramwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrasterhistogramwidget.cpp
3  ---------------------------
4  begin : July 2012
5  copyright : (C) 2012 by Etienne Tourigny
6  email : etourigny dot dev at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsapplication.h"
19 #include "qgisgui.h"
23 
24 #include <QMenu>
25 #include <QFileInfo>
26 #include <QDir>
27 #include <QPainter>
28 #include <QSettings>
29 
30 // QWT Charting widget
31 #include <qwt_global.h>
32 #include <qwt_plot_canvas.h>
33 #include <qwt_legend.h>
34 #include <qwt_plot.h>
35 #include <qwt_plot_curve.h>
36 #include <qwt_plot_grid.h>
37 #include <qwt_plot_marker.h>
38 #include <qwt_plot_picker.h>
39 #include <qwt_picker_machine.h>
40 #include <qwt_plot_zoomer.h>
41 #include <qwt_plot_layout.h>
42 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
43 #include <qwt_plot_renderer.h>
44 #endif
45 
46 #define RASTER_HISTOGRAM_BINS 256
47 
49  : QWidget( parent ),
50  mRasterLayer( lyr ), mRendererWidget( 0 )
51 {
52  setupUi( this );
53 
54  mSaveAsImageButton->setIcon( QgsApplication::getThemeIcon( "/mActionFileSave.svg" ) );
55 
56  mRendererWidget = 0;
57  mRendererName = "singlebandgray";
58 
59  mHistoMin = 0;
60  mHistoMax = 0;
61 
62  mHistoPicker = NULL;
63  mHistoZoomer = NULL;
64  mHistoMarkerMin = NULL;
65  mHistoMarkerMax = NULL;
66 
67  QSettings settings;
68  mHistoShowMarkers = settings.value( "/Raster/histogram/showMarkers", false ).toBool();
69  // mHistoLoadApplyAll = settings.value( "/Raster/histogram/loadApplyAll", false ).toBool();
70  mHistoZoomToMinMax = settings.value( "/Raster/histogram/zoomToMinMax", false ).toBool();
71  mHistoUpdateStyleToMinMax = settings.value( "/Raster/histogram/updateStyleToMinMax", true ).toBool();
72  // mHistoShowBands = (HistoShowBands) settings.value( "/Raster/histogram/showBands", (int) ShowAll ).toInt();
74 
75  if ( true )
76  {
77  //band selector
78  int myBandCountInt = mRasterLayer->bandCount();
79  for ( int myIteratorInt = 1;
80  myIteratorInt <= myBandCountInt;
81  ++myIteratorInt )
82  {
83  cboHistoBand->addItem( mRasterLayer->bandName( myIteratorInt ) );
84  }
85 
86  // histo min/max selectors
87  leHistoMin->setValidator( new QDoubleValidator( this ) );
88  leHistoMax->setValidator( new QDoubleValidator( this ) );
89  // this might generate many refresh events! test..
90  // connect( leHistoMin, SIGNAL( textChanged( const QString & ) ), this, SLOT( updateHistoMarkers() ) );
91  // connect( leHistoMax, SIGNAL( textChanged( const QString & ) ), this, SLOT( updateHistoMarkers() ) );
92  // connect( leHistoMin, SIGNAL( textChanged( const QString & ) ), this, SLOT( applyHistoMin() ) );
93  // connect( leHistoMax, SIGNAL( textChanged( const QString & ) ), this, SLOT( applyHistoMax() ) );
94  connect( leHistoMin, SIGNAL( editingFinished() ), this, SLOT( applyHistoMin() ) );
95  connect( leHistoMax, SIGNAL( editingFinished() ), this, SLOT( applyHistoMax() ) );
96 
97  // histo actions
98  QMenu* menu = new QMenu( this );
99  menu->setSeparatorsCollapsible( false );
100  btnHistoActions->setMenu( menu );
101  QActionGroup* group;
102  QAction* action;
103 
104  // min/max options
105  group = new QActionGroup( this );
106  group->setExclusive( false );
107  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
108  action = new QAction( tr( "Min/Max options" ), group );
109  action->setSeparator( true );
110  menu->addAction( action );
111  action = new QAction( tr( "Always show min/max markers" ), group );
112  action->setData( QVariant( "Show markers" ) );
113  action->setCheckable( true );
114  action->setChecked( mHistoShowMarkers );
115  menu->addAction( action );
116  action = new QAction( tr( "Zoom to min/max" ), group );
117  action->setData( QVariant( "Zoom min_max" ) );
118  action->setCheckable( true );
119  action->setChecked( mHistoZoomToMinMax );
120  menu->addAction( action );
121  action = new QAction( tr( "Update style to min/max" ), group );
122  action->setData( QVariant( "Update min_max" ) );
123  action->setCheckable( true );
124  action->setChecked( mHistoUpdateStyleToMinMax );
125  menu->addAction( action );
126 
127  // visibility options
128  group = new QActionGroup( this );
129  group->setExclusive( false );
130  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
131  action = new QAction( tr( "Visibility" ), group );
132  action->setSeparator( true );
133  menu->addAction( action );
134  group = new QActionGroup( this );
135  group->setExclusive( true ); // these options are exclusive
136  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
137  action = new QAction( tr( "Show all bands" ), group );
138  action->setData( QVariant( "Show all" ) );
139  action->setCheckable( true );
140  action->setChecked( mHistoShowBands == ShowAll );
141  menu->addAction( action );
142  action = new QAction( tr( "Show RGB/Gray band(s)" ), group );
143  action->setData( QVariant( "Show RGB" ) );
144  action->setCheckable( true );
145  action->setChecked( mHistoShowBands == ShowRGB );
146  menu->addAction( action );
147  action = new QAction( tr( "Show selected band" ), group );
148  action->setData( QVariant( "Show selected" ) );
149  action->setCheckable( true );
150  action->setChecked( mHistoShowBands == ShowSelected );
151  menu->addAction( action );
152 
153  // actions
154  action = new QAction( tr( "Actions" ), group );
155  action->setSeparator( true );
156  menu->addAction( action );
157 
158  // load actions
159  group = new QActionGroup( this );
160  group->setExclusive( false );
161  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
162  action = new QAction( tr( "Reset" ), group );
163  action->setData( QVariant( "Load reset" ) );
164  menu->addAction( action );
165 
166  // these actions have been disabled for api cleanup, restore them eventually
167 #if 0
168  // Load min/max needs 3 params (method, extent, accuracy), cannot put it in single item
169  action = new QAction( tr( "Load min/max" ), group );
170  action->setSeparator( true );
171  menu->addAction( action );
172  action = new QAction( tr( "Estimate (faster)" ), group );
173  action->setData( QVariant( "Load estimate" ) );
174  menu->addAction( action );
175  action = new QAction( tr( "Actual (slower)" ), group );
176  action->setData( QVariant( "Load actual" ) );
177  menu->addAction( action );
178  action = new QAction( tr( "Current extent" ), group );
179  action->setData( QVariant( "Load extent" ) );
180  menu->addAction( action );
181  action = new QAction( tr( "Use stddev (1.0)" ), group );
182  action->setData( QVariant( "Load 1 stddev" ) );
183  menu->addAction( action );
184  action = new QAction( tr( "Use stddev (custom)" ), group );
185  action->setData( QVariant( "Load stddev" ) );
186  menu->addAction( action );
187  action = new QAction( tr( "Load for each band" ), group );
188  action->setData( QVariant( "Load apply all" ) );
189  action->setCheckable( true );
190  action->setChecked( mHistoLoadApplyAll );
191  menu->addAction( action );
192 #endif
193 
194  //others
195  action = new QAction( tr( "Recompute Histogram" ), group );
196  action->setData( QVariant( "Compute histogram" ) );
197  menu->addAction( action );
198 
199  }
200 
201 } // QgsRasterHistogramWidget ctor
202 
203 
205 {
206 }
207 
208 void QgsRasterHistogramWidget::setRendererWidget( const QString& name, QgsRasterRendererWidget* rendererWidget )
209 {
210  mRendererName = name;
211  mRendererWidget = rendererWidget;
214 }
215 
216 void QgsRasterHistogramWidget::setActive( bool theActiveFlag )
217 {
218  if ( theActiveFlag )
219  {
222  }
223  else
224  {
225  if ( QApplication::overrideCursor() )
226  QApplication::restoreOverrideCursor();
227  btnHistoMin->setChecked( false );
228  btnHistoMax->setChecked( false );
229  }
230 }
231 
233 {
234 // Histogram computation can be called either by clicking the "Compute Histogram" button
235 // which is only visible if there is no cached histogram or by calling the
236 // "Compute Histogram" action. Due to limitations in the gdal api, it is not possible
237 // to re-calculate the histogramif it has already been calculated
238  computeHistogram( true );
240 }
241 
242 bool QgsRasterHistogramWidget::computeHistogram( bool forceComputeFlag )
243 {
244  const int BINCOUNT = RASTER_HISTOGRAM_BINS;
245  //bool myIgnoreOutOfRangeFlag = true;
246  //bool myThoroughBandScanFlag = false;
247  int myBandCountInt = mRasterLayer->bandCount();
248 
249  // if forceComputeFlag = false make sure raster has cached histogram, else return false
250  if ( ! forceComputeFlag )
251  {
252  for ( int myIteratorInt = 1;
253  myIteratorInt <= myBandCountInt;
254  ++myIteratorInt )
255  {
256  //if ( ! mRasterLayer->hasCachedHistogram( myIteratorInt, BINCOUNT ) )
257  int sampleSize = 250000; // number of sample cells
258  if ( !mRasterLayer->dataProvider()->hasHistogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize ) )
259  {
260  QgsDebugMsg( QString( "band %1 does not have cached histo" ).arg( myIteratorInt ) );
261  return false;
262  }
263  }
264  }
265 
266  // compute histogram
267  stackedWidget2->setCurrentIndex( 1 );
268  connect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
269  QApplication::setOverrideCursor( Qt::WaitCursor );
270 
271  for ( int myIteratorInt = 1;
272  myIteratorInt <= myBandCountInt;
273  ++myIteratorInt )
274  {
275  //mRasterLayer->populateHistogram( myIteratorInt, BINCOUNT, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
276  int sampleSize = 250000; // number of sample cells
277  mRasterLayer->dataProvider()->histogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );
278  }
279 
280  disconnect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
281  // mHistogramProgress->hide();
282  stackedWidget2->setCurrentIndex( 0 );
283  QApplication::restoreOverrideCursor();
284 
285  return true;
286 }
287 
288 
290 {
291  // Explanation:
292  // We use the gdal histogram creation routine is called for each selected
293  // layer. Currently the hist is hardcoded to create 256 bins. Each bin stores
294  // the total number of cells that fit into the range defined by that bin.
295  //
296  // The graph routine below determines the greatest number of pixels in any given
297  // bin in all selected layers, and the min. It then draws a scaled line between min
298  // and max - scaled to image height. 1 line drawn per selected band
299  //
300  const int BINCOUNT = RASTER_HISTOGRAM_BINS;
301  int myBandCountInt = mRasterLayer->bandCount();
302 
303  QgsDebugMsg( "entered." );
304 
305  if ( ! computeHistogram( false ) )
306  {
307  QgsDebugMsg( QString( "raster does not have cached histogram" ) );
308  stackedWidget2->setCurrentIndex( 2 );
309  return;
310  }
311 
312 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
313  mpPlot->detachItems();
314 #else
315  mpPlot->clear();
316 #endif
317  //ensure all children get removed
318  mpPlot->setAutoDelete( true );
319  mpPlot->setTitle( QObject::tr( "Raster Histogram" ) );
320  mpPlot->insertLegend( new QwtLegend(), QwtPlot::BottomLegend );
321  // Set axis titles
322  mpPlot->setAxisTitle( QwtPlot::xBottom, QObject::tr( "Pixel Value" ) );
323  mpPlot->setAxisTitle( QwtPlot::yLeft, QObject::tr( "Frequency" ) );
324  mpPlot->setAxisAutoScale( QwtPlot::yLeft );
325 
326  // x axis scale only set after computing global min/max across bands (see below)
327  // add a grid
328  QwtPlotGrid * myGrid = new QwtPlotGrid();
329  myGrid->attach( mpPlot );
330 
331  // make colors list
332  mHistoColors.clear();
333  mHistoColors << Qt::black; // first element, not used
334  QVector<QColor> myColors;
335  myColors << Qt::red << Qt::green << Qt::blue << Qt::magenta << Qt::darkYellow << Qt::cyan;
336  srand( myBandCountInt * 100 ); // make sure colors are always the same for a given band count
337  while ( myColors.size() <= myBandCountInt )
338  {
339  myColors <<
340  QColor( 1 + ( int )( 255.0 * rand() / ( RAND_MAX + 1.0 ) ),
341  1 + ( int )( 255.0 * rand() / ( RAND_MAX + 1.0 ) ),
342  1 + ( int )( 255.0 * rand() / ( RAND_MAX + 1.0 ) ) );
343  }
344 
345  // assign colors to each band, depending on the current RGB/gray band selection
346  // grayscale
347  QList< int > mySelectedBands = rendererSelectedBands();
348  if ( mRendererName == "singlebandgray" )
349  {
350  int myGrayBand = mySelectedBands[0];
351  for ( int i = 1; i <= myBandCountInt; i++ )
352  {
353  if ( i == myGrayBand )
354  {
355  mHistoColors << Qt::darkGray;
356  cboHistoBand->setItemData( i - 1, Qt::darkGray, Qt::ForegroundRole );
357  }
358  else
359  {
360  if ( ! myColors.isEmpty() )
361  {
362  mHistoColors << myColors.first();
363  myColors.pop_front();
364  }
365  else
366  {
367  mHistoColors << Qt::black;
368  }
369  cboHistoBand->setItemData( i - 1, Qt::black, Qt::ForegroundRole );
370  }
371  }
372  }
373  // RGB
374  else if ( mRendererName == "multibandcolor" )
375  {
376  int myRedBand = mySelectedBands[0];
377  int myGreenBand = mySelectedBands[1];
378  int myBlueBand = mySelectedBands[2];
379  // remove RGB, which are reserved for the actual RGB bands
380  // show name of RGB bands in appropriate color in bold
381  myColors.remove( 0, 3 );
382  for ( int i = 1; i <= myBandCountInt; i++ )
383  {
384  QColor myColor;
385  if ( i == myRedBand )
386  myColor = Qt::red;
387  else if ( i == myGreenBand )
388  myColor = Qt::green;
389  else if ( i == myBlueBand )
390  myColor = Qt::blue;
391  else
392  {
393  if ( ! myColors.isEmpty() )
394  {
395  myColor = myColors.first();
396  myColors.pop_front();
397  }
398  else
399  {
400  myColor = Qt::black;
401  }
402  cboHistoBand->setItemData( i - 1, Qt::black, Qt::ForegroundRole );
403  }
404  if ( i == myRedBand || i == myGreenBand || i == myBlueBand )
405  {
406  cboHistoBand->setItemData( i - 1, myColor, Qt::ForegroundRole );
407  }
408  mHistoColors << myColor;
409  }
410  }
411  else
412  {
413  mHistoColors << myColors;
414  }
415 
416  //
417  //now draw actual graphs
418  //
419 
420  //somtimes there are more bins than needed
421  //we find out the last one that actually has data in it
422  //so we can discard the rest and set the x-axis scales correctly
423  //
424  // scan through to get counts from layers' histograms
425  //
426  mHistoMin = 0;
427  mHistoMax = 0;
428  bool myFirstIteration = true;
429  /* get selected band list, if mHistoShowBands != ShowAll */
430  mySelectedBands = histoSelectedBands();
431  double myBinXStep = 1;
432  double myBinX = 0;
433 
434  for ( int myIteratorInt = 1;
435  myIteratorInt <= myBandCountInt;
436  ++myIteratorInt )
437  {
438  /* skip this band if mHistoShowBands != ShowAll and this band is not selected */
439  if ( mHistoShowBands != ShowAll )
440  {
441  if ( ! mySelectedBands.contains( myIteratorInt ) )
442  continue;
443  }
444  int sampleSize = 250000; // number of sample cells
445  //QgsRasterBandStats myRasterBandStats = mRasterLayer->dataProvider()->bandStatistics( myIteratorInt );
446  // mRasterLayer->populateHistogram( myIteratorInt, BINCOUNT, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
447  QgsRasterHistogram myHistogram = mRasterLayer->dataProvider()->histogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );
448 
449  QwtPlotCurve * mypCurve = new QwtPlotCurve( tr( "Band %1" ).arg( myIteratorInt ) );
450  //mypCurve->setCurveAttribute( QwtPlotCurve::Fitted );
451  mypCurve->setRenderHint( QwtPlotItem::RenderAntialiased );
452  mypCurve->setPen( QPen( mHistoColors.at( myIteratorInt ) ) );
453 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
454  QVector<QPointF> data;
455 #else
456  QVector<double> myX2Data;
457  QVector<double> myY2Data;
458 #endif
459  // calculate first bin x value and bin step size if not Byte data
460  if ( mRasterLayer->dataProvider()->srcDataType( myIteratorInt ) != QGis::Byte )
461  {
462  //myBinXStep = myRasterBandStats.range / BINCOUNT;
463  //myBinX = myRasterBandStats.minimumValue + myBinXStep / 2.0;
464  myBinXStep = ( myHistogram.maximum - myHistogram.minimum ) / BINCOUNT;
465  myBinX = myHistogram.minimum + myBinXStep / 2.0;
466  }
467  else
468  {
469  myBinXStep = 1;
470  myBinX = 0;
471  }
472 
473  for ( int myBin = 0; myBin < BINCOUNT; myBin++ )
474  {
475  //int myBinValue = myRasterBandStats.histogramVector->at( myBin );
476  int myBinValue = myHistogram.histogramVector.at( myBin );
477 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
478  data << QPointF( myBinX, myBinValue );
479 #else
480  myX2Data.append( double( myBinX ) );
481  myY2Data.append( double( myBinValue ) );
482 #endif
483  myBinX += myBinXStep;
484  }
485 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
486  mypCurve->setSamples( data );
487 #else
488  mypCurve->setData( myX2Data, myY2Data );
489 #endif
490  mypCurve->attach( mpPlot );
491  if ( myFirstIteration || mHistoMin > myHistogram.minimum )
492  {
493  mHistoMin = myHistogram.minimum;
494  }
495  if ( myFirstIteration || mHistoMax < myHistogram.maximum )
496  {
497  mHistoMax = myHistogram.maximum;
498  }
499  QgsDebugMsg( QString( "computed histo min = %1 max = %2" ).arg( mHistoMin ).arg( mHistoMax ) );
500  myFirstIteration = false;
501  }
502  // for x axis use band pixel values rather than gdal hist. bin values
503  // subtract -0.5 to prevent rounding errors
504  // see http://www.gdal.org/classGDALRasterBand.html#3f8889607d3b2294f7e0f11181c201c8
505  // fix x range for non-Byte data
506  mpPlot->setAxisScale( QwtPlot::xBottom,
507  mHistoMin - myBinXStep / 2,
508  mHistoMax + myBinXStep / 2 );
509 
510  mpPlot->replot();
511 
512  // histo plot markers
513  // memory leak?
514  mHistoMarkerMin = new QwtPlotMarker();
515  mHistoMarkerMin->attach( mpPlot );
516  mHistoMarkerMax = new QwtPlotMarker();
517  mHistoMarkerMax->attach( mpPlot );
519 
520  // histo picker
521  if ( ! mHistoPicker )
522  {
523  mHistoPicker = new QwtPlotPicker( mpPlot->canvas() );
524  // mHistoPicker->setTrackerMode( QwtPicker::ActiveOnly );
525  mHistoPicker->setTrackerMode( QwtPicker::AlwaysOff );
526  mHistoPicker->setRubberBand( QwtPicker::VLineRubberBand );
527  mHistoPicker->setEnabled( false );
528 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
529  mHistoPicker->setStateMachine( new QwtPickerDragPointMachine );
530  connect( mHistoPicker, SIGNAL( selected( const QPointF & ) ), this, SLOT( histoPickerSelected( const QPointF & ) ) );
531 #else
532  mHistoPicker->setSelectionFlags( QwtPicker::PointSelection | QwtPicker::DragSelection );
533  connect( mHistoPicker, SIGNAL( selected( const QwtDoublePoint & ) ), this, SLOT( histoPickerSelectedQwt5( const QwtDoublePoint & ) ) );
534 #endif
535  }
536 
537  // plot zoomer
538  if ( ! mHistoZoomer )
539  {
540  mHistoZoomer = new QwtPlotZoomer( mpPlot->canvas() );
541 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
542  mHistoZoomer->setStateMachine( new QwtPickerDragRectMachine );
543 #else
544  mHistoZoomer->setSelectionFlags( QwtPicker::RectSelection | QwtPicker::DragSelection );
545 #endif
546  mHistoZoomer->setTrackerMode( QwtPicker::AlwaysOff );
547  mHistoZoomer->setEnabled( true );
548  }
549 
550  disconnect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
551  stackedWidget2->setCurrentIndex( 0 );
552  // icon from http://findicons.com/icon/169577/14_zoom?id=171427
553  mpPlot->canvas()->setCursor( QCursor( QgsApplication::getThemePixmap( "/mIconZoom.svg" ) ) );
554  // on_cboHistoBand_currentIndexChanged( -1 );
555  QApplication::restoreOverrideCursor();
556 }
557 
559 {
560  if ( mpPlot == 0 )
561  {
562  return;
563  }
564 
565  QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
566  QFileInfo myInfo( myFileNameAndFilter.first );
567  if ( QFileInfo( myFileNameAndFilter.first ).baseName() != "" )
568  {
569  histoSaveAsImage( myFileNameAndFilter.first );
570  }
571 }
572 
573 bool QgsRasterHistogramWidget::histoSaveAsImage( const QString& theFilename,
574  int width, int height, int quality )
575 {
576  // make sure dir. exists
577  QFileInfo myInfo( theFilename );
578  QDir myDir( myInfo.dir() );
579  if ( ! myDir.exists() )
580  {
581  QgsDebugMsg( QString( "Error, directory %1 non-existent (theFilename = %2)" ).arg( myDir.absolutePath() ).arg( theFilename ) );
582  return false;
583  }
584 
585  // prepare the pixmap
586  QPixmap myPixmap( width, height );
587  QRect myQRect( 5, 5, width - 10, height - 10 ); // leave a 5px border on all sides
588  myPixmap.fill( Qt::white ); // Qt::transparent ?
589 
590 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
591  QwtPlotRenderer myRenderer;
592  myRenderer.setDiscardFlags( QwtPlotRenderer::DiscardBackground |
593  QwtPlotRenderer::DiscardCanvasBackground );
594  myRenderer.setLayoutFlags( QwtPlotRenderer::FrameWithScales );
595 
596  QPainter myPainter;
597  myPainter.begin( &myPixmap );
598  myRenderer.render( mpPlot, &myPainter, myQRect );
599  myPainter.end();
600 #else
601  QwtPlotPrintFilter myFilter;
602  int myOptions = QwtPlotPrintFilter::PrintAll;
603  myOptions &= ~QwtPlotPrintFilter::PrintBackground;
604  myOptions |= QwtPlotPrintFilter::PrintFrameWithScales;
605  myFilter.setOptions( myOptions );
606 
607  QPainter myPainter;
608  myPainter.begin( &myPixmap );
609  mpPlot->print( &myPainter, myQRect, myFilter );
610  myPainter.end();
611 
612  // "fix" for bug in qwt5 - legend and plot shifts a bit
613  // can't see how to avoid this without picking qwt5 apart...
616 #endif
617 
618  // save pixmap to file
619  myPixmap.save( theFilename, 0, quality );
620 
621  // should do more error checking
622  return true;
623 }
624 
626 {
627  cboHistoBand->setCurrentIndex( theBandNo - 1 );
628 }
629 
631 {
632  if ( mHistoShowBands == ShowSelected )
634 
635  // get the current index value, index can be -1
636  index = cboHistoBand->currentIndex();
637  if ( mHistoPicker != NULL )
638  {
639  mHistoPicker->setEnabled( false );
640  mHistoPicker->setRubberBandPen( QPen( mHistoColors.at( index + 1 ) ) );
641  }
642  if ( mHistoZoomer != NULL )
643  mHistoZoomer->setEnabled( true );
644  btnHistoMin->setEnabled( true );
645  btnHistoMax->setEnabled( true );
646 
647  QPair< QString, QString > myMinMax = rendererMinMax( index + 1 );
648  leHistoMin->setText( myMinMax.first );
649  leHistoMax->setText( myMinMax.second );
650 
651  applyHistoMin();
652  applyHistoMax();
653 }
654 
656 {
657  if ( ! action )
658  return;
659  histoAction( action->data().toString(), action->isChecked() );
660 }
661 
662 void QgsRasterHistogramWidget::histoAction( const QString actionName, bool actionFlag )
663 {
664  if ( actionName == "" )
665  return;
666 
667  // this approach is a bit of a hack, but this way we don't have to define slots for each action
668  QgsDebugMsg( QString( "band = %1 action = %2" ).arg( cboHistoBand->currentIndex() + 1 ).arg( actionName ) );
669 
670  // checkeable actions
671  if ( actionName == "Show markers" )
672  {
673  mHistoShowMarkers = actionFlag;
674  QSettings settings;
675  settings.setValue( "/Raster/histogram/showMarkers", mHistoShowMarkers );
677  return;
678  }
679  else if ( actionName == "Zoom min_max" )
680  {
681  mHistoZoomToMinMax = actionFlag;
682  QSettings settings;
683  settings.setValue( "/Raster/histogram/zoomToMinMax", mHistoZoomToMinMax );
684  return;
685  }
686  else if ( actionName == "Update min_max" )
687  {
688  mHistoUpdateStyleToMinMax = actionFlag;
689  QSettings settings;
690  settings.setValue( "/Raster/histogram/updateStyleToMinMax", mHistoUpdateStyleToMinMax );
691  return;
692  }
693  else if ( actionName == "Show all" )
694  {
696  // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
698  return;
699  }
700  else if ( actionName == "Show selected" )
701  {
703  // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
705  return;
706  }
707  else if ( actionName == "Show RGB" )
708  {
710  // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
712  return;
713  }
714 #if 0
715  else if ( actionName == "Load apply all" )
716  {
717  mHistoLoadApplyAll = actionFlag;
718  settings.setValue( "/Raster/histogram/loadApplyAll", mHistoLoadApplyAll );
719  return;
720  }
721 #endif
722  // Load actions
723  // TODO - separate calculations from rendererwidget so we can do them without
724  else if ( actionName.left( 5 ) == "Load " && mRendererWidget )
725  {
726  QVector<int> myBands;
727  bool ok = false;
728 
729 #if 0
730  double minMaxValues[2];
731 
732  // find which band(s) need updating (all or current)
733  if ( mHistoLoadApplyAll )
734  {
735  int myBandCountInt = mRasterLayer->bandCount();
736  for ( int i = 1; i <= myBandCountInt; i++ )
737  {
738  if ( i != cboHistoBand->currentIndex() + 1 )
739  myBands << i;
740  }
741  }
742 #endif
743 
744  // add current band to the end
745  myBands << cboHistoBand->currentIndex() + 1;
746 
747  // get stddev value once if needed
748  /*
749  double myStdDev = 1.0;
750  if ( actionName == "Load stddev" )
751  {
752  myStdDev = mRendererWidget->stdDev().toDouble();
753  }
754  */
755 
756  // don't update markers every time
757  leHistoMin->blockSignals( true );
758  leHistoMax->blockSignals( true );
759 
760  // process each band
761  foreach ( int theBandNo, myBands )
762  {
763  ok = false;
764 #if 0
765  if ( actionName == "Load actual" )
766  {
768  theBandNo, minMaxValues );
769  }
770  else if ( actionName == "Load estimate" )
771  {
773  theBandNo, minMaxValues );
774  }
775  else if ( actionName == "Load extent" )
776  {
778  theBandNo, minMaxValues );
779  }
780  else if ( actionName == "Load 1 stddev" ||
781  actionName == "Load stddev" )
782  {
783  ok = mRendererWidget->bandMinMaxFromStdDev( myStdDev, theBandNo, minMaxValues );
784  }
785 #endif
786 
787  // apply current item
788  cboHistoBand->setCurrentIndex( theBandNo - 1 );
789  if ( !ok || actionName == "Load reset" )
790  {
791  leHistoMin->clear();
792  leHistoMax->clear();
793 #if 0
794  // TODO - fix gdal provider: changes data type when nodata value is not found
795  // this prevents us from getting proper min and max values here
797  ( QGis::DataType ) mRasterLayer->dataProvider()->dataType( theBandNo ) );
799  ( QGis::DataType ) mRasterLayer->dataProvider()->dataType( theBandNo ) );
800  }
801  else
802  {
803  leHistoMin->setText( QString::number( minMaxValues[0] ) );
804  leHistoMax->setText( QString::number( minMaxValues[1] ) );
805 #endif
806  }
807  applyHistoMin( );
808  applyHistoMax( );
809  }
810  // update markers
811  leHistoMin->blockSignals( false );
812  leHistoMax->blockSignals( false );
814  }
815  else if ( actionName == "Compute histogram" )
816  {
818  }
819  else
820  {
821  QgsDebugMsg( "Invalid action " + actionName );
822  return;
823  }
824 }
825 
827 {
828  if ( ! mRendererWidget )
829  return;
830 
831  int theBandNo = cboHistoBand->currentIndex() + 1;
832  QList< int > mySelectedBands = rendererSelectedBands();
833  QString min;
834  for ( int i = 0; i <= mySelectedBands.size(); i++ )
835  {
836  if ( theBandNo == mRendererWidget->selectedBand( i ) )
837  {
838  min = leHistoMin->text();
840  mRendererWidget->setMin( min, i );
841  }
842  }
843 
845 
846  if ( ! min.isEmpty() && mHistoZoomToMinMax && mHistoZoomer )
847  {
848  QRectF rect = mHistoZoomer->zoomRect();
849  rect.setLeft( min.toDouble() );
850  mHistoZoomer->zoom( rect );
851  }
852 
853 }
854 
856 {
857  if ( ! mRendererWidget )
858  return;
859 
860  int theBandNo = cboHistoBand->currentIndex() + 1;
861  QList< int > mySelectedBands = rendererSelectedBands();
862  QString max;
863  for ( int i = 0; i <= mySelectedBands.size(); i++ )
864  {
865  if ( theBandNo == mRendererWidget->selectedBand( i ) )
866  {
867  max = leHistoMax->text();
869  mRendererWidget->setMax( max, i );
870  }
871  }
872 
874 
875  if ( ! max.isEmpty() && mHistoZoomToMinMax && mHistoZoomer )
876  {
877  QRectF rect = mHistoZoomer->zoomRect();
878  rect.setRight( max.toDouble() );
879  mHistoZoomer->zoom( rect );
880  }
881 }
882 
884 {
885  if ( mpPlot != NULL && mHistoPicker != NULL )
886  {
887  if ( QApplication::overrideCursor() )
888  QApplication::restoreOverrideCursor();
889  if ( btnHistoMin->isChecked() )
890  {
891  btnHistoMax->setChecked( false );
892  QApplication::setOverrideCursor( Qt::PointingHandCursor );
893  }
894  if ( mHistoZoomer != NULL )
895  mHistoZoomer->setEnabled( ! btnHistoMax->isChecked() );
896  mHistoPicker->setEnabled( btnHistoMin->isChecked() );
897  }
899 }
900 
902 {
903  if ( mpPlot != NULL && mHistoPicker != NULL )
904  {
905  if ( QApplication::overrideCursor() )
906  QApplication::restoreOverrideCursor();
907  if ( btnHistoMax->isChecked() )
908  {
909  btnHistoMin->setChecked( false );
910  QApplication::setOverrideCursor( Qt::PointingHandCursor );
911  }
912  if ( mHistoZoomer != NULL )
913  mHistoZoomer->setEnabled( ! btnHistoMax->isChecked() );
914  mHistoPicker->setEnabled( btnHistoMax->isChecked() );
915  }
917 }
918 
919 // local function used by histoPickerSelected(), to get a rounded picked value
920 // this is sensitive and may not always be correct, needs more testing
921 QString findClosestTickVal( double target, const QwtScaleDiv * scale, int div = 100 )
922 {
923  if ( !scale ) return "";
924 
925  QList< double > minorTicks = scale->ticks( QwtScaleDiv::MinorTick );
926  QList< double > majorTicks = scale->ticks( QwtScaleDiv::MajorTick );
927  double diff = ( minorTicks[1] - minorTicks[0] ) / div;
928  double min = majorTicks[0] - diff;
929  if ( min > target )
930  min -= ( majorTicks[1] - majorTicks[0] );
931 #if defined(QWT_VERSION) && QWT_VERSION<0x050200
932  double max = scale->hBound();
933 #else
934  double max = scale->upperBound();
935 #endif
936  double closest = target;
937  double current = min;
938 
939  while ( current < max )
940  {
941  current += diff;
942  if ( current > target )
943  {
944  closest = ( abs( target - current + diff ) < abs( target - current ) ) ? current - diff : current;
945  break;
946  }
947  }
948 
949  // QgsDebugMsg( QString( "target=%1 div=%2 closest=%3" ).arg( target ).arg( div ).arg( closest ) );
950  return QString::number( closest );
951 }
952 
954 {
955  if ( btnHistoMin->isChecked() || btnHistoMax->isChecked() )
956  {
957 #if defined(QWT_VERSION) && QWT_VERSION>=0x060100
958  const QwtScaleDiv * scale = &mpPlot->axisScaleDiv( QwtPlot::xBottom );
959 #else
960  const QwtScaleDiv * scale = mpPlot->axisScaleDiv( QwtPlot::xBottom );
961 #endif
962 
963  if ( btnHistoMin->isChecked() )
964  {
965  leHistoMin->setText( findClosestTickVal( pos.x(), scale ) );
966  applyHistoMin();
967  btnHistoMin->setChecked( false );
968  }
969  else // if ( btnHistoMax->isChecked() )
970  {
971  leHistoMax->setText( findClosestTickVal( pos.x(), scale ) );
972  applyHistoMax();
973  btnHistoMax->setChecked( false );
974  }
975  }
976  if ( QApplication::overrideCursor() )
977  QApplication::restoreOverrideCursor();
978 }
979 
980 void QgsRasterHistogramWidget::histoPickerSelectedQwt5( const QwtDoublePoint & pos )
981 {
982  histoPickerSelected( QPointF( pos.x(), pos.y() ) );
983 }
984 
986 {
987  // hack to not update markers
988  if ( leHistoMin->signalsBlocked() )
989  return;
990  // todo error checking
991  if ( mpPlot == NULL || mHistoMarkerMin == NULL || mHistoMarkerMax == NULL )
992  return;
993 
994  int theBandNo = cboHistoBand->currentIndex() + 1;
995  QList< int > mySelectedBands = histoSelectedBands();
996 
997  if (( ! mHistoShowMarkers && ! btnHistoMin->isChecked() && ! btnHistoMax->isChecked() ) ||
998  ( ! mySelectedBands.isEmpty() && ! mySelectedBands.contains( theBandNo ) ) )
999  {
1000  mHistoMarkerMin->hide();
1001  mHistoMarkerMax->hide();
1002  mpPlot->replot();
1003  return;
1004  }
1005 
1006  double minVal = mHistoMin;
1007  double maxVal = mHistoMax;
1008  QString minStr = leHistoMin->text();
1009  QString maxStr = leHistoMax->text();
1010  if ( minStr != "" )
1011  minVal = minStr.toDouble();
1012  if ( maxStr != "" )
1013  maxVal = maxStr.toDouble();
1014 
1015  QPen linePen = QPen( mHistoColors.at( theBandNo ) );
1016  linePen.setStyle( Qt::DashLine );
1017  mHistoMarkerMin->setLineStyle( QwtPlotMarker::VLine );
1018  mHistoMarkerMin->setLinePen( linePen );
1019  mHistoMarkerMin->setXValue( minVal );
1020  mHistoMarkerMin->show();
1021  mHistoMarkerMax->setLineStyle( QwtPlotMarker::VLine );
1022  mHistoMarkerMax->setLinePen( linePen );
1023  mHistoMarkerMax->setXValue( maxVal );
1024  mHistoMarkerMax->show();
1025 
1026  mpPlot->replot();
1027 }
1028 
1029 
1031 {
1032  QList< int > mySelectedBands;
1033 
1034  if ( mHistoShowBands != ShowAll )
1035  {
1036  if ( mHistoShowBands == ShowSelected )
1037  {
1038  mySelectedBands << cboHistoBand->currentIndex() + 1;
1039  }
1040  else if ( mHistoShowBands == ShowRGB )
1041  {
1042  mySelectedBands = rendererSelectedBands();
1043  }
1044  }
1045 
1046  return mySelectedBands;
1047 }
1048 
1050 {
1051  QList< int > mySelectedBands;
1052 
1053  if ( ! mRendererWidget )
1054  {
1055  mySelectedBands << -1 << -1 << -1; // make sure we return 3 elements
1056  return mySelectedBands;
1057  }
1058 
1059  if ( mRendererName == "singlebandgray" )
1060  {
1061  mySelectedBands << mRendererWidget->selectedBand( );
1062  }
1063  else if ( mRendererName == "multibandcolor" )
1064  {
1065  for ( int i = 0; i <= 2; i++ )
1066  {
1067  mySelectedBands << mRendererWidget->selectedBand( i );
1068  }
1069  }
1070 
1071  return mySelectedBands;
1072 }
1073 
1074 QPair< QString, QString > QgsRasterHistogramWidget::rendererMinMax( int theBandNo )
1075 {
1076  QPair< QString, QString > myMinMax;
1077 
1078  if ( ! mRendererWidget )
1079  return myMinMax;
1080 
1081  if ( mRendererName == "singlebandgray" )
1082  {
1083  if ( theBandNo == mRendererWidget->selectedBand( ) )
1084  {
1085  myMinMax.first = mRendererWidget->min();
1086  myMinMax.second = mRendererWidget->max();
1087  }
1088  }
1089  else if ( mRendererName == "multibandcolor" )
1090  {
1091  for ( int i = 0; i <= 2; i++ )
1092  {
1093  if ( theBandNo == mRendererWidget->selectedBand( i ) )
1094  {
1095  myMinMax.first = mRendererWidget->min( i );
1096  myMinMax.second = mRendererWidget->max( i );
1097  break;
1098  }
1099  }
1100  }
1101 
1102  // TODO - there are 2 definitions of raster data type that should be unified
1103  // QgsRasterDataProvider::DataType and QGis::DataType
1104  // TODO - fix gdal provider: changes data type when nodata value is not found
1105  // this prevents us from getting proper min and max values here
1106  // minStr = QString::number( QgsContrastEnhancement::minimumValuePossible( ( QGis::DataType )
1107  // mRasterLayer->dataProvider()->dataType( theBandNo ) ) );
1108  // maxStr = QString::number( QgsContrastEnhancement::maximumValuePossible( ( QGis::DataType )
1109  // mRasterLayer->dataProvider()->dataType( theBandNo ) ) );
1110 
1111  // if we get an empty result, fill with default value (histo min/max)
1112  if ( myMinMax.first.isEmpty() )
1113  myMinMax.first = QString::number( mHistoMin );
1114  if ( myMinMax.second.isEmpty() )
1115  myMinMax.second = QString::number( mHistoMax );
1116 
1117  QgsDebugMsg( QString( "bandNo %1 got min/max [%2] [%3]" ).arg( theBandNo ).arg( myMinMax.first ).arg( myMinMax.second ) );
1118 
1119  return myMinMax;
1120 }