EVOLUTION-MANAGER
Edit File: nearblack.cpp
/****************************************************************************** * $Id: nearblack.cpp 27044 2014-03-16 23:41:27Z rouault $ * * 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 "gdal.h" #include "cpl_conv.h" #include "cpl_string.h" #include <vector> #include "commonutils.h" CPL_CVSID("$Id: nearblack.cpp 27044 2014-03-16 23:41:27Z rouault $"); typedef std::vector<int> Color; typedef std::vector< Color > Colors; static void ProcessLine( GByte *pabyLine, GByte *pabyMask, int iStart, int iEnd, int nSrcBands, int nDstBands, int nNearDist, int nMaxNonBlack, int bNearWhite, Colors *poColors, int *panLastLineCounts, int bDoHorizontalCheck, int bDoVerticalCheck, int bBottomUp); /************************************************************************/ /* IsInt() */ /************************************************************************/ int 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; } /************************************************************************/ /* Usage() */ /************************************************************************/ void Usage(const char* pszErrorMsg = NULL) { printf( "nearblack [-of format] [-white | [-color c1,c2,c3...cn]*] [-near dist] [-nb non_black_pixels]\n" " [-setalpha] [-setmask] [-o outfile] [-q] [-co \"NAME=VALUE\"]* infile\n" ); if( pszErrorMsg != NULL ) fprintf(stderr, "\nFAILURE: %s\n", pszErrorMsg); exit( 1 ); } /************************************************************************/ /* main() */ /************************************************************************/ #define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg) \ do { if (i + nExtraArg >= argc) \ Usage(CPLSPrintf("%s option requires %d argument(s)", argv[i], nExtraArg)); } while(0) int main( int argc, char ** argv ) { /* Check that we are running against at least GDAL 1.4 (probably older in fact !) */ /* Note to developers : if we use newer API, please change the requirement */ if (atoi(GDALVersionInfo("VERSION_NUM")) < 1400) { fprintf(stderr, "At least, GDAL >= 1.4.0 is required for this version of %s, " "which was compiled against GDAL %s\n", argv[0], GDAL_RELEASE_NAME); exit(1); } /* -------------------------------------------------------------------- */ /* Generic arg processing. */ /* -------------------------------------------------------------------- */ GDALAllRegister(); GDALSetCacheMax( 100000000 ); argc = GDALGeneralCmdLineProcessor( argc, &argv, 0 ); if( argc < 1 ) exit( -argc ); /* -------------------------------------------------------------------- */ /* Parse arguments. */ /* -------------------------------------------------------------------- */ int i; const char *pszOutFile = NULL; const char *pszInFile = NULL; int nMaxNonBlack = 2; int nNearDist = 15; int bNearWhite = FALSE; int bSetAlpha = FALSE; int bSetMask = FALSE; const char* pszDriverName = "HFA"; int bFormatExplicitelySet = FALSE; char** papszCreationOptions = NULL; int bQuiet = FALSE; Colors oColors; for( i = 1; i < argc; i++ ) { if( EQUAL(argv[i], "--utility_version") ) { printf("%s was compiled against GDAL %s and is running against GDAL %s\n", argv[0], GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME")); return 0; } else if( EQUAL(argv[i], "--help") ) Usage(); else if( EQUAL(argv[i], "-o") ) { CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); pszOutFile = argv[++i]; } else if( EQUAL(argv[i], "-of") ) { CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); pszDriverName = argv[++i]; bFormatExplicitelySet = TRUE; } else if( EQUAL(argv[i], "-white") ) { bNearWhite = TRUE; } /***** -color c1,c2,c3...cn *****/ else if( EQUAL(argv[i], "-color") ) { CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); Color oColor; /***** tokenize the arg on , *****/ char **papszTokens; papszTokens = CSLTokenizeString2( argv[++i], ",", 0 ); /***** loop over the tokens *****/ int iToken; for( 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 ); exit(1); } } CSLDestroy( papszTokens ); /***** check if the number of bands is consistant *****/ if ( oColors.size() > 0 && oColors.front().size() != oColor.size() ) { CPLError(CE_Failure, CPLE_AppDefined, "ERROR: all -color args must have the same number of values.\n" ); exit(1); } /***** add the color to the colors *****/ oColors.push_back( oColor ); } else if( EQUAL(argv[i], "-nb") ) { CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); nMaxNonBlack = atoi(argv[++i]); } else if( EQUAL(argv[i], "-near") ) { CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); nNearDist = atoi(argv[++i]); } else if( EQUAL(argv[i], "-setalpha") ) bSetAlpha = TRUE; else if( EQUAL(argv[i], "-setmask") ) bSetMask = TRUE; else if( EQUAL(argv[i], "-q") || EQUAL(argv[i], "-quiet") ) bQuiet = TRUE; else if( EQUAL(argv[i], "-co") ) { CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); papszCreationOptions = CSLAddString(papszCreationOptions, argv[++i]); } else if( argv[i][0] == '-' ) Usage(CPLSPrintf("Unknown option name '%s'", argv[i])); else if( pszInFile == NULL ) pszInFile = argv[i]; else Usage("Too many command options."); } if( pszInFile == NULL ) Usage("No input file specified."); if( pszOutFile == NULL ) pszOutFile = pszInFile; /* -------------------------------------------------------------------- */ /* Open input file. */ /* -------------------------------------------------------------------- */ GDALDatasetH hInDS, hOutDS = NULL; int nXSize, nYSize, nBands; if( pszOutFile == pszInFile ) hInDS = hOutDS = GDALOpen( pszInFile, GA_Update ); else hInDS = GDALOpen( pszInFile, GA_ReadOnly ); if( hInDS == NULL ) exit( 1 ); nXSize = GDALGetRasterXSize( hInDS ); nYSize = GDALGetRasterYSize( hInDS ); nBands = GDALGetRasterCount( hInDS ); int nDstBands = nBands; if( hOutDS != NULL && papszCreationOptions != NULL) { CPLError(CE_Warning, CPLE_AppDefined, "Warning: creation options are ignored when writing to an existing file."); } /* -------------------------------------------------------------------- */ /* Do we need to create output file? */ /* -------------------------------------------------------------------- */ if( hOutDS == NULL ) { GDALDriverH hDriver = GDALGetDriverByName( pszDriverName ); if (hDriver == NULL) exit(1); if (!bQuiet && !bFormatExplicitelySet) CheckExtensionConsistency(pszOutFile, pszDriverName); if (bSetAlpha) { /***** fixme 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 = nBands = 3; } hOutDS = GDALCreate( hDriver, pszOutFile, nXSize, nYSize, nDstBands, GDT_Byte, papszCreationOptions ); if( hOutDS == NULL ) exit( 1 ); double adfGeoTransform[6]; if( GDALGetGeoTransform( hInDS, adfGeoTransform ) == CE_None ) { GDALSetGeoTransform( hOutDS, adfGeoTransform ); GDALSetProjection( hOutDS, GDALGetProjectionRef( hInDS ) ); } } else { if (bSetAlpha) { if (nBands != 4 && (nBands < 2 || GDALGetRasterColorInterpretation(GDALGetRasterBand(hOutDS, nBands)) != GCI_AlphaBand)) { CPLError(CE_Failure, CPLE_AppDefined, "Last band is not an alpha band."); exit(1); } nBands --; } if (bSetMask) { if (nBands == 4) nDstBands = nBands = 3; } } /***** set a color if there are no colors set? *****/ if ( oColors.size() == 0) { Color oColor; /***** loop over the bands to get the right number of values *****/ int iBand; for (iBand = 0; iBand < nBands ; iBand++) { /***** black or white? *****/ if (bNearWhite) oColor.push_back(255); else oColor.push_back(0); } /***** add the color to the colors *****/ oColors.push_back(oColor); } /***** does the number of bands match the number of color values? *****/ if ( (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" ); exit(1); } /***** check the input and output datasets are the same size *****/ if (GDALGetRasterXSize(hOutDS) != nXSize || GDALGetRasterYSize(hOutDS) != nYSize) { CPLError(CE_Failure, CPLE_AppDefined, "The dimensions of the output dataset don't match " "the dimensions of the input dataset."); exit(1); } int iBand; for( iBand = 0; iBand < nBands; iBand++ ) { GDALRasterBandH hBand = GDALGetRasterBand(hInDS, 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) != NULL) { 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 = NULL; if (bSetMask) { /***** if there isn't already a mask band on the output file create one *****/ if ( GMF_PER_DATASET != GDALGetMaskFlags( GDALGetRasterBand(hOutDS, 1) ) ) { if ( CE_None != GDALCreateDatasetMaskBand(hOutDS, GMF_PER_DATASET) ) { CPLError(CE_Failure, CPLE_AppDefined, "Failed to create mask band on output DS"); bSetMask = FALSE; } } if (bSetMask) { hMaskBand = GDALGetMaskBand(GDALGetRasterBand(hOutDS, 1)); } } /* -------------------------------------------------------------------- */ /* Allocate a line buffer. */ /* -------------------------------------------------------------------- */ GByte *pabyLine; GByte *pabyMask=NULL; int *panLastLineCounts; pabyLine = (GByte *) CPLMalloc(nXSize * nDstBands); if (bSetMask) pabyMask = (GByte *) CPLMalloc(nXSize); panLastLineCounts = (int *) CPLCalloc(sizeof(int),nXSize); /* -------------------------------------------------------------------- */ /* Processing data one line at a time. */ /* -------------------------------------------------------------------- */ int iLine; for( iLine = 0; iLine < nYSize; iLine++ ) { CPLErr eErr; eErr = GDALDatasetRasterIO( hInDS, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nBands, NULL, nDstBands, nXSize * nDstBands, 1 ); if( eErr != CE_None ) break; if (bSetAlpha) { int iCol; for(iCol = 0; iCol < nXSize; iCol ++) { pabyLine[iCol * nDstBands + nDstBands - 1] = 255; } } if (bSetMask) { int iCol; for(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( hOutDS, GF_Write, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nDstBands, NULL, nDstBands, nXSize * nDstBands, 1 ); if( eErr != CE_None ) 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 writeing out line to mask band."); break; } } if (!bQuiet) GDALTermProgress( 0.5 * ((iLine+1) / (double) nYSize), NULL, NULL ); } /* -------------------------------------------------------------------- */ /* Now process from the bottom back up .*/ /* -------------------------------------------------------------------- */ memset( panLastLineCounts, 0, sizeof(int) * nXSize); for( iLine = nYSize-1; iLine >= 0; iLine-- ) { CPLErr eErr; eErr = GDALDatasetRasterIO( hOutDS, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nDstBands, NULL, nDstBands, nXSize * nDstBands, 1 ); if( eErr != CE_None ) 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 ) 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( hOutDS, GF_Write, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte, nDstBands, NULL, nDstBands, nXSize * nDstBands, 1 ); if( eErr != CE_None ) 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 ) break; } if (!bQuiet) GDALTermProgress( 0.5 + 0.5 * (nYSize-iLine) / (double) nYSize, NULL, NULL ); } CPLFree(pabyLine); if (bSetMask) CPLFree(pabyMask); CPLFree( panLastLineCounts ); GDALClose( hOutDS ); if( hInDS != hOutDS ) GDALClose( hInDS ); GDALDumpOpenDatasets( stderr ); CSLDestroy( argv ); CSLDestroy( papszCreationOptions ); GDALDestroyDriverManager(); return 0; } /************************************************************************/ /* 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, int bNearWhite, Colors *poColors, int *panLastLineCounts, int bDoHorizontalCheck, int bDoVerticalCheck, int bBottomUp ) { int iDir, i; GByte nReplacevalue = 0; if( bNearWhite ) nReplacevalue = 255; /* -------------------------------------------------------------------- */ /* Vertical checking. */ /* -------------------------------------------------------------------- */ if( bDoVerticalCheck ) { int nXSize = MAX(iStart+1,iEnd+1); for( i = 0; i < nXSize; i++ ) { // are we already terminated for this column? if( panLastLineCounts[i] > nMaxNonBlack ) continue; /***** is the pixel valid data? ****/ int bIsNonBlack = FALSE; /***** loop over the colors *****/ int iColor; for (iColor = 0; iColor < (int)poColors->size(); iColor++) { Color oColor = (*poColors)[iColor]; bIsNonBlack = FALSE; /***** loop over the bands *****/ int iBand; for( iBand = 0; iBand < nSrcBands; iBand++ ) { int nPix = pabyLine[i * nDstBands + iBand]; if( oColor[iBand] - nPix > nNearDist || nPix > nNearDist + oColor[iBand] ) { bIsNonBlack = TRUE; break; } } if (bIsNonBlack == FALSE) break; } if (bIsNonBlack) { panLastLineCounts[i]++; if( panLastLineCounts[i] > nMaxNonBlack ) continue; } //else // panLastLineCounts[i] = 0; // not sure this even makes sense /***** replace the pixel values *****/ int iBand; for( iBand = 0; iBand < nSrcBands; iBand++ ) pabyLine[i * nDstBands + iBand] = nReplacevalue; /***** alpha *****/ if( nDstBands > nSrcBands ) pabyLine[i * nDstBands + nDstBands - 1] = 0; /***** mask *****/ if (pabyMask != NULL) pabyMask[i] = 0; } } /* -------------------------------------------------------------------- */ /* Horizontal Checking. */ /* -------------------------------------------------------------------- */ if( bDoHorizontalCheck ) { int nNonBlackPixels = 0; /***** on a bottom up pass assume nMaxNonBlack is 0 *****/ if (bBottomUp) nMaxNonBlack = 0; if( iStart < iEnd ) iDir = 1; else iDir = -1; int bDoTest = TRUE; for( i = iStart; i != iEnd; i += iDir ) { /***** not seen any valid data? *****/ if ( bDoTest ) { /***** is the pixel valid data? ****/ int bIsNonBlack = FALSE; /***** loop over the colors *****/ int iColor; for (iColor = 0; iColor < (int)poColors->size(); iColor++) { Color oColor = (*poColors)[iColor]; bIsNonBlack = FALSE; /***** loop over the bands *****/ int iBand; for( iBand = 0; iBand < nSrcBands; iBand++ ) { 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 verical pass's grey areas ****/ if( panLastLineCounts[i] <= nMaxNonBlack ) nNonBlackPixels = panLastLineCounts[i]; else nNonBlackPixels++; } if( nNonBlackPixels > nMaxNonBlack ) { bDoTest = FALSE; continue; } /***** replace the pixel values *****/ int iBand; for( iBand = 0; iBand < nSrcBands; iBand++ ) pabyLine[i * nDstBands + iBand] = nReplacevalue; /***** alpha *****/ if( nDstBands > nSrcBands ) pabyLine[i * nDstBands + nDstBands - 1] = 0; /***** mask *****/ if (pabyMask != NULL) 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; } } } }