QgsRasterDataProvider *QgsRasterFileWriter::createMultiBandRaster( Qgis::DataType dataType, int width, int height, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs, int nBands ) { if ( mTiledMode ) return nullptr; // does not make sense with tiled mode double pixelSize; double geoTransform[6]; globalOutputParameters( extent, width, height, geoTransform, pixelSize ); return initOutput( width, height, crs, geoTransform, nBands, dataType, QList<bool>(), QList<double>() ); }
QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRasterIterator* iter, int nCols, int nRows, const QgsRectangle& outputExtent, const QgsCoordinateReferenceSystem& crs, QProgressDialog* progressDialog ) { QgsDebugMsg( "Entered" ); if ( !iter ) { return SourceProviderError; } const QgsRasterInterface* iface = iter->input(); QGis::DataType inputDataType = iface->dataType( 1 ); if ( !iface || ( inputDataType != QGis::ARGB32 && inputDataType != QGis::ARGB32_Premultiplied ) ) { return SourceProviderError; } iter->setMaximumTileWidth( mMaxTileWidth ); iter->setMaximumTileHeight( mMaxTileHeight ); void* redData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); void* greenData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); void* blueData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); void* alphaData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); QgsRectangle mapRect; int iterLeft = 0, iterTop = 0, iterCols = 0, iterRows = 0; int fileIndex = 0; //create destProvider for whole dataset here QgsRasterDataProvider* destProvider = 0; double pixelSize; double geoTransform[6]; globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize ); destProvider = initOutput( nCols, nRows, crs, geoTransform, 4, QGis::Byte ); iter->startRasterRead( 1, nCols, nRows, outputExtent ); int nParts = 0; if ( progressDialog ) { int nPartsX = nCols / iter->maximumTileWidth() + 1; int nPartsY = nRows / iter->maximumTileHeight() + 1; nParts = nPartsX * nPartsY; progressDialog->setMaximum( nParts ); progressDialog->show(); progressDialog->setLabelText( QObject::tr( "Reading raster part %1 of %2" ).arg( fileIndex + 1 ).arg( nParts ) ); } QgsRasterBlock *inputBlock = 0; while ( iter->readNextRasterPart( 1, iterCols, iterRows, &inputBlock, iterLeft, iterTop ) ) { if ( !inputBlock ) { continue; } if ( progressDialog && fileIndex < ( nParts - 1 ) ) { progressDialog->setValue( fileIndex + 1 ); progressDialog->setLabelText( QObject::tr( "Reading raster part %1 of %2" ).arg( fileIndex + 2 ).arg( nParts ) ); QCoreApplication::processEvents( QEventLoop::AllEvents, 1000 ); if ( progressDialog->wasCanceled() ) { delete inputBlock; break; } } //fill into red/green/blue/alpha channels qgssize nPixels = ( qgssize )iterCols * iterRows; // TODO: should be char not int? we are then copying 1 byte int red = 0; int green = 0; int blue = 0; int alpha = 255; for ( qgssize i = 0; i < nPixels; ++i ) { QRgb c = inputBlock->color( i ); alpha = qAlpha( c ); red = qRed( c ); green = qGreen( c ); blue = qBlue( c ); if ( inputDataType == QGis::ARGB32_Premultiplied ) { double a = alpha / 255.; QgsDebugMsgLevel( QString( "red = %1 green = %2 blue = %3 alpha = %4 p = %5 a = %6" ).arg( red ).arg( green ).arg( blue ).arg( alpha ).arg(( int )c, 0, 16 ).arg( a ), 5 ); red /= a; green /= a; blue /= a; } memcpy(( char* )redData + i, &red, 1 ); memcpy(( char* )greenData + i, &green, 1 ); memcpy(( char* )blueData + i, &blue, 1 ); memcpy(( char* )alphaData + i, &alpha, 1 ); } delete inputBlock; //create output file if ( mTiledMode ) { //delete destProvider; QgsRasterDataProvider* partDestProvider = createPartProvider( outputExtent, nCols, iterCols, iterRows, iterLeft, iterTop, mOutputUrl, fileIndex, 4, QGis::Byte, crs ); if ( partDestProvider ) { //write data to output file partDestProvider->write( redData, 1, iterCols, iterRows, 0, 0 ); partDestProvider->write( greenData, 2, iterCols, iterRows, 0, 0 ); partDestProvider->write( blueData, 3, iterCols, iterRows, 0, 0 ); partDestProvider->write( alphaData, 4, iterCols, iterRows, 0, 0 ); addToVRT( partFileName( fileIndex ), 1, iterCols, iterRows, iterLeft, iterTop ); addToVRT( partFileName( fileIndex ), 2, iterCols, iterRows, iterLeft, iterTop ); addToVRT( partFileName( fileIndex ), 3, iterCols, iterRows, iterLeft, iterTop ); addToVRT( partFileName( fileIndex ), 4, iterCols, iterRows, iterLeft, iterTop ); delete partDestProvider; } } else if ( destProvider ) { destProvider->write( redData, 1, iterCols, iterRows, iterLeft, iterTop ); destProvider->write( greenData, 2, iterCols, iterRows, iterLeft, iterTop ); destProvider->write( blueData, 3, iterCols, iterRows, iterLeft, iterTop ); destProvider->write( alphaData, 4, iterCols, iterRows, iterLeft, iterTop ); } ++fileIndex; } if ( destProvider ) delete destProvider; qgsFree( redData ); qgsFree( greenData ); qgsFree( blueData ); qgsFree( alphaData ); if ( progressDialog ) { progressDialog->setValue( progressDialog->maximum() ); } if ( mTiledMode ) { QString vrtFilePath( mOutputUrl + "/" + vrtFileName() ); writeVRT( vrtFilePath ); if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes ) { buildPyramids( vrtFilePath ); } } else { if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes ) { buildPyramids( mOutputUrl ); } } return NoError; }
QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe* pipe, QgsRasterIterator* iter, int nCols, int nRows, const QgsRectangle& outputExtent, const QgsCoordinateReferenceSystem& crs, QProgressDialog* progressDialog ) { QgsDebugMsg( "Entered" ); if ( !iter ) { return SourceProviderError; } const QgsRasterInterface* iface = pipe->last(); if ( !iface ) { return SourceProviderError; } QgsRasterDataProvider* srcProvider = const_cast<QgsRasterDataProvider*>( dynamic_cast<const QgsRasterDataProvider*>( iface->srcInput() ) ); if ( !srcProvider ) { QgsDebugMsg( "Cannot get source data provider" ); return SourceProviderError; } iter->setMaximumTileWidth( mMaxTileWidth ); iter->setMaximumTileHeight( mMaxTileHeight ); int nBands = iface->bandCount(); if ( nBands < 1 ) { return SourceProviderError; } //check if all the bands have the same data type size, otherwise we cannot write it to the provider //(at least not with the current interface) int dataTypeSize = QgsRasterBlock::typeSize( srcProvider->srcDataType( 1 ) ); for ( int i = 2; i <= nBands; ++i ) { if ( QgsRasterBlock::typeSize( srcProvider->srcDataType( 1 ) ) != dataTypeSize ) { return DestProviderError; } } // Output data type - source data type is preferred but it may happen that we need // to set 'no data' value (which was not set on source data) if output extent // is larger than source extent (with or without reprojection) and there is no 'free' // (not used) value available QList<bool> destHasNoDataValueList; QList<double> destNoDataValueList; QList<QGis::DataType> destDataTypeList; for ( int bandNo = 1; bandNo <= nBands; bandNo++ ) { QgsRasterNuller *nuller = pipe->nuller(); bool srcHasNoDataValue = srcProvider->srcHasNoDataValue( bandNo ); bool destHasNoDataValue = false; double destNoDataValue = std::numeric_limits<double>::quiet_NaN(); QGis::DataType destDataType = srcProvider->srcDataType( bandNo ); // TODO: verify what happens/should happen if srcNoDataValue is disabled by setUseSrcNoDataValue QgsDebugMsg( QString( "srcHasNoDataValue = %1 srcNoDataValue = %2" ).arg( srcHasNoDataValue ).arg( srcProvider->srcNoDataValue( bandNo ) ) ); if ( srcHasNoDataValue ) { // If source has no data value, it is used by provider destNoDataValue = srcProvider->srcNoDataValue( bandNo ); destHasNoDataValue = true; } else if ( nuller && nuller->noData( bandNo ).size() > 0 ) { // Use one user defined no data value destNoDataValue = nuller->noData( bandNo ).value( 0 ).min(); destHasNoDataValue = true; } else { // Verify if we realy need no data value, i.e. QgsRectangle srcExtent = outputExtent; QgsRasterProjector *projector = pipe->projector(); if ( projector && projector->destCrs() != projector->srcCrs() ) { QgsCoordinateTransform ct( projector->destCrs(), projector->srcCrs() ); srcExtent = ct.transformBoundingBox( outputExtent ); } if ( !srcProvider->extent().contains( srcExtent ) ) { // Destination extent is larger than source extent, we need destination no data values // Get src sample statistics (estimation from sample) QgsRasterBandStats stats = srcProvider->bandStatistics( bandNo, QgsRasterBandStats::Min | QgsRasterBandStats::Max, srcExtent, 250000 ); // Test if we have free (not used) values double typeMinValue = QgsContrastEnhancement::maximumValuePossible(( QGis::DataType )srcProvider->srcDataType( bandNo ) ); double typeMaxValue = QgsContrastEnhancement::maximumValuePossible(( QGis::DataType )srcProvider->srcDataType( bandNo ) ); if ( stats.minimumValue > typeMinValue ) { destNoDataValue = typeMinValue; } else if ( stats.maximumValue < typeMaxValue ) { destNoDataValue = typeMaxValue; } else { // We have to use wider type destDataType = QgsRasterBlock::typeWithNoDataValue( destDataType, &destNoDataValue ); } destHasNoDataValue = true; } } if ( nuller && destHasNoDataValue ) { nuller->setOutputNoDataValue( bandNo, destNoDataValue ); } QgsDebugMsg( QString( "bandNo = %1 destDataType = %2 destHasNoDataValue = %3 destNoDataValue = %4" ).arg( bandNo ).arg( destDataType ).arg( destHasNoDataValue ).arg( destNoDataValue ) ); destDataTypeList.append( destDataType ); destHasNoDataValueList.append( destHasNoDataValue ); destNoDataValueList.append( destNoDataValue ); } QGis::DataType destDataType = destDataTypeList.value( 0 ); // Currently write API supports one output type for dataset only -> find the widest for ( int i = 1; i < nBands; i++ ) { if ( destDataTypeList.value( i ) > destDataType ) { destDataType = destDataTypeList.value( i ); // no data value may be left per band (for future) } } //create destProvider for whole dataset here QgsRasterDataProvider* destProvider = 0; double pixelSize; double geoTransform[6]; globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize ); // initOutput() returns 0 in tile mode! destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ); WriterError error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, progressDialog ); if ( error == NoDataConflict ) { // The value used for no data was found in source data, we must use wider data type if ( destProvider ) // no tiles { destProvider->remove(); delete destProvider; destProvider = 0; } else // VRT { // TODO: remove created VRT } // But we don't know which band -> wider all for ( int i = 0; i < nBands; i++ ) { double destNoDataValue; QGis::DataType destDataType = QgsRasterBlock::typeWithNoDataValue( destDataTypeList.value( i ), &destNoDataValue ); destDataTypeList.replace( i, destDataType ); destNoDataValueList.replace( i, destNoDataValue ); } destDataType = destDataTypeList.value( 0 ); // Try again destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ); error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, progressDialog ); } if ( destProvider ) delete destProvider; return error; }
QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback ) { QgsDebugMsgLevel( "Entered", 4 ); if ( !iter ) { return SourceProviderError; } const QgsRasterInterface *iface = iter->input(); if ( !iface ) return SourceProviderError; Qgis::DataType inputDataType = iface->dataType( 1 ); if ( inputDataType != Qgis::ARGB32 && inputDataType != Qgis::ARGB32_Premultiplied ) { return SourceProviderError; } iter->setMaximumTileWidth( mMaxTileWidth ); iter->setMaximumTileHeight( mMaxTileHeight ); void *redData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); void *greenData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); void *blueData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); void *alphaData = qgsMalloc( mMaxTileWidth * mMaxTileHeight ); int iterLeft = 0, iterTop = 0, iterCols = 0, iterRows = 0; int fileIndex = 0; //create destProvider for whole dataset here double pixelSize; double geoTransform[6]; globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize ); std::unique_ptr< QgsRasterDataProvider > destProvider( initOutput( nCols, nRows, crs, geoTransform, 4, Qgis::Byte ) ); iter->startRasterRead( 1, nCols, nRows, outputExtent, feedback ); int nParts = 0; if ( feedback ) { int nPartsX = nCols / iter->maximumTileWidth() + 1; int nPartsY = nRows / iter->maximumTileHeight() + 1; nParts = nPartsX * nPartsY; } std::unique_ptr< QgsRasterBlock > inputBlock; while ( iter->readNextRasterPart( 1, iterCols, iterRows, inputBlock, iterLeft, iterTop ) ) { if ( !inputBlock ) { continue; } if ( feedback && fileIndex < ( nParts - 1 ) ) { feedback->setProgress( 100.0 * fileIndex / static_cast< double >( nParts ) ); if ( feedback->isCanceled() ) { break; } } //fill into red/green/blue/alpha channels qgssize nPixels = static_cast< qgssize >( iterCols ) * iterRows; // TODO: should be char not int? we are then copying 1 byte int red = 0; int green = 0; int blue = 0; int alpha = 255; for ( qgssize i = 0; i < nPixels; ++i ) { QRgb c = inputBlock->color( i ); alpha = qAlpha( c ); red = qRed( c ); green = qGreen( c ); blue = qBlue( c ); if ( inputDataType == Qgis::ARGB32_Premultiplied ) { double a = alpha / 255.; QgsDebugMsgLevel( QString( "red = %1 green = %2 blue = %3 alpha = %4 p = %5 a = %6" ).arg( red ).arg( green ).arg( blue ).arg( alpha ).arg( static_cast< int >( c ), 0, 16 ).arg( a ), 5 ); red /= a; green /= a; blue /= a; } memcpy( reinterpret_cast< char * >( redData ) + i, &red, 1 ); memcpy( reinterpret_cast< char * >( greenData ) + i, &green, 1 ); memcpy( reinterpret_cast< char * >( blueData ) + i, &blue, 1 ); memcpy( reinterpret_cast< char * >( alphaData ) + i, &alpha, 1 ); } //create output file if ( mTiledMode ) { //delete destProvider; std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent, nCols, iterCols, iterRows, iterLeft, iterTop, mOutputUrl, fileIndex, 4, Qgis::Byte, crs ) ); if ( partDestProvider ) { //write data to output file partDestProvider->write( redData, 1, iterCols, iterRows, 0, 0 ); partDestProvider->write( greenData, 2, iterCols, iterRows, 0, 0 ); partDestProvider->write( blueData, 3, iterCols, iterRows, 0, 0 ); partDestProvider->write( alphaData, 4, iterCols, iterRows, 0, 0 ); addToVRT( partFileName( fileIndex ), 1, iterCols, iterRows, iterLeft, iterTop ); addToVRT( partFileName( fileIndex ), 2, iterCols, iterRows, iterLeft, iterTop ); addToVRT( partFileName( fileIndex ), 3, iterCols, iterRows, iterLeft, iterTop ); addToVRT( partFileName( fileIndex ), 4, iterCols, iterRows, iterLeft, iterTop ); } } else if ( destProvider ) { destProvider->write( redData, 1, iterCols, iterRows, iterLeft, iterTop ); destProvider->write( greenData, 2, iterCols, iterRows, iterLeft, iterTop ); destProvider->write( blueData, 3, iterCols, iterRows, iterLeft, iterTop ); destProvider->write( alphaData, 4, iterCols, iterRows, iterLeft, iterTop ); } ++fileIndex; } destProvider.reset(); qgsFree( redData ); qgsFree( greenData ); qgsFree( blueData ); qgsFree( alphaData ); if ( feedback ) { feedback->setProgress( 100.0 ); } if ( mTiledMode ) { QString vrtFilePath( mOutputUrl + '/' + vrtFileName() ); writeVRT( vrtFilePath ); if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes ) { buildPyramids( vrtFilePath ); } } else { if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes ) { buildPyramids( mOutputUrl ); } } return ( feedback && feedback->isCanceled() ) ? WriteCanceled : NoError; }