ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/src/px/ra_bmp.c
Revision: 2.20
Committed: Sun Sep 14 17:11:29 2025 UTC (2 weeks, 5 days ago) by greg
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 2.19: +6 -7 lines
Log Message:
fix(ra_bmp): Minor fix -- don't save PRIMARIES for grayscale

File Contents

# User Rev Content
1 greg 2.1 #ifndef lint
2 greg 2.20 static const char RCSid[] = "$Id: ra_bmp.c,v 2.19 2025/09/14 15:08:26 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.19 #define add2info(s) {int _n=strlen(s); memcpy(growInfo(_n), s, _n+1);}
35 greg 2.18 #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 greg 2.19 char ibuf[196];
293     char *cp = ibuf;
294 greg 2.18
295 greg 2.19 sprintf(cp, "GAMMA=%.2f\n", gamma);
296     cp += strlen(cp);
297 greg 2.18 if (pp != NULL) {
298 greg 2.19 sprintf(cp,
299 greg 2.18 "%s %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f\n",
300     PRIMARYSTR,
301     pp[RED][CIEX],pp[RED][CIEY],
302     pp[GRN][CIEX],pp[GRN][CIEY],
303     pp[BLU][CIEX],pp[BLU][CIEY],
304     pp[WHT][CIEX],pp[WHT][CIEY]);
305 greg 2.19 /* cp += strlen(cp); */
306 greg 2.18 }
307     add2info(ibuf);
308     }
309    
310     /* write out Radiance header from BMP info string */
311     void
312     info2rad(char *infs, int len, FILE *fout)
313     {
314     char *cp;
315     /* must fit metadata profile */
316     if (len < 3 || infs[0] == '\n' ||
317     infs[--len] != '\0' || infs[len-1] != '\n')
318     return; /* not what we expected */
319     if (strlen(infs) < len || strstr(infs, "\n\n") != NULL)
320     return; /* also not cool */
321     /* check for gamma */
322     if ((cp = strstr(infs, "GAMMA=")) != NULL) {
323     /* copy what came before */
324 greg 2.19 fwrite(infs, 1, cp-infs, fout);
325 greg 2.18 cp += 6;
326     gamcor = atof(cp); /* record setting */
327     while (*cp++ != '\n')
328     ;
329     len -= cp - infs;
330     infs = cp; /* & elide from output */
331     }
332     fputs(infs, fout); /* copy the remainder */
333     }
334 greg 2.9
335 greg 2.1 /* convert Radiance picture to BMP */
336 greg 2.18 void
337 greg 2.9 rad2bmp(FILE *rfp, BMPWriter *bwr, int inv, RGBPRIMP monpri)
338 greg 2.1 {
339 greg 2.9 int usexfm = 0;
340     COLORMAT xfm;
341 greg 2.1 COLR *scanin;
342 greg 2.9 COLOR cval;
343 greg 2.1 int y, yend, ystp;
344     int x;
345     /* allocate scanline */
346     scanin = (COLR *)malloc(bwr->hdr->width*sizeof(COLR));
347     if (scanin == NULL)
348     quiterr("out of memory in rad2bmp");
349 greg 2.9 /* set up color conversion */
350 greg 2.18 usexfm = (monpri != NULL) ? (rgbinp != monpri) :
351     ((rgbinp != TM_XYZPRIM) & (rgbinp != stdprims));
352 greg 2.9 if (usexfm) {
353 greg 2.14 RGBPRIMP destpri = monpri != NULL ? monpri : stdprims;
354     double expcomp = pow(2.0, (double)bradj);
355 greg 2.9 if (rgbinp == TM_XYZPRIM)
356 greg 2.14 compxyz2rgbWBmat(xfm, destpri);
357 greg 2.9 else
358 greg 2.14 comprgb2rgbWBmat(xfm, rgbinp, destpri);
359 greg 2.9 for (y = 0; y < 3; y++)
360     for (x = 0; x < 3; x++)
361     xfm[y][x] *= expcomp;
362     }
363 greg 2.1 /* convert image */
364     if (inv) {
365     y = bwr->hdr->height - 1;
366     ystp = -1; yend = -1;
367     } else {
368     y = 0;
369     ystp = 1; yend = bwr->hdr->height;
370     }
371     /* convert each scanline */
372     for ( ; y != yend; y += ystp) {
373 greg 2.15 if (fread2colrs(scanin, bwr->hdr->width, rfp, NCSAMP, WLPART) < 0)
374 greg 2.1 quiterr("error reading Radiance picture");
375 greg 2.9 if (usexfm)
376     for (x = bwr->hdr->width; x--; ) {
377     colr_color(cval, scanin[x]);
378     colortrans(cval, xfm, cval);
379     setcolr(scanin[x], colval(cval,RED),
380     colval(cval,GRN),
381     colval(cval,BLU));
382     }
383     else if (bradj)
384 greg 2.1 shiftcolrs(scanin, bwr->hdr->width, bradj);
385 greg 2.20 if ((monpri == NULL) & (rgbinp != TM_XYZPRIM))
386 greg 2.9 for (x = bwr->hdr->width; x--; )
387     scanin[x][GRN] = normbright(scanin[x]);
388 greg 2.1 colrs_gambs(scanin, bwr->hdr->width);
389 greg 2.9 if (monpri == NULL)
390 greg 2.1 for (x = bwr->hdr->width; x--; )
391     bwr->scanline[x] = scanin[x][GRN];
392     else
393     for (x = bwr->hdr->width; x--; ) {
394     bwr->scanline[3*x] = scanin[x][BLU];
395     bwr->scanline[3*x+1] = scanin[x][GRN];
396     bwr->scanline[3*x+2] = scanin[x][RED];
397     }
398     bwr->yscan = y;
399     x = BMPwriteScanline(bwr);
400     if (x != BIR_OK)
401     quiterr(BMPerrorMessage(x));
402     }
403 greg 2.18 free(scanin); /* free scanline */
404 greg 2.1 }
405    
406     /* convert BMP file to Radiance */
407 greg 2.18 void
408 greg 2.1 bmp2rad(BMPReader *brd, FILE *rfp, int inv)
409     {
410     COLR *scanout;
411     int y, yend, ystp;
412     int x;
413     /* allocate scanline */
414     scanout = (COLR *)malloc(brd->hdr->width*sizeof(COLR));
415     if (scanout == NULL)
416     quiterr("out of memory in bmp2rad");
417     /* convert image */
418     if (inv) {
419     y = brd->hdr->height - 1;
420     ystp = -1; yend = -1;
421     } else {
422     y = 0;
423     ystp = 1; yend = brd->hdr->height;
424     }
425     /* convert each scanline */
426     for ( ; y != yend; y += ystp) {
427     x = BMPseekScanline(y, brd);
428     if (x != BIR_OK)
429     quiterr(BMPerrorMessage(x));
430     for (x = brd->hdr->width; x--; ) {
431     RGBquad rgbq = BMPdecodePixel(x, brd);
432     scanout[x][RED] = rgbq.r;
433     scanout[x][GRN] = rgbq.g;
434     scanout[x][BLU] = rgbq.b;
435     }
436     gambs_colrs(scanout, brd->hdr->width);
437     if (bradj)
438     shiftcolrs(scanout, brd->hdr->width, bradj);
439     if (fwritecolrs(scanout, brd->hdr->width, rfp) < 0)
440     quiterr("error writing Radiance picture");
441     }
442 greg 2.18 free(scanout); /* clean up */
443 greg 2.1 }
444 greg 2.4
445     /* Tone-map and convert Radiance picture */
446 greg 2.18 void
447 greg 2.4 tmap2bmp(char *fnin, char *fnout, char *expec, RGBPRIMP monpri, double gamval)
448     {
449     int tmflags;
450     BMPHeader *hdr;
451     BMPWriter *wtr;
452     FILE *fp;
453     int xr, yr;
454 greg 2.11 uby8 *pa;
455 greg 2.4 int i;
456     /* check tone-mapping spec */
457     i = strlen(expec);
458     if (i && !strncmp(expec, "auto", i))
459     tmflags = TM_F_CAMERA;
460     else if (i && !strncmp(expec, "human", i))
461     tmflags = TM_F_HUMAN & ~TM_F_UNIMPL;
462     else if (i && !strncmp(expec, "linear", i))
463     tmflags = TM_F_LINEAR;
464     else
465     quiterr("illegal exposure specification (auto|human|linear)");
466 greg 2.20
467     tmflags |= (monpri == NULL)*TM_F_BW;
468 greg 2.4 /* 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 greg 2.20 if (tmMapPicture(&pa, &xr, &yr, tmflags,
477     tmflags&TM_F_BW ? stdprims : monpri, gamval,
478 greg 2.4 0., 0., fnin, fp) != TM_E_OK)
479     exit(1);
480 greg 2.18 /* try to retrieve info */
481     if (fseek(fp, 0L, SEEK_SET) == 0)
482     getheader(fp, headline, NULL);
483     /* add output color space */
484     addBMPcspace(monpri, gamval);
485 greg 2.4 /* initialize BMP header */
486     if (tmflags & TM_F_BW) {
487 greg 2.18 hdr = BMPmappedHeader(xr, yr, infolen+1, 256);
488 greg 2.5 if (fnout != NULL)
489     hdr->compr = BI_RLE8;
490 greg 2.4 } else
491 greg 2.18 hdr = BMPtruecolorHeader(xr, yr, infolen+1);
492 greg 2.4 if (hdr == NULL)
493     quiterr("cannot initialize BMP header");
494 greg 2.18
495     strcpy(BMPinfo(hdr), info); /* copy info if any */
496     clearInfo();
497 greg 2.4 /* open BMP output */
498     if (fnout != NULL)
499     wtr = BMPopenOutputFile(fnout, hdr);
500     else
501     wtr = BMPopenOutputStream(stdout, hdr);
502     if (wtr == NULL)
503     quiterr("cannot allocate writer structure");
504     /* write to BMP file */
505     while (wtr->yscan < yr) {
506 greg 2.11 uby8 *scn = pa + xr*((tmflags & TM_F_BW) ? 1 : 3)*
507 greg 2.7 (yr-1 - wtr->yscan);
508 greg 2.4 if (tmflags & TM_F_BW)
509 greg 2.18 memcpy(wtr->scanline, scn, xr);
510 greg 2.4 else
511     for (i = xr; i--; ) {
512     wtr->scanline[3*i] = scn[3*i+BLU];
513     wtr->scanline[3*i+1] = scn[3*i+GRN];
514     wtr->scanline[3*i+2] = scn[3*i+RED];
515     }
516     if ((i = BMPwriteScanline(wtr)) != BIR_OK)
517     quiterr(BMPerrorMessage(i));
518     }
519     /* flush output */
520     if (fflush((FILE *)wtr->c_data) < 0)
521     quiterr("error writing BMP output");
522     /* clean up */
523 greg 2.8 if (fnin != NULL)
524     fclose(fp);
525 greg 2.18 free(pa);
526 greg 2.4 BMPcloseOutput(wtr);
527     }