ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/src/px/ra_bmp.c
Revision: 2.18
Committed: Sun Sep 14 04:39:53 2025 UTC (4 weeks, 2 days ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.17: +134 -37 lines
Log Message:
feat(ra_bmp): Added metadata handling and i/o

File Contents

# User Rev Content
1 greg 2.1 #ifndef lint
2 greg 2.18 static const char RCSid[] = "$Id: ra_bmp.c,v 2.17 2025/06/07 05:09:46 greg Exp $";
3 greg 2.1 #endif
4     /*
5     * program to convert between RADIANCE and Windows BMP file
6     */
7    
8 greg 2.9 #include <math.h>
9 schorsch 2.3
10 greg 2.13 #include "rtio.h"
11 greg 2.1 #include "platform.h"
12     #include "color.h"
13 greg 2.4 #include "tonemap.h"
14 greg 2.1 #include "resolu.h"
15     #include "bmpfile.h"
16    
17 greg 2.9 int bradj = 0; /* brightness adjustment */
18 greg 2.1
19 greg 2.9 double gamcor = 2.2; /* gamma correction value */
20 greg 2.1
21 greg 2.18 char *info = ""; /* information header string */
22     int infolen = 0; /* information header length */
23    
24     extern void quiterr(const char *err);
25     extern void addBMPcspace(RGBPRIMP pp, double gamma);
26     extern void tmap2bmp(char *fnin, char *fnout, char *expec,
27 greg 2.4 RGBPRIMP monpri, double gamval);
28 greg 2.18 extern void rad2bmp(FILE *rfp, BMPWriter *bwr, int inv, RGBPRIMP monpri);
29     extern void bmp2rad(BMPReader *brd, FILE *rfp, int inv);
30     extern void info2rad(char *infs, int len, FILE *fout);
31     extern char *growInfo(int n);
32     extern gethfunc headline;
33 greg 2.1
34 greg 2.18 #define add2info(s) strcpy(growInfo(strlen(s)), s)
35     #define clearInfo() growInfo(-infolen)
36 greg 2.9
37 greg 2.18 RGBPRIMP rgbinp = stdprims; /* RGB input primitives */
38     RGBPRIMS myinprims; /* custom primitives holder */
39 greg 2.9
40 greg 2.1
41     int
42     main(int argc, char *argv[])
43     {
44 greg 2.4 char *inpfile=NULL, *outfile=NULL;
45     char *expec = NULL;
46     int reverse = 0;
47     RGBPRIMP rgbp = stdprims;
48     RGBPRIMS myprims;
49     RESOLU rs;
50     int i;
51 greg 2.1
52 greg 2.16 fixargv0(argv[0]); /* assigns progname */
53 greg 2.1
54     for (i = 1; i < argc; i++)
55 greg 2.6 if (argv[i][0] == '-' && argv[i][1])
56 greg 2.1 switch (argv[i][1]) {
57     case 'b':
58 greg 2.4 rgbp = NULL;
59 greg 2.1 break;
60     case 'g':
61     gamcor = atof(argv[++i]);
62     break;
63     case 'e':
64     if (argv[i+1][0] != '+' && argv[i+1][0] != '-')
65 greg 2.4 expec = argv[++i];
66     else
67     bradj = atoi(argv[++i]);
68     break;
69     case 'p':
70     if (argc-i < 9)
71 greg 2.1 goto userr;
72 greg 2.4 myprims[RED][CIEX] = atof(argv[++i]);
73     myprims[RED][CIEY] = atof(argv[++i]);
74     myprims[GRN][CIEX] = atof(argv[++i]);
75     myprims[GRN][CIEY] = atof(argv[++i]);
76     myprims[BLU][CIEX] = atof(argv[++i]);
77     myprims[BLU][CIEY] = atof(argv[++i]);
78     myprims[WHT][CIEX] = atof(argv[++i]);
79     myprims[WHT][CIEY] = atof(argv[++i]);
80     if (rgbp == stdprims)
81     rgbp = myprims;
82 greg 2.1 break;
83     case 'r':
84     reverse = !reverse;
85     break;
86     default:
87     goto userr;
88     }
89     else
90     break;
91    
92     if (i < argc-2)
93     goto userr;
94    
95     SET_FILE_BINARY(stdin);
96     SET_FILE_BINARY(stdout);
97     SET_DEFAULT_BINARY();
98    
99     if (i <= argc-1 && strcmp(argv[i], "-"))
100     inpfile = argv[i];
101    
102     if (i == argc-2 && strcmp(argv[i+1], "-"))
103     outfile = argv[i+1];
104 greg 2.18
105     if (expec != NULL) { /* check for tone-mapping */
106 greg 2.4 if (reverse)
107     goto userr;
108     tmap2bmp(inpfile, outfile, expec, rgbp, gamcor);
109     return(0);
110     }
111 greg 2.1 if (reverse) {
112     BMPReader *rdr;
113     /* open BMP file or stream */
114     if (inpfile != NULL)
115     rdr = BMPopenInputFile(inpfile);
116     else
117     rdr = BMPopenInputStream(stdin);
118    
119     if (rdr == NULL) {
120     fprintf(stderr, "%s: cannot open or recognize BMP\n",
121     inpfile != NULL ? inpfile : "<stdin>");
122     exit(1);
123     }
124     /* open Radiance output */
125     if (outfile != NULL && freopen(outfile, "w", stdout) == NULL) {
126     fprintf(stderr, "%s: cannot open for output\n",
127     outfile);
128     exit(1);
129     }
130     /* put Radiance header */
131     newheader("RADIANCE", stdout);
132 greg 2.18 info2rad(BMPinfo(rdr->hdr), rdr->hdr->infoSiz, stdout);
133 greg 2.1 printargs(i, argv, stdout);
134     fputformat(COLRFMT, stdout);
135     putchar('\n');
136     rs.xr = rdr->hdr->width;
137     rs.yr = rdr->hdr->height;
138     rs.rt = YMAJOR;
139 greg 2.2 /* write scans downward if we can */
140 greg 2.1 if (rdr->hdr->yIsDown || inpfile != NULL)
141     rs.rt |= YDECR;
142     fputsresolu(&rs, stdout);
143 greg 2.18 /* set up conversion */
144     setcolrgam(gamcor);
145 greg 2.1 /* convert file */
146 greg 2.18 bmp2rad(rdr, stdout, !rdr->hdr->yIsDown & (inpfile!=NULL));
147 greg 2.1 /* flush output */
148 greg 2.2 BMPcloseInput(rdr);
149 greg 2.1 if (fflush(stdout) < 0)
150     quiterr("error writing Radiance output");
151     } else {
152     BMPHeader *hdr;
153     BMPWriter *wtr;
154     /* open Radiance input */
155     if (inpfile != NULL && freopen(inpfile, "r", stdin) == NULL) {
156     fprintf(stderr, "%s: cannot open input file\n",
157     inpfile);
158     exit(1);
159     }
160 greg 2.18 /* get/save header info. */
161 greg 2.9 if (getheader(stdin, headline, NULL) < 0 ||
162 greg 2.18 !fgetsresolu(&rs, stdin))
163 greg 2.1 quiterr("bad Radiance picture format");
164 greg 2.18 /* record color space */
165     addBMPcspace(rgbp, gamcor);
166     /* open output/write BMP header */
167 greg 2.4 if (rgbp == NULL) {
168     hdr = BMPmappedHeader(scanlen(&rs),
169 greg 2.18 numscans(&rs), infolen+1, 256);
170 greg 2.10 /*
171 greg 2.2 if (outfile != NULL)
172     hdr->compr = BI_RLE8;
173 greg 2.10 */
174 greg 2.2 } else
175 greg 2.4 hdr = BMPtruecolorHeader(scanlen(&rs),
176 greg 2.18 numscans(&rs), infolen+1);
177 greg 2.1 if (hdr == NULL)
178 greg 2.18 quiterr("cannot create BMP output");
179     /* copy info to BMP header */
180     strcpy(BMPinfo(hdr), info);
181     clearInfo();
182 greg 2.2 /* set up output direction */
183 greg 2.7 hdr->yIsDown = ((outfile == NULL) | (hdr->compr == BI_RLE8));
184 greg 2.1 /* open BMP output */
185     if (outfile != NULL)
186     wtr = BMPopenOutputFile(outfile, hdr);
187     else
188     wtr = BMPopenOutputStream(stdout, hdr);
189 greg 2.2 if (wtr == NULL)
190     quiterr("cannot allocate writer structure");
191 greg 2.18 /* set up conversion */
192     setcolrgam(gamcor);
193 greg 2.1 /* convert file */
194 greg 2.9 rad2bmp(stdin, wtr, !hdr->yIsDown, rgbp);
195 greg 2.1 /* flush output */
196     if (fflush((FILE *)wtr->c_data) < 0)
197     quiterr("error writing BMP output");
198 greg 2.2 BMPcloseOutput(wtr);
199 greg 2.1 }
200 greg 2.4 return(0); /* success */
201 greg 2.1 userr:
202     fprintf(stderr,
203 greg 2.4 "Usage: %s [-b][-g gamma][-e spec][-p xr yr xg yg xb yb xw yw] [input|- [output]]\n",
204 greg 2.1 progname);
205 greg 2.4 fprintf(stderr,
206     " or: %s -r [-g gamma][-e +/-stops] [input|- [output]]\n",
207     progname);
208     return(1);
209 greg 2.1 }
210    
211     /* print message and exit */
212 greg 2.18 void
213 greg 2.1 quiterr(const char *err)
214     {
215     if (err != NULL) {
216     fprintf(stderr, "%s: %s\n", progname, err);
217     exit(1);
218     }
219     exit(0);
220     }
221    
222 greg 2.18 /* grow (or shrink) saved info header string */
223     char *
224     growInfo(int n)
225     {
226     char *ns = NULL;
227    
228     if (infolen + n <= 0) {
229     if (info) free(info);
230     info = "";
231     infolen = 0;
232     return(NULL);
233     }
234     if (infolen)
235     info = (char *)realloc(info, infolen+n+1);
236     else
237     info = (char *)malloc(n+1);
238    
239     if (info == NULL)
240     quiterr("out of memory in growInfo()");
241    
242     if (n > 0) memset(ns = info+infolen, 0, n+1);
243    
244     infolen += n;
245     return(ns);
246     }
247    
248 greg 2.9 /* process header line (don't echo) */
249 greg 2.18 int
250 greg 2.9 headline(char *s, void *p)
251     {
252 greg 2.12 char fmt[MAXFMTLEN];
253 greg 2.9
254 greg 2.18 if (isheadid(s)) /* skip header magic ID */
255     return(0);
256 greg 2.9 if (formatval(fmt, s)) { /* check if format string */
257     if (!strcmp(fmt,COLRFMT))
258     return(0);
259     if (!strcmp(fmt,CIEFMT)) {
260     rgbinp = TM_XYZPRIM;
261     return(0);
262     }
263 greg 2.15 if (!strcmp(fmt,SPECFMT))
264     return(0);
265 greg 2.9 return(-1);
266     }
267     if (isprims(s)) { /* get input primaries */
268     primsval(myinprims, s);
269     rgbinp = myinprims;
270     return(0);
271     }
272 greg 2.18 if (isexpos(s))
273     return(0); /* ignore this on input */
274     if (!strncmp(s, "GAMMA=", 6))
275     return(0); /* should not be here! */
276 greg 2.15 if (isncomp(s)) {
277     NCSAMP = ncompval(s);
278     return(0);
279     }
280     if (iswlsplit(s)) {
281     wlsplitval(WLPART, s);
282     return(0);
283     }
284 greg 2.18 add2info(s); /* else save info string */
285     return(1);
286 greg 2.9 }
287    
288 greg 2.18 /* add BMP output color space to info string */
289     void
290     addBMPcspace(RGBPRIMP pp, double gamma)
291     {
292     char ibuf[128];
293    
294     if (pp != NULL) {
295     sprintf(ibuf,
296     "%s %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f\n",
297     PRIMARYSTR,
298     pp[RED][CIEX],pp[RED][CIEY],
299     pp[GRN][CIEX],pp[GRN][CIEY],
300     pp[BLU][CIEX],pp[BLU][CIEY],
301     pp[WHT][CIEX],pp[WHT][CIEY]);
302     add2info(ibuf);
303     }
304     sprintf(ibuf, "GAMMA=%.2f\n", gamma);
305     add2info(ibuf);
306     }
307    
308     /* write out Radiance header from BMP info string */
309     void
310     info2rad(char *infs, int len, FILE *fout)
311     {
312     char *cp;
313     /* must fit metadata profile */
314     if (len < 3 || infs[0] == '\n' ||
315     infs[--len] != '\0' || infs[len-1] != '\n')
316     return; /* not what we expected */
317     if (strlen(infs) < len || strstr(infs, "\n\n") != NULL)
318     return; /* also not cool */
319     /* check for gamma */
320     if ((cp = strstr(infs, "GAMMA=")) != NULL) {
321     /* copy what came before */
322     fwrite(infs, cp-infs, 1, fout);
323     cp += 6;
324     gamcor = atof(cp); /* record setting */
325     while (*cp++ != '\n')
326     ;
327     len -= cp - infs;
328     infs = cp; /* & elide from output */
329     }
330     fputs(infs, fout); /* copy the remainder */
331     }
332 greg 2.9
333 greg 2.1 /* convert Radiance picture to BMP */
334 greg 2.18 void
335 greg 2.9 rad2bmp(FILE *rfp, BMPWriter *bwr, int inv, RGBPRIMP monpri)
336 greg 2.1 {
337 greg 2.9 int usexfm = 0;
338     COLORMAT xfm;
339 greg 2.1 COLR *scanin;
340 greg 2.9 COLOR cval;
341 greg 2.1 int y, yend, ystp;
342     int x;
343     /* allocate scanline */
344     scanin = (COLR *)malloc(bwr->hdr->width*sizeof(COLR));
345     if (scanin == NULL)
346     quiterr("out of memory in rad2bmp");
347 greg 2.9 /* set up color conversion */
348 greg 2.18 usexfm = (monpri != NULL) ? (rgbinp != monpri) :
349     ((rgbinp != TM_XYZPRIM) & (rgbinp != stdprims));
350 greg 2.9 if (usexfm) {
351 greg 2.14 RGBPRIMP destpri = monpri != NULL ? monpri : stdprims;
352     double expcomp = pow(2.0, (double)bradj);
353 greg 2.9 if (rgbinp == TM_XYZPRIM)
354 greg 2.14 compxyz2rgbWBmat(xfm, destpri);
355 greg 2.9 else
356 greg 2.14 comprgb2rgbWBmat(xfm, rgbinp, destpri);
357 greg 2.9 for (y = 0; y < 3; y++)
358     for (x = 0; x < 3; x++)
359     xfm[y][x] *= expcomp;
360     }
361 greg 2.1 /* convert image */
362     if (inv) {
363     y = bwr->hdr->height - 1;
364     ystp = -1; yend = -1;
365     } else {
366     y = 0;
367     ystp = 1; yend = bwr->hdr->height;
368     }
369     /* convert each scanline */
370     for ( ; y != yend; y += ystp) {
371 greg 2.15 if (fread2colrs(scanin, bwr->hdr->width, rfp, NCSAMP, WLPART) < 0)
372 greg 2.1 quiterr("error reading Radiance picture");
373 greg 2.9 if (usexfm)
374     for (x = bwr->hdr->width; x--; ) {
375     colr_color(cval, scanin[x]);
376     colortrans(cval, xfm, cval);
377     setcolr(scanin[x], colval(cval,RED),
378     colval(cval,GRN),
379     colval(cval,BLU));
380     }
381     else if (bradj)
382 greg 2.1 shiftcolrs(scanin, bwr->hdr->width, bradj);
383 greg 2.9 if (monpri == NULL && rgbinp != TM_XYZPRIM)
384     for (x = bwr->hdr->width; x--; )
385     scanin[x][GRN] = normbright(scanin[x]);
386 greg 2.1 colrs_gambs(scanin, bwr->hdr->width);
387 greg 2.9 if (monpri == NULL)
388 greg 2.1 for (x = bwr->hdr->width; x--; )
389     bwr->scanline[x] = scanin[x][GRN];
390     else
391     for (x = bwr->hdr->width; x--; ) {
392     bwr->scanline[3*x] = scanin[x][BLU];
393     bwr->scanline[3*x+1] = scanin[x][GRN];
394     bwr->scanline[3*x+2] = scanin[x][RED];
395     }
396     bwr->yscan = y;
397     x = BMPwriteScanline(bwr);
398     if (x != BIR_OK)
399     quiterr(BMPerrorMessage(x));
400     }
401 greg 2.18 free(scanin); /* free scanline */
402 greg 2.1 }
403    
404     /* convert BMP file to Radiance */
405 greg 2.18 void
406 greg 2.1 bmp2rad(BMPReader *brd, FILE *rfp, int inv)
407     {
408     COLR *scanout;
409     int y, yend, ystp;
410     int x;
411     /* allocate scanline */
412     scanout = (COLR *)malloc(brd->hdr->width*sizeof(COLR));
413     if (scanout == NULL)
414     quiterr("out of memory in bmp2rad");
415     /* convert image */
416     if (inv) {
417     y = brd->hdr->height - 1;
418     ystp = -1; yend = -1;
419     } else {
420     y = 0;
421     ystp = 1; yend = brd->hdr->height;
422     }
423     /* convert each scanline */
424     for ( ; y != yend; y += ystp) {
425     x = BMPseekScanline(y, brd);
426     if (x != BIR_OK)
427     quiterr(BMPerrorMessage(x));
428     for (x = brd->hdr->width; x--; ) {
429     RGBquad rgbq = BMPdecodePixel(x, brd);
430     scanout[x][RED] = rgbq.r;
431     scanout[x][GRN] = rgbq.g;
432     scanout[x][BLU] = rgbq.b;
433     }
434     gambs_colrs(scanout, brd->hdr->width);
435     if (bradj)
436     shiftcolrs(scanout, brd->hdr->width, bradj);
437     if (fwritecolrs(scanout, brd->hdr->width, rfp) < 0)
438     quiterr("error writing Radiance picture");
439     }
440 greg 2.18 free(scanout); /* clean up */
441 greg 2.1 }
442 greg 2.4
443     /* Tone-map and convert Radiance picture */
444 greg 2.18 void
445 greg 2.4 tmap2bmp(char *fnin, char *fnout, char *expec, RGBPRIMP monpri, double gamval)
446     {
447     int tmflags;
448     BMPHeader *hdr;
449     BMPWriter *wtr;
450     FILE *fp;
451     int xr, yr;
452 greg 2.11 uby8 *pa;
453 greg 2.4 int i;
454     /* check tone-mapping spec */
455     i = strlen(expec);
456     if (i && !strncmp(expec, "auto", i))
457     tmflags = TM_F_CAMERA;
458     else if (i && !strncmp(expec, "human", i))
459     tmflags = TM_F_HUMAN & ~TM_F_UNIMPL;
460     else if (i && !strncmp(expec, "linear", i))
461     tmflags = TM_F_LINEAR;
462     else
463     quiterr("illegal exposure specification (auto|human|linear)");
464     if (monpri == NULL) {
465     tmflags |= TM_F_BW;
466     monpri = stdprims;
467     }
468     /* open Radiance input */
469     if (fnin == NULL)
470     fp = stdin;
471     else if ((fp = fopen(fnin, "r")) == NULL) {
472     fprintf(stderr, "%s: cannot open\n", fnin);
473     exit(1);
474     }
475     /* tone-map picture */
476     if (tmMapPicture(&pa, &xr, &yr, tmflags, monpri, gamval,
477     0., 0., fnin, fp) != TM_E_OK)
478     exit(1);
479 greg 2.18 /* try to retrieve info */
480     if (fseek(fp, 0L, SEEK_SET) == 0)
481     getheader(fp, headline, NULL);
482     /* add output color space */
483     addBMPcspace(monpri, gamval);
484 greg 2.4 /* initialize BMP header */
485     if (tmflags & TM_F_BW) {
486 greg 2.18 hdr = BMPmappedHeader(xr, yr, infolen+1, 256);
487 greg 2.5 if (fnout != NULL)
488     hdr->compr = BI_RLE8;
489 greg 2.4 } else
490 greg 2.18 hdr = BMPtruecolorHeader(xr, yr, infolen+1);
491 greg 2.4 if (hdr == NULL)
492     quiterr("cannot initialize BMP header");
493 greg 2.18
494     strcpy(BMPinfo(hdr), info); /* copy info if any */
495     clearInfo();
496 greg 2.4 /* open BMP output */
497     if (fnout != NULL)
498     wtr = BMPopenOutputFile(fnout, hdr);
499     else
500     wtr = BMPopenOutputStream(stdout, hdr);
501     if (wtr == NULL)
502     quiterr("cannot allocate writer structure");
503     /* write to BMP file */
504     while (wtr->yscan < yr) {
505 greg 2.11 uby8 *scn = pa + xr*((tmflags & TM_F_BW) ? 1 : 3)*
506 greg 2.7 (yr-1 - wtr->yscan);
507 greg 2.4 if (tmflags & TM_F_BW)
508 greg 2.18 memcpy(wtr->scanline, scn, xr);
509 greg 2.4 else
510     for (i = xr; i--; ) {
511     wtr->scanline[3*i] = scn[3*i+BLU];
512     wtr->scanline[3*i+1] = scn[3*i+GRN];
513     wtr->scanline[3*i+2] = scn[3*i+RED];
514     }
515     if ((i = BMPwriteScanline(wtr)) != BIR_OK)
516     quiterr(BMPerrorMessage(i));
517     }
518     /* flush output */
519     if (fflush((FILE *)wtr->c_data) < 0)
520     quiterr("error writing BMP output");
521     /* clean up */
522 greg 2.8 if (fnin != NULL)
523     fclose(fp);
524 greg 2.18 free(pa);
525 greg 2.4 BMPcloseOutput(wtr);
526     }