1 /*
2  * imgsav.c
3  *
4  * Implementation of the image save functionality.
5  *
6  * (C) 1997 Randall Hopper
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are
10  * met: 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer. 2.
12  * Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  */
29 
30 /*      ******************** Include Files                ************** */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <tiffio.h>
36 #include <assert.h>
37 #include <X11/X.h>
38 #include "tvdefines.h"
39 #include "imgsav.h"
40 #include "xutil.h"
41 #include "rawvideo.h"
42 
43 /*      ******************** Local defines                ************** */
44 
45 #define WRITE_BUF_SIZE     (64*1024L)
46 #define TV_BITS_PER_COMP   8
47 #define TV_COMP_PER_PIX    3
48 #define TV_BYTES_PER_PIX   3
49 
50 #define WRITE_PLANAR_TIFFS
51      /*  NOTE: libtiff doesn't write correct Planar TIFFs when         */
52      /*    ROWSPERSTRIP is > 1 (e.g. height of image).  If you can't   */
53      /*    do that, no compression advantage, so no reason to use it.  */
54 
55 #ifndef WRITE_PLANAR_TIFFS
56 # error This code worked for tiff3.3, but broke for tiff3.4.
57 /*   NOTE:  Fortunately, what we wanted was PLANAR tiffs anyway, so with  */
58 /*   tiff3.4, what we want now works whereas with 3.3 it didn't.          */
59 #endif
60 
61 /*      ******************** Private variables            ************** */
62 /*      ******************** Forward declarations         ************** */
63 /*      ******************** Function Definitions         ************** */
64 
65 /**@BEGINFUNC**************************************************************
66 
67     Prototype  : static void TVIMGSAVFmtScanline24bpp(
68                       TV_IMAGE *img,
69                       TV_INT32  y,
70                       TV_INT32  compon,
71                       TV_UINT8 *dst )
72 
73     Purpose    : Formats the pixel data from scanline "y" in the image "img",
74                  form whatever strange and surreal format it happens to be
75                  in into straight 24bpp RGB format (R-G-B, respectively).
76                  Only the color components specified by compon are written.
77 
78                  NOTE:  dst is assumed to be a pointer to a buffer of
79                  sufficient length ( N * img->geom.w, where N is the
80                  number of components specified by compon).
81 
82     Programmer : 03-Sep-97  Randall Hopper
83 
84     Parameters : img    - I: an image
85                  y      - I: which scanline to extract (and fmt cnvt if needed)
86                  compon - I: desired components (mask of DoRed DoGreen DoBlue)
87                  dst - O: pointer to the output buffer (size 3*width or more)
88 
89     Returns    : None.
90 
91     Globals    : None.
92 
93  **@ENDFUNC*****************************************************************/
94 
TVIMGSAVFmtScanline24bpp(TV_IMAGE * img,TV_INT32 y,TV_INT32 compon,TV_UINT8 * dst)95 static void TVIMGSAVFmtScanline24bpp(
96                TV_IMAGE *img,
97                TV_INT32  y,
98                TV_INT32  compon,
99                TV_UINT8 *dst )
100 {
101     static TV_BOOL   S_cache_valid = FALSE;
102     static TV_INT32  S_cache_shf[3];
103     static TV_UINT32 S_cache_msk[3],
104                      S_cache_pixmsk[3],
105                      S_dst_rel_mask[3] = { 0xFF0000, 0x00FF00, 0x0000FF };
106     TV_UINT32       *shf = S_cache_shf,
107                     *msk = S_cache_msk;
108 
109     TV_UINT8  *src8  = (TV_UINT8  *) (img->buf +
110                                       y * img->geom.w * img->pix_geom.Bpp);
111     TV_UINT16 *src16 = (TV_UINT16 *) src8;
112     TV_UINT32 *src32 = (TV_UINT32 *) src8;
113     TV_INT32   x;
114 
115     assert( (y >= 0) && (y < img->geom.h) );
116 
117     if ( !S_cache_valid ||
118         !memcmp( img->pix_geom.mask, S_cache_pixmsk, sizeof(S_cache_pixmsk)) ){
119 
120         memcpy( S_cache_pixmsk, img->pix_geom.mask, sizeof( S_cache_pixmsk ) );
121         XUTILGetPixelConvInfo( S_cache_pixmsk, S_dst_rel_mask,
122                                S_cache_shf, S_cache_msk );
123         S_cache_valid = TRUE;
124     }
125 
126     /*  Format scanline buffer  */
127     for ( x = 0; x < img->geom.w; x++ ) {
128         register TV_UINT32 pix;
129 
130         /*  Grab pixel  */
131         switch ( img->pix_geom.Bpp ) {
132             case 2 : pix  = *(src16++);  break;
133             case 4 : pix  = *(src32++);  break;
134             case 3 : pix  = *(src8++);
135                      pix |= *(src8++) <<  8;
136                      pix |= *(src8++) << 16;
137                      break;
138             default:
139                 fprintf( stderr,
140                          "TVIMGSAVFmtScanline24bpp: Unsupported Bpp %ld\n",
141                          img->pix_geom.Bpp );
142                 exit(1);
143         }
144 
145         /*  Swap bytes as needed  */
146         if (( !img->pix_geom.swap_shorts ) &&
147             ( img->pix_geom.Bpp == 4 ))
148             pix = (pix >> 16) | (pix << 16);
149         if ( !img->pix_geom.swap_bytes )
150             if ( img->pix_geom.Bpp == 3 )
151                 pix = ((pix & 0x00FF0000) >> 16) |
152                       ((pix & 0x000000FF) << 16);
153             else
154                 pix = ((pix & 0xFF000000) >> 8) |
155                       ((pix & 0x00FF0000) << 8) |
156                       ((pix & 0x0000FF00) >> 8) |
157                       ((pix & 0x000000FF) << 8);
158 
159         pix       = SHIFT_AND_MASK( pix, shf[0], msk[0] ) |
160                     SHIFT_AND_MASK( pix, shf[1], msk[1] ) |
161                     SHIFT_AND_MASK( pix, shf[2], msk[2] );
162 
163         /*  Finally, got an 8-8-8 RGB 3Bpp pixel in "pix".  */
164         /*    Slap it in the buffer.                        */
165         if ( compon & DoRed )
166            *(dst++) = (pix >> 16) & 0xFF;
167         if ( compon & DoGreen )
168            *(dst++) = (pix >>  8) & 0xFF;
169         if ( compon & DoBlue )
170            *(dst++) = pix         & 0xFF;
171     }
172 }
173 
174 
TVIMGSAVDoSaveTIFF(char filename[],TV_IMAGE * img)175 void TVIMGSAVDoSaveTIFF( char filename[], TV_IMAGE *img )
176 {
177     char       errmsg[160];
178     TIFF      *out;
179     TV_INT32   linebytes,
180                y,
181                pass,
182                compon;
183     TV_UINT8  *buf;
184 
185     if ( img->pix_geom.type != TV_PIXELTYPE_RGB ) {
186         fprintf( stderr, "Attempt to save non-RGB data as TIFF\n" );
187         exit(1);
188     }
189 
190     /*  Open output file  */
191     if ( (out = TIFFOpen( filename, "w" )) == NULL ) {
192         sprintf( errmsg, "Can't open output file '%s'", filename );
193         XUTILDialogPause( TVTOPLEVEL, "Error", errmsg, TV_DIALOG_TYPE_OK );
194         return;
195     }
196 
197     /*  Setup image format info (tags)  */
198     TIFFSetField( out, TIFFTAG_IMAGEWIDTH     , img->geom.w );
199     TIFFSetField( out, TIFFTAG_IMAGELENGTH    , img->geom.h );
200     TIFFSetField( out, TIFFTAG_ORIENTATION    , ORIENTATION_TOPLEFT );
201     TIFFSetField( out, TIFFTAG_SAMPLESPERPIXEL, TV_COMP_PER_PIX  );
202     TIFFSetField( out, TIFFTAG_BITSPERSAMPLE  , TV_BITS_PER_COMP );
203     TIFFSetField( out, TIFFTAG_PHOTOMETRIC    , PHOTOMETRIC_RGB );
204     TIFFSetField( out, TIFFTAG_COMPRESSION    , COMPRESSION_LZW );
205 
206     /*  Allocate a buffer to hold each scanline  */
207 #ifdef WRITE_PLANAR_TIFFS
208     /*  Note: w/ ROWSPERSTRIP > 1, libtiff doesn't write pixels correctly.  */
209     /*    Valid TIFF file, but there's some weird pixel shifting going on.  */
210     TIFFSetField( out, TIFFTAG_ROWSPERSTRIP   , img->geom.h );
211     TIFFSetField( out, TIFFTAG_PLANARCONFIG   , PLANARCONFIG_SEPARATE );
212     linebytes = img->geom.w * 1;
213 #else
214     TIFFSetField( out, TIFFTAG_PLANARCONFIG   , PLANARCONFIG_CONTIG );
215     linebytes = img->geom.w * TV_BYTES_PER_PIX;
216 #endif
217 
218     if ( TIFFScanlineSize(out) != linebytes ) {
219         fprintf( stderr, "Linebytes mismatch: TIFF says %ld, we say %ld\n",
220                  TIFFScanlineSize(out), linebytes );
221         exit(1);
222     }
223     if ( (buf = malloc( linebytes )) == NULL )
224         TVUTILOutOfMemory();
225 
226     /*  Convert & write the image data  */
227 #ifdef WRITE_PLANAR_TIFFS
228     for ( pass = 0; pass < TV_COMP_PER_PIX; pass++ ) {
229         compon = ( pass == 0 ? DoRed : pass == 1 ? DoGreen : DoBlue );
230 #else
231     for ( pass = 0; pass < 1; pass++ ) {
232         compon = DoRed | DoGreen | DoBlue;
233 #endif
234         for ( y = 0; y < img->geom.h; y++ ) {
235 
236             /*  Format scanline  */
237             TVIMGSAVFmtScanline24bpp( img, y, compon, buf );
238 
239             /*  And write it in TIFF  */
240             if ( !TIFFWriteScanline( out, buf, y, pass ) ) {
241                 fprintf( stderr, "TIFFWriteScanline() failed\n" );
242                 XBell( TVDISPLAY, 100 );
243                 TIFFClose( out );
244                 free( buf );
245                 unlink( filename );
246                 return;
247             }
248         }
249     }
250 
251     /*  All done.  Close up shop and go home  */
252     TIFFClose(out);
253     free( buf );
254 }
255 
256 
257 void TVIMGSAVDoSavePPM( char filename[], TV_IMAGE *img )
258 {
259     static char     *S_fp_buf   = NULL;
260     static char     *S_buf      = NULL;
261     static TV_INT32  S_buf_size = 0;
262 
263     char       errmsg[160];
264     FILE      *out;
265     TV_INT32   y;
266     TV_INT32   linebytes;
267 
268     if ( img->pix_geom.type != TV_PIXELTYPE_RGB ) {
269         fprintf( stderr, "Attempt to save non-RGB data as PPM\n" );
270         exit(1);
271     }
272 
273     /*  Allocate a buffer for PPM writing to encourage large block writes  */
274     if ( !S_fp_buf &&
275          ( (S_fp_buf = malloc( WRITE_BUF_SIZE )) == NULL ) )
276         TVUTILOutOfMemory();
277 
278     /*  Open output file  */
279     if ( filename ) {
280         if ( (out = fopen( filename, "wb" )) == NULL ) {
281             sprintf( errmsg, "Can't open output file '%s'", filename );
282             XUTILDialogPause( TVTOPLEVEL, "Error", errmsg, TV_DIALOG_TYPE_OK );
283             return;
284         }
285         setvbuf( out, S_fp_buf, _IOFBF, WRITE_BUF_SIZE );
286     }
287     else
288         out = stdout;
289 
290     /*  Write binary PPM header  */
291     fprintf( out, "P6\n%ld %ld\n%d\n", img->geom.w, img->geom.h,
292                                      (1 << TV_BITS_PER_COMP) - 1 );
293 
294     linebytes = img->geom.w * TV_BYTES_PER_PIX;
295 
296     if (( S_buf == NULL ) || ( S_buf_size < linebytes )) {
297         if ( (S_buf = realloc( S_buf, linebytes )) == NULL )
298             TVUTILOutOfMemory();
299         S_buf_size = MAX( S_buf_size, linebytes );
300     }
301 
302     /*  Now write the pixel data -- just a simple raw block of       */
303     /*    R G B pixel values, ordered left-to-right, top-to-bottom.  */
304     for ( y = 0; y < img->geom.h; y++ ) {
305 
306         /*  Format scanline  */
307         TVIMGSAVFmtScanline24bpp( img, y, DoRed | DoGreen | DoBlue, S_buf );
308 
309         /*  And write it in PPM  */
310         if ( fwrite( S_buf, 3, img->geom.w, out ) != img->geom.w ) {
311             fprintf( stderr, "TVIMGSAVDoSavePPM(): Error writing scanline\n" );
312             XBell( TVDISPLAY, 100 );
313             if ( filename ) {
314                 fclose( out );
315                 unlink( filename );
316             }
317             return;
318         }
319     }
320 
321     /*  All done.  Close up shop and go home  */
322     if ( filename )
323         fclose(out);
324 }
325 
326 
327 void TVIMGSAVDoSaveYUV( char filename[], TV_IMAGE *img )
328 {
329     /*  YUV isn't really a format.  It's just the raw frame chunked into   */
330     /*    a file as-is (packed/planar, whatever H/V Y/U/V sampling, etc.)  */
331 
332     static char     *S_fp_buf   = NULL;
333 
334     char       errmsg[160];
335     FILE      *out;
336     TV_UINT32  img_size;
337 
338     if ( img->pix_geom.type != TV_PIXELTYPE_YUV ) {
339         fprintf( stderr, "Attempt to save non-YUV data as YUV\n" );
340         exit(1);
341     }
342 
343     /*  Allocate a buffer for YUV writing to encourage large block writes  */
344     if ( !S_fp_buf &&
345          ( (S_fp_buf = malloc( WRITE_BUF_SIZE )) == NULL ) )
346         TVUTILOutOfMemory();
347 
348     /*  Open output file  */
349     if ( filename ) {
350         if ( (out = fopen( filename, "wb" )) == NULL ) {
351             sprintf( errmsg, "Can't open output file '%s'", filename );
352             XUTILDialogPause( TVTOPLEVEL, "Error", errmsg, TV_DIALOG_TYPE_OK );
353             return;
354         }
355         setvbuf( out, S_fp_buf, _IOFBF, WRITE_BUF_SIZE );
356     }
357     else
358         out = stdout;
359 
360     img_size = TVRAWVIDEOCalcImageSize( img );
361 
362     if ( fwrite( img->buf, img_size, 1, out ) != 1 ) {
363         fprintf( stderr, "TVIMGSAVDoSaveYUV(): Error writing YUV file\n" );
364         XBell( TVDISPLAY, 100 );
365         fclose( out );
366         unlink( filename );
367         return;
368     }
369 
370     /*  All done.  Close up shop and go home  */
371     if ( filename )
372         fclose(out);
373 }
374 
375 
376 
377 /**@BEGINFUNC**************************************************************
378 
379     Prototype  : void TVIMGSAVDoSave(
380                       char filename[],
381                       TV_STILL_FMT fmt,
382                       TV_IMAGE *img )
383 
384     Purpose    : Write an image to a file (or stdout) in the
385                  specified format.
386 
387     Programmer : 15-Jan-98  Randall Hopper
388 
389     Parameters : filename - I: filename (NULL = stdout)
390                  fmt      - I: format to write it in
391                  img      - I: image to write.
392 
393     Returns    : None.
394 
395     Globals    : None.
396 
397  **@ENDFUNC*****************************************************************/
398 
399 void TVIMGSAVDoSave( char filename[], TV_STILL_FMT fmt,
400                      TV_IMAGE *img )
401 {
402     switch ( fmt ) {
403         case TV_STILL_FMT_TIFF :
404             if ( !filename ) {
405                 fprintf( stderr, "TVIMGSAVDoSave: stdout mode not supported "
406                                  "for TIFF\n" );
407                 exit(1);
408             }
409             TVIMGSAVDoSaveTIFF( filename, img );  break;
410 
411         case TV_STILL_FMT_PPM :
412             TVIMGSAVDoSavePPM ( filename, img );  break;
413 
414         case TV_STILL_FMT_YUV :
415             TVIMGSAVDoSaveYUV ( filename, img );  break;
416 
417         default:
418             fprintf( stderr, "TVIMGSAVDoSave: Unsupported format %d\n", fmt );
419             exit(1);
420     }
421 }
422