Hey Marco,

Here's the code I have for a bilinear-based implementation (attached),
though I've omitted the actual NN and bilinear resampling code itself for
brevity.  This does have a number of implementation-specific assumptions
which would need to be reworked for the general case.  (the general case in
GIS is hard and makes my brain bleed.)  So it's not really close to the
point of being ready for a pull request.

It could be adapted to work on RGBA output, though with some modifications
mostly involving using the alpha channel with a NN resample in place of the
NODATA values.

It should be all ANSI C with no external dependencies. (except for one
irrelevant Accelerate framework call)

Hope this is useful -- let me know if anything else is needed.


On Tue, Feb 18, 2014 at 1:14 AM, Marco Hugentobler <
marco.hugentob...@sourcepole.ch> wrote:

> Hi Nick
>
> Resampling on data level does not work in case of colormap rasters,
> therefore the resampling filter currently comes after assigning rgba. One
> possibility would be to provide a user choice if resampling should be done
> on color values or on data.
>
> I think it is best if you post your code (as a pull request on github or
> via mailing list), then others can give it a try and provide feedback. I'm
> not really a raster power user myself (mostly working with rasters as
> background images for maps).
>
> Regards,
> Marco
// ============================
// GetZoomedTile_BilinearPunch:
// ============================
//
// Wrapper around bilinear resize for a raster that preserves NODATA values
// without artifacts.
//
// Evenly resizes (zooming in only) a planar 32-bit float tile of varying resolution,
//    normally 256x256 - 384x384.
//
// It will work on other widths and heights, but some of the logic expecting evenly
//    divisible srcWH and resampleBufferWH may need to change, or need a ceilf.
//
// Preserves NODATA values vaguely (but not really) derived from PVR textures; 
//    a "punch through" technique is used based upon NN resampling.
//
// To avoid interpolating NODATAs, a neighborhood raster stats mean function is used.
//
// Currently assumes NODATA = 0.0F and that interpolated values will not be transformed 
//    into NODATA.
//
void gbImage_GetZoomedTile_BilinearPunch_v2(float*       src,
                                            const size_t srcWidth,
                                            const size_t srcHeight,
                                            const size_t srcWidthUnpadded,
                                            const size_t srcHeightUnpadded,
                                            const size_t srcDataCount,
                                            float*       dest,
                                            const size_t destWidth,
                                            const size_t destHeight,
                                            float*       resampleBuffer,
                                            const size_t resampleBufferWidth,
                                            const size_t resampleBufferHeight,
                                            float*       tempBuffer,
                                            const size_t tempBufferBytes,
                                            const int    interpolationTypeId,
                                            const bool   doNotTile,
                                            const float  NODATA)
{
    const bool useBitmask  = srcDataCount < srcWidth * srcHeight;
    const bool cleanOutput = destWidth == resampleBufferWidth && destHeight == resampleBufferHeight;
    
    // first, get NN resampled mask to filter resampled output with later
    if (useBitmask && interpolationTypeId == kGB_TileEngine_Interp_Bilinear)
    {
	// returns NN resampled raster tile of 0 or 1, which is somewhat more efficient than using conditionals later, but will *not* work in the general case.
        // for the general case, do a normal NN resize here, and use a conditional in the final filtering step instead of vmul
        gbImage_GetZoomedTile_NN_FromCrop_Bitmask(src, srcWidthUnpadded, srcHeightUnpadded, srcWidth, dest, destWidth, destHeight, NODATA);
        
        _gbImage_FillNODATA(src, srcWidth, srcHeight, srcWidthUnpadded, srcHeightUnpadded, srcDataCount, tempBuffer, tempBufferBytes, interpolationTypeId, NODATA);
    }//if
    
    // always do NoNODATA if possible, other version is branchy and slower due to inline NODATA conditionals
    if (interpolationTypeId == kGB_TileEngine_Interp_Bilinear || !useBitmask)
    {
        gbImage_BilinearVectorized_NoNODATA(src,
                                            srcWidth,
                                            srcHeight,
                                            cleanOutput ? dest : resampleBuffer,
                                            resampleBufferWidth,
                                            resampleBufferHeight,
                                            true);
    }//if
    else
    {
	// strict bilinear implementation with 3rd pass and inline NODATA detection with NN / horizontal linear fallback
	// generally, looks worse and performs about the same
        gbImage_BilinearVectorized(src,
                                   srcWidth,
                                   srcHeight,
                                   resampleBuffer,
                                   resampleBufferWidth,
                                   resampleBufferHeight,
                                   width*height-1,
                                   (srcWidthUnpadded * srcHeightUnpadded - srcDataCount),
                                   tempBuffer,
                                   tempBufferBytes,
                                   doNotTile);
    }//else
    
    if (useBitmask || !cleanOutput)
    {
        _gbImage_FilterWithBitmaskByRow_Any(cleanOutput ? dest : resampleBuffer,
                                            resampleBufferWidth,
                                            useBitmask  ? tempBuffer : NULL,
                                            useBitmask  ? destHeight : 0,
                                            dest,
                                            destWidth,
                                            destHeight);
    }//if
}//gbImage_GetZoomedTile_BilinearPunch_v2

