1 /*
2  * ux_pic.c - Unix interface, picture outline functions
3  *
4  * This file is part of Frotz.
5  *
6  * Frotz is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Frotz is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  * Or visit http://www.fsf.org/
20  */
21 
22 #define __UNIX_PORT_FILE
23 
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "ux_defines.h"
28 
29 #ifdef USE_NCURSES_H
30 #include <ncurses.h>
31 #else
32 #include <curses.h>
33 #endif
34 
35 #include "ux_frotz.h"
36 #include "ux_blorb.h"
37 
38 #define PIC_FILE_HEADER_FLAGS 1
39 #define PIC_FILE_HEADER_NUM_IMAGES 4
40 #define PIC_FILE_HEADER_ENTRY_SIZE 8
41 #define PIC_FILE_HEADER_VERSION 14
42 
43 #define PIC_HEADER_NUMBER 0
44 #define PIC_HEADER_WIDTH 2
45 #define PIC_HEADER_HEIGHT 4
46 
47 extern bb_map_t *blorb_map;
48 
49 static struct {
50 	int z_num;
51 	int width;
52 	int height;
53 	int orig_width;
54 	int orig_height;
55 	uint32 type;
56 } *pict_info;
57 static int num_pictures = 0;
58 
59 
60 #ifndef NO_BLORB
61 
62 static void safe_mvaddch(int, int, int);
63 
64 /*
65  * Do a rounding division, rounding to even if fraction part is 1/2.
66  * We assume x and y are nonnegative.
67  *
68  */
round_div(int x,int y)69 static int round_div(int x, int y)
70 {
71 	int quotient = x / y;
72 	int dblremain = (x % y) << 1;
73 
74 	if ((dblremain > y) || ((dblremain == y) && (quotient & 1)))
75 		quotient++;
76 	return quotient;
77 } /* round_div */
78 #endif
79 
80 
unix_init_pictures(void)81 bool unix_init_pictures (void)
82 {
83 #ifndef NO_BLORB
84 	int maxlegalpic = 0;
85 	int i, x_scale, y_scale;
86 	bool success = FALSE;
87 
88 	unsigned char png_magic[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
89 	unsigned char ihdr_name[]	  = "IHDR";
90 	unsigned char jpg_magic[3]	  = {0xFF, 0xD8, 0xFF};
91 	unsigned char jfif_name[5]	  = {'J', 'F', 'I', 'F', 0x00};
92 
93 	bb_result_t res;
94 	uint32 pos;
95 
96 	if (blorb_map == NULL) return FALSE;
97 
98 	bb_count_resources(blorb_map, bb_ID_Pict, &num_pictures, NULL, &maxlegalpic);
99 	pict_info = malloc((num_pictures + 1) * sizeof(*pict_info));
100 	pict_info[0].z_num = 0;
101 	pict_info[0].height = num_pictures;
102 	pict_info[0].width = bb_get_release_num(blorb_map);
103 
104 	y_scale = 200;
105 	x_scale = 320;
106 
107   	for (i = 1; i <= num_pictures; i++) {
108 		if (bb_load_resource(blorb_map, bb_method_Memory, &res, bb_ID_Pict, i) == bb_err_None) {
109 			pict_info[i].type = blorb_map->chunks[res.chunknum].type;
110 			/* Copy and scale. */
111 			pict_info[i].z_num = i;
112 			/* Check to see if we're dealing with a PNG file. */
113 			if (pict_info[i].type == bb_ID_PNG) {
114 				if (memcmp(res.data.ptr, png_magic, 8) == 0) {
115 					/* Check for IHDR chunk.  If it's not there, PNG file is invalid. */
116 					if (memcmp(res.data.ptr+12, ihdr_name, 4) == 0) {
117 						pict_info[i].orig_width =
118 						    (*((unsigned char *)res.data.ptr+16) << 24) +
119 						    (*((unsigned char *)res.data.ptr+17) << 16) +
120 						    (*((unsigned char *)res.data.ptr+18) <<  8) +
121 						    (*((unsigned char *)res.data.ptr+19) <<  0);
122 						pict_info[i].orig_height =
123 						    (*((unsigned char *)res.data.ptr+20) << 24) +
124 						    (*((unsigned char *)res.data.ptr+21) << 16) +
125 						    (*((unsigned char *)res.data.ptr+22) <<  8) +
126 						    (*((unsigned char *)res.data.ptr+23) <<  0);
127 					}
128 				}
129 			} else if (pict_info[i].type == bb_ID_Rect) {
130 				pict_info[i].orig_width =
131 				    (*((unsigned char *)res.data.ptr+0) << 24) +
132 				    (*((unsigned char *)res.data.ptr+1) << 16) +
133 				    (*((unsigned char *)res.data.ptr+2) <<  8) +
134 				    (*((unsigned char *)res.data.ptr+3) <<  0);
135 				pict_info[i].orig_height =
136 				    (*((unsigned char *)res.data.ptr+4) << 24) +
137 				    (*((unsigned char *)res.data.ptr+5) << 16) +
138 				    (*((unsigned char *)res.data.ptr+6) <<  8) +
139 				    (*((unsigned char *)res.data.ptr+7) <<  0);
140 			} else if (pict_info[i].type == bb_ID_JPEG) {
141 				if (memcmp(res.data.ptr, jpg_magic, 3) == 0) { /* Is it JPEG? */
142 					if (memcmp(res.data.ptr+6, jfif_name, 5) == 0) { /* Look for JFIF */
143 						pos = 11;
144 						while (pos < res.length) {
145 							pos++;
146 							if (pos >= res.length) break;	/* Avoid segfault */
147 							if (*((unsigned char *)res.data.ptr+pos) != 0xFF) continue;
148 							if (*((unsigned char *)res.data.ptr+pos+1) != 0xC0) continue;
149 							pict_info[i].orig_width =
150 							   (*((unsigned char *)res.data.ptr+pos+7)*256) +
151 							   *((unsigned char *)res.data.ptr+pos+8);
152 							pict_info[i].orig_height =
153 							    (*((unsigned char *)res.data.ptr+pos+5)*256) +
154 							    *((unsigned char *)res.data.ptr+pos+6);
155 						} /* while */
156 					} /* JFIF */
157 				} /* JPEG */
158 			}
159 		} /* for */
160 
161 		pict_info[i].height = round_div(pict_info[i].orig_height * z_header.screen_rows, y_scale);
162 		pict_info[i].width = round_div(pict_info[i].orig_width *
163 		z_header.screen_cols, x_scale);
164 
165 		/* Don't let dimensions get rounded to nothing. */
166 		if (pict_info[i].orig_height && !pict_info[i].height)
167 			pict_info[1].height = 1;
168 		if (pict_info[i].orig_width && !pict_info[i].width)
169 			pict_info[i].width = 1;
170 
171 		success = TRUE;
172 	} /* for */
173 
174 	if (success) z_header.config |= CONFIG_PICTURES;
175 	else z_header.flags &= ~GRAPHICS_FLAG;
176 
177 	return success;
178 #else
179 	return FALSE;
180 #endif
181 } /* unix_init_pictures */
182 
183 
184 /* Convert a Z picture number to an index into pict_info.  */
z_num_to_index(int n)185 static int z_num_to_index(int n)
186 {
187 	int i;
188 	for (i = 0; i <= num_pictures; i++) {
189 		if (pict_info[i].z_num == n)
190 			return i;
191 	}
192 	return -1;
193 } /* z_num_to_index */
194 
195 
196 /*
197  * os_picture_data
198  *
199  * Return true if the given picture is available. If so, write the
200  * width and height of the picture into the appropriate variables.
201  * Only when picture 0 is asked for, write the number of available
202  * pictures and the release number instead.
203  *
204  */
os_picture_data(int num,int * height,int * width)205 int os_picture_data(int num, int *height, int *width)
206 {
207 	int index;
208 
209 	*height = 0;
210 	*width = 0;
211 
212 	if (!pict_info)
213 		return FALSE;
214 
215 	if ((index = z_num_to_index(num)) == -1)
216 		return FALSE;
217 
218 	*height = pict_info[index].height;
219 	*width = pict_info[index].width;
220 
221 	return TRUE;
222 } /* os_picture_data */
223 
224 
225 #ifndef NO_BLORB
226 /*
227  * Do a mvaddch if the coordinates aren't too large.
228  *
229  */
safe_mvaddch(int y,int x,int ch)230 static void safe_mvaddch(int y, int x, int ch)
231 {
232 	if ((y < z_header.screen_rows) && (x < z_header.screen_cols))
233 		mvaddch(y, x, ch);
234 } /* safe_mvaddch */
235 
236 
237 /*
238  * Set n chars starting at (x, y), doing bounds checking.
239  *
240  */
safe_scrnset(int y,int x,int ch,int n)241 static void safe_scrnset(int y, int x, int ch, int n)
242 {
243 	if ((y < z_header.screen_rows) && (x < z_header.screen_cols)) {
244 		move(y, x);
245 		if (x + n > z_header.screen_cols)
246 			n = z_header.screen_cols - x;
247 		while (n--)
248 			addch(ch);
249 	}
250 } /* safe_scrnset */
251 #endif
252 
253 
254 /*
255  * os_draw_picture
256  *
257  * Display a picture at the given coordinates. Top left is (1,1).
258  *
259  */
260 /* TODO: handle truncation correctly.  Spec 8.8.3 says all graphics should
261  * be clipped to the current window.  To do that, we should probably
262  * modify z_draw_picture in the frotz core to pass some extra parameters.
263  */
os_draw_picture(int num,int row,int col)264 void os_draw_picture(int num, int row, int col)
265 {
266 #ifndef NO_BLORB
267 	int width, height, r, c;
268 	int saved_x, saved_y;
269 	static int plus, ltee, rtee, ttee, btee, hline, vline, ckboard;
270 	static int urcorner, ulcorner, llcorner, lrcorner;
271 	static bool acs_initialized = FALSE;
272 
273 	if (!acs_initialized) {
274 		plus     = u_setup.plain_ascii ? '+'  : ACS_PLUS;
275 		ltee     = u_setup.plain_ascii ? '<'  : ACS_LTEE;
276 		rtee     = u_setup.plain_ascii ? '>'  : ACS_RTEE;
277 		ttee     = u_setup.plain_ascii ? '^'  : ACS_TTEE;
278 		btee     = u_setup.plain_ascii ? 'v'  : ACS_BTEE;
279 		hline    = u_setup.plain_ascii ? '-'  : ACS_HLINE;
280 		vline    = u_setup.plain_ascii ? '|'  : ACS_VLINE;
281 		ckboard  = u_setup.plain_ascii ? ':'  : ACS_CKBOARD;
282 		urcorner = u_setup.plain_ascii ? '\\' : ACS_URCORNER;
283 		ulcorner = u_setup.plain_ascii ? '/'  : ACS_ULCORNER;
284 		llcorner = u_setup.plain_ascii ? '\\' : ACS_LLCORNER;
285 		lrcorner = u_setup.plain_ascii ? '/'  : ACS_LRCORNER;
286 		acs_initialized = TRUE;
287 	}
288 
289 	if (!os_picture_data(num, &height, &width) || !width || !height)
290 		return;
291 	col--, row--;
292 
293 	getyx(stdscr, saved_y, saved_x);
294 
295 	/* General case looks like:
296 	 *                            /----\
297 	 *                            |::::|
298 	 *                            |::42|
299 	 *                            \----/
300 	 *
301 	 * Special cases are:  1 x n:   n x 1:   1 x 1:
302 	 *
303 	 *                                ^
304 	 *                                |
305 	 *                     <----->    |        +
306 	 *                                |
307 	 *                                v
308 	 */
309 
310 	if ((height == 1) && (width == 1))
311 		safe_mvaddch(row, col, plus);
312 	else if (height == 1) {
313 		safe_mvaddch(row, col, ltee);
314 		safe_scrnset(row, col + 1, hline, width - 2);
315 		safe_mvaddch(row, col + width - 1, rtee);
316 	} else if (width == 1) {
317 		safe_mvaddch(row, col, ttee);
318 		for (r = row + 1; r < row + height - 1; r++)
319 			safe_mvaddch(r, col, vline);
320 		safe_mvaddch(row + height - 1, col, btee);
321 	} else {
322 		safe_mvaddch(row, col, ulcorner);
323 		safe_scrnset(row, col + 1, hline, width - 2);
324 		safe_mvaddch(row, col + width - 1, urcorner);
325 		for (r = row + 1; r < row + height - 1; r++) {
326 			safe_mvaddch(r, col, vline);
327 			safe_scrnset(r, col + 1, ckboard, width - 2);
328 			safe_mvaddch(r, col + width - 1, vline);
329 		}
330 		safe_mvaddch(row + height - 1, col, llcorner);
331 		safe_scrnset(row + height - 1, col + 1, hline, width - 2);
332 		safe_mvaddch(row + height - 1, col + width - 1, lrcorner);
333 	}
334 
335 	/* Picture number.  */
336 	if (height > 2) {
337 		for (c = col + width - 2; c > col && num > 0; c--, (num /= 10))
338 			safe_mvaddch(row + height - 2, c, '0' + num % 10);
339 	}
340 
341 	move(saved_y, saved_x);
342 #endif
343 } /* os_draw_picture */
344 
345 
346 /*
347  * os_peek_colour
348  *
349  * Return the colour of the pixel below the cursor. This is used
350  * by V6 games to print text on top of pictures. The coulor need
351  * not be in the standard set of Z-machine colours. To handle
352  * this situation, Frotz extends the colour scheme: Values above
353  * 15 (and below 256) may be used by the interface to refer to
354  * non-standard colours. Of course, os_set_colour must be able to
355  * deal with these colours. Interfaces which refer to characters
356  * instead of pixels might return the current background colour
357  * instead.
358  *
359  */
os_peek_colour(void)360 int os_peek_colour(void)
361 {
362 	if (u_setup.color_enabled) {
363 #ifdef COLOR_SUPPORT
364 		short fg, bg;
365 		pair_content(PAIR_NUMBER(inch() & A_COLOR), &fg, &bg);
366 		switch(bg) {
367 		  case COLOR_BLACK: return BLACK_COLOUR;
368 		  case COLOR_RED: return RED_COLOUR;
369 		  case COLOR_GREEN: return GREEN_COLOUR;
370 		  case COLOR_YELLOW: return YELLOW_COLOUR;
371 		  case COLOR_BLUE: return BLUE_COLOUR;
372 		  case COLOR_MAGENTA: return MAGENTA_COLOUR;
373 		  case COLOR_CYAN: return CYAN_COLOUR;
374 		  case COLOR_WHITE: return WHITE_COLOUR;
375 		}
376 		return 0;
377 #endif /* COLOR_SUPPORT */
378   	} else {
379    		 return (inch() & A_REVERSE) ?
380 			z_header.default_foreground : z_header.default_background;
381 	}
382 } /* os_peek_colour */
383