Index: src/app/composer/qgscomposer.cpp =================================================================== --- src/app/composer/qgscomposer.cpp (revision 12507) +++ src/app/composer/qgscomposer.cpp (working copy) @@ -574,7 +574,7 @@ int width = ( int )( mComposition->printResolution() * mComposition->paperWidth() / 25.4 ); int height = ( int )( mComposition-> printResolution() * mComposition->paperHeight() / 25.4 ); - int memuse = width * height * 3 / 1000000; // pixmap + image + int memuse = width * height * 4 / 1000000; // pixmap + image QgsDebugMsg( QString( "Image %1 x %2" ).arg( width ).arg( height ) ); QgsDebugMsg( QString( "memuse = %1" ).arg( memuse ) ); Index: src/core/raster/qgsrasterlayer.cpp =================================================================== --- src/core/raster/qgsrasterlayer.cpp (revision 12507) +++ src/core/raster/qgsrasterlayer.cpp (working copy) @@ -22,6 +22,7 @@ #include "qgsmaptopixel.h" #include "qgsproviderregistry.h" #include "qgsrasterbandstats.h" +#include "qgsrasterimagebuffer.h" #include "qgsrasterlayer.h" #include "qgsrasterpyramid.h" #include "qgsrectangle.h" @@ -1578,90 +1579,88 @@ mDataProvider->setDpi( rendererContext.rasterScaleFactor() * 25.4 * rendererContext.scaleFactor() ); - QImage* image = - mDataProvider->draw( - myRasterExtent, - // Below should calculate to the actual pixel size of the - // part of the layer that's visible. - static_cast( fabs(( myRasterViewPort->clippedXMax - myRasterViewPort->clippedXMin ) - / theQgsMapToPixel.mapUnitsPerPixel() * mGeoTransform[1] ) + 1 ), - static_cast( fabs(( myRasterViewPort->clippedYMax - myRasterViewPort->clippedYMin ) - / theQgsMapToPixel.mapUnitsPerPixel() * mGeoTransform[5] ) + 1 ) -// myRasterViewPort->drawableAreaXDim, -// myRasterViewPort->drawableAreaYDim - ); + //fetch image in several parts if it is too memory consuming + //also some WMS servers have a pixel limit, so it's better to make several requests + int totalPixelWidth = fabs(( myRasterViewPort->clippedXMax - myRasterViewPort->clippedXMin ) + / theQgsMapToPixel.mapUnitsPerPixel() * mGeoTransform[1] ) + 1; + int totalPixelHeight = fabs(( myRasterViewPort->clippedYMax - myRasterViewPort->clippedYMin ) + / theQgsMapToPixel.mapUnitsPerPixel() * mGeoTransform[5] ) + 1; + int numParts = totalPixelWidth * totalPixelHeight / 2000000 + 1.0; + int numRowsPerPart = totalPixelHeight / numParts + 1.0; - if ( !image ) + + int currentPixelOffsetY = 0; //top y-coordinate of current raster part + //the width of a WMS image part + int pixelWidth = (myRasterExtent.xMaximum() - myRasterExtent.xMinimum()) / theQgsMapToPixel.mapUnitsPerPixel(); + for(int i = 0; i < numParts; ++i) { - // An error occurred. - mErrorCaption = mDataProvider->lastErrorTitle(); - mError = mDataProvider->lastError(); + //fetch a small overlap of 2 pixels between two adjacent tiles to avoid white stripes + QgsRectangle rasterPartRect(myRasterExtent.xMinimum(), myRasterExtent.yMaximum() - (currentPixelOffsetY + numRowsPerPart + 2) * theQgsMapToPixel.mapUnitsPerPixel(), \ + myRasterExtent.xMaximum(), myRasterExtent.yMaximum() - currentPixelOffsetY * theQgsMapToPixel.mapUnitsPerPixel()); - delete myRasterViewPort; - return FALSE; - } + int pixelHeight = rasterPartRect.height() / theQgsMapToPixel.mapUnitsPerPixel(); + QImage* image = mDataProvider->draw(rasterPartRect, pixelWidth, pixelHeight); - QgsDebugMsg( "Done mDataProvider->draw." ); - QgsDebugMsg( "image stats: " ); + if ( !image ) + { + // An error occurred. + mErrorCaption = mDataProvider->lastErrorTitle(); + mError = mDataProvider->lastError(); - QgsDebugMsg( QString( "depth=%1" ).arg( image->depth() ) ); - QgsDebugMsg( QString( "bytes=%1" ).arg( image->numBytes() ) ); - QgsDebugMsg( QString( "width=%1" ).arg( image->width() ) ); - QgsDebugMsg( QString( "height=%1" ).arg( image->height() ) ); + delete myRasterViewPort; + return FALSE; + } - QgsDebugMsg( "Want to theQPainter->drawImage with" ); + QgsDebugMsg( "Done mDataProvider->draw." ); + QgsDebugMsg( "image stats: " ); - QgsDebugMsg( QString( "origin x: %1" ).arg( myRasterViewPort->topLeftPoint.x() ) ); - QgsDebugMsg( QString( "(int)origin x: %1" ).arg( static_cast( myRasterViewPort->topLeftPoint.x() ) ) ); - QgsDebugMsg( QString( "origin y: %1" ).arg( myRasterViewPort->topLeftPoint.y() ) ); - QgsDebugMsg( QString( "(int)origin y: %1" ).arg( static_cast( myRasterViewPort->topLeftPoint.y() ) ) ); + QgsDebugMsg( QString( "depth=%1" ).arg( image->depth() ) ); + QgsDebugMsg( QString( "bytes=%1" ).arg( image->numBytes() ) ); + QgsDebugMsg( QString( "width=%1" ).arg( image->width() ) ); + QgsDebugMsg( QString( "height=%1" ).arg( image->height() ) ); - //Set the transparency for the whole layer - //QImage::setAlphaChannel does not work quite as expected so set each pixel individually - //Currently this is only done for WMS images, which should be small enough not to impact performance + QgsDebugMsg( "Want to theQPainter->drawImage with" ); - if ( mTransparencyLevel != 255 ) //improve performance if layer transparency not altered - { - QImage* transparentImageCopy = new QImage( *image ); //copy image if there is user transparency - image = transparentImageCopy; - int myWidth = image->width(); - int myHeight = image->height(); - QRgb myRgb; - int newTransparency; - for ( int myHeightRunner = 0; myHeightRunner < myHeight; myHeightRunner++ ) + QgsDebugMsg( QString( "origin x: %1" ).arg( myRasterViewPort->topLeftPoint.x() ) ); + QgsDebugMsg( QString( "(int)origin x: %1" ).arg( static_cast( myRasterViewPort->topLeftPoint.x() ) ) ); + QgsDebugMsg( QString( "origin y: %1" ).arg( myRasterViewPort->topLeftPoint.y() ) ); + QgsDebugMsg( QString( "(int)origin y: %1" ).arg( static_cast( myRasterViewPort->topLeftPoint.y() ) ) ); + + //Set the transparency for the whole layer + //QImage::setAlphaChannel does not work quite as expected so set each pixel individually + //Currently this is only done for WMS images, which should be small enough not to impact performance + + if ( mTransparencyLevel != 255 ) //improve performance if layer transparency not altered { - QRgb* myLineBuffer = ( QRgb* ) transparentImageCopy->scanLine( myHeightRunner ); - for ( int myWidthRunner = 0; myWidthRunner < myWidth; myWidthRunner++ ) + QImage* transparentImageCopy = new QImage( *image ); //copy image if there is user transparency + image = transparentImageCopy; + int myWidth = image->width(); + int myHeight = image->height(); + QRgb myRgb; + int newTransparency; + for ( int myHeightRunner = 0; myHeightRunner < myHeight; myHeightRunner++ ) { - myRgb = image->pixel( myWidthRunner, myHeightRunner ); - //combine transparency from WMS and layer transparency - newTransparency = ( double ) mTransparencyLevel / 255.0 * ( double )( qAlpha( myRgb ) ); - myLineBuffer[ myWidthRunner ] = qRgba( qRed( myRgb ), qGreen( myRgb ), qBlue( myRgb ), newTransparency ); + QRgb* myLineBuffer = ( QRgb* ) transparentImageCopy->scanLine( myHeightRunner ); + for ( int myWidthRunner = 0; myWidthRunner < myWidth; myWidthRunner++ ) + { + myRgb = image->pixel( myWidthRunner, myHeightRunner ); + //combine transparency from WMS and layer transparency + newTransparency = ( double ) mTransparencyLevel / 255.0 * ( double )( qAlpha( myRgb ) ); + myLineBuffer[ myWidthRunner ] = qRgba( qRed( myRgb ), qGreen( myRgb ), qBlue( myRgb ), newTransparency ); + } } } - } - // Since GDAL's RasterIO can't handle floating point, we have to round to - // the nearest pixel. Add 0.5 to get rounding instead of truncation - // out of static_cast. - theQPainter->drawImage( static_cast( - myRasterViewPort->topLeftPoint.x() - + 0.5 // try simulating rounding instead of truncation, to avoid off-by-one errors - // TODO: Check for rigorous correctness - ), - static_cast( - myRasterViewPort->topLeftPoint.y() - + 0.5 // try simulating rounding instead of truncation, to avoid off-by-one errors - // TODO: Check for rigorous correctness - ), - *image ); + theQPainter->drawImage(myRasterViewPort->topLeftPoint.x(), myRasterViewPort->topLeftPoint.y() + currentPixelOffsetY, *image); + currentPixelOffsetY += numRowsPerPart; - if ( mTransparencyLevel != 255 ) - { - delete image; + if ( mTransparencyLevel != 255 ) + { + delete image; + } + + emit statusChanged( tr( "%1 retrieved using %2" ).arg( name() ).arg( mProviderKey ) ); } - - emit statusChanged( tr( "%1 retrieved using %2" ).arg( name() ).arg( mProviderKey ) ); } else { @@ -2917,7 +2916,7 @@ // Only do this for the non-provider (hard-coded GDAL) scenario... // Maybe WMS can do this differently using QImage::numColors and QImage::color() - if ( mProviderKey.isEmpty() && hasBand( "Palette" ) && theBandNumber > 0 ) //dont tr() this its a gdal word! + if ( mProviderKey.isEmpty() && hasBand( "Palette" ) && theBandNumber > 0 ) //don't tr() this its a gdal word! { QgsDebugMsg( "....found paletted image" ); QgsColorRampShader myShader; @@ -2977,7 +2976,7 @@ GDALRasterBandH myGdalBand = GDALGetRasterBand( mGdalDataset, theBandNo ); QgsRasterBandStats myRasterBandStats = bandStatistics( theBandNo ); //calculate the histogram for this band - //we assume that it only needs to be calculated if the lenght of the histogram + //we assume that it only needs to be calculated if the length of the histogram //vector is not equal to the number of bins //i.e if the histogram has never previously been generated or the user has //selected a new number of bins. @@ -3786,7 +3785,7 @@ { QgsColorRampShader* myColorRampShader = ( QgsColorRampShader* ) mRasterShader->rasterShaderFunction(); - //TODO: Remove the customColorRampType check and following if() in v2.0, added for compatability with older ( bugged ) project files + //TODO: Remove the customColorRampType check and following if() in v2.0, added for compatibility with older ( bugged ) project files QDomNode customColorRampTypeNode = customColorRampNode.namedItem( "customColorRampType" ); QDomNode colorRampTypeNode = customColorRampNode.namedItem( "colorRampType" ); QString myRampType = ""; @@ -4505,6 +4504,61 @@ GDALRasterBandH myGdalBand = GDALGetRasterBand( mGdalDataset, theBandNo ); GDALDataType myDataType = GDALGetRasterDataType( myGdalBand ); + QgsRasterImageBuffer imageBuffer( myGdalBand, theQPainter, theRasterViewPort, theQgsMapToPixel, &mGeoTransform[0] ); + imageBuffer.reset(); + + QRgb* imageScanLine = 0; + void* rasterScanLine = 0; + + QRgb myDefaultColor = qRgba( 255, 255, 255, 0 ); + double myPixelValue = 0.0; + int myRedValue = 0; + int myGreenValue = 0; + int myBlueValue = 0; + int myAlphaValue = 0; + + while( imageBuffer.nextScanLine( &imageScanLine, &rasterScanLine ) ) + { + for( int i = 0; i < theRasterViewPort->drawableAreaXDim; ++i ) + { + myRedValue = 0; myGreenValue = 0; myBlueValue = 0; + myPixelValue = readValue( rasterScanLine, ( GDALDataType )myDataType, i); + + if ( mValidNoDataValue && ( fabs( myPixelValue - mNoDataValue ) <= TINY_VALUE || myPixelValue != myPixelValue ) ) + { + imageScanLine[ i ] = myDefaultColor; + continue; + } + + myAlphaValue = mRasterTransparency.alphaValue( myPixelValue, mTransparencyLevel ); + if ( 0 == myAlphaValue ) + { + imageScanLine[ i ] = myDefaultColor; + continue; + } + + if ( !mRasterShader->shade( myPixelValue, &myRedValue, &myGreenValue, &myBlueValue ) ) + { + imageScanLine[ i ] = myDefaultColor; + continue; + } + + if ( mInvertColor ) + { + //Invert flag, flip blue and read + imageScanLine[ i ] = qRgba( myBlueValue, myGreenValue, myRedValue, myAlphaValue ); + } + else + { + //Normal + imageScanLine[ i ] = qRgba( myRedValue, myGreenValue, myBlueValue, myAlphaValue ); + } + } + } + +#if 0 + + void *myGdalScanData = readData( myGdalBand, theRasterViewPort ); /* Check for out of memory error */ @@ -4568,6 +4622,7 @@ CPLFree( myGdalScanData ); paintImageToCanvas( theQPainter, theRasterViewPort, theQgsMapToPixel, &myQImage ); +#endif //0 } /** @@ -5314,7 +5369,7 @@ mRasterType = Multiband; } //TODO hasBand is really obsolete and only used in the Palette instance, change to new function hasPalette(int) - else if ( hasBand( "Palette" ) ) //dont tr() this its a gdal word! + else if ( hasBand( "Palette" ) ) //don't tr() this its a gdal word! { mRasterType = Palette; } Index: src/core/raster/qgsrasterimagebuffer.cpp =================================================================== --- src/core/raster/qgsrasterimagebuffer.cpp (revision 0) +++ src/core/raster/qgsrasterimagebuffer.cpp (revision 0) @@ -0,0 +1,163 @@ +/*************************************************************************** + qgsrasterimagebuffer.cpp + ------------------------ + begin : December 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco at hugis dot net + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsrasterimagebuffer.h" +#include "qgsmaptopixel.h" +#include "qgsrasterviewport.h" +#include +#include +#include +#include "cpl_conv.h" + +#ifndef Q_OS_MACX +#include +#else +#include +#endif + + +QgsRasterImageBuffer::QgsRasterImageBuffer(GDALRasterBandH rasterBand, QPainter* p, QgsRasterViewPort* viewPort, const QgsMapToPixel* mapToPixel, double* geoTransform): \ +mRasterBand(rasterBand), mPainter(p), mViewPort(viewPort), mMapToPixel(mapToPixel), mValid(false), mCurrentImage(0), mCurrentGDALData(0), mGeoTransform(geoTransform) +{ + +} + +QgsRasterImageBuffer::~QgsRasterImageBuffer() +{ + delete mCurrentImage; + CPLFree(mCurrentGDALData); +} + +void QgsRasterImageBuffer::reset() +{ + if( mRasterBand && mPainter && mViewPort && mMapToPixel ) + { + mValid = true; + } + else + { + mValid = false; + return; + } + + //decide on the partition of the image + + int pixels = mViewPort->drawableAreaXDim * mViewPort->drawableAreaYDim; + int mNumPartImages = std::max(pixels / 50000000.0, 1.0); + mNumRasterRowsPerPart = (double)mViewPort->clippedHeight / (double)mNumPartImages + 0.5; + + mCurrentPartRasterMin = -1; + mCurrentPartRasterMax = -1; + mCurrentPartImageRow = 0; + mNumCurrentImageRows = 0; + + createNextPartImage(); +} + +bool QgsRasterImageBuffer::nextScanLine(QRgb** imageScanLine, void** rasterScanLine) +{ + if(!mValid) + { + return false; + } + + if( !mCurrentGDALData || ! mCurrentImage ) + { + return false; + } + + if(mCurrentPartImageRow >= (mNumCurrentImageRows)) + { + if(!createNextPartImage()) + { + return false; + } + } + + *imageScanLine = ( QRgb* )(mCurrentImage->scanLine(mCurrentPartImageRow)); + GDALDataType type = GDALGetRasterDataType( mRasterBand ); + int size = GDALGetDataTypeSize( type ) / 8; + *rasterScanLine = mCurrentGDALData + mCurrentPartImageRow * mViewPort->drawableAreaXDim * size; + + ++mCurrentPartImageRow; + ++mCurrentRow; + return true; +} + +bool QgsRasterImageBuffer::createNextPartImage() +{ + //draw the last image if mCurrentImage if it exists + if(mCurrentImage) + { + double xLeft = mViewPort->topLeftPoint.x(); + double yTop = mViewPort->topLeftPoint.y() + fabs(mGeoTransform[5]) * mCurrentPartRasterMin / mMapToPixel->mapUnitsPerPixel(); + mPainter->drawImage(QPointF(xLeft, yTop), *mCurrentImage); + } + + delete mCurrentImage; mCurrentImage = 0; + CPLFree(mCurrentGDALData); mCurrentGDALData = 0; + + if(mCurrentPartRasterMax >= mViewPort->clippedHeight) + { + return false; //already at the end... + } + + mCurrentPartRasterMin = mCurrentPartRasterMax + 1; + mCurrentPartRasterMax = mCurrentPartRasterMin + mNumRasterRowsPerPart; + if(mCurrentPartRasterMax > mViewPort->clippedHeight) + { + mCurrentPartRasterMax = mViewPort->clippedHeight; + } + mCurrentRow = mCurrentPartRasterMin; + mCurrentPartImageRow = 0; + + //read GDAL image data + GDALDataType type = GDALGetRasterDataType( mRasterBand ); + int size = GDALGetDataTypeSize( type ) / 8; + int xSize = mViewPort->drawableAreaXDim; + + //make the raster tiles overlap at least 2 pixels to avoid white stripes + int overlapRows = 0; + overlapRows = mMapToPixel->mapUnitsPerPixel() / fabs(mGeoTransform[5]) + 2; + if(mCurrentPartRasterMax + overlapRows >= mViewPort->clippedHeight) + { + overlapRows = 0; + } + int rasterYSize = mCurrentPartRasterMax - mCurrentPartRasterMin + overlapRows; + + int ySize = fabs(( (rasterYSize) / mMapToPixel->mapUnitsPerPixel() * mGeoTransform[5] ) ) + 0.5; + if(ySize == 0) + { + return false; + } + mNumCurrentImageRows = ySize; + mCurrentGDALData = VSIMalloc(size * xSize * ySize); + CPLErr myErr = GDALRasterIO( mRasterBand, GF_Read, mViewPort->rectXOffset, \ + mViewPort->rectYOffset + mCurrentRow, mViewPort->clippedWidth, rasterYSize, \ + mCurrentGDALData, xSize, ySize, type, 0, 0); + + if ( myErr != CPLE_None ) + { + CPLFree(mCurrentGDALData); + mCurrentGDALData = 0; + return false; + } + + //create the QImage + mCurrentImage = new QImage( xSize, ySize, QImage::Format_ARGB32 ); + mCurrentImage->fill(qRgba(255, 255, 255, 0)); + return true; +} + Index: src/core/raster/qgsrasterimagebuffer.h =================================================================== --- src/core/raster/qgsrasterimagebuffer.h (revision 0) +++ src/core/raster/qgsrasterimagebuffer.h (revision 0) @@ -0,0 +1,65 @@ +/*************************************************************************** + qgsrasterimagebuffer.h + --------------------- + begin : December 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco at hugis dot net + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSRASTERIMAGEBUFFER_H +#define QGSRASTERIMAGEBUFFER_H + +#include + +typedef void* GDALRasterBandH; +class QgsMapToPixel; +struct QgsRasterViewPort; +class QImage; +class QPainter; + +/**A class encapsulates reading from a raster band and drawing the pixels to a painter. + The class allows sequential reading of the scan lines and setting the image scan line pixels. It automatically decides + on how much of the band / image should stay in virtual memory at a time*/ +class QgsRasterImageBuffer +{ + public: + QgsRasterImageBuffer(GDALRasterBandH rasterBand, QPainter* p, \ + QgsRasterViewPort* viewPort, const QgsMapToPixel* mapToPixel, double* mGeoTransform); + ~QgsRasterImageBuffer(); + void reset(); + /**Returns a pointer to the next scan line (or 0 if end)*/ + bool nextScanLine(QRgb** imageScanLine, void** rasterScanLine); + + private: + QgsRasterImageBuffer(); //forbidden + /**Creates next part image. Returns false if at end*/ + bool createNextPartImage(); + + GDALRasterBandH mRasterBand; //raster band + QPainter* mPainter; + QgsRasterViewPort* mViewPort; + const QgsMapToPixel* mMapToPixel; + double* mGeoTransform; + + bool mValid; + int mCurrentRow; + int mNumPartImages; //number of part images + int mNumRasterRowsPerPart; //number of (raster source) rows per part + int mCurrentPartRasterMin; //minimum (raster source) row of current image + int mCurrentPartRasterMax; //maximum (raster source) row of current image + int mCurrentPartImageRow; //current image row + int mNumCurrentImageRows; //number of image rows for the current part + + //current memory image and gdal scan data + QImage* mCurrentImage; + void* mCurrentGDALData; +}; + +#endif // QGSRASTERIMAGEBUFFER_H Index: src/core/CMakeLists.txt =================================================================== --- src/core/CMakeLists.txt (revision 12507) +++ src/core/CMakeLists.txt (working copy) @@ -110,6 +110,7 @@ raster/qgslinearminmaxenhancement.cpp raster/qgslinearminmaxenhancementwithclip.cpp raster/qgspseudocolorshader.cpp + raster/qgsrasterimagebuffer.cpp raster/qgsrasterlayer.cpp raster/qgsrastertransparency.cpp raster/qgsrastershader.cpp