xref: /reactos/dll/3rdparty/libjpeg/wrbmp.c (revision 1d574191)
1 /*
2  * wrbmp.c
3  *
4  * Copyright (C) 1994-1996, Thomas G. Lane.
5  * Modified 2017-2019 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   register JSAMPROW inptr, outptr;
78   register JDIMENSION col;
79   int pad;
80 
81   /* Access next row in virtual array */
82   outptr = * (*cinfo->mem->access_virt_sarray) ((j_common_ptr) cinfo,
83     dest->whole_image, dest->cur_output_row, (JDIMENSION) 1, TRUE);
84   dest->cur_output_row++;
85 
86   /* Transfer data.  Note destination values must be in BGR order
87    * (even though Microsoft's own documents say the opposite).
88    */
89   inptr = dest->pub.buffer[0];
90   for (col = cinfo->output_width; col > 0; col--) {
91     outptr[2] = *inptr++;	/* can omit GETJSAMPLE() safely */
92     outptr[1] = *inptr++;
93     outptr[0] = *inptr++;
94     outptr += 3;
95   }
96 
97   /* Zero out the pad bytes. */
98   pad = dest->pad_bytes;
99   while (--pad >= 0)
100     *outptr++ = 0;
101 }
102 
103 METHODDEF(void)
put_gray_rows(j_decompress_ptr cinfo,djpeg_dest_ptr dinfo,JDIMENSION rows_supplied)104 put_gray_rows (j_decompress_ptr cinfo, djpeg_dest_ptr dinfo,
105 	       JDIMENSION rows_supplied)
106 /* This version is for grayscale OR quantized color output */
107 {
108   bmp_dest_ptr dest = (bmp_dest_ptr) dinfo;
109   register JSAMPROW inptr, outptr;
110   register JDIMENSION col;
111   int pad;
112 
113   /* Access next row in virtual array */
114   outptr = * (*cinfo->mem->access_virt_sarray) ((j_common_ptr) cinfo,
115     dest->whole_image, dest->cur_output_row, (JDIMENSION) 1, TRUE);
116   dest->cur_output_row++;
117 
118   /* Transfer data. */
119   inptr = dest->pub.buffer[0];
120   for (col = cinfo->output_width; col > 0; col--) {
121     *outptr++ = *inptr++;	/* can omit GETJSAMPLE() safely */
122   }
123 
124   /* Zero out the pad bytes. */
125   pad = dest->pad_bytes;
126   while (--pad >= 0)
127     *outptr++ = 0;
128 }
129 
130 
131 /*
132  * Startup: normally writes the file header.
133  * In this module we may as well postpone everything until finish_output.
134  */
135 
136 METHODDEF(void)
start_output_bmp(j_decompress_ptr cinfo,djpeg_dest_ptr dinfo)137 start_output_bmp (j_decompress_ptr cinfo, djpeg_dest_ptr dinfo)
138 {
139   /* no work here */
140 }
141 
142 
143 /*
144  * Finish up at the end of the file.
145  *
146  * Here is where we really output the BMP file.
147  *
148  * First, routines to write the Windows and OS/2 variants of the file header.
149  */
150 
151 LOCAL(void)
write_bmp_header(j_decompress_ptr cinfo,bmp_dest_ptr dest)152 write_bmp_header (j_decompress_ptr cinfo, bmp_dest_ptr dest)
153 /* Write a Windows-style BMP file header, including colormap if needed */
154 {
155   char bmpfileheader[14];
156   char bmpinfoheader[40];
157 #define PUT_2B(array, offset, value)  \
158 	(array[offset] = (char) ((value) & 0xFF), \
159 	 array[offset+1] = (char) (((value) >> 8) & 0xFF))
160 #define PUT_4B(array, offset, value)  \
161 	(array[offset] = (char) ((value) & 0xFF), \
162 	 array[offset+1] = (char) (((value) >> 8) & 0xFF), \
163 	 array[offset+2] = (char) (((value) >> 16) & 0xFF), \
164 	 array[offset+3] = (char) (((value) >> 24) & 0xFF))
165   INT32 headersize, bfSize;
166   int bits_per_pixel, cmap_entries;
167 
168   /* Compute colormap size and total file size */
169   if (cinfo->out_color_space == JCS_RGB) {
170     if (cinfo->quantize_colors) {
171       /* Colormapped RGB */
172       bits_per_pixel = 8;
173       cmap_entries = 256;
174     } else {
175       /* Unquantized, full color RGB */
176       bits_per_pixel = 24;
177       cmap_entries = 0;
178     }
179   } else {
180     /* Grayscale output.  We need to fake a 256-entry colormap. */
181     bits_per_pixel = 8;
182     cmap_entries = 256;
183   }
184   /* File size */
185   headersize = 14 + 40 + cmap_entries * 4; /* Header and colormap */
186   bfSize = headersize + (INT32) dest->row_width * (INT32) cinfo->output_height;
187 
188   /* Set unused fields of header to 0 */
189   MEMZERO(bmpfileheader, SIZEOF(bmpfileheader));
190   MEMZERO(bmpinfoheader, SIZEOF(bmpinfoheader));
191 
192   /* Fill the file header */
193   bmpfileheader[0] = 0x42;	/* first 2 bytes are ASCII 'B', 'M' */
194   bmpfileheader[1] = 0x4D;
195   PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
196   /* we leave bfReserved1 & bfReserved2 = 0 */
197   PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */
198 
199   /* Fill the info header (Microsoft calls this a BITMAPINFOHEADER) */
200   PUT_2B(bmpinfoheader, 0, 40);	/* biSize */
201   PUT_4B(bmpinfoheader, 4, cinfo->output_width); /* biWidth */
202   PUT_4B(bmpinfoheader, 8, cinfo->output_height); /* biHeight */
203   PUT_2B(bmpinfoheader, 12, 1);	/* biPlanes - must be 1 */
204   PUT_2B(bmpinfoheader, 14, bits_per_pixel); /* biBitCount */
205   /* we leave biCompression = 0, for none */
206   /* we leave biSizeImage = 0; this is correct for uncompressed data */
207   if (cinfo->density_unit == 2) { /* if have density in dots/cm, then */
208     PUT_4B(bmpinfoheader, 24, (INT32) (cinfo->X_density*100)); /* XPels/M */
209     PUT_4B(bmpinfoheader, 28, (INT32) (cinfo->Y_density*100)); /* XPels/M */
210   }
211   PUT_2B(bmpinfoheader, 32, cmap_entries); /* biClrUsed */
212   /* we leave biClrImportant = 0 */
213 
214   if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t) 14)
215     ERREXIT(cinfo, JERR_FILE_WRITE);
216   if (JFWRITE(dest->pub.output_file, bmpinfoheader, 40) != (size_t) 40)
217     ERREXIT(cinfo, JERR_FILE_WRITE);
218 
219   if (cmap_entries > 0)
220     write_colormap(cinfo, dest, cmap_entries, 4);
221 }
222 
223 
224 LOCAL(void)
write_os2_header(j_decompress_ptr cinfo,bmp_dest_ptr dest)225 write_os2_header (j_decompress_ptr cinfo, bmp_dest_ptr dest)
226 /* Write an OS2-style BMP file header, including colormap if needed */
227 {
228   char bmpfileheader[14];
229   char bmpcoreheader[12];
230   INT32 headersize, bfSize;
231   int bits_per_pixel, cmap_entries;
232 
233   /* Compute colormap size and total file size */
234   if (cinfo->out_color_space == JCS_RGB) {
235     if (cinfo->quantize_colors) {
236       /* Colormapped RGB */
237       bits_per_pixel = 8;
238       cmap_entries = 256;
239     } else {
240       /* Unquantized, full color RGB */
241       bits_per_pixel = 24;
242       cmap_entries = 0;
243     }
244   } else {
245     /* Grayscale output.  We need to fake a 256-entry colormap. */
246     bits_per_pixel = 8;
247     cmap_entries = 256;
248   }
249   /* File size */
250   headersize = 14 + 12 + cmap_entries * 3; /* Header and colormap */
251   bfSize = headersize + (INT32) dest->row_width * (INT32) cinfo->output_height;
252 
253   /* Set unused fields of header to 0 */
254   MEMZERO(bmpfileheader, SIZEOF(bmpfileheader));
255   MEMZERO(bmpcoreheader, SIZEOF(bmpcoreheader));
256 
257   /* Fill the file header */
258   bmpfileheader[0] = 0x42;	/* first 2 bytes are ASCII 'B', 'M' */
259   bmpfileheader[1] = 0x4D;
260   PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
261   /* we leave bfReserved1 & bfReserved2 = 0 */
262   PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */
263 
264   /* Fill the info header (Microsoft calls this a BITMAPCOREHEADER) */
265   PUT_2B(bmpcoreheader, 0, 12);	/* bcSize */
266   PUT_2B(bmpcoreheader, 4, cinfo->output_width); /* bcWidth */
267   PUT_2B(bmpcoreheader, 6, cinfo->output_height); /* bcHeight */
268   PUT_2B(bmpcoreheader, 8, 1);	/* bcPlanes - must be 1 */
269   PUT_2B(bmpcoreheader, 10, bits_per_pixel); /* bcBitCount */
270 
271   if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t) 14)
272     ERREXIT(cinfo, JERR_FILE_WRITE);
273   if (JFWRITE(dest->pub.output_file, bmpcoreheader, 12) != (size_t) 12)
274     ERREXIT(cinfo, JERR_FILE_WRITE);
275 
276   if (cmap_entries > 0)
277     write_colormap(cinfo, dest, cmap_entries, 3);
278 }
279 
280 
281 /*
282  * Write the colormap.
283  * Windows uses BGR0 map entries; OS/2 uses BGR entries.
284  */
285 
286 LOCAL(void)
write_colormap(j_decompress_ptr cinfo,bmp_dest_ptr dest,int map_colors,int map_entry_size)287 write_colormap (j_decompress_ptr cinfo, bmp_dest_ptr dest,
288 		int map_colors, int map_entry_size)
289 {
290   JSAMPARRAY colormap = cinfo->colormap;
291   int num_colors = cinfo->actual_number_of_colors;
292   FILE * outfile = dest->pub.output_file;
293   int i;
294 
295   if (colormap != NULL) {
296     if (cinfo->out_color_components == 3) {
297       /* Normal case with RGB colormap */
298       for (i = 0; i < num_colors; i++) {
299 	putc(GETJSAMPLE(colormap[2][i]), outfile);
300 	putc(GETJSAMPLE(colormap[1][i]), outfile);
301 	putc(GETJSAMPLE(colormap[0][i]), outfile);
302 	if (map_entry_size == 4)
303 	  putc(0, outfile);
304       }
305     } else {
306       /* Grayscale colormap (only happens with grayscale quantization) */
307       for (i = 0; i < num_colors; i++) {
308 	putc(GETJSAMPLE(colormap[0][i]), outfile);
309 	putc(GETJSAMPLE(colormap[0][i]), outfile);
310 	putc(GETJSAMPLE(colormap[0][i]), outfile);
311 	if (map_entry_size == 4)
312 	  putc(0, outfile);
313       }
314     }
315   } else {
316     /* If no colormap, must be grayscale data.  Generate a linear "map". */
317     for (i = 0; i < 256; i++) {
318       putc(i, outfile);
319       putc(i, outfile);
320       putc(i, outfile);
321       if (map_entry_size == 4)
322 	putc(0, outfile);
323     }
324   }
325   /* Pad colormap to ensure specified number of colormap entries */
326   if (i > map_colors)
327     ERREXIT1(cinfo, JERR_TOO_MANY_COLORS, i);
328   for (; i < map_colors; i++) {
329     putc(CENTERJSAMPLE, outfile);
330     putc(CENTERJSAMPLE, outfile);
331     putc(CENTERJSAMPLE, outfile);
332     if (map_entry_size == 4)
333       putc(0, outfile);
334   }
335 }
336 
337 
338 METHODDEF(void)
finish_output_bmp(j_decompress_ptr cinfo,djpeg_dest_ptr dinfo)339 finish_output_bmp (j_decompress_ptr cinfo, djpeg_dest_ptr dinfo)
340 {
341   bmp_dest_ptr dest = (bmp_dest_ptr) dinfo;
342   register FILE * outfile = dest->pub.output_file;
343   register JSAMPROW data_ptr;
344   JDIMENSION row;
345   register JDIMENSION col;
346   cd_progress_ptr progress = (cd_progress_ptr) cinfo->progress;
347 
348   /* Write the header and colormap */
349   if (dest->is_os2)
350     write_os2_header(cinfo, dest);
351   else
352     write_bmp_header(cinfo, dest);
353 
354   /* Write the file body from our virtual array */
355   for (row = cinfo->output_height; row > 0; row--) {
356     if (progress != NULL) {
357       progress->pub.pass_counter = (long) (cinfo->output_height - row);
358       progress->pub.pass_limit = (long) cinfo->output_height;
359       (*progress->pub.progress_monitor) ((j_common_ptr) cinfo);
360     }
361     data_ptr = * (*cinfo->mem->access_virt_sarray) ((j_common_ptr) cinfo,
362       dest->whole_image, row - 1, (JDIMENSION) 1, FALSE);
363     for (col = dest->row_width; col > 0; col--) {
364       putc(GETJSAMPLE(*data_ptr), outfile);
365       data_ptr++;
366     }
367   }
368   if (progress != NULL)
369     progress->completed_extra_passes++;
370 
371   /* Make sure we wrote the output file OK */
372   JFFLUSH(outfile);
373   if (JFERROR(outfile))
374     ERREXIT(cinfo, JERR_FILE_WRITE);
375 }
376 
377 
378 /*
379  * The module selection routine for BMP format output.
380  */
381 
382 GLOBAL(djpeg_dest_ptr)
jinit_write_bmp(j_decompress_ptr cinfo,boolean is_os2)383 jinit_write_bmp (j_decompress_ptr cinfo, boolean is_os2)
384 {
385   bmp_dest_ptr dest;
386   JDIMENSION row_width;
387 
388   /* Create module interface object, fill in method pointers */
389   dest = (bmp_dest_ptr) (*cinfo->mem->alloc_small)
390     ((j_common_ptr) cinfo, JPOOL_IMAGE, SIZEOF(bmp_dest_struct));
391   dest->pub.start_output = start_output_bmp;
392   dest->pub.finish_output = finish_output_bmp;
393   dest->is_os2 = is_os2;
394 
395   switch (cinfo->out_color_space) {
396   case JCS_GRAYSCALE:
397     dest->pub.put_pixel_rows = put_gray_rows;
398     break;
399   case JCS_RGB:
400     if (cinfo->quantize_colors)
401       dest->pub.put_pixel_rows = put_gray_rows;
402     else
403       dest->pub.put_pixel_rows = put_pixel_rows;
404     break;
405   default:
406     ERREXIT(cinfo, JERR_BMP_COLORSPACE);
407   }
408 
409   /* Calculate output image dimensions so we can allocate space */
410   jpeg_calc_output_dimensions(cinfo);
411 
412   /* Determine width of rows in the BMP file (padded to 4-byte boundary). */
413   row_width = cinfo->output_width * cinfo->output_components;
414   dest->data_width = row_width;
415   while ((row_width & 3) != 0) row_width++;
416   dest->row_width = row_width;
417   dest->pad_bytes = (int) (row_width - dest->data_width);
418 
419   /* Allocate space for inversion array, prepare for write pass */
420   dest->whole_image = (*cinfo->mem->request_virt_sarray)
421     ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE,
422      row_width, cinfo->output_height, (JDIMENSION) 1);
423   dest->cur_output_row = 0;
424   if (cinfo->progress != NULL) {
425     cd_progress_ptr progress = (cd_progress_ptr) cinfo->progress;
426     progress->total_extra_passes++; /* count file input as separate pass */
427   }
428 
429   /* Create decompressor output buffer. */
430   dest->pub.buffer = (*cinfo->mem->alloc_sarray)
431     ((j_common_ptr) cinfo, JPOOL_IMAGE, row_width, (JDIMENSION) 1);
432   dest->pub.buffer_height = 1;
433 
434   return &dest->pub;
435 }
436 
437 #endif /* BMP_SUPPORTED */
438