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