EVOLUTION-MANAGER
Edit File: nearblack_lib.cpp
/****************************************************************************** * * Project: GDAL Utilities * Purpose: Convert nearly black or nearly white border to exact black/white. * Author: Frank Warmerdam, warmerdam@pobox.com * * **************************************************************************** * Copyright (c) 2006, MapShots Inc (www.mapshots.com) * Copyright (c) 2007-2013, Even Rouault <even dot rouault at mines-paris dot org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "cpl_port.h" #include "gdal_utils.h" #include "gdal_utils_priv.h" #include "commonutils.h" #include <cassert> #include <cstdlib> #include <cstring> #include <algorithm> #include <memory> #include <vector> #include "cpl_conv.h" #include "cpl_error.h" #include "cpl_progress.h" #include "cpl_string.h" #include "gdal.h" CPL_CVSID("$Id: nearblack_lib.cpp 88eda08930b6dafb9ea1374ba19e0b1cf5ded3d3 2018-08-11 20:16:37 +0200 Even Rouault $") typedef std::vector<int> Color; typedef std::vector< Color > Colors; struct GDALNearblackOptions { /*! output format. Use the short format name. */ char *pszFormat; /*! the progress function to use */ GDALProgressFunc pfnProgress; /*! pointer to the progress data variable */ void *pProgressData; int nMaxNonBlack; int nNearDist; bool bNearWhite; bool bSetAlpha; bool bSetMask; Colors oColors; char** papszCreationOptions; }; static void ProcessLine( GByte *pabyLine, GByte *pabyMask, int iStart, int iEnd, int nSrcBands, int nDstBands, int nNearDist, int nMaxNonBlack, bool bNearWhite, Colors *poColors, int *panLastLineCounts, bool bDoHorizontalCheck, bool bDoVerticalCheck, bool bBottomUp ); /************************************************************************/ /* GDALNearblack() */ /************************************************************************/ /** * Convert nearly black/white borders to exact value. * * This is the equivalent of the <a href="nearblack.html">nearblack</a> utility. * * GDALNearblackOptions* must be allocated and freed with GDALNearblackOptionsNew() * and GDALNearblackOptionsFree() respectively. * pszDest and hDstDS cannot be used at the same time. * * In-place update (i.e. hDstDS == hSrcDataset) is possible for formats that * support it, and if the dataset is opened in update mode. * * @param pszDest the destination dataset path or NULL. * @param hDstDS the destination dataset or NULL. Might be equal to hSrcDataset. * @param hSrcDataset the source dataset handle. * @param psOptionsIn the options struct returned by GDALNearblackOptionsNew() or NULL. * @param pbUsageError the pointer to int variable to determine any usage error has occurred or NULL. * @return the output dataset (new dataset that must be closed using GDALClose(), or hDstDS is not NULL) or NULL in case of error. * * @since GDAL 2.1 */ GDALDatasetH CPL_DLL GDALNearblack( const char *pszDest, GDALDatasetH hDstDS, GDALDatasetH hSrcDataset, const GDALNearblackOptions *psOptionsIn, int *pbUsageError ) { if( pszDest == nullptr && hDstDS == nullptr ) { CPLError(CE_Failure, CPLE_AppDefined, "pszDest == NULL && hDstDS == NULL"); if( pbUsageError ) *pbUsageError = TRUE; return nullptr; } if( hSrcDataset == nullptr ) { CPLError(CE_Failure, CPLE_AppDefined, "hSrcDataset== NULL"); if( pbUsageError ) *pbUsageError = TRUE; return nullptr; } GDALNearblackOptions* psOptionsToFree = nullptr; const GDALNearblackOptions* psOptions; if( psOptionsIn ) { psOptions = psOptionsIn; } else { psOptionsToFree = GDALNearblackOptionsNew(nullptr, nullptr); psOptions = psOptionsToFree; } const bool bCloseOutDSOnError = hDstDS == nullptr; if( pszDest == nullptr ) pszDest = GDALGetDescription(hDstDS); const int nXSize = GDALGetRasterXSize(hSrcDataset); const int nYSize = GDALGetRasterYSize(hSrcDataset); int nBands = GDALGetRasterCount(hSrcDataset); int nDstBands = nBands; const int nMaxNonBlack = psOptions->nMaxNonBlack; const int nNearDist = psOptions->nNearDist; const bool bNearWhite = psOptions->bNearWhite; const bool bSetAlpha = psOptions->bSetAlpha; bool bSetMask = psOptions->bSetMask; Colors oColors = psOptions->oColors; /* -------------------------------------------------------------------- */ /* Do we need to create output file? */ /* -------------------------------------------------------------------- */ if( hDstDS == nullptr ) { CPLString osFormat; if( psOptions->pszFormat == nullptr ) { osFormat = GetOutputDriverForRaster(pszDest); if( osFormat.empty() ) { GDALNearblackOptionsFree(psOptionsToFree); return nullptr; } } else { osFormat = psOptions->pszFormat; } GDALDriverH hDriver = GDALGetDriverByName(osFormat); if( hDriver == nullptr ) { GDALNearblackOptionsFree(psOptionsToFree); return nullptr; } if( bSetAlpha ) { // TODO(winkey): There should be a way to preserve alpha // band data not in the collar. if( nBands == 4 ) nBands--; else nDstBands++; } if( bSetMask ) { if( nBands == 4 ) { nDstBands = 3; nBands = 3; } } hDstDS = GDALCreate(hDriver, pszDest, nXSize, nYSize, nDstBands, GDT_Byte, psOptions->papszCreationOptions); if( hDstDS == nullptr ) { GDALNearblackOptionsFree(psOptionsToFree); return nullptr; } double adfGeoTransform[6] = {}; if( GDALGetGeoTransform(hSrcDataset, adfGeoTransform) == CE_None ) { GDALSetGeoTransform(hDstDS, adfGeoTransform); GDALSetProjection(hDstDS, GDALGetProjectionRef(hSrcDataset)); } } else { if( psOptions->papszCreationOptions != nullptr) { CPLError(CE_Warning, CPLE_AppDefined, "Warning: creation options are ignored when writing to " "an existing file."); } /***** check the input and output datasets are the same size *****/ if( GDALGetRasterXSize(hDstDS) != nXSize || GDALGetRasterYSize(hDstDS) != nYSize ) { CPLError(CE_Failure, CPLE_AppDefined, "The dimensions of the output dataset don't match " "the dimensions of the input dataset."); GDALNearblackOptionsFree(psOptionsToFree); return nullptr; } if( bSetAlpha ) { if( nBands != 4 && (nBands < 2 || GDALGetRasterColorInterpretation( GDALGetRasterBand(hDstDS, nBands)) != GCI_AlphaBand)) { CPLError(CE_Failure, CPLE_AppDefined, "Last band is not an alpha band."); GDALNearblackOptionsFree(psOptionsToFree); return nullptr; } nBands--; } if( bSetMask ) { if( nBands == 4 ) { nDstBands = 3; nBands = 3; } } } /***** set a color if there are no colors set? *****/ if( oColors.empty() ) { Color oColor; /***** loop over the bands to get the right number of values *****/ for( int iBand = 0; iBand < nBands ; iBand++ ) { // black or white? oColor.push_back(bNearWhite ? 255 : 0); } /***** add the color to the colors *****/ oColors.push_back(oColor); assert( !oColors.empty() ); } /***** does the number of bands match the number of color values? *****/ if ( static_cast<int>(oColors.front().size()) != nBands ) { CPLError(CE_Failure, CPLE_AppDefined, "-color args must have the same number of values as " "the non alpha input band count.\n" ); GDALNearblackOptionsFree(psOptionsToFree); if( bCloseOutDSOnError ) GDALClose(hDstDS); return nullptr; } for( int iBand = 0; iBand < nBands; iBand++ ) { GDALRasterBandH hBand = GDALGetRasterBand(hSrcDataset, iBand+1); if( GDALGetRasterDataType(hBand) != GDT_Byte ) { CPLError(CE_Warning, CPLE_AppDefined, "Band %d is not of type GDT_Byte. " "It can lead to unexpected results.", iBand+1); } if( GDALGetRasterColorTable(hBand) != nullptr ) { CPLError( CE_Warning, CPLE_AppDefined, "Band %d has a color table, which is ignored by nearblack. " "It can lead to unexpected results.", iBand + 1); } } GDALRasterBandH hMaskBand = nullptr; if( bSetMask ) { // If there isn't already a mask band on the output file create one. if ( GMF_PER_DATASET != GDALGetMaskFlags(GDALGetRasterBand(hDstDS, 1)) ) { if( CE_None != GDALCreateDatasetMaskBand(hDstDS, GMF_PER_DATASET) ) { CPLError(CE_Failure, CPLE_AppDefined, "Failed to create mask band on output DS"); bSetMask = false; } } if( bSetMask ) { hMaskBand = GDALGetMaskBand(GDALGetRasterBand(hDstDS, 1)); } } /* -------------------------------------------------------------------- */ /* Allocate a line buffer. */ /* -------------------------------------------------------------------- */ GByte *pabyLine = static_cast<GByte *>(CPLMalloc(nXSize * nDstBands)); GByte *pabyMask = nullptr; if( bSetMask ) pabyMask = static_cast<GByte *>(CPLMalloc(nXSize)); int *panLastLineCounts = static_cast<int *>(CPLCalloc(sizeof(int), nXSize)); /* -------------------------------------------------------------------- */ /* Processing data one line at a time. */ /* -------------------------------------------------------------------- */ int iLine; for( iLine = 0; iLine < nYSize; iLine++ ) { CPLErr eErr = GDALDatasetRasterIO(hSrcDataset, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nBands, nullptr, nDstBands, nXSize * nDstBands, 1); if( eErr != CE_None ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } if( bSetAlpha ) { for( int iCol = 0; iCol < nXSize; iCol++ ) { pabyLine[iCol * nDstBands + nDstBands - 1] = 255; } } if( bSetMask ) { for( int iCol = 0; iCol < nXSize; iCol ++ ) { pabyMask[iCol] = 255; } } ProcessLine(pabyLine, pabyMask, 0, nXSize-1, nBands, nDstBands, nNearDist, nMaxNonBlack, bNearWhite, &oColors, panLastLineCounts, true, // bDoHorizontalCheck true, // bDoVerticalCheck false // bBottomUp ); ProcessLine(pabyLine, pabyMask, nXSize-1, 0, nBands, nDstBands, nNearDist, nMaxNonBlack, bNearWhite, &oColors, panLastLineCounts, true, // bDoHorizontalCheck false, // bDoVerticalCheck false // bBottomUp ); eErr = GDALDatasetRasterIO(hDstDS, GF_Write, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nDstBands, nullptr, nDstBands, nXSize * nDstBands, 1); if( eErr != CE_None ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } /***** write out the mask band line *****/ if( bSetMask ) { eErr = GDALRasterIO (hMaskBand, GF_Write, 0, iLine, nXSize, 1, pabyMask, nXSize, 1, GDT_Byte, 0, 0); if( eErr != CE_None ) { CPLError(CE_Warning, CPLE_AppDefined, "ERROR writing out line to mask band."); if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } } if( !(psOptions->pfnProgress( 0.5 * ((iLine+1) / static_cast<double>(nYSize)), nullptr, psOptions->pProgressData)) ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } } /* -------------------------------------------------------------------- */ /* Now process from the bottom back up .*/ /* -------------------------------------------------------------------- */ memset(panLastLineCounts, 0, sizeof(int) * nXSize); for( iLine = nYSize-1; hDstDS != nullptr && iLine >= 0; iLine-- ) { CPLErr eErr = GDALDatasetRasterIO(hDstDS, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nDstBands, nullptr, nDstBands, nXSize * nDstBands, 1 ); if( eErr != CE_None ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } /***** read the mask band line back in *****/ if( bSetMask ) { eErr = GDALRasterIO(hMaskBand, GF_Read, 0, iLine, nXSize, 1, pabyMask, nXSize, 1, GDT_Byte, 0, 0); if( eErr != CE_None ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } } ProcessLine(pabyLine, pabyMask, 0, nXSize-1, nBands, nDstBands, nNearDist, nMaxNonBlack, bNearWhite, &oColors, panLastLineCounts, true, // bDoHorizontalCheck true, // bDoVerticalCheck true // bBottomUp ); ProcessLine(pabyLine, pabyMask, nXSize-1, 0, nBands, nDstBands, nNearDist, nMaxNonBlack, bNearWhite, &oColors, panLastLineCounts, true, // bDoHorizontalCheck false, // bDoVerticalCheck true // bBottomUp ); eErr = GDALDatasetRasterIO(hDstDS, GF_Write, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nDstBands, nullptr, nDstBands, nXSize * nDstBands, 1); if( eErr != CE_None ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } /***** write out the mask band line *****/ if( bSetMask ) { eErr = GDALRasterIO (hMaskBand, GF_Write, 0, iLine, nXSize, 1, pabyMask, nXSize, 1, GDT_Byte, 0, 0); if( eErr != CE_None ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } } if( !(psOptions->pfnProgress( 0.5 + 0.5 * (nYSize-iLine) / static_cast<double>(nYSize), nullptr, psOptions->pProgressData )) ) { if( bCloseOutDSOnError ) GDALClose(hDstDS); hDstDS = nullptr; break; } } CPLFree(pabyLine); if( bSetMask ) CPLFree(pabyMask); CPLFree( panLastLineCounts ); return hDstDS; } /************************************************************************/ /* ProcessLine() */ /* */ /* Process a single scanline of image data. */ /************************************************************************/ static void ProcessLine( GByte *pabyLine, GByte *pabyMask, int iStart, int iEnd, int nSrcBands, int nDstBands, int nNearDist, int nMaxNonBlack, bool bNearWhite, Colors *poColors, int *panLastLineCounts, bool bDoHorizontalCheck, bool bDoVerticalCheck, bool bBottomUp ) { const GByte nReplacevalue = bNearWhite ? 255 : 0; /* -------------------------------------------------------------------- */ /* Vertical checking. */ /* -------------------------------------------------------------------- */ if( bDoVerticalCheck ) { const int nXSize = std::max(iStart + 1, iEnd + 1); for( int i = 0; i < nXSize; i++ ) { // are we already terminated for this column? if( panLastLineCounts[i] > nMaxNonBlack ) continue; /***** is the pixel valid data? ****/ bool bIsNonBlack = false; /***** loop over the colors *****/ for( int iColor = 0; iColor < static_cast<int>(poColors->size() ); iColor++) { Color oColor = (*poColors)[iColor]; bIsNonBlack = false; /***** loop over the bands *****/ for( int iBand = 0; iBand < nSrcBands; iBand++ ) { const int nPix = pabyLine[i * nDstBands + iBand]; if( oColor[iBand] - nPix > nNearDist || nPix > nNearDist + oColor[iBand] ) { bIsNonBlack = true; break; } } if( !bIsNonBlack ) break; } if( bIsNonBlack ) { panLastLineCounts[i]++; if( panLastLineCounts[i] > nMaxNonBlack ) continue; } //else // panLastLineCounts[i] = 0; // not sure this even makes sense /***** replace the pixel values *****/ for( int iBand = 0; iBand < nSrcBands; iBand++ ) pabyLine[i * nDstBands + iBand] = nReplacevalue; /***** alpha *****/ if( nDstBands > nSrcBands ) pabyLine[i * nDstBands + nDstBands - 1] = 0; /***** mask *****/ if (pabyMask != nullptr) pabyMask[i] = 0; } } /* -------------------------------------------------------------------- */ /* Horizontal Checking. */ /* -------------------------------------------------------------------- */ if( bDoHorizontalCheck ) { int nNonBlackPixels = 0; /***** on a bottom up pass assume nMaxNonBlack is 0 *****/ if( bBottomUp ) nMaxNonBlack = 0; const int iDir = iStart < iEnd ? 1 : -1; bool bDoTest = TRUE; for( int i = iStart; i != iEnd; i += iDir ) { /***** not seen any valid data? *****/ if( bDoTest ) { /***** is the pixel valid data? ****/ bool bIsNonBlack = false; /***** loop over the colors *****/ for( int iColor = 0; iColor < static_cast<int>(poColors->size()); iColor++ ) { Color oColor = (*poColors)[iColor]; bIsNonBlack = false; /***** loop over the bands *****/ for( int iBand = 0; iBand < nSrcBands; iBand++ ) { const int nPix = pabyLine[i * nDstBands + iBand]; if( oColor[iBand] - nPix > nNearDist || nPix > nNearDist + oColor[iBand] ) { bIsNonBlack = true; break; } } if( bIsNonBlack == false ) break; } if( bIsNonBlack ) { /***** use nNonBlackPixels in grey areas *****/ /***** from the vertical pass's grey areas ****/ if( panLastLineCounts[i] <= nMaxNonBlack ) nNonBlackPixels = panLastLineCounts[i]; else nNonBlackPixels++; } if( nNonBlackPixels > nMaxNonBlack ) { bDoTest = false; continue; } /***** replace the pixel values *****/ for( int iBand = 0; iBand < nSrcBands; iBand++ ) pabyLine[i * nDstBands + iBand] = nReplacevalue; /***** alpha *****/ if( nDstBands > nSrcBands ) pabyLine[i * nDstBands + nDstBands - 1] = 0; /***** mask *****/ if (pabyMask != nullptr) pabyMask[i] = 0; } /***** seen valid data but test if the *****/ /***** vertical pass saw any non valid data *****/ else if( panLastLineCounts[i] == 0 ) { bDoTest = true; nNonBlackPixels = 0; } } } } /************************************************************************/ /* IsInt() */ /************************************************************************/ static bool IsInt( const char *pszArg ) { if( pszArg[0] == '-' ) pszArg++; if( *pszArg == '\0' ) return false; while( *pszArg != '\0' ) { if( *pszArg < '0' || *pszArg > '9' ) return false; pszArg++; } return true; } /************************************************************************/ /* GDALNearblackOptionsNew() */ /************************************************************************/ /** * Allocates a GDALNearblackOptions struct. * * @param papszArgv NULL terminated list of options (potentially including filename and open options too), or NULL. * The accepted options are the ones of the <a href="nearblack.html">nearblack</a> utility. * @param psOptionsForBinary (output) may be NULL (and should generally be NULL), * otherwise (gdal_translate_bin.cpp use case) must be allocated with * GDALNearblackOptionsForBinaryNew() prior to this function. Will be * filled with potentially present filename, open options,... * @return pointer to the allocated GDALNearblackOptions struct. Must be freed with GDALNearblackOptionsFree(). * * @since GDAL 2.1 */ GDALNearblackOptions *GDALNearblackOptionsNew( char** papszArgv, GDALNearblackOptionsForBinary* psOptionsForBinary ) { GDALNearblackOptions *psOptions = new GDALNearblackOptions; psOptions->pszFormat = nullptr; psOptions->pfnProgress = GDALDummyProgress; psOptions->pProgressData = nullptr; psOptions->papszCreationOptions = nullptr; psOptions->nMaxNonBlack = 2; psOptions->nNearDist = 15; psOptions->bNearWhite = false; psOptions->bSetAlpha = false; psOptions->bSetMask = false; /* -------------------------------------------------------------------- */ /* Handle command line arguments. */ /* -------------------------------------------------------------------- */ const int argc = CSLCount(papszArgv); for( int i = 0; papszArgv != nullptr && i < argc; i++ ) { if( i < argc - 1 && (EQUAL(papszArgv[i], "-of") || EQUAL(papszArgv[i], "-f")) ) { ++i; CPLFree(psOptions->pszFormat); psOptions->pszFormat = CPLStrdup(papszArgv[i]); } else if( EQUAL(papszArgv[i], "-q") || EQUAL(papszArgv[i], "-quiet") ) { if( psOptionsForBinary ) psOptionsForBinary->bQuiet = TRUE; } else if( i + 1 < argc && EQUAL(papszArgv[i], "-co") ) { psOptions->papszCreationOptions = CSLAddString(psOptions->papszCreationOptions, papszArgv[++i]); } else if( i + 1 < argc && EQUAL(papszArgv[i], "-o") ) { i++; if( psOptionsForBinary ) { CPLFree(psOptionsForBinary->pszOutFile); psOptionsForBinary->pszOutFile = CPLStrdup(papszArgv[i]); } } else if( EQUAL(papszArgv[i], "-white") ) { psOptions->bNearWhite = true; } /***** -color c1,c2,c3...cn *****/ else if( i + 1 < argc && EQUAL(papszArgv[i], "-color") ) { Color oColor; /***** tokenize the arg on , *****/ char **papszTokens = CSLTokenizeString2( papszArgv[++i], ",", 0 ); /***** loop over the tokens *****/ for( int iToken = 0; papszTokens && papszTokens[iToken]; iToken++ ) { /***** ensure the token is an int and add it to the color *****/ if( IsInt(papszTokens[iToken]) ) { oColor.push_back( atoi( papszTokens[iToken] ) ); } else { CPLError(CE_Failure, CPLE_AppDefined, "Colors must be valid integers." ); CSLDestroy(papszTokens); GDALNearblackOptionsFree(psOptions); return nullptr; } } CSLDestroy(papszTokens); /***** check if the number of bands is consistent *****/ if ( !psOptions->oColors.empty() && psOptions->oColors.front().size() != oColor.size() ) { CPLError( CE_Failure, CPLE_AppDefined, "all -color args must have the same number of values.\n"); GDALNearblackOptionsFree(psOptions); return nullptr; } /***** add the color to the colors *****/ psOptions->oColors.push_back( oColor ); } else if( i+1<argc && EQUAL(papszArgv[i], "-nb") ) { psOptions->nMaxNonBlack = atoi(papszArgv[++i]); } else if( i+1<argc && EQUAL(papszArgv[i], "-near") ) { psOptions->nNearDist = atoi(papszArgv[++i]); } else if( EQUAL(papszArgv[i], "-setalpha") ) { psOptions->bSetAlpha = true; } else if( EQUAL(papszArgv[i], "-setmask") ) { psOptions->bSetMask = true; } else if( papszArgv[i][0] == '-' ) { CPLError(CE_Failure, CPLE_NotSupported, "Unknown option name '%s'", papszArgv[i]); GDALNearblackOptionsFree(psOptions); return nullptr; } else if( psOptionsForBinary && psOptionsForBinary->pszInFile == nullptr ) { psOptionsForBinary->pszInFile = CPLStrdup(papszArgv[i]); } else { CPLError(CE_Failure, CPLE_NotSupported, "Too many command options '%s'", papszArgv[i]); GDALNearblackOptionsFree(psOptions); return nullptr; } } return psOptions; } /************************************************************************/ /* GDALNearblackOptionsFree() */ /************************************************************************/ /** * Frees the GDALNearblackOptions struct. * * @param psOptions the options struct for GDALNearblack(). * * @since GDAL 2.1 */ void GDALNearblackOptionsFree( GDALNearblackOptions *psOptions ) { if( psOptions == nullptr ) return; CPLFree(psOptions->pszFormat); CSLDestroy(psOptions->papszCreationOptions); delete psOptions; } /************************************************************************/ /* GDALNearblackOptionsSetProgress() */ /************************************************************************/ /** * Set a progress function. * * @param psOptions the options struct for GDALNearblack(). * @param pfnProgress the progress callback. * @param pProgressData the user data for the progress callback. * * @since GDAL 2.1 */ void GDALNearblackOptionsSetProgress( GDALNearblackOptions *psOptions, GDALProgressFunc pfnProgress, void *pProgressData ) { psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress; psOptions->pProgressData = pProgressData; }