1/*-
2 * Copyright (c) 1998 Andrzej Bialecki
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29/*
30 * Small PNG viewer with scripting abilities
31 */
32
33#include <stdio.h>
34#include <errno.h>
35#include <fcntl.h>
36#include <signal.h>
37#include <termios.h>
38#include <sys/types.h>
39#include <sys/fbio.h>
40#include <sys/consio.h>
41#include <sys/mouse.h>
42#include <vgl.h>
43#include <png.h>
44
45#define NUMBER	8
46
47extern char *optarg;
48extern int optind;
49
50/* Prototypes */
51int kbd_action(int x, int y, char hotkey);
52
53struct action {
54	int zoom;
55	int rotate;
56	int Xshift,Yshift;
57};
58
59struct menu_item {
60	char *descr;
61	char hotkey;
62	int (*func)(int x, int y, char hotkey);
63};
64
65struct menu_item std_menu[]= {
66	{"q  Quit",'q',kbd_action},
67	{"n  Next",'n',kbd_action},
68	{"p  Previous",'p',kbd_action},
69	{"Z  Zoom in",'Z',kbd_action},
70	{"z  Zoom out",'z',kbd_action},
71	{"r  Rotate",'r',kbd_action},
72	{"R  Refresh",'R',kbd_action},
73	{"l  Left",'l',kbd_action},
74	{"h  Right",'h',kbd_action},
75	{"j  Up",'j',kbd_action},
76	{"k  Down",'k',kbd_action},
77	{NULL,0,NULL}
78};
79
80char *progname;
81VGLBitmap pic,bkg;
82struct action a;
83byte pal_red[256];
84byte pal_green[256];
85byte pal_blue[256];
86byte pal_colors;
87double screen_gamma;
88int max_screen_colors=15;
89int quit,changed;
90char **pres;
91int nimg=0;
92int auto_chg=0;
93int cur_img=0;
94char act;
95FILE *log;
96
97void
98usage()
99{
100	fprintf(stderr,"\nVGL graphics viewer, 1.0 (c) Andrzej Bialecki.\n");
101	fprintf(stderr,"\nUsage:\n");
102	fprintf(stderr,"\t%s [-r n] [-g n.n] filename\n",progname);
103	fprintf(stderr,"\nwhere:\n");
104	fprintf(stderr,"\t-r n\tchoose resolution:\n");
105	fprintf(stderr,"\t\t0 - 640x480x16 (default)\n");
106	fprintf(stderr,"\t\t1 - 640x200x256\n");
107	fprintf(stderr,"\t\t2 - 320x240x256\n");
108	fprintf(stderr,"\t-g n.n\tset screen gamma (1.3 by default)\n");
109	fprintf(stderr,"\n");
110}
111
112int
113pop_up(char *title,int x, int y)
114{
115	VGLBitmap sav,clr;
116	int x1,y1,width,height,i,j;
117	int last_pos,cur_pos,max_item;
118	char buttons;
119	char *t;
120
121	sav.Type=VGLDisplay->Type;
122	clr.Type=VGLDisplay->Type;
123	width=0;
124	height=0;
125	max_item=0;
126	i=0;
127	while(std_menu[i].descr!=NULL) {
128		height++;
129		max_item++;
130		if(strlen(std_menu[i].descr)>width) width=strlen(std_menu[i].descr);
131		i++;
132	}
133	width=width*8+2;
134	height=height*9+4+8;
135	sav.Xsize=width;
136	sav.Ysize=height;
137	clr.Xsize=width;
138	clr.Ysize=height;
139	sav.Bitmap=(byte *)calloc(width*height,1);
140	clr.Bitmap=(byte *)calloc(width*height,1);
141	if(x>(VGLDisplay->Xsize-width)) x1=VGLDisplay->Xsize-width;
142	else x1=x;
143	if(y>(VGLDisplay->Ysize-height)) y1=VGLDisplay->Ysize-height;
144	else y1=y;
145	VGLMouseMode(VGL_MOUSEHIDE);
146	VGLBitmapCopy(VGLDisplay,x1,y1,&sav,0,0,width,height);
147	VGLFilledBox(VGLDisplay,x1,y1,x1+width-1,y1+height-1,pal_colors-1);
148	VGLBitmapString(VGLDisplay,x1+1,y1+1,title,0,pal_colors-1,0,0);
149	VGLLine(VGLDisplay,x1,y1+9,x1+width,y1+9,0);
150	i=0;
151	while(std_menu[i].descr!=NULL) {
152		VGLBitmapString(VGLDisplay,x1+1,y1+11+i*9,std_menu[i].descr,0,pal_colors-1,0,0);
153		i++;
154	}
155	last_pos=-1;
156	VGLMouseMode(VGL_MOUSESHOW);
157	do {
158		pause();
159		VGLMouseStatus(&x,&y,&buttons);
160		cur_pos=(y-y1-11)/9;
161		if((cur_pos<0)||(cur_pos>max_item-1)) {
162			if(last_pos==-1) last_pos=0;
163			VGLBitmapString(VGLDisplay,x1+1,y1+11+last_pos*9,std_menu[last_pos].descr,0,pal_colors-1,0,0);
164			last_pos=-1;
165		} else if(last_pos!=cur_pos) {
166			if(last_pos==-1) last_pos=0;
167			VGLBitmapString(VGLDisplay,x1+1,y1+11+last_pos*9,std_menu[last_pos].descr,0,pal_colors-1,0,0);
168			VGLBitmapString(VGLDisplay,x1+1,y1+11+cur_pos*9,std_menu[cur_pos].descr,pal_colors/2+1,pal_colors-1,0,0);
169			last_pos=cur_pos;
170		}
171	} while (buttons & MOUSE_BUTTON3DOWN);
172	VGLMouseMode(VGL_MOUSEHIDE);
173	/* XXX Screws up totally when r==3. Libvgl bug! */
174	VGLBitmapCopy(&clr,0,0,VGLDisplay,x1,y1,width,height);
175	VGLBitmapCopy(&sav,0,0,VGLDisplay,x1,y1,width,height);
176	VGLMouseMode(VGL_MOUSESHOW);
177	free(sav.Bitmap);
178	free(clr.Bitmap);
179	changed++;
180	if((cur_pos>=0) && (cur_pos<max_item)) {
181		std_menu[cur_pos].func(x,y,std_menu[cur_pos].hotkey);
182	}
183	changed++;
184	return(0);
185}
186
187void
188display(	VGLBitmap *pic,
189		byte *red,
190		byte *green,
191		byte *blue,
192		struct action *e)
193{
194	VGLBitmap target;
195	int x,y,i=0,j=0;
196
197	VGLMouseMode(VGL_MOUSEHIDE);
198	VGLRestorePalette();
199	/* XXX Broken in r!=2. Libvgl bug. */
200	//VGLClear(VGLDisplay,0);
201	VGLBitmapCopy(&bkg,0,0,VGLDisplay,0,0,bkg.Xsize,bkg.Ysize);
202
203	if(e!=NULL) {
204		if(e->zoom!=1 || e->rotate) {
205			target.Bitmap=(byte *)calloc(pic->Xsize*pic->Ysize*e->zoom*e->zoom,1);
206			if(e->rotate) {
207				target.Xsize=pic->Ysize*e->zoom;
208				target.Ysize=pic->Xsize*e->zoom;
209			} else {
210				target.Xsize=pic->Xsize*e->zoom;
211				target.Ysize=pic->Ysize*e->zoom;
212			}
213			target.Type=pic->Type;
214			for(x=0;x<pic->Xsize;x++) {
215				for(y=0;y<pic->Ysize;y++) {
216					for(i=0;i<e->zoom;i++) {
217						for(j=0;j<e->zoom;j++) {
218							if(e->rotate) {
219								VGLSetXY(&target,target.Xsize-(e->zoom*y+i),e->zoom*x+j,VGLGetXY(pic,x,y));
220							} else {
221								VGLSetXY(&target,e->zoom*x+i,e->zoom*y+j,VGLGetXY(pic,x,y));
222							}
223						}
224					}
225				}
226			}
227		} else {
228			target.Bitmap=(byte *)calloc(pic->Xsize*pic->Ysize,sizeof(byte));
229			target.Xsize=pic->Xsize;
230			target.Ysize=pic->Ysize;
231			target.Type=pic->Type;
232			VGLBitmapCopy(pic,0,0,&target,0,0,pic->Xsize,pic->Ysize);
233		}
234	} else {
235		target.Bitmap=(byte *)calloc(pic->Xsize*pic->Ysize,sizeof(byte));
236		target.Xsize=pic->Xsize;
237		target.Ysize=pic->Ysize;
238		target.Type=pic->Type;
239		VGLBitmapCopy(pic,0,0,&target,0,0,pic->Xsize,pic->Ysize);
240	}
241	VGLSetPalette(red, green, blue);
242	if(e!=NULL) {
243		VGLBitmapCopy(&target,0,0,VGLDisplay,e->Xshift,e->Yshift,target.Xsize,target.Ysize);
244	} else {
245		VGLBitmapCopy(&target,0,0,VGLDisplay,0,0,target.Xsize,target.Ysize);
246	}
247	VGLMouseMode(VGL_MOUSESHOW);
248	free(target.Bitmap);
249}
250
251int
252png_load(char *filename)
253{
254	int i,j,k;
255	FILE *fd;
256	u_char header[NUMBER];
257	png_structp png_ptr;
258	png_infop info_ptr,end_info;
259	png_uint_32 width,height;
260	int bit_depth,color_type,interlace_type;
261	int compression_type,filter_type;
262	int channels,rowbytes;
263	double gamma;
264	png_colorp palette;
265	int num_palette;
266	png_bytep *row_pointers;
267	char c;
268	int res=0;
269
270	fd=fopen(filename,"rb");
271
272	if(fd==NULL) {
273		VGLEnd();
274		perror("fopen");
275		exit(1);
276	}
277	fread(header,1,NUMBER,fd);
278	if(!png_check_sig(header,NUMBER)) {
279		fprintf(stderr,"Not a PNG file.\n");
280		return(-1);
281	}
282	png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,(void *)NULL,
283		NULL,NULL);
284	info_ptr=png_create_info_struct(png_ptr);
285	end_info=png_create_info_struct(png_ptr);
286	if(!png_ptr || !info_ptr || !end_info) {
287		VGLEnd();
288		fprintf(stderr,"failed to allocate needed structs!\n");
289		png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
290		return(-1);
291	}
292	png_set_sig_bytes(png_ptr,NUMBER);
293	png_init_io(png_ptr,fd);
294	png_read_info(png_ptr,info_ptr);
295	png_get_IHDR(png_ptr,info_ptr,&width,&height,&bit_depth,
296		&color_type,&interlace_type,&compression_type,&filter_type);
297	png_get_PLTE(png_ptr,info_ptr,&palette,&num_palette);
298	channels=png_get_channels(png_ptr,info_ptr);
299	rowbytes=png_get_rowbytes(png_ptr,info_ptr);
300	if(bit_depth==16)
301		png_set_strip_16(png_ptr);
302	if(color_type & PNG_COLOR_MASK_ALPHA)
303		png_set_strip_alpha(png_ptr);
304	if(png_get_gAMA(png_ptr,info_ptr,&gamma))
305		png_set_gamma(png_ptr,screen_gamma,gamma);
306	else
307	png_set_gamma(png_ptr,screen_gamma,0.45);
308	if(res==0) {
309		/* Dither */
310		if(color_type & PNG_COLOR_MASK_COLOR) {
311			if(png_get_valid(png_ptr,info_ptr,PNG_INFO_PLTE)) {
312				png_uint_16p histogram;
313				png_get_hIST(png_ptr,info_ptr,&histogram);
314				png_set_dither(png_ptr,palette,num_palette,max_screen_colors,histogram,0);
315			} else {
316				png_color std_color_cube[16]={
317					{0x00,0x00,0x00},
318					{0x02,0x02,0x02},
319					{0x04,0x04,0x04},
320					{0x06,0x06,0x06},
321					{0x08,0x08,0x08},
322					{0x0a,0x0a,0x0a},
323					{0x0c,0x0c,0x0c},
324					{0x0e,0x0e,0x0e},
325					{0x10,0x10,0x10},
326					{0x12,0x12,0x12},
327					{0x14,0x14,0x14},
328					{0x16,0x16,0x16},
329					{0x18,0x18,0x18},
330					{0x1a,0x1a,0x1a},
331					{0x1d,0x1d,0x1d},
332					{0xff,0xff,0xff},
333				};
334				png_set_dither(png_ptr,std_color_cube,max_screen_colors,max_screen_colors,NULL,0);
335			}
336		}
337	}
338	png_set_packing(png_ptr);
339	if(png_get_valid(png_ptr,info_ptr,PNG_INFO_sBIT)) {
340		png_color_8p sig_bit;
341
342		png_get_sBIT(png_ptr,info_ptr,&sig_bit);
343		png_set_shift(png_ptr,sig_bit);
344	}
345	png_read_update_info(png_ptr,info_ptr);
346	png_get_IHDR(png_ptr,info_ptr,&width,&height,&bit_depth,
347		&color_type,&interlace_type,&compression_type,&filter_type);
348	png_get_PLTE(png_ptr,info_ptr,&palette,&num_palette);
349	channels=png_get_channels(png_ptr,info_ptr);
350	rowbytes=png_get_rowbytes(png_ptr,info_ptr);
351	row_pointers=malloc(height*sizeof(png_bytep));
352	for(i=0;i<height;i++) {
353		row_pointers[i]=malloc(rowbytes);
354	}
355	png_read_image(png_ptr,row_pointers);
356	png_read_end(png_ptr,end_info);
357	png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
358	fclose(fd);
359	/* Set palette */
360	if(res) k=2;
361	else k=2;
362	for(i=0;i<256;i++) {
363	 	pal_red[i]=255;
364	 	pal_green[i]=255;
365	 	pal_blue[i]=255;
366	}
367	for(i=0;i<num_palette;i++) {
368	 	pal_red[i]=(palette+i)->red>>k;
369	 	pal_green[i]=(palette+i)->green>>k;
370	 	pal_blue[i]=(palette+i)->blue>>k;
371	}
372	pal_colors=num_palette;
373	if(pic.Bitmap!=NULL) free(pic.Bitmap);
374	pic.Bitmap=(byte *)calloc(rowbytes*height,sizeof(byte));
375	pic.Type=MEMBUF;
376	pic.Xsize=rowbytes;
377	pic.Ysize=height;
378	for(i=0;i<rowbytes;i++) {
379		for(j=0;j<height;j++) {
380			VGLSetXY(&pic,
381			i,j,row_pointers[j][i]);
382		}
383	}
384	a.zoom=1;
385	a.Xshift=(VGLDisplay->Xsize-pic.Xsize)/2;
386	a.Yshift=(VGLDisplay->Ysize-pic.Ysize)/2;
387	a.rotate=0;
388	return(0);
389}
390
391void
392kbd_handler(int sig)
393{
394	u_char buf[10];
395	int res;
396
397	res=read(0,&buf,10);
398	changed++;
399	act=buf[res-1];
400}
401
402int
403kbd_action(int x, int y, char key)
404{
405	changed=0;
406	if(key!='n') auto_chg=0;
407	switch(key) {
408	case 'q':
409		quit=1;
410		break;
411	case 'Z':
412		a.zoom++;
413		changed++;
414		break;
415	case 'z':
416		a.zoom--;
417		if(a.zoom<1) a.zoom=1;
418		changed++;
419		break;
420	case 'l':
421		a.Xshift+=VGLDisplay->Xsize/5;
422		changed++;
423		break;
424	case 'h':
425		a.Xshift-=VGLDisplay->Xsize/5;
426		changed++;
427		break;
428	case 'k':
429		a.Yshift+=VGLDisplay->Ysize/5;
430		changed++;
431		break;
432	case 'j':
433		a.Yshift-=VGLDisplay->Ysize/5;
434		changed++;
435		break;
436	case 'R':
437		changed++;
438		break;
439	case 'r':
440		if(a.rotate) a.rotate=0;
441		else a.rotate=1;
442		changed++;
443		break;
444	case '\n':
445	case 'n':
446		if(nimg>0) {
447			if(cur_img<nimg-1) {
448				cur_img++;
449			} else {
450				cur_img=0;
451			}
452			png_load(pres[cur_img]);
453			changed++;
454		}
455		break;
456	case 'p':
457		if(nimg>0) {
458			if(cur_img>0) {
459				cur_img--;
460			} else {
461				cur_img=nimg-1;
462			}
463			png_load(pres[cur_img]);
464			changed++;
465		}
466		break;
467	}
468	act=0;
469}
470
471int
472main(int argc, char *argv[])
473{
474	int i,j,k;
475	char c;
476	int res=0;
477	int x,y;
478	char buttons;
479	struct termios t_new,t_old;
480	FILE *fsc;
481
482	char buf[100];
483
484	progname=argv[0];
485	screen_gamma=1.5;
486#ifdef DEBUG
487	log=fopen("/png/view.log","w");
488#endif
489	while((c=getopt(argc,argv,"r:g:"))!=-1) {
490		switch(c) {
491		case 'r':
492			res=atoi(optarg);
493			if(res>0) max_screen_colors=256;
494			break;
495		case 'g':
496			screen_gamma=atof(optarg);
497			break;
498		case '?':
499		default:
500			usage();
501			exit(0);
502		}
503	}
504	switch(res) {
505	case 0:
506		VGLInit(SW_CG640x480);
507		break;
508	case 1:
509		VGLInit(SW_VGA_CG320);
510		break;
511	case 2:
512		VGLInit(SW_VGA_MODEX);
513		break;
514	default:
515		fprintf(stderr,"No such resolution!\n");
516		usage();
517		exit(-1);
518	}
519#ifdef DEBUG
520	fprintf(log,"VGL initialised\n");
521#endif
522	VGLSavePalette();
523	if(argc>optind) {
524		res=png_load(argv[optind]);
525	} else {
526		VGLEnd();
527		usage();
528		exit(0);
529	}
530	if(res) {
531		/* Hmm... Script? */
532		fsc=fopen(argv[optind],"r");
533#ifdef DEBUG
534		fprintf(log,"Trying script %s\n",argv[optind]);
535#endif
536		fgets(buf,99,fsc);
537		buf[strlen(buf)-1]='\0';
538		if(strncmp("VIEW SCRIPT",buf,11)!=NULL) {
539			VGLEnd();
540			usage();
541		}
542		if(strlen(buf)>12) {
543			auto_chg=atoi(buf+12);
544		}
545		fgets(buf,99,fsc);
546		buf[strlen(buf)-1]='\0';
547		nimg=atoi(buf);
548		if(nimg==0) {
549			VGLEnd();
550			usage();
551		}
552		pres=(char **)calloc(nimg,sizeof(char *));
553		for(i=0;i<nimg;i++) {
554			fgets(buf,99,fsc);
555			buf[strlen(buf)-1]='\0';
556			pres[i]=strdup(buf);
557		}
558		fclose(fsc);
559		cur_img=0;
560#ifdef DEBUG
561		fprintf(log,"Script with %d entries\n",nimg);
562#endif
563		png_load(pres[cur_img]);
564	}
565	VGLMouseInit(VGL_MOUSEHIDE);
566	/* Prepare the keyboard */
567	tcgetattr(0,&t_old);
568	memcpy(&t_new,&t_old,sizeof(struct termios));
569	cfmakeraw(&t_new);
570	tcsetattr(0,TCSAFLUSH,&t_new);
571	fcntl(0,F_SETFL,O_ASYNC);
572	/* XXX VGLClear doesn't work.. :-(( Prepare a blank background */
573	bkg.Bitmap=(byte *)calloc(VGLDisplay->Xsize*VGLDisplay->Ysize,1);
574	bkg.Xsize=VGLDisplay->Xsize;
575	bkg.Ysize=VGLDisplay->Ysize;
576	bkg.Type=VGLDisplay->Type;
577	signal(SIGIO,kbd_handler);
578	a.zoom=1;
579	a.Xshift=(VGLDisplay->Xsize-pic.Xsize)/2;
580	a.Yshift=(VGLDisplay->Ysize-pic.Ysize)/2;
581	a.rotate=0;
582	quit=0;
583	changed=0;
584	display(&pic,pal_red,pal_green,pal_blue,&a);
585	while(!quit) {
586		if(act) {
587#ifdef DEBUG
588			fprintf(log,"kbd_action(%c)\n",act);
589#endif
590			kbd_action(x,y,act);
591		}
592		if(quit) break;
593		if(changed) {
594#ifdef DEBUG
595			fprintf(log,"changed, redisplaying\n");
596#endif
597			display(&pic,pal_red,pal_green,pal_blue,&a);
598			changed=0;
599		}
600		if(auto_chg) {
601			sleep(auto_chg);
602			kbd_action(x,y,'n');
603		} else {
604			pause();
605		}
606		VGLMouseStatus(&x,&y,&buttons);
607		if(buttons & MOUSE_BUTTON3DOWN) {
608#ifdef DEBUG
609			fprintf(log,"pop_up called\n");
610#endif
611			pop_up("View",x,y);
612		}
613	}
614	VGLEnd();
615#ifdef DEBUG
616	fclose(log);
617#endif
618	exit(0);
619}
620