1 /*
2  * wrbmp.c
3  *
4  * Copyright (C) 1994-1996, Thomas G. Lane.
5  * Modified 2017 by Guido Vollbeding.
6  * This file is part of the Independent JPEG Group's software.
7  * For conditions of distribution and use, see the accompanying README file.
8  *
9  * This file contains routines to write output images in Microsoft "BMP"
10  * format (MS Windows 3.x and OS/2 1.x flavors).
11  * Either 8-bit colormapped or 24-bit full-color format can be written.
12  * No compression is supported.
13  *
14  * These routines may need modification for non-Unix environments or
15  * specialized applications.  As they stand, they assume output to
16  * an ordinary stdio stream.
17  *
18  * This code contributed by James Arthur Boucher.
19  */
20 
21 #include "cdjpeg.h"		/* Common decls for cjpeg/djpeg applications */
22 
23 #ifdef BMP_SUPPORTED
24 
25 
26 /*
27  * To support 12-bit JPEG data, we'd have to scale output down to 8 bits.
28  * This is not yet implemented.
29  */
30 
31 #if BITS_IN_JSAMPLE != 8
32   Sorry, this code only copes with 8-bit JSAMPLEs. /* deliberate syntax err */
33 #endif
34 
35 /*
36  * Since BMP stores scanlines bottom-to-top, we have to invert the image
37  * from JPEG's top-to-bottom order.  To do this, we save the outgoing data
38  * in a virtual array during put_pixel_row calls, then actually emit the
39  * BMP file during finish_output.  The virtual array contains one JSAMPLE per
40  * pixel if the output is grayscale or colormapped, three if it is full color.
41  */
42 
43 /* Private version of data destination object */
44 
45 typedef struct {
46   struct djpeg_dest_struct pub;	/* public fields */
47 
48   boolean is_os2;		/* saves the OS2 format request flag */
49 
50   jvirt_sarray_ptr whole_image;	/* needed to reverse row order */
51   JDIMENSION data_width;	/* JSAMPLEs per row */
52   JDIMENSION row_width;		/* physical width of one row in the BMP file */
53   int pad_bytes;		/* number of padding bytes needed per row */
54   JDIMENSION cur_output_row;	/* next row# to write to virtual array */
55 } bmp_dest_struct;
56 
57 typedef bmp_dest_struct * bmp_dest_ptr;
58 
59 
60 /* Forward declarations */
61 LOCAL(void) write_colormap
62 	JPP((j_decompress_ptr cinfo, bmp_dest_ptr dest,
63 	     int map_colors, int map_entry_size));
64 
65 
66 /*
67  * Write some pixel data.
68  * In this module rows_supplied will always be 1.
69  */
70 
71 METHODDEF(void)
put_pixel_rows(j_decompress_ptr cinfo,djpeg_dest_ptr dinfo,JDIMENSION rows_supplied)72 put_pixel_rows (j_decompress_ptr cinfo, djpeg_dest_ptr dinfo,
73 		JDIMENSION rows_supplied)
74 /* This version is for writing 24-bit pixels */
75 {
76   bmp_dest_ptr dest = (bmp_dest_ptr) dinfo;
77   JSAMPARRAY image_ptr;
78   register JSAMPROW inptr, outptr;
79   register JDIMENSION col;
80   int pad;
81 
82   /* Access next row in virtual array */
83   image_ptr = (*cinfo->mem->access_virt_sarray)
84     ((j_common_ptr) cinfo, dest->whole_image,
85      dest->cur_output_row, (JDIMENSION) 1, TRUE);
86   dest->cur_output_row++;
87 
88   /* Transfer data.  Note destination values must be in BGR order
89    * (even though Microsoft's own documents say the opposite).
90    */
91   inptr = dest->pub.buffer[0];
92   outptr = image_ptr[0];
93   for (col = cinfo->output_width; col > 0; col--) {
94     outptr[2] = *inptr++;	/* can omit GETJSAMPLE() safely */
95     outptr[1] = *inptr++;
96     outptr[0] = *inptr++;
97     outptr += 3;
98   }
99 
100   /* Zero out the pad bytes. */
101   pad = dest->pad_bytes;
102   while (--pad >= 0)
103     *outptr++ = 0;
104 }
105 
106 METHODDEF(void)
put_gray_rows(j_decompress_ptr cinfo,djpeg_dest_ptr dinfo,JDIMENSION rows_supplied)107 put_gray_rows (j_decompress_ptr cinfo, djpeg_dest_ptr dinfo,
108 	       JDIMENSION rows_supplied)
109 /* This version is for grayscale OR quantized color output */
110 {
111   bmp_dest_ptr dest = (bmp_dest_ptr) dinfo;
112   JSAMPARRAY image_ptr;
113   register JSAMPROW inptr, outptr;
114   register JDIMENSION col;
115   int pad;
116 
117   /* Access next row in virtual array */
118   image_ptr = (*cinfo->mem->access_virt_sarray)
119     ((j_common_ptr) cinfo, dest->whole_image,
120      dest->cur_output_row, (JDIMENSION) 1, TRUE);
121   dest->cur_output_row++;
122 
123   /* Transfer data. */
124   inptr = dest->pub.buffer[0];
125   outptr = image_ptr[0];
126   for (col = cinfo->output_width; col > 0; col--) {
127     *outptr++ = *inptr++;	/* can omit GETJSAMPLE() safely */
128   }
129 
130   /* Zero out the pad bytes. */
131   pad = dest->pad_bytes;
132   while (--pad >= 0)
133     *outptr++ = 0;
134 }
135 
136 
137 /*
138  * Startup: normally writes the file header.
139  * In this module we may as well postpone everything until finish_output.
140  */
141 
142 METHODDEF(void)
start_output_bmp(j_decompress_ptr cinfo,djpeg_dest_ptr dinfo)143 start_output_bmp (j_decompress_ptr cinfo, djpeg_dest_ptr dinfo)
144 {
145   /* no work here */
146 }
147 
148 
149 /*
150  * Finish up at the end of the file.
151  *
152  * Here is where we really output the BMP file.
153  *
154  * First, routines to write the Windows and OS/2 variants of the file header.
155  */
156 
157 LOCAL(void)
write_bmp_header(j_decompress_ptr cinfo,bmp_dest_ptr dest)158 write_bmp_header (j_decompress_ptr cinfo, bmp_dest_ptr dest)
159 /* Write a Windows-style BMP file header, including colormap if needed */
160 {
161   char bmpfileheader[14];
162   char bmpinfoheader[40];
163 #define PUT_2B(array,offset,value)  \
164 	(array[offset] = (char) ((value) & 0xFF), \
165 	 array[offset+1] = (char) (((value) >> 8) & 0xFF))
166 #define PUT_4B(array,offset,value)  \
167 	(array[offset] = (char) ((value) & 0xFF), \
168 	 array[offset+1] = (char) (((value) >> 8) & 0xFF), \
169 	 array[offset+2] = (char) (((value) >> 16) & 0xFF), \
170 	 array[offset+3] = (char) (((value) >> 24) & 0xFF))
171   INT32 headersize, bfSize;
172   int bits_per_pixel, cmap_entries;
173 
174   /* Compute colormap size and total file size */
175   if (cinfo->out_color_space == JCS_RGB) {
176     if (cinfo->quantize_colors) {
177       /* Colormapped RGB */
178       bits_per_pixel = 8;
179       cmap_entries = 256;
180     } else {
181       /* Unquantized, full color RGB */
182       bits_per_pixel = 24;
183       cmap_entries = 0;
184     }
185   } else {
186     /* Grayscale output.  We need to fake a 256-entry colormap. */
187     bits_per_pixel = 8;
188     cmap_entries = 256;
189   }
190   /* File size */
191   headersize = 14 + 40 + cmap_entries * 4; /* Header and colormap */
192   bfSize = headersize + (INT32) dest->row_width * (INT32) cinfo->output_height;
193 
194   /* Set unused fields of header to 0 */
195   MEMZERO(bmpfileheader, SIZEOF(bmpfileheader));
196   MEMZERO(bmpinfoheader, SIZEOF(bmpinfoheader));
197 
198   /* Fill the file header */
199   bmpfileheader[0] = 0x42;	/* first 2 bytes are ASCII 'B', 'M' */
200   bmpfileheader[1] = 0x4D;
201   PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
202   /* we leave bfReserved1 & bfReserved2 = 0 */
203   PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */
204 
205   /* Fill the info header (Microsoft calls this a BITMAPINFOHEADER) */
206   PUT_2B(bmpinfoheader, 0, 40);	/* biSize */
207   PUT_4B(bmpinfoheader, 4, cinfo->output_width); /* biWidth */
208   PUT_4B(bmpinfoheader, 8, cinfo->output_height); /* biHeight */
209   PUT_2B(bmpinfoheader, 12, 1);	/* biPlanes - must be 1 */
210   PUT_2B(bmpinfoheader, 14, bits_per_pixel); /* biBitCount */
211   /* we leave biCompression = 0, for none */
212   /* we leave biSizeImage = 0; this is correct for uncompressed data */
213   if (cinfo->density_unit == 2) { /* if have density in dots/cm, then */
214     PUT_4B(bmpinfoheader, 24, (INT32) (cinfo->X_density*100)); /* XPels/M */
215     PUT_4B(bmpinfoheader, 28, (INT32) (cinfo->Y_density*100)); /* XPels/M */
216   }
217   PUT_2B(bmpinfoheader, 32, cmap_entries); /* biClrUsed */
218   /* we leave biClrImportant = 0 */
219 
220   if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t) 14)
221     ERREXIT(cinfo, JERR_FILE_WRITE);
222   if (JFWRITE(dest->pub.output_file, bmpinfoheader, 40) != (size_t) 40)
223     ERREXIT(cinfo, JERR_FILE_WRITE);
224 
225   if (cmap_entries > 0)
226     write_colormap(cinfo, dest, cmap_entries, 4);
227 }
228 
229 
230 LOCAL(void)
write_os2_header(j_decompress_ptr cinfo,bmp_dest_ptr dest)231 write_os2_header (j_decompress_ptr cinfo, bmp_dest_ptr dest)
232 /* Write an OS2-style BMP file header, including colormap if needed */
233 {
234   char bmpfileheader[14];
235   char bmpcoreheader[12];
236   INT32 headersize, bfSize;
237   int bits_per_pixel, cmap_entries;
238 
239   /* Compute colormap size and total file size */
240   if (cinfo->out_color_space == JCS_RGB) {
241     if (cinfo->quantize_colors) {
242       /* Colormapped RGB */
243       bits_per_pixel = 8;
244       cmap_entries = 256;
245     } else {
246       /* Unquantized, full color RGB */
247       bits_per_pixel = 24;
248       cmap_entries = 0;
249     }
250   } else {
251     /* Grayscale output.  We need to fake a 256-entry colormap. */
252     bits_per_pixel = 8;
253     cmap_entries = 256;
254   }
255   /* File size */
256   headersize = 14 + 12 + cmap_entries * 3; /* Header and colormap */
257   bfSize = headersize + (INT32) dest->row_width * (INT32) cinfo->output_height;
258 
259   /* Set unused fields of header to 0 */
260   MEMZERO(bmpfileheader, SIZEOF(bmpfileheader));
261   MEMZERO(bmpcoreheader, SIZEOF(bmpcoreheader));
262 
263   /* Fill the file header */
264   bmpfileheader[0] = 0x42;	/* first 2 bytes are ASCII 'B', 'M' */
265   bmpfileheader[1] = 0x4D;
266   PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
267   /* we leave bfReserved1 & bfReserved2 = 0 */
268   PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */
269 
270   /* Fill the info header (Microsoft calls this a BITMAPCOREHEADER) */
271   PUT_2B(bmpcoreheader, 0, 12);	/* bcSize */
272   PUT_2B(bmpcoreheader, 4, cinfo->output_width); /* bcWidth */
273   PUT_2B(bmpcoreheader, 6, cinfo->output_height); /* bcHeight */
274   PUT_2B(bmpcoreheader, 8, 1);	/* bcPlanes - must be 1 */
275   PUT_2B(bmpcoreheader, 10, bits_per_pixel); /* bcBitCount */
276 
277   if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t) 14)
278     ERREXIT(cinfo, JERR_FILE_WRITE);
279   if (JFWRITE(dest->pub.output_file, bmpcoreheader, 12) != (size_t) 12)
280     ERREXIT(cinfo, JERR_FILE_WRITE);
281 
282   if (cmap_entries > 0)
283     write_colormap(cinfo, dest, cmap_entries, 3);
284 }
285 
286 
287 /*
288  * Write the colormap.
289  * Windows uses BGR0 map entries; OS/2 uses BGR entries.
290  */
291 
292 LOCAL(void)
write_colormap(j_decompress_ptr cinfo,bmp_dest_ptr dest,int map_colors,int map_entry_size)293 write_colormap (j_decompress_ptr cinfo, bmp_dest_ptr dest,
294 		int map_colors, int map_entry_size)
295 {
296   JSAMPARRAY colormap = cinfo->colormap;
297   int num_colors = cinfo->actual_number_of_colors;
298   FILE * outfile = dest->pub.output_file;
299   int i;
300 
301   if (colormap != NULL) {
302     if (cinfo->out_color_components == 3) {
303       /* Normal case with RGB colormap */
304       for (i = 0; i < num_colors; i++) {
305 	putc(GETJSAMPLE(colormap[2][i]), outfile);
306 	putc(GETJSAMPLE(colormap[1][i]), outfile);
307 	putc(GETJSAMPLE(colormap[0][i]), outfile);
308 	if (map_entry_size == 4)
309 	  putc(0, outfile);
310       }
311     } else {
312       /* Grayscale colormap (only happens with grayscale quantization) */
313       for (i = 0; i < num_colors; i++) {
314 	putc(GETJSAMPLE(colormap[0][i]), outfile);
315 	putc(GETJSAMPLE(colormap[0][i]), outfile);
316 	putc(GETJSAMPLE(colormap[0][i]), outfile);
317 	if (map_entry_size == 4)
318 	  putc(0, outfile);
319       }
320     }
321   } else {
322     /* If no colormap, must be grayscale data.  Generate a linear "map". */
323     for (i = 0; i < 256; i++) {
324       putc(i, outfile);
325       putc(i, outfile);
326       putc(i, outfile);
327       if (map_entry_size == 4)
328 	putc(0, outfile);
329     }
330   }
331   /* Pad colormap with zeros to ensure specified number of colormap entries */
332   if (i > map_colors)
333     ERREXIT1(cinfo, JERR_TOO_MANY_COLORS, i);
334   for (; i < map_colors; i++) {
335     putc(0, outfile);
336     putc(0, outfile);
337     putc(0, outfile);
338     if (map_entry_size == 4)
339       putc(0, outfile);
340   }
341 }
342 
343 
344 METHODDEF(void)
finish_output_bmp(j_decompress_ptr cinfo,djpeg_dest_ptr dinfo)345 finish_output_bmp (j_decompress_ptr cinfo, djpeg_dest_ptr dinfo)
346 {
347   bmp_dest_ptr dest = (bmp_dest_ptr) dinfo;
348   register FILE * outfile = dest->pub.output_file;
349   JSAMPARRAY image_ptr;
350   register JSAMPROW data_ptr;
351   JDIMENSION row;
352   register JDIMENSION col;
353   cd_progress_ptr progress = (cd_progress_ptr) cinfo->progress;
354 
355   /* Write the header and colormap */
356   if (dest->is_os2)
357     write_os2_header(cinfo, dest);
358   else
359     write_bmp_header(cinfo, dest);
360 
361   /* Write the file body from our virtual array */
362   for (row = cinfo->output_height; row > 0; row--) {
363     if (progress != NULL) {
364       progress->pub.pass_counter = (long) (cinfo->output_height - row);
365       progress->pub.pass_limit = (long) cinfo->output_height;
366       (*progress->pub.progress_monitor) ((j_common_ptr) cinfo);
367     }
368     image_ptr = (*cinfo->mem->access_virt_sarray)
369       ((j_common_ptr) cinfo, dest->whole_image, row-1, (JDIMENSION) 1, FALSE);
370     data_ptr = image_ptr[0];
371     for (col = dest->row_width; col > 0; col--) {
372       putc(GETJSAMPLE(*data_ptr), outfile);
373       data_ptr++;
374     }
375   }
376   if (progress != NULL)
377     progress->completed_extra_passes++;
378 
379   /* Make sure we wrote the output file OK */
380   JFFLUSH(outfile);
381   if (JFERROR(outfile))
382     ERREXIT(cinfo, JERR_FILE_WRITE);
383 }
384 
385 
386 /*
387  * The module selection routine for BMP format output.
388  */
389 
390 GLOBAL(djpeg_dest_ptr)
jinit_write_bmp(j_decompress_ptr cinfo,boolean is_os2)391 jinit_write_bmp (j_decompress_ptr cinfo, boolean is_os2)
392 {
393   bmp_dest_ptr dest;
394   JDIMENSION row_width;
395 
396   /* Create module interface object, fill in method pointers */
397   dest = (bmp_dest_ptr)
398       (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
399 				  SIZEOF(bmp_dest_struct));
400   dest->pub.start_output = start_output_bmp;
401   dest->pub.finish_output = finish_output_bmp;
402   dest->is_os2 = is_os2;
403 
404   if (cinfo->out_color_space == JCS_GRAYSCALE) {
405     dest->pub.put_pixel_rows = put_gray_rows;
406   } else if (cinfo->out_color_space == JCS_RGB) {
407     if (cinfo->quantize_colors)
408       dest->pub.put_pixel_rows = put_gray_rows;
409     else
410       dest->pub.put_pixel_rows = put_pixel_rows;
411   } else {
412     ERREXIT(cinfo, JERR_BMP_COLORSPACE);
413   }
414 
415   /* Calculate output image dimensions so we can allocate space */
416   jpeg_calc_output_dimensions(cinfo);
417 
418   /* Determine width of rows in the BMP file (padded to 4-byte boundary). */
419   row_width = cinfo->output_width * cinfo->output_components;
420   dest->data_width = row_width;
421   while ((row_width & 3) != 0) row_width++;
422   dest->row_width = row_width;
423   dest->pad_bytes = (int) (row_width - dest->data_width);
424 
425   /* Allocate space for inversion array, prepare for write pass */
426   dest->whole_image = (*cinfo->mem->request_virt_sarray)
427     ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE,
428      row_width, cinfo->output_height, (JDIMENSION) 1);
429   dest->cur_output_row = 0;
430   if (cinfo->progress != NULL) {
431     cd_progress_ptr progress = (cd_progress_ptr) cinfo->progress;
432     progress->total_extra_passes++; /* count file input as separate pass */
433   }
434 
435   /* Create decompressor output buffer. */
436   dest->pub.buffer = (*cinfo->mem->alloc_sarray)
437     ((j_common_ptr) cinfo, JPOOL_IMAGE, row_width, (JDIMENSION) 1);
438   dest->pub.buffer_height = 1;
439 
440   return &dest->pub;
441 }
442 
443 #endif /* BMP_SUPPORTED */
444