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

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: ra_bmp.c,v 2.17 2025/06/07 05:09:46 greg Exp $";
3 #endif
4 /*
5 * program to convert between RADIANCE and Windows BMP file
6 */
7
8 #include <math.h>
9
10 #include "rtio.h"
11 #include "platform.h"
12 #include "color.h"
13 #include "tonemap.h"
14 #include "resolu.h"
15 #include "bmpfile.h"
16
17 int bradj = 0; /* brightness adjustment */
18
19 double gamcor = 2.2; /* gamma correction value */
20
21 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 RGBPRIMP monpri, double gamval);
28 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
34 #define add2info(s) strcpy(growInfo(strlen(s)), s)
35 #define clearInfo() growInfo(-infolen)
36
37 RGBPRIMP rgbinp = stdprims; /* RGB input primitives */
38 RGBPRIMS myinprims; /* custom primitives holder */
39
40
41 int
42 main(int argc, char *argv[])
43 {
44 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
52 fixargv0(argv[0]); /* assigns progname */
53
54 for (i = 1; i < argc; i++)
55 if (argv[i][0] == '-' && argv[i][1])
56 switch (argv[i][1]) {
57 case 'b':
58 rgbp = NULL;
59 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 expec = argv[++i];
66 else
67 bradj = atoi(argv[++i]);
68 break;
69 case 'p':
70 if (argc-i < 9)
71 goto userr;
72 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 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
105 if (expec != NULL) { /* check for tone-mapping */
106 if (reverse)
107 goto userr;
108 tmap2bmp(inpfile, outfile, expec, rgbp, gamcor);
109 return(0);
110 }
111 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 info2rad(BMPinfo(rdr->hdr), rdr->hdr->infoSiz, stdout);
133 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 /* write scans downward if we can */
140 if (rdr->hdr->yIsDown || inpfile != NULL)
141 rs.rt |= YDECR;
142 fputsresolu(&rs, stdout);
143 /* set up conversion */
144 setcolrgam(gamcor);
145 /* convert file */
146 bmp2rad(rdr, stdout, !rdr->hdr->yIsDown & (inpfile!=NULL));
147 /* flush output */
148 BMPcloseInput(rdr);
149 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 /* get/save header info. */
161 if (getheader(stdin, headline, NULL) < 0 ||
162 !fgetsresolu(&rs, stdin))
163 quiterr("bad Radiance picture format");
164 /* record color space */
165 addBMPcspace(rgbp, gamcor);
166 /* open output/write BMP header */
167 if (rgbp == NULL) {
168 hdr = BMPmappedHeader(scanlen(&rs),
169 numscans(&rs), infolen+1, 256);
170 /*
171 if (outfile != NULL)
172 hdr->compr = BI_RLE8;
173 */
174 } else
175 hdr = BMPtruecolorHeader(scanlen(&rs),
176 numscans(&rs), infolen+1);
177 if (hdr == NULL)
178 quiterr("cannot create BMP output");
179 /* copy info to BMP header */
180 strcpy(BMPinfo(hdr), info);
181 clearInfo();
182 /* set up output direction */
183 hdr->yIsDown = ((outfile == NULL) | (hdr->compr == BI_RLE8));
184 /* open BMP output */
185 if (outfile != NULL)
186 wtr = BMPopenOutputFile(outfile, hdr);
187 else
188 wtr = BMPopenOutputStream(stdout, hdr);
189 if (wtr == NULL)
190 quiterr("cannot allocate writer structure");
191 /* set up conversion */
192 setcolrgam(gamcor);
193 /* convert file */
194 rad2bmp(stdin, wtr, !hdr->yIsDown, rgbp);
195 /* flush output */
196 if (fflush((FILE *)wtr->c_data) < 0)
197 quiterr("error writing BMP output");
198 BMPcloseOutput(wtr);
199 }
200 return(0); /* success */
201 userr:
202 fprintf(stderr,
203 "Usage: %s [-b][-g gamma][-e spec][-p xr yr xg yg xb yb xw yw] [input|- [output]]\n",
204 progname);
205 fprintf(stderr,
206 " or: %s -r [-g gamma][-e +/-stops] [input|- [output]]\n",
207 progname);
208 return(1);
209 }
210
211 /* print message and exit */
212 void
213 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 /* 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 /* process header line (don't echo) */
249 int
250 headline(char *s, void *p)
251 {
252 char fmt[MAXFMTLEN];
253
254 if (isheadid(s)) /* skip header magic ID */
255 return(0);
256 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 if (!strcmp(fmt,SPECFMT))
264 return(0);
265 return(-1);
266 }
267 if (isprims(s)) { /* get input primaries */
268 primsval(myinprims, s);
269 rgbinp = myinprims;
270 return(0);
271 }
272 if (isexpos(s))
273 return(0); /* ignore this on input */
274 if (!strncmp(s, "GAMMA=", 6))
275 return(0); /* should not be here! */
276 if (isncomp(s)) {
277 NCSAMP = ncompval(s);
278 return(0);
279 }
280 if (iswlsplit(s)) {
281 wlsplitval(WLPART, s);
282 return(0);
283 }
284 add2info(s); /* else save info string */
285 return(1);
286 }
287
288 /* 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
333 /* convert Radiance picture to BMP */
334 void
335 rad2bmp(FILE *rfp, BMPWriter *bwr, int inv, RGBPRIMP monpri)
336 {
337 int usexfm = 0;
338 COLORMAT xfm;
339 COLR *scanin;
340 COLOR cval;
341 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 /* set up color conversion */
348 usexfm = (monpri != NULL) ? (rgbinp != monpri) :
349 ((rgbinp != TM_XYZPRIM) & (rgbinp != stdprims));
350 if (usexfm) {
351 RGBPRIMP destpri = monpri != NULL ? monpri : stdprims;
352 double expcomp = pow(2.0, (double)bradj);
353 if (rgbinp == TM_XYZPRIM)
354 compxyz2rgbWBmat(xfm, destpri);
355 else
356 comprgb2rgbWBmat(xfm, rgbinp, destpri);
357 for (y = 0; y < 3; y++)
358 for (x = 0; x < 3; x++)
359 xfm[y][x] *= expcomp;
360 }
361 /* 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 if (fread2colrs(scanin, bwr->hdr->width, rfp, NCSAMP, WLPART) < 0)
372 quiterr("error reading Radiance picture");
373 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 shiftcolrs(scanin, bwr->hdr->width, bradj);
383 if (monpri == NULL && rgbinp != TM_XYZPRIM)
384 for (x = bwr->hdr->width; x--; )
385 scanin[x][GRN] = normbright(scanin[x]);
386 colrs_gambs(scanin, bwr->hdr->width);
387 if (monpri == NULL)
388 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 free(scanin); /* free scanline */
402 }
403
404 /* convert BMP file to Radiance */
405 void
406 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 free(scanout); /* clean up */
441 }
442
443 /* Tone-map and convert Radiance picture */
444 void
445 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 uby8 *pa;
453 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 /* 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 /* initialize BMP header */
485 if (tmflags & TM_F_BW) {
486 hdr = BMPmappedHeader(xr, yr, infolen+1, 256);
487 if (fnout != NULL)
488 hdr->compr = BI_RLE8;
489 } else
490 hdr = BMPtruecolorHeader(xr, yr, infolen+1);
491 if (hdr == NULL)
492 quiterr("cannot initialize BMP header");
493
494 strcpy(BMPinfo(hdr), info); /* copy info if any */
495 clearInfo();
496 /* 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 uby8 *scn = pa + xr*((tmflags & TM_F_BW) ? 1 : 3)*
506 (yr-1 - wtr->yscan);
507 if (tmflags & TM_F_BW)
508 memcpy(wtr->scanline, scn, xr);
509 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 if (fnin != NULL)
523 fclose(fp);
524 free(pa);
525 BMPcloseOutput(wtr);
526 }