static inline void _gbImage_FillNODATA(float*       src,
                                       const size_t srcWidth,
                                       const size_t srcHeight,
                                       const size_t srcWidthUnpadded,
                                       const size_t srcHeightUnpadded,
                                       const size_t srcDataCount,
                                       float*       tempBuffer,
                                       const size_t tempBufferBytes,
                                       const int    interpolationTypeId,
                                       const float  NODATA)
{
    if (   srcDataCount >= srcWidth * srcHeight
        || interpolationTypeId == kGB_TileEngine_Interp_NN
        || interpolationTypeId == kGB_TileEngine_Interp_BilinearCareful)
    {
        return;
    }//if
    
    size_t cellSize;
    
    if (interpolationTypeId == kGB_TileEngine_Interp_Lanczos3x3 || interpolationTypeId == kGB_TileEngine_Interp_Lanczos5x5)
    {
        const size_t largestDiff = MAX(srcWidth - srcWidthUnpadded, srcHeight-srcHeightUnpadded);
        cellSize                 = MAX(MIN(largestDiff * 2 + 2, srcWidth), 1);
        
        gbImage_ExtendEdgesRightAndDownOnly(src, srcWidth, srcHeight, srcWidthUnpadded, srcHeightUnpadded);
    }//if
    else
    {
        cellSize = 3;
    }//else
    
    gbImage_RasterNeighborhood_Mean(src, (int)cellSize, srcWidth*srcHeight, 0, 0, srcWidth-1, srcHeight-1, srcWidth, srcHeight, true, tempBuffer, tempBufferBytes);
}//_gbImage_FillNODATA

// this did not vectorize well, at all.
void gbImage_RasterNeighborhood_Mean(float*       src,
                                     const int    cellSize,
                                     const size_t dataCount,
                                     const size_t rwx0,
                                     const size_t rwy0,
                                     const size_t rwx1,
                                     const size_t rwy1,
                                     const size_t width,
                                     const size_t height,
                                     const bool   smoothing,
                                     float*       tempBuffer,
                                     const size_t tempBufferBytes)
{
    const size_t n         = width * height;
    size_t       dataFound = 0;
    bool         didMalloc = false;
    
    if (tempBuffer == NULL)
    {
        didMalloc  = true;
        tempBuffer = malloc(sizeof(float) * n);
    }//if
    
    size_t _cellSize = (size_t)cellSize;
    
    _cellSize = _cellSize > width && _cellSize > height ? MAX(width, height) : _cellSize;
    
    const size_t cellOffsetBack = _cellSize % 2 == 0 ? (_cellSize >> 1) - 1 : (_cellSize - 1) >> 1;
    const size_t cellOffsetFwd  = _cellSize % 2 == 0 ?  _cellSize >> 1      : (_cellSize - 1) >> 1;
    
    memcpy(tempBuffer, src, sizeof(float) * n);
    
    size_t startAX;
    size_t startAY;
    size_t endAX;
    size_t endAY;
    size_t y_width;
    
    size_t aY_width;
    
    size_t y;
    size_t x;
    size_t aY;
    size_t aX;
    float f_mean;
    float f_n;
    
    // don't use additional smoothing if windowing
    const bool useSmoothing = smoothing && rwx0 == 0 && rwy0 == 0 && rwx1 == (width-1) && rwy1 == (height-1);
    
    for (y = rwy0; y <= rwy1; y++)
    {
        startAY = y >=        cellOffsetBack ? y - cellOffsetBack : rwy0;
        endAY   = y <= rwy1 - cellOffsetFwd  ? y + cellOffsetFwd  : rwy1;
        y_width = y * width;
        
        for (x = rwx0; x <= rwx1; x++)
        {
            if (tempBuffer[y_width+x] != 0.0F)
            {
                dataFound++;
                
                startAX = x >=        cellOffsetBack ? x - cellOffsetBack : rwx0;
                endAX   = x <= rwx1 - cellOffsetFwd  ? x + cellOffsetFwd  : rwx1;
                
                f_mean = 0.0F;
                f_n    = 0.0F;
                
                // 1. get mean value
                for (aY = startAY; aY <= endAY; aY++)
                {
                    aY_width = aY * width;
                    
                    for (aX = startAX; aX <= endAX; aX++)
                    {
                        if (tempBuffer[aY_width + aX] != 0.0F)
                        {
                            f_mean += tempBuffer[aY_width + aX];
                            f_n    += 1.0F;
                        }//if
                        else if (useSmoothing && src[aY_width + aX] != 0.0F) // optional smoothing
                        {
                            f_mean += src[aY_width + aX];
                            f_n    += 1.0F;
                        }//else if
                    }//for AX
                }//for AY
                
                f_mean = f_n != 0.0F ? f_mean / f_n : 0.0F;
                
                // 2. set NODATAs to mean value
                for (aY = startAY; aY <= endAY; aY++)
                {
                    aY_width = aY * width;
                    
                    for (aX = startAX; aX <= endAX; aX++)
                    {
                        if (tempBuffer[aY_width + aX] == 0.0F)
                        {
                            src[aY_width + aX] = f_mean;
                        }//if
                    }//for AX
                }//for AY
            }//if
        }//for X
        
        if (dataFound >= dataCount)
        {
            break;
        }//if
    }//for Y
    
    if (didMalloc)
    {
        free(tempBuffer);
        tempBuffer = NULL;
    }//if
}//gbImage_RasterNeighborhood_Mean

