Intel® Integrated Performance Primitives (Intel® IPP) Developer Guide and Reference

ID 790148
Date 3/22/2024
Public
Document Table of Contents

Using Intel® IPP Resize Functions with Prior Initialization

You can use one of the following approaches to image resizing:

Interpolation algorithms of the Lanczos, Linear, and Cubic types use edge pixels of the source image that are out of the image origin. When calling the ippiResize<Filter> function with one of these interpolation algorithms applied, you need to specify the appropriate border type. The following border types are supported:

  • Replicated borders: border pixels are replicated from the source image boundary pixels;
  • Borders in memory: the source image border pixels are obtained from the source image pixels in memory;
  • Mixed borders: a combined approach is applied.

NOTE:

If you want to resize an image with antialiasing, follow the same instructions as provided below, but use ippiResizeAntialiasing<Filter>Init instead of ippiResize<Filter>Init for initialization, and ippiResizeAntialiasing<Filter> instead of ippiResize<Filter>, as a processing function.

Resizing the Whole Image

You can apply the approach described below to resize when source and destination images are fully accessible in memory. However, this method only runs on a single thread.

To resize the whole image:

  1. Call the ippiResizeGetSize function with the appropriate interpolation type. This function uses source and destination image sizes to calculate how much memory must be allocated for the IppResizeSpec structure and initialization work buffer.
  2. Initialize the IppResizeSpec structure by calling the ippiResize<Filter>Init, where <Filter> can take one of the following values: Nearest, Linear, Cubic, Lanczos, and Super. These prerequisite steps allow resize to be called multiple times without recalculations.
  3. Call the ippiResizeGetBufferSize function for the initialized IppResizeSpec structure. This function uses the destination image size to calculate how much memory must be allocated for the resize work buffer.
  4. Call ippiResize<Filter> with the appropriate image type.
  5. If you call the ippiResize<Filter> function with a ippBorderInMem border or any mixed border type, the applied interpolation algorithm uses weighted values from edge pixels of the source image when outside the image boundaries. To obtain the size of the border required for correct edge calculation, call the ippiResizeGetBorderSize function for the appropriate flavor. In case of mixed border type, out of image pixels are used only behind the non-replicated edge.
  6. You can use mixed borders by using the bitwise OR operation between the ippBorderRepl type and the following border types: ippBorderInMemTop, ippBorderInMemBottom, ippBorderInMemLeft, ippBorderInMemRight.

Figure Simple Image Resize shows a simple image resizing example, in which image resolution is increased by 1.5x.

Simple Image Resize

Example

The code example below demonstrates whole image resizing with the Lanczos interpolation method:

IppStatus resizeExample_C3R(Ipp8u* pSrc, IppiSize srcSize, Ipp32s srcStep, Ipp8u* pDst, IppiSize dstSize, Ipp32s dstStep)
{
    IppiResizeSpec_32f* pSpec = 0;
    int specSize = 0, initSize = 0, bufSize = 0;
    Ipp8u* pBuffer  = 0;
    Ipp8u* pInitBuf = 0;
    Ipp32u numChannels = 3;
    IppiPoint dstOffset = {0, 0};
    IppStatus status = ippStsNoErr;
    IppiBorderType border = ippBorderRepl;

    /* Spec and init buffer sizes */
    status = ippiResizeGetSize_8u(srcSize, dstSize, ippLanczos, 0, &specSize, &initSize);

    if (status != ippStsNoErr) return status;

    /* Memory allocation */
    pInitBuf = ippsMalloc_8u(initSize);
    pSpec    = (IppiResizeSpec_32f*)ippsMalloc_8u(specSize);

    if (pInitBuf == NULL || pSpec == NULL)
    {
        ippsFree(pInitBuf);
        ippsFree(pSpec);
        return ippStsNoMemErr;
    }

    /* Filter initialization */
    status = ippiResizeLanczosInit_8u(srcSize, dstSize, 3, pSpec, pInitBuf);
    ippsFree(pInitBuf);

    if (status != ippStsNoErr)
    {
        ippsFree(pSpec);
        return status;
    }

    /* work buffer size */
    status = ippiResizeGetBufferSize_8u(pSpec, dstSize, numChannels, &bufSize);
    if (status != ippStsNoErr)
    {
        ippsFree(pSpec);
        return status;
    }

    pBuffer = ippsMalloc_8u(bufSize);
    if (pBuffer == NULL)
    {
        ippsFree(pSpec);
        return ippStsNoMemErr;
    }

    /* Resize processing */
    status = ippiResizeLanczos_8u_C3R(pSrc, srcStep, pDst, dstStep, dstOffset, dstSize, border, 0, pSpec, pBuffer);

    ippsFree(pSpec);
    ippsFree(pBuffer);

    return status;
}

