/****************************************************************************** * $Id: tif_overview.c,v 1.6 2004-08-08 15:32:29 dron Exp $ * * Project: TIFF Overview Builder * Purpose: Library function for building overviews in a TIFF file. * Author: Frank Warmerdam, warmerdam@pobox.com * * Notes: * o Currently only images with bits_per_sample of a multiple of eight * will work. * * o The downsampler currently just takes the top left pixel from the * source rectangle. Eventually sampling options of averaging, mode, and * ``center pixel'' should be offered. * * o The code will attempt to use the same kind of compression, * photometric interpretation, and organization as the source image, but * it doesn't copy geotiff tags to the reduced resolution images. * * o Reduced resolution overviews for multi-sample files will currently * always be generated as PLANARCONFIG_SEPARATE. This could be fixed * reasonable easily if needed to improve compatibility with other * packages. Many don't properly support PLANARCONFIG_SEPARATE. * ****************************************************************************** * Copyright (c) 1999, Frank Warmerdam * * 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 #include #include #include #include "tiffio.h" #include "tif_ovrcache.h" #ifndef FALSE # define FALSE 0 # define TRUE 1 #endif #ifndef MAX # define MIN(a,b) ((ab) ? a : b) #endif void TIFFBuildOverviews( TIFF *, int, int *, int, const char *, int (*)(double,void*), void * ); /************************************************************************/ /* TIFF_WriteOverview() */ /* */ /* Create a new directory, without any image data for an overview. */ /* Returns offset of newly created overview directory, but the */ /* current directory is reset to be the one in used when this */ /* function is called. */ /************************************************************************/ uint32 TIFF_WriteOverview( TIFF *hTIFF, int nXSize, int nYSize, int nBitsPerPixel, int nSamples, int nBlockXSize, int nBlockYSize, int bTiled, int nCompressFlag, int nPhotometric, int nSampleFormat, unsigned short *panRed, unsigned short *panGreen, unsigned short *panBlue, int bUseSubIFDs ) { uint32 nBaseDirOffset; uint32 nOffset; nBaseDirOffset = TIFFCurrentDirOffset( hTIFF ); TIFFCreateDirectory( hTIFF ); /* -------------------------------------------------------------------- */ /* Setup TIFF fields. */ /* -------------------------------------------------------------------- */ TIFFSetField( hTIFF, TIFFTAG_IMAGEWIDTH, nXSize ); TIFFSetField( hTIFF, TIFFTAG_IMAGELENGTH, nYSize ); if( nSamples == 1 ) TIFFSetField( hTIFF, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG ); else TIFFSetField( hTIFF, TIFFTAG_PLANARCONFIG, PLANARCONFIG_SEPARATE ); TIFFSetField( hTIFF, TIFFTAG_BITSPERSAMPLE, nBitsPerPixel ); TIFFSetField( hTIFF, TIFFTAG_SAMPLESPERPIXEL, nSamples ); TIFFSetField( hTIFF, TIFFTAG_COMPRESSION, nCompressFlag ); TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, nPhotometric ); TIFFSetField( hTIFF, TIFFTAG_SAMPLEFORMAT, nSampleFormat ); if( bTiled ) { TIFFSetField( hTIFF, TIFFTAG_TILEWIDTH, nBlockXSize ); TIFFSetField( hTIFF, TIFFTAG_TILELENGTH, nBlockYSize ); } else TIFFSetField( hTIFF, TIFFTAG_ROWSPERSTRIP, nBlockYSize ); TIFFSetField( hTIFF, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE ); /* -------------------------------------------------------------------- */ /* Write color table if one is present. */ /* -------------------------------------------------------------------- */ if( panRed != NULL ) { TIFFSetField( hTIFF, TIFFTAG_COLORMAP, panRed, panGreen, panBlue ); } /* -------------------------------------------------------------------- */ /* Write directory, and return byte offset. */ /* -------------------------------------------------------------------- */ if( TIFFWriteCheck( hTIFF, bTiled, "TIFFBuildOverviews" ) == 0 ) return 0; TIFFWriteDirectory( hTIFF ); TIFFSetDirectory( hTIFF, (tdir_t) (TIFFNumberOfDirectories(hTIFF)-1) ); nOffset = TIFFCurrentDirOffset( hTIFF ); TIFFSetSubDirectory( hTIFF, nBaseDirOffset ); return nOffset; } /************************************************************************/ /* TIFF_GetSourceSamples() */ /************************************************************************/ static void TIFF_GetSourceSamples( double * padfSamples, unsigned char *pabySrc, int nPixelBytes, int nSampleFormat, int nXSize, int nYSize, int nPixelOffset, int nLineOffset ) { int iXOff, iYOff, iSample; iSample = 0; for( iYOff = 0; iYOff < nYSize; iYOff++ ) { for( iXOff = 0; iXOff < nXSize; iXOff++ ) { unsigned char *pabyData; pabyData = pabySrc + iYOff * nLineOffset + iXOff * nPixelOffset; if( nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 1 ) { padfSamples[iSample++] = *pabyData; } else if( nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 2 ) { padfSamples[iSample++] = ((uint16 *) pabyData)[0]; } else if( nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 4 ) { padfSamples[iSample++] = ((uint32 *) pabyData)[0]; } else if( nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 2 ) { padfSamples[iSample++] = ((int16 *) pabyData)[0]; } else if( nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 32 ) { padfSamples[iSample++] = ((int32 *) pabyData)[0]; } else if( nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 4 ) { padfSamples[iSample++] = ((float *) pabyData)[0]; } else if( nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 8 ) { padfSamples[iSample++] = ((double *) pabyData)[0]; } } } } /************************************************************************/ /* TIFF_SetSample() */ /************************************************************************/ static void TIFF_SetSample( unsigned char * pabyData, int nPixelBytes, int nSampleFormat, double dfValue ) { if( nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 1 ) { *pabyData = (unsigned char) MAX(0,MIN(255,dfValue)); } else if( nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 2 ) { *((uint16 *)pabyData) = (uint16) MAX(0,MIN(65535,dfValue)); } else if( nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 4 ) { *((uint32 *)pabyData) = (uint32) dfValue; } else if( nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 2 ) { *((int16 *)pabyData) = (int16) MAX(-32768,MIN(32767,dfValue)); } else if( nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 32 ) { *((int32 *)pabyData) = (int32) dfValue; } else if( nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 4 ) { *((float *)pabyData) = (float) dfValue; } else if( nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 8 ) { *((double *)pabyData) = dfValue; } } /************************************************************************/ /* TIFF_DownSample() */ /* */ /* Down sample a tile of full res data into a window of a tile */ /* of downsampled data. */ /************************************************************************/ static void TIFF_DownSample( unsigned char *pabySrcTile, int nBlockXSize, int nBlockYSize, int nPixelSkewBits, int nBitsPerPixel, unsigned char * pabyOTile, int nOBlockXSize, int nOBlockYSize, int nTXOff, int nTYOff, int nOMult, int nSampleFormat, const char * pszResampling ) { int i, j, k, nPixelBytes = (nBitsPerPixel) / 8; int nPixelGroupBytes = (nBitsPerPixel+nPixelSkewBits)/8; unsigned char *pabySrc, *pabyDst; double *padfSamples; assert( nBitsPerPixel >= 8 ); padfSamples = (double *) malloc(sizeof(double) * nOMult * nOMult); /* ==================================================================== */ /* Loop over scanline chunks to process, establishing where the */ /* data is going. */ /* ==================================================================== */ for( j = 0; j*nOMult < nBlockYSize; j++ ) { if( j + nTYOff >= nOBlockYSize ) break; pabyDst = pabyOTile + ((j+nTYOff)*nOBlockXSize + nTXOff) * nPixelBytes; /* -------------------------------------------------------------------- */ /* Handler nearest resampling ... we don't even care about the */ /* data type, we just do a bytewise copy. */ /* -------------------------------------------------------------------- */ if( strncmp(pszResampling,"nearest",4) == 0 || strncmp(pszResampling,"NEAR",4) == 0 ) { pabySrc = pabySrcTile + j*nOMult*nBlockXSize * nPixelGroupBytes; for( i = 0; i*nOMult < nBlockXSize; i++ ) { if( i + nTXOff >= nOBlockXSize ) break; /* * For now use simple subsampling, from the top left corner * of the source block of pixels. */ for( k = 0; k < nPixelBytes; k++ ) { *(pabyDst++) = pabySrc[k]; } pabySrc += nOMult * nPixelGroupBytes; } } /* -------------------------------------------------------------------- */ /* Handle the case of averaging. For this we also have to */ /* handle each sample format we are concerned with. */ /* -------------------------------------------------------------------- */ else if( strncmp(pszResampling,"averag",6) == 0 || strncmp(pszResampling,"AVERAG",6) == 0 ) { pabySrc = pabySrcTile + j*nOMult*nBlockXSize * nPixelGroupBytes; for( i = 0; i*nOMult < nBlockXSize; i++ ) { double dfTotal; int iSample; int nXSize, nYSize; if( i + nTXOff >= nOBlockXSize ) break; nXSize = MIN(nOMult,nBlockXSize-i); nYSize = MIN(nOMult,nBlockYSize-j); TIFF_GetSourceSamples( padfSamples, pabySrc, nPixelBytes, nSampleFormat, nXSize, nYSize, nPixelGroupBytes, nPixelGroupBytes * nBlockXSize ); dfTotal = 0; for( iSample = 0; iSample < nXSize*nYSize; iSample++ ) { dfTotal += padfSamples[iSample]; } TIFF_SetSample( pabyDst, nPixelBytes, nSampleFormat, dfTotal / (nXSize*nYSize) ); pabySrc += nOMult * nPixelGroupBytes; pabyDst += nPixelBytes; } } } free( padfSamples ); } /************************************************************************/ /* TIFF_ProcessFullResBlock() */ /* */ /* Process one block of full res data, downsampling into each */ /* of the overviews. */ /************************************************************************/ void TIFF_ProcessFullResBlock( TIFF *hTIFF, int nPlanarConfig, int nOverviews, int * panOvList, int nBitsPerPixel, int nSamples, TIFFOvrCache ** papoRawBIs, int nSXOff, int nSYOff, unsigned char *pabySrcTile, int nBlockXSize, int nBlockYSize, int nSampleFormat, const char * pszResampling ) { int iOverview, iSample; for( iSample = 0; iSample < nSamples; iSample++ ) { /* * We have to read a tile/strip for each sample for * PLANARCONFIG_SEPARATE. Otherwise, we just read all the samples * at once when handling the first sample. */ if( nPlanarConfig == PLANARCONFIG_SEPARATE || iSample == 0 ) { if( TIFFIsTiled(hTIFF) ) { TIFFReadEncodedTile( hTIFF, TIFFComputeTile(hTIFF, nSXOff, nSYOff, 0, (tsample_t)iSample ), pabySrcTile, TIFFTileSize(hTIFF)); } else { TIFFReadEncodedStrip( hTIFF, TIFFComputeStrip(hTIFF, nSYOff, (tsample_t) iSample), pabySrcTile, TIFFStripSize(hTIFF) ); } } /* * Loop over destination overview layers */ for( iOverview = 0; iOverview < nOverviews; iOverview++ ) { TIFFOvrCache *poRBI = papoRawBIs[iOverview]; unsigned char *pabyOTile; int nTXOff, nTYOff, nOXOff, nOYOff, nOMult; int nOBlockXSize = poRBI->nBlockXSize; int nOBlockYSize = poRBI->nBlockYSize; int nSkewBits, nSampleByteOffset; /* * Fetch the destination overview tile */ nOMult = panOvList[iOverview]; nOXOff = (nSXOff/nOMult) / nOBlockXSize; nOYOff = (nSYOff/nOMult) / nOBlockYSize; pabyOTile = TIFFGetOvrBlock( poRBI, nOXOff, nOYOff, iSample ); /* * Establish the offset into this tile at which we should * start placing data. */ nTXOff = (nSXOff - nOXOff*nOMult*nOBlockXSize) / nOMult; nTYOff = (nSYOff - nOYOff*nOMult*nOBlockYSize) / nOMult; /* * Figure out the skew (extra space between ``our samples'') and * the byte offset to the first sample. */ assert( (nBitsPerPixel % 8) == 0 ); if( nPlanarConfig == PLANARCONFIG_SEPARATE ) { nSkewBits = 0; nSampleByteOffset = 0; } else { nSkewBits = nBitsPerPixel * (nSamples-1); nSampleByteOffset = (nBitsPerPixel/8) * iSample; } /* * Perform the downsampling. */ #ifdef DBMALLOC malloc_chain_check( 1 ); #endif TIFF_DownSample( pabySrcTile + nSampleByteOffset, nBlockXSize, nBlockYSize, nSkewBits, nBitsPerPixel, pabyOTile, poRBI->nBlockXSize, poRBI->nBlockYSize, nTXOff, nTYOff, nOMult, nSampleFormat, pszResampling ); #ifdef DBMALLOC malloc_chain_check( 1 ); #endif } } } /************************************************************************/ /* TIFF_BuildOverviews() */ /* */ /* Build the requested list of overviews. Overviews are */ /* maintained in a bunch of temporary files and then these are */ /* written back to the TIFF file. Only one pass through the */ /* source TIFF file is made for any number of output */ /* overviews. */ /************************************************************************/ void TIFFBuildOverviews( TIFF *hTIFF, int nOverviews, int * panOvList, int bUseSubIFDs, const char *pszResampleMethod, int (*pfnProgress)( double, void * ), void * pProgressData ) { TIFFOvrCache **papoRawBIs; uint32 nXSize, nYSize, nBlockXSize, nBlockYSize; uint16 nBitsPerPixel, nPhotometric, nCompressFlag, nSamples, nPlanarConfig, nSampleFormat; int bTiled, nSXOff, nSYOff, i; unsigned char *pabySrcTile; uint16 *panRedMap, *panGreenMap, *panBlueMap; TIFFErrorHandler pfnWarning; /* -------------------------------------------------------------------- */ /* Get the base raster size. */ /* -------------------------------------------------------------------- */ TIFFGetField( hTIFF, TIFFTAG_IMAGEWIDTH, &nXSize ); TIFFGetField( hTIFF, TIFFTAG_IMAGELENGTH, &nYSize ); TIFFGetField( hTIFF, TIFFTAG_BITSPERSAMPLE, &nBitsPerPixel ); TIFFGetField( hTIFF, TIFFTAG_SAMPLESPERPIXEL, &nSamples ); TIFFGetFieldDefaulted( hTIFF, TIFFTAG_PLANARCONFIG, &nPlanarConfig ); TIFFGetFieldDefaulted( hTIFF, TIFFTAG_PHOTOMETRIC, &nPhotometric ); TIFFGetFieldDefaulted( hTIFF, TIFFTAG_COMPRESSION, &nCompressFlag ); TIFFGetFieldDefaulted( hTIFF, TIFFTAG_SAMPLEFORMAT, &nSampleFormat ); if( nBitsPerPixel < 8 ) { TIFFError( "TIFFBuildOverviews", "File `%s' has samples of %d bits per sample. Sample\n" "sizes of less than 8 bits per sample are not supported.\n", TIFFFileName(hTIFF), nBitsPerPixel ); return; } /* -------------------------------------------------------------------- */ /* Turn off warnings to avoid alot of repeated warnings while */ /* rereading directories. */ /* -------------------------------------------------------------------- */ pfnWarning = TIFFSetWarningHandler( NULL ); /* -------------------------------------------------------------------- */ /* Get the base raster block size. */ /* -------------------------------------------------------------------- */ if( TIFFGetField( hTIFF, TIFFTAG_ROWSPERSTRIP, &(nBlockYSize) ) ) { nBlockXSize = nXSize; bTiled = FALSE; } else { TIFFGetField( hTIFF, TIFFTAG_TILEWIDTH, &nBlockXSize ); TIFFGetField( hTIFF, TIFFTAG_TILELENGTH, &nBlockYSize ); bTiled = TRUE; } /* -------------------------------------------------------------------- */ /* Capture the pallette if there is one. */ /* -------------------------------------------------------------------- */ if( TIFFGetField( hTIFF, TIFFTAG_COLORMAP, &panRedMap, &panGreenMap, &panBlueMap ) ) { uint16 *panRed2, *panGreen2, *panBlue2; panRed2 = (uint16 *) _TIFFmalloc(2*256); panGreen2 = (uint16 *) _TIFFmalloc(2*256); panBlue2 = (uint16 *) _TIFFmalloc(2*256); memcpy( panRed2, panRedMap, 512 ); memcpy( panGreen2, panGreenMap, 512 ); memcpy( panBlue2, panBlueMap, 512 ); panRedMap = panRed2; panGreenMap = panGreen2; panBlueMap = panBlue2; } else { panRedMap = panGreenMap = panBlueMap = NULL; } /* -------------------------------------------------------------------- */ /* Initialize overviews. */ /* -------------------------------------------------------------------- */ papoRawBIs = (TIFFOvrCache **) _TIFFmalloc(nOverviews*sizeof(void*)); for( i = 0; i < nOverviews; i++ ) { int nOXSize, nOYSize, nOBlockXSize, nOBlockYSize; uint32 nDirOffset; nOXSize = (nXSize + panOvList[i] - 1) / panOvList[i]; nOYSize = (nYSize + panOvList[i] - 1) / panOvList[i]; nOBlockXSize = MIN((int)nBlockXSize,nOXSize); nOBlockYSize = MIN((int)nBlockYSize,nOYSize); if( bTiled ) { if( (nOBlockXSize % 16) != 0 ) nOBlockXSize = nOBlockXSize + 16 - (nOBlockXSize % 16); if( (nOBlockYSize % 16) != 0 ) nOBlockYSize = nOBlockYSize + 16 - (nOBlockYSize % 16); } nDirOffset = TIFF_WriteOverview( hTIFF, nOXSize, nOYSize, nBitsPerPixel, nSamples, nOBlockXSize, nOBlockYSize, bTiled, nCompressFlag, nPhotometric, nSampleFormat, panRedMap, panGreenMap, panBlueMap, bUseSubIFDs ); papoRawBIs[i] = TIFFCreateOvrCache( hTIFF, nDirOffset ); } if( panRedMap != NULL ) { _TIFFfree( panRedMap ); _TIFFfree( panGreenMap ); _TIFFfree( panBlueMap ); } /* -------------------------------------------------------------------- */ /* Allocate a buffer to hold a source block. */ /* -------------------------------------------------------------------- */ if( bTiled ) pabySrcTile = (unsigned char *) _TIFFmalloc(TIFFTileSize(hTIFF)); else pabySrcTile = (unsigned char *) _TIFFmalloc(TIFFStripSize(hTIFF)); /* -------------------------------------------------------------------- */ /* Loop over the source raster, applying data to the */ /* destination raster. */ /* -------------------------------------------------------------------- */ for( nSYOff = 0; nSYOff < (int) nYSize; nSYOff += nBlockYSize ) { for( nSXOff = 0; nSXOff < (int) nXSize; nSXOff += nBlockXSize ) { /* * Read and resample into the various overview images. */ TIFF_ProcessFullResBlock( hTIFF, nPlanarConfig, nOverviews, panOvList, nBitsPerPixel, nSamples, papoRawBIs, nSXOff, nSYOff, pabySrcTile, nBlockXSize, nBlockYSize, nSampleFormat, pszResampleMethod ); } } _TIFFfree( pabySrcTile ); /* -------------------------------------------------------------------- */ /* Cleanup the rawblockedimage files. */ /* -------------------------------------------------------------------- */ for( i = 0; i < nOverviews; i++ ) { TIFFDestroyOvrCache( papoRawBIs[i] ); } if( papoRawBIs != NULL ) _TIFFfree( papoRawBIs ); TIFFSetWarningHandler( pfnWarning ); }