// useful for extending edges for Lanczos kernel resampling of continuous
// raster data without needing to load another tile.  mitigates, but does
// not eliminate, tile boundary artifacts in such a case.
void gbImage_ExtendEdgesRightAndDownOnly(float*       src,
                                         const size_t width,
                                         const size_t height,
                                         const size_t dataWidth,
                                         const size_t dataHeight)
{
    size_t x;
    size_t y;
    size_t y_width;
    size_t y_width_x;
    float  dataVal;
    
    if (dataWidth > 0 && width > dataWidth)
    {
        for (y=0; y<dataHeight; y++)
        {
            y_width = y * width;
            dataVal = src[y_width + dataWidth - 1];
            
            for (x=dataWidth; x<width; x++)
            {
                y_width_x = y_width + x;
                
                if (src[y_width_x] == 0.0F)
                {
                    src[y_width_x] = dataVal;
                }//if
            }//for
        }//for
    }//if
    
    if (dataHeight > 0 && height > dataHeight)
    {
        size_t ysb1_width;
        
        for (y=dataHeight; y<height; y++)
        {
            y_width    =  y    * width;
            ysb1_width = (y-1) * width;
            
            for (x=0; x<width; x++)
            {
                y_width_x = y_width + x;
                
                if (src[y_width_x] == 0.0F)
                {
                    src[y_width_x] = src[ysb1_width + x];
                }//if
            }//for
        }//for
    }//if
}//ExtendEdgesRightAndDownOnly

// this both filters resampled output with a NODATA mask of 0.0F, 
// and copies rows into dest from src which are typically padded 
// for resampling due to the kernel extent.
//
// this is for bilinear interpolation -- Lanczos (or cubic) needs
// a clip or some analog to clean up data values turned into NODATAs
// by the negative kernel lobe, or to clamp to the original data range
// if desired.
static inline void _gbImage_FilterWithBitmaskByRow_Any(const float* src,
                                                       const size_t srcWidth,
                                                       const float* bitmask,
                                                       const size_t bitmaskWidth,
                                                       float*       dest,
                                                       const size_t destWidth,
                                                       const size_t destHeight)
{
    const size_t procWidth = bitmask != NULL ? MIN(destWidth, bitmaskWidth) : destWidth;
    
    size_t y_bitmaskWidth;
    size_t y_srcWidth;
    size_t y_destWidth;
    
    if (bitmask != NULL)
    {
        for (size_t y=0; y<destHeight; y++)
        {
            y_bitmaskWidth = y * bitmaskWidth;
            y_srcWidth     = y * srcWidth;
            y_destWidth    = y * destWidth;
            
            // oh noes, the Accelerate framework
            vDSP_vmul(src     + y_srcWidth,     1,
                      bitmask + y_bitmaskWidth, 1,
                      dest    + y_destWidth,    1,
                      procWidth);
        }//for
    }//if
    else
    {
        const size_t rowBytes = sizeof(float) * destWidth;
        
        for (size_t y=0; y<destHeight; y++)
        {
            y_srcWidth  = y * srcWidth;
            y_destWidth = y * destWidth;
            
            memcpy(dest + y_destWidth, src + y_srcWidth, rowBytes);
        }//for
    }//else
}//_gbImage_FilterTargetWithBitmaskByRow_Any
_______________________________________________
Qgis-developer mailing list
Qgis-developer@lists.osgeo.org
http://lists.osgeo.org/mailman/listinfo/qgis-developer

Reply via email to