Resizing a Tiled Image with One Prior Initialization

You can apply the approach described below to resize when source and destination images are not fully accessible in memory, or to improve the performance of resizing by external threading.

The main difference between this approach and whole image resizing is that the processing is split into sections of the image called tiles. Each call of the Resize<Filter> function works with the destination image origin region of interest (ROI) that is defined by dstOffset and dstSize parameters. The destination and source ROI must be fully accessible in memory.

To resize an image with the tiled approach:

  1. Call the ippiResizeGetSize function with the appropriate interpolation type. This function uses the source and destination image sizes to calculate how much memory must be allocated for the IppResizeSpec structure and initialization work buffer.
  2. Initialize the IppResizeSpec structure by calling ippiResize<Filter>Init, where <Filter> can take one of the following values: Nearest, Cubic, Linear, and Lanczos.
  3. Determine an appropriate partitioning scheme to divide the destination image into tiles. Tiles can be sets of rows or a regular grid of subimages. A simple vertical subdivision into sets of lines is often sufficient.
  4. Obtain the source ROI for the defined destination tile by calling the ippiResizeGetSrcRoi function for the corresponding flavor. The algorithm uses edge pixels that are out of the source ROI to calculate edge pixels of the destination ROI. These out of the source ROI edge pixels must be accessible in memory.
  5. If the source ROI is an interior field of the source image origin, obtain the border ROI size by calling the ippiResizeGetBorderSize function for the corresponding flavor.
  6. If the source ROI is an edge tile, the algorithm can interpolate pixels beyond the image boundary as in the previous method.
  7. If the source and destination images are fully accessible in memory, you can use the source ROI offset for the pSrc calculation. To obtain the offset, call the ippiResizeGetSrcOffset function for the corresponding flavor.
  8. Call the ippiResizeGetBufferSize function to obtain the size of the resize work buffer required for each tile processing. The dstSize parameter must be equal to the tile size.
  9. Call ippiResize<Filter> for each tile (ROI). The dstOffset parameter must specify the image ROI offset with respect to the destination image origin. The dstSize parameter must be equal to the ROI size. Parameters pSrc and pDst must point to the beginning of the source and destination ROI in memory respectively. The source and destination ROIs must be fully accessible in memory.

    You can process tiles in any order. When using multitple threads you can process all tiles simultaneously.

NOTE:

If you resize a tiled image with the Super Sampling algorithm, and the source image width to destination image width ratio is m/n, you can reach better performance of resize operation if all destination tiles have width that is a multiple of n.

Figure Tiling Image Resize shows the resize of the image divided into tiles.

Tiling Image Resize

Example

The code example below demonstrates a multithreading resize operation using OpenMP* with parallelization only in the y direction:

#define MAX_NUM_THREADS 16

