/* * imgsav.c * * Implementation of the image save functionality. * * (C) 1997 Randall Hopper * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* ******************** Include Files ************** */ #include #include #include #include #include #include #include "tvdefines.h" #include "imgsav.h" #include "xutil.h" #include "rawvideo.h" /* ******************** Local defines ************** */ #define WRITE_BUF_SIZE (64*1024L) #define TV_BITS_PER_COMP 8 #define TV_COMP_PER_PIX 3 #define TV_BYTES_PER_PIX 3 #define WRITE_PLANAR_TIFFS /* NOTE: libtiff doesn't write correct Planar TIFFs when */ /* ROWSPERSTRIP is > 1 (e.g. height of image). If you can't */ /* do that, no compression advantage, so no reason to use it. */ #ifndef WRITE_PLANAR_TIFFS # error This code worked for tiff3.3, but broke for tiff3.4. /* NOTE: Fortunately, what we wanted was PLANAR tiffs anyway, so with */ /* tiff3.4, what we want now works whereas with 3.3 it didn't. */ #endif /* ******************** Private variables ************** */ /* ******************** Forward declarations ************** */ /* ******************** Function Definitions ************** */ /**@BEGINFUNC************************************************************** Prototype : static void TVIMGSAVFmtScanline24bpp( TV_IMAGE *img, TV_INT32 y, TV_INT32 compon, TV_UINT8 *dst ) Purpose : Formats the pixel data from scanline "y" in the image "img", form whatever strange and surreal format it happens to be in into straight 24bpp RGB format (R-G-B, respectively). Only the color components specified by compon are written. NOTE: dst is assumed to be a pointer to a buffer of sufficient length ( N * img->geom.w, where N is the number of components specified by compon). Programmer : 03-Sep-97 Randall Hopper Parameters : img - I: an image y - I: which scanline to extract (and fmt cnvt if needed) compon - I: desired components (mask of DoRed DoGreen DoBlue) dst - O: pointer to the output buffer (size 3*width or more) Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ static void TVIMGSAVFmtScanline24bpp( TV_IMAGE *img, TV_INT32 y, TV_INT32 compon, TV_UINT8 *dst ) { static TV_BOOL S_cache_valid = FALSE; static TV_INT32 S_cache_shf[3]; static TV_UINT32 S_cache_msk[3], S_cache_pixmsk[3], S_dst_rel_mask[3] = { 0xFF0000, 0x00FF00, 0x0000FF }; TV_UINT32 *shf = S_cache_shf, *msk = S_cache_msk; TV_UINT8 *src8 = (TV_UINT8 *) (img->buf + y * img->geom.w * img->pix_geom.Bpp); TV_UINT16 *src16 = (TV_UINT16 *) src8; TV_UINT32 *src32 = (TV_UINT32 *) src8; TV_INT32 x; assert( (y >= 0) && (y < img->geom.h) ); if ( !S_cache_valid || !memcmp( img->pix_geom.mask, S_cache_pixmsk, sizeof(S_cache_pixmsk)) ){ memcpy( S_cache_pixmsk, img->pix_geom.mask, sizeof( S_cache_pixmsk ) ); XUTILGetPixelConvInfo( S_cache_pixmsk, S_dst_rel_mask, S_cache_shf, S_cache_msk ); S_cache_valid = TRUE; } /* Format scanline buffer */ for ( x = 0; x < img->geom.w; x++ ) { register TV_UINT32 pix; /* Grab pixel */ switch ( img->pix_geom.Bpp ) { case 2 : pix = *(src16++); break; case 4 : pix = *(src32++); break; case 3 : pix = *(src8++); pix |= *(src8++) << 8; pix |= *(src8++) << 16; break; default: fprintf( stderr, "TVIMGSAVFmtScanline24bpp: Unsupported Bpp %ld\n", img->pix_geom.Bpp ); exit(1); } /* Swap bytes as needed */ if (( !img->pix_geom.swap_shorts ) && ( img->pix_geom.Bpp == 4 )) pix = (pix >> 16) | (pix << 16); if ( !img->pix_geom.swap_bytes ) if ( img->pix_geom.Bpp == 3 ) pix = ((pix & 0x00FF0000) >> 16) | ((pix & 0x000000FF) << 16); else pix = ((pix & 0xFF000000) >> 8) | ((pix & 0x00FF0000) << 8) | ((pix & 0x0000FF00) >> 8) | ((pix & 0x000000FF) << 8); pix = SHIFT_AND_MASK( pix, shf[0], msk[0] ) | SHIFT_AND_MASK( pix, shf[1], msk[1] ) | SHIFT_AND_MASK( pix, shf[2], msk[2] ); /* Finally, got an 8-8-8 RGB 3Bpp pixel in "pix". */ /* Slap it in the buffer. */ if ( compon & DoRed ) *(dst++) = (pix >> 16) & 0xFF; if ( compon & DoGreen ) *(dst++) = (pix >> 8) & 0xFF; if ( compon & DoBlue ) *(dst++) = pix & 0xFF; } } void TVIMGSAVDoSaveTIFF( char filename[], TV_IMAGE *img ) { char errmsg[160]; TIFF *out; TV_INT32 linebytes, y, pass, compon; TV_UINT8 *buf; if ( img->pix_geom.type != TV_PIXELTYPE_RGB ) { fprintf( stderr, "Attempt to save non-RGB data as TIFF\n" ); exit(1); } /* Open output file */ if ( (out = TIFFOpen( filename, "w" )) == NULL ) { sprintf( errmsg, "Can't open output file '%s'", filename ); XUTILDialogPause( TVTOPLEVEL, "Error", errmsg, TV_DIALOG_TYPE_OK ); return; } /* Setup image format info (tags) */ TIFFSetField( out, TIFFTAG_IMAGEWIDTH , img->geom.w ); TIFFSetField( out, TIFFTAG_IMAGELENGTH , img->geom.h ); TIFFSetField( out, TIFFTAG_ORIENTATION , ORIENTATION_TOPLEFT ); TIFFSetField( out, TIFFTAG_SAMPLESPERPIXEL, TV_COMP_PER_PIX ); TIFFSetField( out, TIFFTAG_BITSPERSAMPLE , TV_BITS_PER_COMP ); TIFFSetField( out, TIFFTAG_PHOTOMETRIC , PHOTOMETRIC_RGB ); TIFFSetField( out, TIFFTAG_COMPRESSION , COMPRESSION_LZW ); /* Allocate a buffer to hold each scanline */ #ifdef WRITE_PLANAR_TIFFS /* Note: w/ ROWSPERSTRIP > 1, libtiff doesn't write pixels correctly. */ /* Valid TIFF file, but there's some weird pixel shifting going on. */ TIFFSetField( out, TIFFTAG_ROWSPERSTRIP , img->geom.h ); TIFFSetField( out, TIFFTAG_PLANARCONFIG , PLANARCONFIG_SEPARATE ); linebytes = img->geom.w * 1; #else TIFFSetField( out, TIFFTAG_PLANARCONFIG , PLANARCONFIG_CONTIG ); linebytes = img->geom.w * TV_BYTES_PER_PIX; #endif if ( TIFFScanlineSize(out) != linebytes ) { fprintf( stderr, "Linebytes mismatch: TIFF says %ld, we say %ld\n", TIFFScanlineSize(out), linebytes ); exit(1); } if ( (buf = malloc( linebytes )) == NULL ) TVUTILOutOfMemory(); /* Convert & write the image data */ #ifdef WRITE_PLANAR_TIFFS for ( pass = 0; pass < TV_COMP_PER_PIX; pass++ ) { compon = ( pass == 0 ? DoRed : pass == 1 ? DoGreen : DoBlue ); #else for ( pass = 0; pass < 1; pass++ ) { compon = DoRed | DoGreen | DoBlue; #endif for ( y = 0; y < img->geom.h; y++ ) { /* Format scanline */ TVIMGSAVFmtScanline24bpp( img, y, compon, buf ); /* And write it in TIFF */ if ( !TIFFWriteScanline( out, buf, y, pass ) ) { fprintf( stderr, "TIFFWriteScanline() failed\n" ); XBell( TVDISPLAY, 100 ); TIFFClose( out ); free( buf ); unlink( filename ); return; } } } /* All done. Close up shop and go home */ TIFFClose(out); free( buf ); } void TVIMGSAVDoSavePPM( char filename[], TV_IMAGE *img ) { static char *S_fp_buf = NULL; static char *S_buf = NULL; static TV_INT32 S_buf_size = 0; char errmsg[160]; FILE *out; TV_INT32 y; TV_INT32 linebytes; if ( img->pix_geom.type != TV_PIXELTYPE_RGB ) { fprintf( stderr, "Attempt to save non-RGB data as PPM\n" ); exit(1); } /* Allocate a buffer for PPM writing to encourage large block writes */ if ( !S_fp_buf && ( (S_fp_buf = malloc( WRITE_BUF_SIZE )) == NULL ) ) TVUTILOutOfMemory(); /* Open output file */ if ( filename ) { if ( (out = fopen( filename, "wb" )) == NULL ) { sprintf( errmsg, "Can't open output file '%s'", filename ); XUTILDialogPause( TVTOPLEVEL, "Error", errmsg, TV_DIALOG_TYPE_OK ); return; } setvbuf( out, S_fp_buf, _IOFBF, WRITE_BUF_SIZE ); } else out = stdout; /* Write binary PPM header */ fprintf( out, "P6\n%ld %ld\n%d\n", img->geom.w, img->geom.h, (1 << TV_BITS_PER_COMP) - 1 ); linebytes = img->geom.w * TV_BYTES_PER_PIX; if (( S_buf == NULL ) || ( S_buf_size < linebytes )) { if ( (S_buf = realloc( S_buf, linebytes )) == NULL ) TVUTILOutOfMemory(); S_buf_size = MAX( S_buf_size, linebytes ); } /* Now write the pixel data -- just a simple raw block of */ /* R G B pixel values, ordered left-to-right, top-to-bottom. */ for ( y = 0; y < img->geom.h; y++ ) { /* Format scanline */ TVIMGSAVFmtScanline24bpp( img, y, DoRed | DoGreen | DoBlue, S_buf ); /* And write it in PPM */ if ( fwrite( S_buf, 3, img->geom.w, out ) != img->geom.w ) { fprintf( stderr, "TVIMGSAVDoSavePPM(): Error writing scanline\n" ); XBell( TVDISPLAY, 100 ); if ( filename ) { fclose( out ); unlink( filename ); } return; } } /* All done. Close up shop and go home */ if ( filename ) fclose(out); } void TVIMGSAVDoSaveYUV( char filename[], TV_IMAGE *img ) { /* YUV isn't really a format. It's just the raw frame chunked into */ /* a file as-is (packed/planar, whatever H/V Y/U/V sampling, etc.) */ static char *S_fp_buf = NULL; char errmsg[160]; FILE *out; TV_UINT32 img_size; if ( img->pix_geom.type != TV_PIXELTYPE_YUV ) { fprintf( stderr, "Attempt to save non-YUV data as YUV\n" ); exit(1); } /* Allocate a buffer for YUV writing to encourage large block writes */ if ( !S_fp_buf && ( (S_fp_buf = malloc( WRITE_BUF_SIZE )) == NULL ) ) TVUTILOutOfMemory(); /* Open output file */ if ( filename ) { if ( (out = fopen( filename, "wb" )) == NULL ) { sprintf( errmsg, "Can't open output file '%s'", filename ); XUTILDialogPause( TVTOPLEVEL, "Error", errmsg, TV_DIALOG_TYPE_OK ); return; } setvbuf( out, S_fp_buf, _IOFBF, WRITE_BUF_SIZE ); } else out = stdout; img_size = TVRAWVIDEOCalcImageSize( img ); if ( fwrite( img->buf, img_size, 1, out ) != 1 ) { fprintf( stderr, "TVIMGSAVDoSaveYUV(): Error writing YUV file\n" ); XBell( TVDISPLAY, 100 ); fclose( out ); unlink( filename ); return; } /* All done. Close up shop and go home */ if ( filename ) fclose(out); } /**@BEGINFUNC************************************************************** Prototype : void TVIMGSAVDoSave( char filename[], TV_STILL_FMT fmt, TV_IMAGE *img ) Purpose : Write an image to a file (or stdout) in the specified format. Programmer : 15-Jan-98 Randall Hopper Parameters : filename - I: filename (NULL = stdout) fmt - I: format to write it in img - I: image to write. Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ void TVIMGSAVDoSave( char filename[], TV_STILL_FMT fmt, TV_IMAGE *img ) { switch ( fmt ) { case TV_STILL_FMT_TIFF : if ( !filename ) { fprintf( stderr, "TVIMGSAVDoSave: stdout mode not supported " "for TIFF\n" ); exit(1); } TVIMGSAVDoSaveTIFF( filename, img ); break; case TV_STILL_FMT_PPM : TVIMGSAVDoSavePPM ( filename, img ); break; case TV_STILL_FMT_YUV : TVIMGSAVDoSaveYUV ( filename, img ); break; default: fprintf( stderr, "TVIMGSAVDoSave: Unsupported format %d\n", fmt ); exit(1); } }