1 // -*- mode: c++; c-set-style: "stroustrup"; tab-width: 4; -*-
2 //
3 // CAppFV.c
4 //
5 // Copyright (C) 2004 Koji Nakamaru
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 //
21
22 #include "common.h"
23 #include <getopt.h>
24 #include <png.h>
25 #include <time.h>
26 #include "CFile.h"
27 #include "CImage.h"
28 #include "CReaderHDR.h"
29 #include "CReaderPFM.h"
30 #include "CAppFV.h"
31
32 #define k_app_name "fv"
33 #define k_version "1.03"
34 #define k_title_length (36)
35 #define k_wait (1000000)
36 #define k_tbl_size (65536)
37
38 static void *worker(void *);
39 static void initializeTable();
40 static void initializeTextures(bool is_full);
41 static void saveAsPNG();
42 static void resetOrigin();
43 static void moveOrigin(int d);
44
45 static CAppFV _app;
46 static float _gamma = 2.2;
47 static char *_title = "fv";
48 static GLubyte _tbl[k_tbl_size];
49 static CFile _file;
50 static bool _is_stdin;
51 static CReader *_readers[] = {
52 new CReaderHDR(),
53 new CReaderPFM(),
54 NULL,
55 };
56 static CReader *_reader;
57 static char _magic[8];
58 static CImage<float, 4> *_image;
59 static pthread_t _thread;
60 static int _mx, _my;
61 static float _x, _y;
62 static int _zoom;
63 static float _stops;
64 static bool _is_flip_h, _is_flip_v;
65 static GLint _tex_dim0;
66 static GLint _tex_dim;
67 static GLuint *_tex;
68 static int _iw, _ih;
69 static GLubyte *_img;
70
71 // public functions
72
CAppFV()73 CAppFV::CAppFV()
74 {
75 }
76
~CAppFV()77 CAppFV::~CAppFV()
78 {
79 }
80
81
82 // protected functions
83
84 static struct option _options[] = {
85 { "gamma", 1, NULL, 'g' },
86 { "title", 1, NULL, 't' },
87 { "help", 0, NULL, 'h' },
88 { "version", 0, NULL, 'v' },
89 { NULL, 0, NULL, 0 },
90 };
91
initialize(int argc,char * argv[])92 void CAppFV::initialize(
93 int argc,
94 char *argv[])
95 {
96 opterr = 0;
97 optind = 1;
98 int c;
99 while ((c = getopt_long(argc, argv, "g:t:hv", _options, NULL)) != -1) {
100 switch (c) {
101 case 'g':
102 if (sscanf(optarg, "%f", &_gamma) != 1 || _gamma < 0.05 || _gamma > 3.0) {
103 error("the gamma value should be in [0.05, 3.0]");
104 }
105 break;
106 case 't':
107 _title = optarg;
108 break;
109 case 'v':
110 fprintf(
111 stderr,
112 "%s %s\n"
113 "Copyright (C) 2004 Koji Nakamaru.\n"
114 "This is free software; see the source for copying conditions. There is NO\n"
115 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
116 k_app_name,
117 k_version);
118 exit(0);
119 break;
120 case 'h':
121 default:
122 fprintf(
123 stderr,
124 "%s %s\n"
125 "Usage: %s [OPTION]... [FILE]...\n"
126 "displays hdr/pfm images.\n"
127 " -g, --gamma=GAMMA use GAMMA as the gamma value (default %f).\n"
128 " -t, --title=TITLE use TITLE as the string displayed in the titlebar (default %s).\n"
129 " -h, --help display this help and exit.\n"
130 " -v, --version output version information and exit.\n",
131 k_app_name,
132 k_version,
133 progname(),
134 _gamma,
135 _title);
136 exit(1);
137 break;
138 }
139 }
140 switch (argc - optind) {
141 case 0:
142 _file.open(NULL);
143 _is_stdin = true;
144 break;
145 case 1:
146 if (_title == k_app_name) {
147 _title = argv[optind];
148 }
149 if (! _file.open(argv[optind])) {
150 error("cannot open %s", argv[optind]);
151 }
152 _is_stdin = false;
153 break;
154 default:
155 for (int i = optind; i < argc; i++) {
156 if (access(argv[i], R_OK) == -1) {
157 message("cannot open %s", argv[i]);
158 continue;
159 }
160 char buf[64];
161 sprintf(buf, "%f", _gamma);
162 run((string(argv[0]) + " -g " + buf + " -t " + argv[i] + " " + argv[i]).c_str());
163 }
164 exit(0);
165 break;
166 }
167 {
168 int c;
169 for (int i = 0; (c = _file.getc()) != EOF && i < 2; i++) {
170 _magic[i] = c;
171 for (int j = 0; _readers[j] != NULL; j++) {
172 for (int k = 0; _readers[j]->magic[k] != NULL; k++) {
173 if (strcmp(_magic, _readers[j]->magic[k]) == 0) {
174 _reader = _readers[j];
175 goto end;
176 }
177 }
178 }
179 }
180 end:
181 if (_reader == NULL) {
182 error("found unknown format");
183 }
184 }
185 _image = new CImage<float, 4>();
186 if (_is_stdin) {
187 if (! _reader->initialize(&_file, _magic)) {
188 error("failed to read data");
189 }
190 } else {
191 size_t pos = _file.tell();
192 while (! _reader->initialize(&_file, _magic)) {
193 usleep(k_wait);
194 _file.seek(pos);
195 }
196 }
197 if (! _image->initialize(_reader->w(), _reader->h())) {
198 error("cannot allocate memory");
199 }
200 pthread_create(&_thread, NULL, worker, NULL);
201
202 // adjust the tilte
203 {
204 int n0 = mbstowcs(NULL, _title, strlen(_title));
205 if (n0 > k_title_length) {
206 wchar_t bufw[n0 + 1];
207 memset(bufw, 0, n0 + 1);
208 mbstowcs(bufw, _title, strlen(_title));
209 bufw[n0 - (k_title_length - 0)] = L'.';
210 bufw[n0 - (k_title_length - 1)] = L'.';
211 bufw[n0 - (k_title_length - 2)] = L'.';
212 int n1 = wcstombs(NULL, &bufw[n0 - k_title_length], k_title_length);
213 _title = new char[n1 + 1];
214 memset(_title, 0, n1 + 1);
215 wcstombs(_title, &bufw[n0 - k_title_length], n1);
216 }
217 }
218 glutInitWindowSize(_image->w() + 20, _image->h() + 20);
219 // glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
220 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
221 glutCreateWindow(_title);
222 // glutSetCursor(GLUT_CURSOR_CROSSHAIR);
223
224 glutAddMenuEntry("Zoom In (E/I)", 'I');
225 glutAddMenuEntry("Zoom Out (W/O)", 'O');
226 glutAddMenuEntry("Reset Zoom (Q/P)", 'P');
227 glutAddMenuEntry("Expose With the Current Pixel (F/J)", 'J');
228 glutAddMenuEntry("Expose Automatically (B)", 'B');
229 glutAddMenuEntry("Expose Up (D/K)", 'K');
230 glutAddMenuEntry("Expose Down (S/L)", 'L');
231 glutAddMenuEntry("Reset Exposure Scale (A/;)", ';');
232 glutAddMenuEntry("Flip Horizontally (C/M)", 'M');
233 glutAddMenuEntry("Flip Vertically (V/N)", 'N');
234 glutAddMenuEntry("Reset Origin (Space)", ' ');
235 glutAddMenuEntry("Save as PNG (Enter)", '\r');
236
237 glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
238 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
239
240 glDisable(GL_DEPTH_TEST);
241 glDisable(GL_CULL_FACE);
242 glEnable(GL_TEXTURE_2D);
243 glEnable(GL_BLEND);
244 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
245 glEnable(GL_ALPHA_TEST);
246 glAlphaFunc(GL_GREATER, 0.02);
247
248 glDisable(GL_LIGHTING);
249 glDisable(GL_LIGHT0);
250 GLfloat background[] = { 0.75f, 0.75f, 0.75f, 1.0f };
251 glClearColor(background[0], background[1], background[2], background[3]);
252
253 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &_tex_dim0);
254 if (256 < _tex_dim0) {
255 _tex_dim0 = 256;
256 }
257 _tex_dim = _tex_dim0 - 2;
258 initializeTable();
259 _img = new GLubyte[_tex_dim0 * _tex_dim0 * 4];
260 memset(_img, 255, _tex_dim0 * _tex_dim0 * 4);
261 _iw = (_image->w() + (_tex_dim - 1)) / _tex_dim;
262 _ih = (_image->h() + (_tex_dim - 1)) / _tex_dim;
263 _tex = new GLuint[_iw * _ih];
264 glGenTextures(_iw * _ih, _tex);
265 initializeTextures(true);
266
267 startTimer(k_wait / 1000);
268 }
269
finalize()270 void CAppFV::finalize()
271 {
272 CApp::finalize();
273 }
274
display()275 void CAppFV::display()
276 {
277 glViewport(0, 0, _view.w, _view.h);
278 // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
279 glClear(GL_COLOR_BUFFER_BIT);
280 glMatrixMode(GL_MODELVIEW);
281 glLoadIdentity();
282 float zoom = powf(2.0, _zoom);
283 // NOTE: we limit all coordinates to be intergers for avoiding
284 // numerical errors in texture mapping.
285 glTranslatef((int)(_x * zoom), (int)(_y * zoom), 0.0f);
286 glScalef((_is_flip_h) ? -1.0f : 1.0f, (_is_flip_v) ? -1.0f : 1.0f, 1.0f);
287 const float k_min_xy = 1.0f / _tex_dim0;
288 const float k_max_xy = 1.0f - 1.0f / _tex_dim0;
289 int w2 = _image->w() / 2;
290 int h2 = _image->h() / 2;
291 for (int y = 0; y < _image->h(); y += _tex_dim) {
292 for (int x = 0; x < _image->w(); x += _tex_dim) {
293 glBindTexture(GL_TEXTURE_2D, _tex[y / _tex_dim * _iw + x / _tex_dim]);
294 glBegin(GL_QUADS);
295 glTexCoord3f(k_min_xy, k_min_xy, 0.0f);
296 glVertex3f(
297 (int)((x - w2) * zoom),
298 (int)((y - h2) * -zoom),
299 0.0f);
300 glTexCoord3f(k_min_xy, k_max_xy, 0.0f);
301 glVertex3f(
302 (int)((x - w2) * zoom),
303 (int)((y - h2 + _tex_dim) * -zoom),
304 0.0f);
305 glTexCoord3f(k_max_xy, k_max_xy, 0.0f);
306 glVertex3f(
307 (int)((x - w2 + _tex_dim) * zoom),
308 (int)((y - h2 + _tex_dim) * -zoom),
309 0.0f);
310 glTexCoord3f(k_max_xy, k_min_xy, 0.0f);
311 glVertex3f(
312 (int)((x - w2 + _tex_dim) * zoom),
313 (int)((y - h2) * -zoom),
314 0.0f);
315 glEnd();
316 }
317 }
318 glutSwapBuffers();
319 }
320
reshape(int width,int height)321 void CAppFV::reshape(
322 int width,
323 int height)
324 {
325 // NOTE: we limit all coordinates to be integers for avoiding
326 // numerical errors in texture mapping.
327 glMatrixMode(GL_PROJECTION);
328 glLoadIdentity();
329 int w = width / 2;
330 int h = height / 2;
331 gluOrtho2D(-w, width - w, -h, height - h);
332 glMatrixMode(GL_MODELVIEW);
333 glutPostRedisplay();
334 }
335
menu(int value)336 void CAppFV::menu(
337 int value)
338 {
339 key((unsigned char)value, _mx, _my);
340 }
341
key(unsigned char key,int x,int y)342 void CAppFV::key(
343 unsigned char key,
344 int x,
345 int y)
346 {
347 switch (toupper(key)) {
348 case 'E':
349 case 'I':
350 if (_zoom < 7) {
351 _zoom++;
352 }
353 break;
354 case 'W':
355 case 'O':
356 if (_zoom > -7) {
357 _zoom--;
358 }
359 break;
360 case 'Q':
361 case 'P':
362 _zoom = 0;
363 break;
364 case 'F':
365 case 'J':
366 {
367 getPixelPosition(&x, &y);
368 float *pixel = _image->pixel(x, y);
369 double gray = pixel[0] * 0.30 + pixel[1] * 0.59 + pixel[2] * 0.11;
370 if (gray < 1e-3) {
371 gray = 1e-3;
372 }
373 _stops = log(0.5 / gray) / log(1.2);
374 initializeTextures(false);
375 }
376 break;
377 case 'B':
378 {
379 // NOTE: we avoid "fully zero" pixels in determining the
380 // log-average luminance. this approach is better than
381 // adding a small delta.
382 float *pixel = _image->pixel(0, 0);
383 double avg = 0.0;
384 int count = 0;
385 for (int y = 0; y < _image->h(); y++) {
386 for (int x = 0; x < _image->w(); x++) {
387 double gray = pixel[0] * 0.30 + pixel[1] * 0.59 + pixel[2] * 0.11;
388 if (gray > 1e-6) {
389 avg += log(gray);
390 count++;
391 }
392 pixel += 4;
393 }
394 }
395 if (count > 0) {
396 if ((avg = exp(avg / count)) < 1e-3) {
397 avg = 1e-3;
398 }
399 _stops = log(0.18 / avg) / log(1.2);
400 initializeTextures(false);
401 }
402 }
403 break;
404 case 'D':
405 case 'K':
406 _stops += 1.0;
407 initializeTextures(false);
408 break;
409 case 'S':
410 case 'L':
411 _stops -= 1.0;
412 initializeTextures(false);
413 break;
414 case 'A':
415 case ';':
416 _stops = 0.0;
417 initializeTextures(false);
418 break;
419 case 'C':
420 case 'M':
421 _is_flip_h = ! _is_flip_h;
422 break;
423 case 'V':
424 case 'N':
425 _is_flip_v = ! _is_flip_v;
426 break;
427 case ' ':
428 resetOrigin();
429 break;
430 case '\r':
431 saveAsPNG();
432 break;
433 case 'Z':
434 default:
435 CApp::key(key, x, y);
436 return;
437 }
438 passive(_mx, _my);
439 glutPostRedisplay();
440 }
441
special(int key,int x,int y)442 void CAppFV::special(
443 int key,
444 int x,
445 int y)
446 {
447 CApp::special(key, x, y);
448 switch (key) {
449 case GLUT_KEY_LEFT:
450 moveOrigin(0);
451 break;
452 case GLUT_KEY_RIGHT:
453 moveOrigin(1);
454 break;
455 case GLUT_KEY_DOWN:
456 moveOrigin(2);
457 break;
458 case GLUT_KEY_UP:
459 moveOrigin(3);
460 break;
461 }
462 passive(_mx, _my);
463 glutPostRedisplay();
464 }
465
mouse(int button,int state,int x,int y)466 void CAppFV::mouse(
467 int button,
468 int state,
469 int x,
470 int y)
471 {
472 CApp::mouse(button, state, x, y);
473 _mx = x;
474 _my = y;
475 }
476
drag(int x,int y)477 void CAppFV::drag(
478 int x,
479 int y)
480 {
481 CApp::drag(x, y);
482 float zoom = powf(2.0, _zoom);
483 _x += (x - _mx) / zoom;
484 _y -= (y - _my) / zoom;
485 _mx = x;
486 _my = y;
487 glutPostRedisplay();
488 }
489
passive(int x,int y)490 void CAppFV::passive(
491 int x,
492 int y)
493 {
494 CApp::passive(x, y);
495 _mx = x;
496 _my = y;
497 getPixelPosition(&x, &y);
498 float zoom = powf(2.0, _zoom);
499 float scale = powf(1.2f, _stops);
500 float *pixel = _image->pixel(x, y);
501 char buf[1024];
502 sprintf(
503 buf,
504 "%s P(%5d, %5d) C(%6.3f, %6.3f, %6.3f, %6.3f) Z(%.3f) S(%.4f)",
505 _title,
506 x,
507 y,
508 pixel[0],
509 pixel[1],
510 pixel[2],
511 pixel[3],
512 zoom,
513 scale);
514 glutSetWindowTitle(buf);
515 }
516
timer(unsigned dmillis)517 void CAppFV::timer(
518 unsigned dmillis)
519 {
520 _image->lock();
521 if (_image->isChanged()) {
522 _image->setChanged(false);
523 initializeTextures(false);
524 glutPostRedisplay();
525 }
526 _image->unlock();
527 }
528
getPixelPosition(int * x,int * y)529 void CAppFV::getPixelPosition(
530 int *x,
531 int *y)
532 {
533 float zoom = powf(2.0, _zoom);
534 int w = _view.w / 2;
535 int h = _view.h / 2;
536 int x0 = (int)(_x * zoom) + (int)(-(_image->w() / 2) * zoom) + w;
537 int y0 = (int)(-_y * zoom) + (int)(-(_image->h() / 2) * zoom) + h;
538 *x = clamp((int)((*x - x0) / zoom), 0, _image->w() - 1);
539 *y = clamp((int)((*y - y0) / zoom), 0, _image->h() - 1);
540 if (_is_flip_h) {
541 *x = _image->w() - 1 - *x;
542 }
543 if (_is_flip_v) {
544 *y = _image->h() - 1 - *y;
545 }
546 }
547
548 // private functions
549 // local functions
550
worker(void *)551 static void *worker(
552 void *)
553 {
554 if (_is_stdin) {
555 _image->clear();
556 _reader->read(&_file, _image);
557 } else {
558 size_t pos = _file.tell();
559 struct stat stbuf0, stbuf;
560 if (! _file.stat(&stbuf0)) {
561 error("failed to read data");
562 }
563 _image->clear();
564 _file.seek(pos);
565 _reader->read(&_file, _image);
566 for (;;) {
567 usleep(k_wait);
568 if (! _file.stat(&stbuf)) {
569 error("failed to read data");
570 }
571 if (stbuf0.st_mtime != stbuf.st_mtime) {
572 _image->clear();
573 _file.seek(pos);
574 _reader->read(&_file, _image);
575 }
576 stbuf0 = stbuf;
577 }
578 }
579 return NULL;
580 }
581
initializeTable()582 static void initializeTable()
583 {
584 double inv_gamma = 1.0 / _gamma;
585 for (int i = 0; i < 65536; i++) {
586 _tbl[i] = (GLubyte)(pow(i / 65536.0, inv_gamma) * 255.0);
587 }
588 }
589
initializeTextures(bool is_full)590 static void initializeTextures(
591 bool is_full)
592 {
593 float scale = powf(1.2f, _stops) * (k_tbl_size - 1);
594 for (int y = 0; y < _image->h(); y += _tex_dim) {
595 int h = _image->h() - y;
596 if (h > _tex_dim) {
597 h = _tex_dim;
598 }
599 for (int x = 0; x < _image->w(); x += _tex_dim) {
600 int w = _image->w() - x;
601 if (w > _tex_dim) {
602 w = _tex_dim;
603 }
604 for (int j = 0; j < h; j++) {
605 float *o = _image->pixel(x, y + j);
606 GLubyte *p = &_img[((j + 1) * _tex_dim0 + 1) * 4];
607 for (int i = 0; i < w; i++) {
608 *p++ = _tbl[(int)clamp(*o++ * scale, 0.0f, (float)(k_tbl_size - 1))];
609 *p++ = _tbl[(int)clamp(*o++ * scale, 0.0f, (float)(k_tbl_size - 1))];
610 *p++ = _tbl[(int)clamp(*o++ * scale, 0.0f, (float)(k_tbl_size - 1))];
611 *p++ = (GLubyte)(*o++ * 255.0f);
612 }
613 for (int i = w; i < _tex_dim; i++) {
614 *p++ = 255;
615 *p++ = 255;
616 *p++ = 255;
617 *p++ = 0;
618 }
619 }
620 for (int j = h; j < _tex_dim; j++) {
621 GLubyte *p = &_img[((j + 1) * _tex_dim0 + 1) * 4];
622 for (int i = 0; i < _tex_dim; i++) {
623 *p++ = 255;
624 *p++ = 255;
625 *p++ = 255;
626 *p++ = 0;
627 }
628 }
629 // fill edges: top and its corners
630 {
631 GLuint *p = (GLuint *)_img + (1 * _tex_dim0 + 1);
632 GLuint *q = p - _tex_dim0 - 1;
633 *q++ = *p;
634 for (int i = 0; i < _tex_dim; i++) {
635 *q++ = *p++;
636 }
637 *q++ = *p;
638 }
639 // fill edges: bottom and its corners
640 {
641 GLuint *p = (GLuint *)_img + ((_tex_dim0 - 2) * _tex_dim0 + 1);
642 GLuint *q = p + _tex_dim0 - 1;
643 *q++ = *p;
644 for (int i = 0; i < _tex_dim; i++) {
645 *q++ = *p++;
646 }
647 *q++ = *p;
648 }
649 // fill edges: left
650 {
651 GLuint *p = (GLuint *)_img + _tex_dim0 + 1;
652 GLuint *q = p - 1;
653 for (int j = 0; j < _tex_dim; j++) {
654 *q = *p;
655 p += _tex_dim0;
656 q += _tex_dim0;
657 }
658 }
659 // fill edges: right
660 {
661 GLuint *p = (GLuint *)_img + _tex_dim0 + _tex_dim0 - 2;
662 GLuint *q = p + 1;
663 for (int j = 0; j < _tex_dim; j++) {
664 *q = *p;
665 p += _tex_dim0;
666 q += _tex_dim0;
667 }
668 }
669 glBindTexture(GL_TEXTURE_2D, _tex[y / _tex_dim * _iw + x / _tex_dim]);
670 if (is_full) {
671 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
672 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
673 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
674 // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
675 // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
676 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
677 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
678 glTexImage2D(
679 GL_TEXTURE_2D, 0, 4, _tex_dim0, _tex_dim0, 0, GL_RGBA, GL_UNSIGNED_BYTE, _img);
680 } else {
681 glTexSubImage2D(
682 GL_TEXTURE_2D, 0, 0, 0, _tex_dim0, _tex_dim0, GL_RGBA, GL_UNSIGNED_BYTE, _img);
683 }
684 }
685 }
686 }
687
saveAsPNG()688 static void saveAsPNG()
689 {
690 char name[128];
691 {
692 time_t t = time(NULL);
693 struct tm tl = *localtime(&t);
694 sprintf(
695 name,
696 "%04d%02d%02d-%02d%02d%02d-" k_app_name ".png",
697 tl.tm_year + 1900, tl.tm_mon + 1, tl.tm_mday,
698 tl.tm_hour, tl.tm_min, tl.tm_sec);
699 }
700 png_structp png_ptr = NULL;
701 png_infop info_ptr = NULL;
702 png_bytep row = NULL;
703 FILE *fp = NULL;
704 if ((fp = fopen(name, "w")) == NULL
705 || (png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL
706 || (info_ptr = png_create_info_struct(png_ptr)) == NULL
707 || (row = new png_byte[sizeof(png_byte) * 4 * _image->w()]) == NULL) {
708 goto err;
709 }
710 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
711 goto err;
712 }
713 png_init_io(png_ptr, fp);
714 png_set_IHDR(
715 png_ptr,
716 info_ptr,
717 _image->w(),
718 _image->h(),
719 8,
720 PNG_COLOR_TYPE_RGBA,
721 PNG_INTERLACE_NONE,
722 PNG_COMPRESSION_TYPE_BASE,
723 PNG_FILTER_TYPE_BASE);
724 png_write_info(png_ptr, info_ptr);
725 {
726 float scale = powf(1.2f, _stops) * (k_tbl_size - 1);
727 for (int y = 0; y < _image->h(); y++) {
728 png_byte *p = row;
729 for (int x = 0; x < _image->w(); x++) {
730 int ix, iy;
731 ix = (_is_flip_h) ? _image->w() - 1 - x : x;
732 iy = (_is_flip_v) ? _image->h() - 1 - y : y;
733 float *o = _image->pixel(ix, iy);
734 *p++ = _tbl[(int)clamp(*o++ * scale, 0.0f, (float)(k_tbl_size - 1))];
735 *p++ = _tbl[(int)clamp(*o++ * scale, 0.0f, (float)(k_tbl_size - 1))];
736 *p++ = _tbl[(int)clamp(*o++ * scale, 0.0f, (float)(k_tbl_size - 1))];
737 *p++ = (GLubyte)(*o++ * 255.0f);
738 }
739 png_write_row(png_ptr, row);
740 }
741 }
742 png_write_end(png_ptr, info_ptr);
743 png_destroy_write_struct(&png_ptr, &info_ptr);
744 forgetArray(&row);
745 forgetFILE(&fp);
746 return;
747 err:
748 png_destroy_write_struct(&png_ptr, &info_ptr);
749 forgetArray(&row);
750 forgetFILE(&fp);
751 return;
752 }
753
resetOrigin()754 static void resetOrigin()
755 {
756 _x = 0.0f;
757 _y = 0.0f;
758 }
759
moveOrigin(int d)760 static void moveOrigin(
761 int d)
762 {
763 float zoom = powf(2.0, _zoom);
764 float offset = 16.0f / zoom;
765 switch (d) {
766 case 0:
767 _x -= offset;
768 break;
769 case 1:
770 _x += offset;
771 break;
772 case 2:
773 _y -= offset;
774 break;
775 case 3:
776 _y += offset;
777 break;
778 }
779 }
780