IppStatus tileResizeExample_C3R(Ipp8u* pSrc, IppiSize srcSize, Ipp32s srcStep, Ipp8u* pDst, IppiSize dstSize, Ipp32s dstStep)
{
    IppiResizeSpec_32f* pSpec = 0;
    int specSize = 0, initSize = 0, bufSize = 0;
    Ipp8u* pBuffer  = 0;
    Ipp8u* pInitBuf = 0;
    Ipp32u numChannels = 3;
    IppiPoint dstOffset = {0, 0};
    IppiPoint srcOffset = {0, 0};
    IppStatus status = ippStsNoErr;
    IppiBorderSize borderSize = {0, 0, 0, 0};
    IppiBorderType border = ippBorderRepl;
    int numThreads, slice, tail;
    int bufSize1, bufSize2;
    IppiSize dstTileSize, dstLastTileSize;
    IppStatus pStatus[MAX_NUM_THREADS];


    /* Spec and init buffer sizes */    
    status = ippiResizeGetSize_8u(srcSize, dstSize, ippLinear, 0, &specSize, &initSize);

    if (status != ippStsNoErr) return status;

    /* Memory allocation */
    pInitBuf = ippsMalloc_8u(initSize);
    pSpec    = (IppiResizeSpec_32f*)ippsMalloc_8u(specSize);

    if (pInitBuf == NULL || pSpec == NULL)
    {
        ippsFree(pInitBuf);
        ippsFree(pSpec);
        return ippStsNoMemErr;
    }

    /* Filter initialization */
    status = ippiResizeLinearInit_8u(srcSize, dstSize, pSpec);   
    ippsFree(pInitBuf);

    if (status != ippStsNoErr)
    {
        ippsFree(pSpec);
        return status;
    }

    status = ippiResizeGetBorderSize_8u(pSpec, &borderSize);
    if (status != ippStsNoErr)
    {
        ippsFree(pSpec);
        return status;
    }

    /* General transform function */
    /* Parallelized only by Y-direction here */
    #pragma omp parallel num_threads(MAX_NUM_THREADS)
    {
        #pragma omp master
        {
            numThreads = omp_get_num_threads();
            slice = dstSize.height / numThreads;
            tail  = dstSize.height % numThreads;

            dstTileSize.width = dstLastTileSize.width = dstSize.width;
            dstTileSize.height = slice;
            dstLastTileSize.height = slice + tail;

            ippiResizeGetBufferSize_8u(pSpec, dstTileSize, ippC3, &bufSize1);
            ippiResizeGetBufferSize_8u(pSpec, dstLastTileSize, ippC3, &bufSize2);

            pBuffer = ippsMalloc_8u(bufSize1 * (numThreads - 1) + bufSize2);
        }

        #pragma omp barrier
        {
            if (pBuffer)
            {
                Ipp32u  i;
                Ipp8u  *pSrcT, *pDstT;
                Ipp8u  *pOneBuf;
                IppiPoint srcOffset = {0, 0};
                IppiPoint dstOffset = {0, 0};
                IppiSize  srcSizeT = srcSize;
                IppiSize  dstSizeT = dstTileSize;
                
                i = omp_get_thread_num();
                dstSizeT.height = slice;
                dstOffset.y += i * slice;

                if (i == numThreads - 1) dstSizeT = dstLastTileSize;

                pStatus[i] = ippiResizeGetSrcRoi_8u(pSpec, dstOffset, dstSizeT, &srcOffset, &srcSizeT);

                if (pStatus[i] == ippStsNoErr)
                {
                    pSrcT = (Ipp8u*)((char*)pSrc + srcOffset.y * srcStep);
                    pDstT = (Ipp8u*)((char*)pDst + dstOffset.y * dstStep);

                    pOneBuf = pBuffer + i * bufSize1;

                    pStatus[i] = ippiResizeLinear_8u_C3R (pSrcT, srcStep, pDstT, dstStep, dstOffset, dstSizeT, border, 0, pSpec, pOneBuf);
                }
            }
        }
    }

    ippsFree(pSpec);

    if (pBuffer == NULL) return ippStsNoMemErr;

    ippsFree(pBuffer);    

    for (Ipp32u i = 0; i < numThreads; ++i)
    {
        /* Return bad status */
        if(pStatus[i] != ippStsNoErr) return pStatus[i];
    }

    return status;
}

Resizing a Tiled Image with Prior Initialization for Each Tile

You can apply this approach only in cases when the destination image can be divided into tiles so that each destination tile corresponds to a source image tile that starts with an integer pixel value origin. For example, if the ratio of the source and destination images sizes is 2/3, the destination image can be divided into 3x3 tiles, each of which corresponds to the source image tile 2x2.

This approach is useful if there are restrictions on memory size when processing an image, or if the image size is large and ippiResizeGetBufferSize function returns ippStsSizeErr error. The initialization data for a tile is less than the same data for the whole image.

Each tile of the source image can be considered as an independent image that can be resized. For interior tile processing, the border must be always of the ippBorderInMem type. If you need to replicate any borders of the source image origin, you should combine the border type of the outer tiles so that interior tiles edges have border in memory and external tile borders are of the specified border type. This approach enables the right linking order of tiles.

