QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle  const & extent, int width, int height )
{
  QgsDebugMsg( QString( "extent:\n%1" ).arg( extent.toString() ) );
  QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) );
  if ( !mInput )
  {
    QgsDebugMsg( "Input not set" );
    return new QgsRasterBlock();
  }

  if ( ! mSrcCRS.isValid() || ! mDestCRS.isValid() || mSrcCRS == mDestCRS )
  {
    QgsDebugMsg( "No projection necessary" );
    return mInput->block( bandNo, extent, width, height );
  }

  mDestExtent = extent;
  mDestRows = height;
  mDestCols = width;
  calc();

  QgsDebugMsg( QString( "srcExtent:\n%1" ).arg( srcExtent().toString() ) );
  QgsDebugMsg( QString( "srcCols = %1 srcRows = %2" ).arg( srcCols() ).arg( srcRows() ) );

  // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers
  if ( srcRows() <= 0 || srcCols() <= 0 )
  {
    QgsDebugMsg( "Zero srcRows or srcCols" );
    return new QgsRasterBlock();
  }

  QgsRasterBlock *inputBlock = mInput->block( bandNo, srcExtent(), srcCols(), srcRows() );
  if ( !inputBlock || inputBlock->isEmpty() )
  {
    QgsDebugMsg( "No raster data!" );
    delete inputBlock;
    return new QgsRasterBlock();
  }

  qgssize pixelSize = QgsRasterBlock::typeSize( mInput->dataType( bandNo ) );

  QgsRasterBlock *outputBlock;
  if ( inputBlock->hasNoDataValue() )
  {
    outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height, inputBlock->noDataValue() );
  }
  else
  {
    outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
  }
  if ( !outputBlock->isValid() )
  {
    QgsDebugMsg( "Cannot create block" );
    delete inputBlock;
    return outputBlock;
  }

  // set output to no data, it should be fast
  outputBlock->setIsNoData();

  // No data: because isNoData()/setIsNoData() is slow with respect to simple memcpy,
  // we use if only if necessary:
  // 1) no data value exists (numerical) -> memcpy, not necessary isNoData()/setIsNoData()
  // 2) no data value does not exist but it may contain no data (numerical no data bitmap)
  //    -> must use isNoData()/setIsNoData()
  // 3) no data are not used (no no data value, no no data bitmap) -> simple memcpy
  // 4) image - simple memcpy

  // To copy no data values stored in bitmaps we have to use isNoData()/setIsNoData(),
  // we cannot fill output block with no data because we use memcpy for data, not setValue().
  bool doNoData = !QgsRasterBlock::typeIsNumeric( inputBlock->dataType() ) && inputBlock->hasNoData() && !inputBlock->hasNoDataValue();

  const QgsCoordinateTransform* inverseCt = nullptr;
  if ( !mApproximate )
  {
    inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform );
  }

  outputBlock->setIsNoData();

  int srcRow, srcCol;
  for ( int i = 0; i < height; ++i )
  {
    for ( int j = 0; j < width; ++j )
    {
      bool inside = srcRowCol( i, j, &srcRow, &srcCol, inverseCt );
      if ( !inside ) continue; // we have everything set to no data

      qgssize srcIndex = ( qgssize )srcRow * mSrcCols + srcCol;
      QgsDebugMsgLevel( QString( "row = %1 col = %2 srcRow = %3 srcCol = %4" ).arg( i ).arg( j ).arg( srcRow ).arg( srcCol ), 5 );

      // isNoData() may be slow so we check doNoData first
      if ( doNoData && inputBlock->isNoData( srcRow, srcCol ) )
      {
        outputBlock->setIsNoData( i, j );
        continue;
      }

      qgssize destIndex = ( qgssize )i * width + j;
      char *srcBits = inputBlock->bits( srcIndex );
      char *destBits = outputBlock->bits( destIndex );
      if ( !srcBits )
      {
        QgsDebugMsg( QString( "Cannot get input block data: row = %1 col = %2" ).arg( i ).arg( j ) );
        continue;
      }
      if ( !destBits )
      {
        QgsDebugMsg( QString( "Cannot set output block data: srcRow = %1 srcCol = %2" ).arg( srcRow ).arg( srcCol ) );
        continue;
      }
      memcpy( destBits, srcBits, pixelSize );
      outputBlock->setIsData( i, j );
    }
  }

  delete inputBlock;

  return outputBlock;
}
void * QgsRasterProjector::readBlock( int bandNo, QgsRectangle  const & extent, int width, int height )
{
    QgsDebugMsg( QString( "extent:\n%1" ).arg( extent.toString() ) );
    QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) );
    if ( !mInput ) return 0;

    if ( ! mSrcCRS.isValid() || ! mDestCRS.isValid() || mSrcCRS == mDestCRS )
    {
        QgsDebugMsg( "No projection necessary" );
        return mInput->block( bandNo, extent, width, height );
    }

    mDestExtent = extent;
    mDestRows = height;
    mDestCols = width;
    calc();

    QgsDebugMsg( QString( "srcExtent:\n%1" ).arg( srcExtent().toString() ) );
    QgsDebugMsg( QString( "srcCols = %1 srcRows = %2" ).arg( srcCols() ).arg( srcRows() ) );

    // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers
    if ( srcRows() <= 0 || srcCols() <= 0 )
    {
        return 0;
    }

    void * inputData = mInput->block( bandNo, srcExtent(), srcCols(), srcRows() );

    if ( !inputData ) return 0;

    size_t pixelSize = mInput->typeSize( mInput->dataType( bandNo ) ) / 8;

    size_t inputSize = pixelSize * srcCols() * srcRows();

    size_t outputSize = width * height * pixelSize;
    void * outputData = malloc( outputSize );

    // Check for allcoation error
    if ( ! outputData )
    {
        QgsDebugMsg( QString( "Couldn't malloc %1 bytes!" ).arg( outputSize ) );
        free( inputData );
        return 0;
    }
    // TODO: fill by transparent

    int srcRow, srcCol;
    for ( int i = 0; i < height; ++i )
    {
        for ( int j = 0; j < width; ++j )
        {
            srcRowCol( i, j, &srcRow, &srcCol );
            size_t srcIndex = pixelSize * ( srcRow * mSrcCols + srcCol );
            size_t destIndex = pixelSize * ( i * width + j );

            if ( srcIndex >= inputSize || destIndex >= outputSize ) continue; // should not happen

            memcpy(( char* )outputData + destIndex, ( char* )inputData + srcIndex, pixelSize );
        }
    }

    free( inputData );

    return outputData;
}