1 /***************************************************************************
2     qgsrastertransparencywidget.cpp
3     ---------------------
4     begin                : May 2016
5     copyright            : (C) 2016 by Nathan Woodrow
6     email                : woodrow dot nathan at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include <QWidget>
16 #include <QIntValidator>
17 #include <QFile>
18 #include <QTextStream>
19 #include <QMessageBox>
20 #include <QFileDialog>
21 #include <QRegularExpression>
22 
23 #include "qgssettings.h"
24 #include "qgsrastertransparencywidget.h"
25 #include "qgsrasterlayer.h"
26 #include "qgsraster.h"
27 #include "qgsrasterlayerrenderer.h"
28 #include "qgsrasterdataprovider.h"
29 #include "qgsrastertransparency.h"
30 #include "qgsmaptoolemitpoint.h"
31 #include "qgsmapsettings.h"
32 #include "qgsrectangle.h"
33 #include "qgsmapcanvas.h"
34 #include "qgsrasteridentifyresult.h"
35 #include "qgsmultibandcolorrenderer.h"
36 #include "qgsdoublevalidator.h"
37 #include "qgsexpressioncontextutils.h"
38 #include "qgstemporalcontroller.h"
39 
QgsRasterTransparencyWidget(QgsRasterLayer * layer,QgsMapCanvas * canvas,QWidget * parent)40 QgsRasterTransparencyWidget::QgsRasterTransparencyWidget( QgsRasterLayer *layer, QgsMapCanvas *canvas, QWidget *parent )
41   : QgsMapLayerConfigWidget( layer, canvas, parent )
42   , TRSTRING_NOT_SET( tr( "Not Set" ) )
43   , mRasterLayer( layer )
44   , mMapCanvas( canvas )
45 {
46   setupUi( this );
47   connect( pbnAddValuesFromDisplay, &QToolButton::clicked, this, &QgsRasterTransparencyWidget::pbnAddValuesFromDisplay_clicked );
48   connect( pbnAddValuesManually, &QToolButton::clicked, this, &QgsRasterTransparencyWidget::pbnAddValuesManually_clicked );
49   connect( pbnDefaultValues, &QToolButton::clicked, this, &QgsRasterTransparencyWidget::pbnDefaultValues_clicked );
50   connect( pbnExportTransparentPixelValues, &QToolButton::clicked, this, &QgsRasterTransparencyWidget::pbnExportTransparentPixelValues_clicked );
51   connect( pbnImportTransparentPixelValues, &QToolButton::clicked, this, &QgsRasterTransparencyWidget::pbnImportTransparentPixelValues_clicked );
52   connect( pbnRemoveSelectedRow, &QToolButton::clicked, this, &QgsRasterTransparencyWidget::pbnRemoveSelectedRow_clicked );
53 
54   mNodataColorButton->setShowNoColor( true );
55   mNodataColorButton->setColorDialogTitle( tr( "Select No Data Color" ) );
56   syncToLayer();
57 
58   connect( mOpacityWidget, &QgsOpacityWidget::opacityChanged, this, &QgsPanelWidget::widgetChanged );
59   connect( cboxTransparencyBand, &QgsRasterBandComboBox::bandChanged, this, &QgsPanelWidget::widgetChanged );
60   connect( mSrcNoDataValueCheckBox, &QCheckBox::stateChanged, this, &QgsPanelWidget::widgetChanged );
61   connect( leNoDataValue, &QLineEdit::textEdited, this, &QgsPanelWidget::widgetChanged );
62   leNoDataValue->setValidator( new QgsDoubleValidator( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), this ) );
63   connect( mNodataColorButton, &QgsColorButton::colorChanged, this, &QgsPanelWidget::widgetChanged );
64 
65   mPixelSelectorTool = nullptr;
66   if ( mMapCanvas )
67   {
68     mPixelSelectorTool = new QgsMapToolEmitPoint( mMapCanvas );
69     connect( mPixelSelectorTool, &QgsMapToolEmitPoint::canvasClicked, this, &QgsRasterTransparencyWidget::pixelSelected );
70   }
71   else
72   {
73     pbnAddValuesFromDisplay->setEnabled( false );
74   }
75 
76   initializeDataDefinedButton( mOpacityDDBtn, QgsRasterPipe::RendererOpacity );
77 }
78 
setContext(const QgsSymbolWidgetContext & context)79 void QgsRasterTransparencyWidget::setContext( const QgsSymbolWidgetContext &context )
80 {
81   mContext = context;
82 }
83 
createExpressionContext() const84 QgsExpressionContext QgsRasterTransparencyWidget::createExpressionContext() const
85 {
86   QgsExpressionContext expContext;
87   expContext << QgsExpressionContextUtils::globalScope()
88              << QgsExpressionContextUtils::projectScope( QgsProject::instance() )
89              << QgsExpressionContextUtils::atlasScope( nullptr );
90 
91   if ( QgsMapCanvas *canvas = mContext.mapCanvas() )
92   {
93     expContext << QgsExpressionContextUtils::mapSettingsScope( canvas->mapSettings() )
94                << new QgsExpressionContextScope( canvas->expressionContextScope() );
95     if ( const QgsExpressionContextScopeGenerator *generator = dynamic_cast< const QgsExpressionContextScopeGenerator * >( canvas->temporalController() ) )
96     {
97       expContext << generator->createExpressionContextScope();
98     }
99   }
100   else
101   {
102     expContext << QgsExpressionContextUtils::mapSettingsScope( QgsMapSettings() );
103   }
104 
105   if ( mRasterLayer )
106     expContext << QgsExpressionContextUtils::layerScope( mRasterLayer );
107 
108   // additional scopes
109   const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
110   for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
111   {
112     expContext.appendScope( new QgsExpressionContextScope( scope ) );
113   }
114 
115   return expContext;
116 }
117 
syncToLayer()118 void QgsRasterTransparencyWidget::syncToLayer()
119 {
120   QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
121   QgsRasterRenderer *renderer = mRasterLayer->renderer();
122   if ( provider )
123   {
124     if ( provider->dataType( 1 ) == Qgis::DataType::ARGB32
125          || provider->dataType( 1 ) == Qgis::DataType::ARGB32_Premultiplied )
126     {
127       gboxNoDataValue->setEnabled( false );
128       gboxCustomTransparency->setEnabled( false );
129     }
130 
131     cboxTransparencyBand->setShowNotSetOption( true, tr( "None" ) );
132     cboxTransparencyBand->setLayer( mRasterLayer );
133   }
134 
135   if ( mRasterLayer->dataProvider()->sourceHasNoDataValue( 1 ) )
136   {
137     lblSrcNoDataValue->setText( QgsRasterBlock::printValue( mRasterLayer->dataProvider()->sourceNoDataValue( 1 ) ) );
138   }
139   else
140   {
141     lblSrcNoDataValue->setText( tr( "not defined" ) );
142   }
143 
144   mSrcNoDataValueCheckBox->setChecked( mRasterLayer->dataProvider()->useSourceNoDataValue( 1 ) );
145 
146   const bool enableSrcNoData = mRasterLayer->dataProvider()->sourceHasNoDataValue( 1 ) && !std::isnan( mRasterLayer->dataProvider()->sourceNoDataValue( 1 ) );
147 
148   mSrcNoDataValueCheckBox->setEnabled( enableSrcNoData );
149   lblSrcNoDataValue->setEnabled( enableSrcNoData );
150 
151   if ( renderer )
152   {
153     if ( renderer->nodataColor().isValid() )
154       mNodataColorButton->setColor( renderer->nodataColor() );
155     else
156       mNodataColorButton->setToNull();
157 
158     mOpacityWidget->setOpacity( renderer->opacity() );
159 
160     cboxTransparencyBand->setBand( renderer->alphaBand() );
161   }
162 
163   const QgsRasterRangeList noDataRangeList = mRasterLayer->dataProvider()->userNoDataValues( 1 );
164   QgsDebugMsg( QStringLiteral( "noDataRangeList.size = %1" ).arg( noDataRangeList.size() ) );
165   if ( !noDataRangeList.isEmpty() )
166   {
167     const double v = QgsRasterBlock::printValue( noDataRangeList.value( 0 ).min() ).toDouble();
168     leNoDataValue->setText( QLocale().toString( v ) );
169   }
170   else
171   {
172     leNoDataValue->setText( QString() );
173   }
174 
175   mPropertyCollection = mRasterLayer->pipe()->dataDefinedProperties();
176   updateDataDefinedButtons();
177 
178   populateTransparencyTable( mRasterLayer->renderer() );
179 }
180 
transparencyCellTextEdited(const QString & text)181 void QgsRasterTransparencyWidget::transparencyCellTextEdited( const QString &text )
182 {
183   Q_UNUSED( text )
184   QgsDebugMsg( QStringLiteral( "text = %1" ).arg( text ) );
185   QgsRasterRenderer *renderer = mRasterLayer->renderer();
186   if ( !renderer )
187   {
188     return;
189   }
190   const int nBands = renderer->usesBands().size();
191   if ( nBands == 1 )
192   {
193     QLineEdit *lineEdit = qobject_cast<QLineEdit *>( sender() );
194     if ( !lineEdit ) return;
195     int row = -1;
196     int column = -1;
197     for ( int r = 0; r < tableTransparency->rowCount(); r++ )
198     {
199       for ( int c = 0; c < tableTransparency->columnCount(); c++ )
200       {
201         if ( tableTransparency->cellWidget( r, c ) == sender() )
202         {
203           row = r;
204           column = c;
205           break;
206         }
207       }
208       if ( row != -1 ) break;
209     }
210     QgsDebugMsg( QStringLiteral( "row = %1 column =%2" ).arg( row ).arg( column ) );
211 
212     if ( column == 0 )
213     {
214       QLineEdit *toLineEdit = dynamic_cast<QLineEdit *>( tableTransparency->cellWidget( row, 1 ) );
215       if ( !toLineEdit ) return;
216       const bool toChanged = mTransparencyToEdited.value( row );
217       QgsDebugMsg( QStringLiteral( "toChanged = %1" ).arg( toChanged ) );
218       if ( !toChanged )
219       {
220         toLineEdit->setText( lineEdit->text() );
221       }
222     }
223     else if ( column == 1 )
224     {
225       setTransparencyToEdited( row );
226     }
227   }
228   emit widgetChanged();
229 }
230 
pbnAddValuesFromDisplay_clicked()231 void QgsRasterTransparencyWidget::pbnAddValuesFromDisplay_clicked()
232 {
233   if ( mMapCanvas && mPixelSelectorTool )
234   {
235     mMapCanvas->setMapTool( mPixelSelectorTool );
236   }
237 }
238 
pbnAddValuesManually_clicked()239 void QgsRasterTransparencyWidget::pbnAddValuesManually_clicked()
240 {
241   QgsRasterRenderer *renderer = mRasterLayer->renderer();
242   if ( !renderer )
243   {
244     return;
245   }
246 
247   tableTransparency->insertRow( tableTransparency->rowCount() );
248 
249   int n = renderer->usesBands().size();
250   if ( n == 1 ) n++;
251 
252   for ( int i = 0; i < n; i++ )
253   {
254     setTransparencyCell( tableTransparency->rowCount() - 1, i, std::numeric_limits<double>::quiet_NaN() );
255   }
256 
257   setTransparencyCell( tableTransparency->rowCount() - 1, n, 100 );
258 
259   //tableTransparency->resizeColumnsToContents();
260   //tableTransparency->resizeRowsToContents();
261 }
262 
pbnDefaultValues_clicked()263 void QgsRasterTransparencyWidget::pbnDefaultValues_clicked()
264 {
265   QgsRasterRenderer *r = mRasterLayer->renderer();
266   if ( !r )
267   {
268     return;
269   }
270 
271   const int nBands = r->usesBands().size();
272 
273   setupTransparencyTable( nBands );
274 
275   //tableTransparency->resizeColumnsToContents(); // works only with values
276   //tableTransparency->resizeRowsToContents();
277 
278 }
279 
pbnExportTransparentPixelValues_clicked()280 void QgsRasterTransparencyWidget::pbnExportTransparentPixelValues_clicked()
281 {
282   const QgsSettings myQSettings;
283   const QString myLastDir = myQSettings.value( QStringLiteral( "lastRasterFileFilterDir" ), QDir::homePath() ).toString();
284   QString myFileName = QFileDialog::getSaveFileName( this, tr( "Save Pixel Values as File" ), myLastDir, tr( "Textfile" ) + " (*.txt)" );
285   if ( !myFileName.isEmpty() )
286   {
287     if ( !myFileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
288     {
289       myFileName = myFileName + ".txt";
290     }
291 
292     QFile myOutputFile( myFileName );
293     if ( myOutputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
294     {
295       QTextStream myOutputStream( &myOutputFile );
296       myOutputStream << "# " << tr( "QGIS Generated Transparent Pixel Value Export File" ) << '\n';
297       if ( rasterIsMultiBandColor() )
298       {
299         myOutputStream << "#\n#\n# " << tr( "Red" ) << "\t" << tr( "Green" ) << "\t" << tr( "Blue" ) << "\t" << tr( "Percent Transparent" );
300         for ( int myTableRunner = 0; myTableRunner < tableTransparency->rowCount(); myTableRunner++ )
301         {
302           myOutputStream << '\n' << QString::number( transparencyCellValue( myTableRunner, 0 ) ) << "\t"
303                          << QString::number( transparencyCellValue( myTableRunner, 1 ) ) << "\t"
304                          << QString::number( transparencyCellValue( myTableRunner, 2 ) ) << "\t"
305                          << QString::number( transparencyCellValue( myTableRunner, 3 ) );
306         }
307       }
308       else
309       {
310         myOutputStream << "#\n#\n# " << tr( "Value" ) << "\t" << tr( "Percent Transparent" );
311 
312         for ( int myTableRunner = 0; myTableRunner < tableTransparency->rowCount(); myTableRunner++ )
313         {
314           myOutputStream << '\n' << QString::number( transparencyCellValue( myTableRunner, 0 ) ) << "\t"
315                          << QString::number( transparencyCellValue( myTableRunner, 1 ) ) << "\t"
316                          << QString::number( transparencyCellValue( myTableRunner, 2 ) );
317         }
318       }
319     }
320     else
321     {
322       QMessageBox::warning( this, tr( "Save Pixel Values as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
323     }
324   }
325 }
326 
pbnImportTransparentPixelValues_clicked()327 void QgsRasterTransparencyWidget::pbnImportTransparentPixelValues_clicked()
328 {
329   int myLineCounter = 0;
330   bool myImportError = false;
331   QString myBadLines;
332   const QgsSettings myQSettings;
333   const QString myLastDir = myQSettings.value( QStringLiteral( "lastRasterFileFilterDir" ), QDir::homePath() ).toString();
334   const QString myFileName = QFileDialog::getOpenFileName( this, tr( "Load Pixel Values from File" ), myLastDir, tr( "Textfile" ) + " (*.txt)" );
335   QFile myInputFile( myFileName );
336   if ( myInputFile.open( QFile::ReadOnly ) )
337   {
338     QTextStream myInputStream( &myInputFile );
339     QString myInputLine;
340     if ( rasterIsMultiBandColor() )
341     {
342       for ( int myTableRunner = tableTransparency->rowCount() - 1; myTableRunner >= 0; myTableRunner-- )
343       {
344         tableTransparency->removeRow( myTableRunner );
345       }
346 
347       while ( !myInputStream.atEnd() )
348       {
349         myLineCounter++;
350         myInputLine = myInputStream.readLine();
351         if ( !myInputLine.isEmpty() )
352         {
353           if ( !myInputLine.simplified().startsWith( '#' ) )
354           {
355 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
356             QStringList myTokens = myInputLine.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
357 #else
358             QStringList myTokens = myInputLine.split( QRegularExpression( "\\s+" ), Qt::SkipEmptyParts );
359 #endif
360             if ( myTokens.count() != 4 )
361             {
362               myImportError = true;
363               myBadLines = myBadLines + QString::number( myLineCounter ) + ":\t[" + myInputLine + "]\n";
364             }
365             else
366             {
367               tableTransparency->insertRow( tableTransparency->rowCount() );
368               for ( int col = 0; col < 4; col++ )
369               {
370                 setTransparencyCell( tableTransparency->rowCount() - 1, col, myTokens[col].toDouble() );
371               }
372             }
373           }
374         }
375       }
376     }
377     else
378     {
379       for ( int myTableRunner = tableTransparency->rowCount() - 1; myTableRunner >= 0; myTableRunner-- )
380       {
381         tableTransparency->removeRow( myTableRunner );
382       }
383 
384       while ( !myInputStream.atEnd() )
385       {
386         myLineCounter++;
387         myInputLine = myInputStream.readLine();
388         if ( !myInputLine.isEmpty() )
389         {
390           if ( !myInputLine.simplified().startsWith( '#' ) )
391           {
392 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
393             QStringList myTokens = myInputLine.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
394 #else
395             QStringList myTokens = myInputLine.split( QRegularExpression( "\\s+" ), Qt::SkipEmptyParts );
396 #endif
397             if ( myTokens.count() != 3 && myTokens.count() != 2 ) // 2 for QGIS < 1.9 compatibility
398             {
399               myImportError = true;
400               myBadLines = myBadLines + QString::number( myLineCounter ) + ":\t[" + myInputLine + "]\n";
401             }
402             else
403             {
404               if ( myTokens.count() == 2 )
405               {
406                 myTokens.insert( 1, myTokens[0] ); // add 'to' value, QGIS < 1.9 compatibility
407               }
408               tableTransparency->insertRow( tableTransparency->rowCount() );
409               for ( int col = 0; col < 3; col++ )
410               {
411                 setTransparencyCell( tableTransparency->rowCount() - 1, col, myTokens[col].toDouble() );
412               }
413             }
414           }
415         }
416       }
417     }
418 
419     if ( myImportError )
420     {
421       QMessageBox::warning( this, tr( "Load Pixel Values from File" ), tr( "The following lines contained errors\n\n%1" ).arg( myBadLines ) );
422     }
423   }
424   else if ( !myFileName.isEmpty() )
425   {
426     QMessageBox::warning( this, tr( "Load Pixel Values from File" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
427   }
428   //tableTransparency->resizeColumnsToContents();
429   //tableTransparency->resizeRowsToContents();
430   emit widgetChanged();
431 }
432 
pbnRemoveSelectedRow_clicked()433 void QgsRasterTransparencyWidget::pbnRemoveSelectedRow_clicked()
434 {
435   if ( 0 < tableTransparency->rowCount() )
436   {
437     tableTransparency->removeRow( tableTransparency->currentRow() );
438   }
439   emit widgetChanged();
440 }
441 
rasterIsMultiBandColor()442 bool QgsRasterTransparencyWidget::rasterIsMultiBandColor()
443 {
444   return mRasterLayer && nullptr != dynamic_cast<QgsMultiBandColorRenderer *>( mRasterLayer->renderer() );
445 }
446 
apply()447 void QgsRasterTransparencyWidget::apply()
448 {
449   //set NoDataValue
450   QgsRasterRangeList myNoDataRangeList;
451   if ( "" != leNoDataValue->text() )
452   {
453     bool myDoubleOk = false;
454     const double myNoDataValue = QgsDoubleValidator::toDouble( leNoDataValue->text(), &myDoubleOk );
455     if ( myDoubleOk )
456     {
457       const QgsRasterRange myNoDataRange( myNoDataValue, myNoDataValue );
458       myNoDataRangeList << myNoDataRange;
459     }
460   }
461   for ( int bandNo = 1; bandNo <= mRasterLayer->dataProvider()->bandCount(); bandNo++ )
462   {
463     mRasterLayer->dataProvider()->setUserNoDataValue( bandNo, myNoDataRangeList );
464     mRasterLayer->dataProvider()->setUseSourceNoDataValue( bandNo, mSrcNoDataValueCheckBox->isChecked() );
465   }
466 
467   //transparency settings
468   QgsRasterRenderer *rasterRenderer = mRasterLayer->renderer();
469   if ( rasterRenderer )
470   {
471     rasterRenderer->setAlphaBand( cboxTransparencyBand->currentBand() );
472     rasterRenderer->setNodataColor( mNodataColorButton->color() );
473 
474     //Walk through each row in table and test value. If not valid set to 0.0 and continue building transparency list
475     QgsRasterTransparency *rasterTransparency = new QgsRasterTransparency();
476     if ( tableTransparency->columnCount() == 4 )
477     {
478       QgsRasterTransparency::TransparentThreeValuePixel myTransparentPixel;
479       QList<QgsRasterTransparency::TransparentThreeValuePixel> myTransparentThreeValuePixelList;
480       myTransparentThreeValuePixelList.reserve( tableTransparency->rowCount() );
481       for ( int myListRunner = 0; myListRunner < tableTransparency->rowCount(); myListRunner++ )
482       {
483         myTransparentPixel.red = transparencyCellValue( myListRunner, 0 );
484         myTransparentPixel.green = transparencyCellValue( myListRunner, 1 );
485         myTransparentPixel.blue = transparencyCellValue( myListRunner, 2 );
486         myTransparentPixel.percentTransparent = transparencyCellValue( myListRunner, 3 );
487         myTransparentThreeValuePixelList.append( myTransparentPixel );
488       }
489       rasterTransparency->setTransparentThreeValuePixelList( myTransparentThreeValuePixelList );
490     }
491     else if ( tableTransparency->columnCount() == 3 )
492     {
493       QgsRasterTransparency::TransparentSingleValuePixel myTransparentPixel;
494       QList<QgsRasterTransparency::TransparentSingleValuePixel> myTransparentSingleValuePixelList;
495       myTransparentSingleValuePixelList.reserve( tableTransparency->rowCount() );
496       for ( int myListRunner = 0; myListRunner < tableTransparency->rowCount(); myListRunner++ )
497       {
498         myTransparentPixel.min = transparencyCellValue( myListRunner, 0 );
499         myTransparentPixel.max = transparencyCellValue( myListRunner, 1 );
500         myTransparentPixel.percentTransparent = transparencyCellValue( myListRunner, 2 );
501 
502         myTransparentSingleValuePixelList.append( myTransparentPixel );
503       }
504       rasterTransparency->setTransparentSingleValuePixelList( myTransparentSingleValuePixelList );
505     }
506 
507     rasterRenderer->setRasterTransparency( rasterTransparency );
508 
509     //set global transparency
510     rasterRenderer->setOpacity( mOpacityWidget->opacity() );
511   }
512 
513   mRasterLayer->pipe()->setDataDefinedProperties( mPropertyCollection );
514 }
515 
initializeDataDefinedButton(QgsPropertyOverrideButton * button,QgsRasterPipe::Property key)516 void QgsRasterTransparencyWidget::initializeDataDefinedButton( QgsPropertyOverrideButton *button, QgsRasterPipe::Property key )
517 {
518   button->blockSignals( true );
519   button->init( key, mPropertyCollection, QgsRasterPipe::propertyDefinitions(), nullptr );
520   connect( button, &QgsPropertyOverrideButton::changed, this, &QgsRasterTransparencyWidget::updateProperty );
521   button->registerExpressionContextGenerator( this );
522   button->blockSignals( false );
523 }
524 
updateDataDefinedButtons()525 void QgsRasterTransparencyWidget::updateDataDefinedButtons()
526 {
527   const auto propertyOverrideButtons { findChildren< QgsPropertyOverrideButton * >() };
528   for ( QgsPropertyOverrideButton *button : propertyOverrideButtons )
529   {
530     updateDataDefinedButton( button );
531   }
532 }
533 
updateDataDefinedButton(QgsPropertyOverrideButton * button)534 void QgsRasterTransparencyWidget::updateDataDefinedButton( QgsPropertyOverrideButton *button )
535 {
536   if ( !button )
537     return;
538 
539   if ( button->propertyKey() < 0 )
540     return;
541 
542   const QgsRasterPipe::Property key = static_cast< QgsRasterPipe::Property >( button->propertyKey() );
543   whileBlocking( button )->setToProperty( mPropertyCollection.property( key ) );
544 }
545 
updateProperty()546 void QgsRasterTransparencyWidget::updateProperty()
547 {
548   QgsPropertyOverrideButton *button = qobject_cast<QgsPropertyOverrideButton *>( sender() );
549   const QgsRasterPipe::Property key = static_cast<  QgsRasterPipe::Property >( button->propertyKey() );
550   mPropertyCollection.setProperty( key, button->toProperty() );
551   emit widgetChanged();
552 }
553 
pixelSelected(const QgsPointXY & canvasPoint)554 void QgsRasterTransparencyWidget::pixelSelected( const QgsPointXY &canvasPoint )
555 {
556   QgsRasterRenderer *renderer = mRasterLayer->renderer();
557   if ( !renderer )
558   {
559     return;
560   }
561 
562   //Get the pixel values and add a new entry to the transparency table
563   if ( mMapCanvas && mPixelSelectorTool )
564   {
565     mMapCanvas->unsetMapTool( mPixelSelectorTool );
566 
567     const QgsMapSettings &ms = mMapCanvas->mapSettings();
568     const QgsPointXY myPoint = ms.mapToLayerCoordinates( mRasterLayer, canvasPoint );
569 
570     const QgsRectangle myExtent = ms.mapToLayerCoordinates( mRasterLayer, mMapCanvas->extent() );
571     const double mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
572     const int myWidth = mMapCanvas->extent().width() / mapUnitsPerPixel;
573     const int myHeight = mMapCanvas->extent().height() / mapUnitsPerPixel;
574 
575     const QMap<int, QVariant> myPixelMap = mRasterLayer->dataProvider()->identify( myPoint, QgsRaster::IdentifyFormatValue, myExtent, myWidth, myHeight ).results();
576 
577     const QList<int> bands = renderer->usesBands();
578 
579     QList<double> values;
580     for ( int i = 0; i < bands.size(); ++i )
581     {
582       const int bandNo = bands.value( i );
583       if ( myPixelMap.count( bandNo ) == 1 )
584       {
585         if ( myPixelMap.value( bandNo ).isNull() )
586         {
587           return; // Don't add nodata, transparent anyway
588         }
589         const double value = myPixelMap.value( bandNo ).toDouble();
590         QgsDebugMsg( QStringLiteral( "value = %1" ).arg( value, 0, 'g', 17 ) );
591         values.append( value );
592       }
593     }
594     if ( bands.size() == 1 )
595     {
596       // Set 'to'
597       values.insert( 1, values.value( 0 ) );
598     }
599     tableTransparency->insertRow( tableTransparency->rowCount() );
600     for ( int i = 0; i < values.size(); i++ )
601     {
602       setTransparencyCell( tableTransparency->rowCount() - 1, i, values.value( i ) );
603     }
604     setTransparencyCell( tableTransparency->rowCount() - 1, tableTransparency->columnCount() - 1, 100 );
605   }
606 
607   //tableTransparency->resizeColumnsToContents();
608   //tableTransparency->resizeRowsToContents();
609 }
610 
populateTransparencyTable(QgsRasterRenderer * renderer)611 void QgsRasterTransparencyWidget::populateTransparencyTable( QgsRasterRenderer *renderer )
612 {
613   if ( !mRasterLayer )
614   {
615     return;
616   }
617 
618   if ( !renderer )
619   {
620     return;
621   }
622 
623   const int nBands = renderer->usesBands().size();
624   setupTransparencyTable( nBands );
625 
626   const QgsRasterTransparency *rasterTransparency = renderer->rasterTransparency();
627   if ( !rasterTransparency )
628   {
629     return;
630   }
631 
632   if ( nBands == 1 )
633   {
634     QList<QgsRasterTransparency::TransparentSingleValuePixel> pixelList = rasterTransparency->transparentSingleValuePixelList();
635     for ( int i = 0; i < pixelList.size(); ++i )
636     {
637       tableTransparency->insertRow( i );
638       setTransparencyCell( i, 0, pixelList[i].min );
639       setTransparencyCell( i, 1, pixelList[i].max );
640       setTransparencyCell( i, 2, pixelList[i].percentTransparent );
641       // break synchronization only if values differ
642       if ( pixelList[i].min != pixelList[i].max )
643       {
644         setTransparencyToEdited( i );
645       }
646     }
647   }
648   else if ( nBands == 3 )
649   {
650     QList<QgsRasterTransparency::TransparentThreeValuePixel> pixelList = rasterTransparency->transparentThreeValuePixelList();
651     for ( int i = 0; i < pixelList.size(); ++i )
652     {
653       tableTransparency->insertRow( i );
654       setTransparencyCell( i, 0, pixelList[i].red );
655       setTransparencyCell( i, 1, pixelList[i].green );
656       setTransparencyCell( i, 2, pixelList[i].blue );
657       setTransparencyCell( i, 3, pixelList[i].percentTransparent );
658     }
659   }
660 
661   tableTransparency->resizeColumnsToContents();
662   tableTransparency->resizeRowsToContents();
663 
664 }
665 
setupTransparencyTable(int nBands)666 void QgsRasterTransparencyWidget::setupTransparencyTable( int nBands )
667 {
668   tableTransparency->clear();
669   tableTransparency->setColumnCount( 0 );
670   tableTransparency->setRowCount( 0 );
671   mTransparencyToEdited.clear();
672 
673   if ( nBands == 3 )
674   {
675     tableTransparency->setColumnCount( 4 );
676     tableTransparency->setHorizontalHeaderItem( 0, new QTableWidgetItem( tr( "Red" ) ) );
677     tableTransparency->setHorizontalHeaderItem( 1, new QTableWidgetItem( tr( "Green" ) ) );
678     tableTransparency->setHorizontalHeaderItem( 2, new QTableWidgetItem( tr( "Blue" ) ) );
679     tableTransparency->setHorizontalHeaderItem( 3, new QTableWidgetItem( tr( "Percent Transparent" ) ) );
680   }
681   else //1 band
682   {
683     tableTransparency->setColumnCount( 3 );
684 // Is it important to distinguish the header? It becomes difficult with range.
685 #if 0
686     if ( QgsRasterLayer::PalettedColor != mRasterLayer->drawingStyle() &&
687          QgsRasterLayer::PalettedSingleBandGray != mRasterLayer->drawingStyle() &&
688          QgsRasterLayer::PalettedSingleBandPseudoColor != mRasterLayer->drawingStyle() &&
689          QgsRasterLayer::PalettedMultiBandColor != mRasterLayer->drawingStyle() )
690     {
691       tableTransparency->setHorizontalHeaderItem( 0, new QTableWidgetItem( tr( "Gray" ) ) );
692     }
693     else
694     {
695       tableTransparency->setHorizontalHeaderItem( 0, new QTableWidgetItem( tr( "Indexed Value" ) ) );
696     }
697 #endif
698     tableTransparency->setHorizontalHeaderItem( 0, new QTableWidgetItem( tr( "From" ) ) );
699     tableTransparency->setHorizontalHeaderItem( 1, new QTableWidgetItem( tr( "To" ) ) );
700     tableTransparency->setHorizontalHeaderItem( 2, new QTableWidgetItem( tr( "Percent Transparent" ) ) );
701   }
702 }
703 
setTransparencyCell(int row,int column,double value)704 void QgsRasterTransparencyWidget::setTransparencyCell( int row, int column, double value )
705 {
706   QgsDebugMsg( QStringLiteral( "value = %1" ).arg( value, 0, 'g', 17 ) );
707   QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
708   if ( !provider ) return;
709 
710   QgsRasterRenderer *renderer = mRasterLayer->renderer();
711   if ( !renderer ) return;
712   const int nBands = renderer->usesBands().size();
713 
714   QLineEdit *lineEdit = new QLineEdit();
715   lineEdit->setFrame( false ); // frame looks bad in table
716   // Without margins row selection is not displayed (important for delete row)
717   lineEdit->setContentsMargins( 1, 1, 1, 1 );
718 
719   if ( column == tableTransparency->columnCount() - 1 )
720   {
721     // transparency
722     // Who needs transparency as floating point?
723     lineEdit->setValidator( new QIntValidator( nullptr ) );
724     lineEdit->setText( QString::number( static_cast<int>( value ) ) );
725   }
726   else
727   {
728     // value
729     QString valueString;
730     switch ( provider->sourceDataType( 1 ) )
731     {
732       case Qgis::DataType::Float32:
733       case Qgis::DataType::Float64:
734         lineEdit->setValidator( new QgsDoubleValidator( nullptr ) );
735         if ( !std::isnan( value ) )
736         {
737           const double v = QgsRasterBlock::printValue( value ).toDouble();
738           valueString = QLocale().toString( v );
739         }
740         break;
741       default:
742         lineEdit->setValidator( new QIntValidator( nullptr ) );
743         if ( !std::isnan( value ) )
744         {
745           valueString = QString::number( static_cast<int>( value ) );
746         }
747         break;
748     }
749     lineEdit->setText( valueString );
750     connect( lineEdit, &QLineEdit::textEdited, this, &QgsPanelWidget::widgetChanged );
751   }
752   tableTransparency->setCellWidget( row, column, lineEdit );
753   adjustTransparencyCellWidth( row, column );
754 
755   if ( nBands == 1 && ( column == 0 || column == 1 ) )
756   {
757     connect( lineEdit, &QLineEdit::textEdited, this, &QgsRasterTransparencyWidget::transparencyCellTextEdited );
758   }
759   //tableTransparency->resizeColumnsToContents();
760   emit widgetChanged();
761 }
762 
adjustTransparencyCellWidth(int row,int column)763 void QgsRasterTransparencyWidget::adjustTransparencyCellWidth( int row, int column )
764 {
765   QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( tableTransparency->cellWidget( row, column ) );
766   if ( !lineEdit ) return;
767 
768   int width = std::max( lineEdit->fontMetrics().boundingRect( lineEdit->text() ).width() + 10, 100 );
769   width = std::max( width, tableTransparency->columnWidth( column ) );
770 
771   lineEdit->setFixedWidth( width );
772 }
773 
setTransparencyToEdited(int row)774 void QgsRasterTransparencyWidget::setTransparencyToEdited( int row )
775 {
776   if ( row >= mTransparencyToEdited.size() )
777   {
778     mTransparencyToEdited.resize( row + 1 );
779   }
780   mTransparencyToEdited[row] = true;
781 }
782 
transparencyCellValue(int row,int column)783 double QgsRasterTransparencyWidget::transparencyCellValue( int row, int column )
784 {
785   QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( tableTransparency->cellWidget( row, column ) );
786   if ( !lineEdit || lineEdit->text().isEmpty() )
787   {
788     return std::numeric_limits<double>::quiet_NaN();
789   }
790   return QgsDoubleValidator::toDouble( lineEdit->text() );
791 
792 }
793 
pixelSelectorTool() const794 QgsMapToolEmitPoint *QgsRasterTransparencyWidget::pixelSelectorTool() const
795 {
796   return mPixelSelectorTool;
797 }
798