Figure Resize of the Image Divided into Subimages shows the approach, when the source image is divided into several subimages that are resized independently.

Resize of the Image Divided into Subimages

Example

The code example below divides the source image into tiles and resizes each image independently:

IppStatus separateTileResizeExample_C3R(Ipp8u* pSrc, IppiSize srcTileSize, Ipp32s srcStep, Ipp8u* pDst, IppiSize dstTileSize, Ipp32s dstStep, Ipp32s xNumTiles, Ipp32s yNumTiles)
{
    IppiResizeSpec_32f* pSpec = 0;
    int specSize = 0, initSize = 0, bufSize = 0;
    Ipp8u* pBuffer  = 0;
    Ipp8u* pInitBuf = 0;
    Ipp32u numChannels = 3;
    IppStatus status = ippStsNoErr;

    /* tiles cicle */
    for (Ipp32s j = 0; j < xNumTiles; j ++)
    {
        for (Ipp32s i = 0; i < yNumTiles; i ++)
        {
            /* calculation of the destination image ROI offset */
            IppiPoint dstOffset = {j * dstTileSize.width, i * dstTileSize.height};
            Ipp8u* pDstT = pDst + dstStep * dstOffset.y + dstOffset.x * numChannels * sizeof(Ipp8u);

            /* calculation of the source image ROI offset */
            IppiPoint srcOffset = {j * srcTileSize.width, i * srcTileSize.height};
            Ipp8u* pSrcT = pSrc + srcStep * srcOffset.y + srcOffset.x * numChannels * sizeof(Ipp8u);

            IppiBorderType borderT = ippBorderRepl;

            IppiPoint dstOffsetZero = {0, 0};

            /* correction of the border type for the tile processing */
            if (j > 0) /* the processed tile is not on the left image origin edge*/
            {
                borderT = (IppiBorderType)((int)borderT | (int)ippBorderInMemLeft);
            }
            if (j < xNumTiles - 1) /* the processed tile is not on the right image origin edge*/
            {
                borderT = (IppiBorderType)((int)borderT | (int)ippBorderInMemRight);
            }
            if (i > 0) /* the processed tile is not on the top image origin edge*/
            {
                borderT = (IppiBorderType)((int)borderT | (int)ippBorderInMemTop);
            }
            if (i < yNumTiles - 1) /* the processed tile is not on the bottom image origin edge*/
            {
                borderT = (IppiBorderType)((int)borderT | (int)ippBorderInMemBottom);
            }

            /* Spec and init buffer sizes */
            status = ippiResizeGetSize_8u(srcTileSize, dstTileSize, ippLanczos, 0, &specSize, &initSize);

            if (status != ippStsNoErr) return status;

            /* Memory allocation */
            pInitBuf = ippsMalloc_8u(initSize);
            pSpec    = (IppiResizeSpec_32f*)ippsMalloc_8u(specSize);

            if (pInitBuf == NULL || pSpec == NULL)
            {
                ippsFree(pInitBuf);
                ippsFree(pSpec);
                return ippStsNoMemErr;
            }

            /* Filter initialization */
            status = ippiResizeLanczosInit_8u(srcTileSize, dstTileSize, 3, pSpec, pInitBuf);
            ippsFree(pInitBuf);

            if (status != ippStsNoErr)
            {
                ippsFree(pSpec);
                return status;
            }

            /* work buffer size */
            status = ippiResizeGetBufferSize_8u(pSpec, dstTileSize, numChannels, &bufSize);
            if (status != ippStsNoErr)
            {
                ippsFree(pSpec);
                return status;
            }

            pBuffer = ippsMalloc_8u(bufSize);
            if (pBuffer == NULL)
            {
                ippsFree(pSpec);
                return ippStsNoMemErr;
            }

            /* Resize processing */
            status = ippiResizeLanczos_8u_C3R(pSrcT, srcStep, pDstT, dstStep, dstOffsetZero, dstTileSize, borderT, 0, pSpec, pBuffer);

            ippsFree(pSpec);
            ippsFree(pBuffer);

            if (status != ippStsNoErr) return status;
        }
    }

    return ippStsNoErr;
}