Use SDL_ttf for font rendering; drop old font code
[tennix.git] / graphics.c
blob9357545f0a9b7ef4704c41f2abe969e512d67ccc
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
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.
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
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
22 **/
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <math.h>
27 #include <unistd.h>
28 #include <assert.h>
29 #include <ctype.h>
30 #include <string.h>
31 #include <assert.h>
33 #include "tennix.h"
34 #include "graphics.h"
35 #include "archive.h"
36 #include "sound.h"
38 static Image* images;
40 static Font* fonts;
41 FreeList* font_freelist = NULL;
43 static FontDescription font_desc[] = {
44 /**
45 * This is the list of fonts to be loaded. Be sure to
46 * use NULL as filename when using the same .ttf file
47 * the second time in a row - this saves memory in the
48 * loader function.
49 **/
50 { "dustismo.ttf", 13, TTF_STYLE_NORMAL },
51 { NULL, 20, TTF_STYLE_NORMAL },
52 { "itwasntme.ttf", 22, TTF_STYLE_NORMAL },
53 { NULL, 30, TTF_STYLE_BOLD }
56 static SDL_Surface *buffer;
57 static SDL_Surface *background;
59 static SDL_Rect *rect_update_cache;
60 static SDL_Rect *rect_update_old;
61 static int rect_update_cache_current;
62 static int rect_update_old_count;
64 Uint32 fading_start = 0;
66 static const char* graphics[] = {
67 "court.png",
68 "shadow.png",
69 "player-racket.png",
70 "ball.png",
71 "referee.png",
72 "ctt_hard.png",
73 "ctt_clay.png",
74 "ctt_grass.png",
75 "sidebar.png",
76 "tennixlogo.png",
77 "btnplay.png",
78 "btnresume.png",
79 "btnquit.png",
80 "stadium.png",
81 "fog.png",
82 "fog2.png",
83 "night.png",
84 "talk.png",
85 "cursor.png",
86 "worldmap.png",
87 "loc_margaret_court_arena.png",
88 "loc_stade_roland_garros.png",
89 "loc_court_no_1.png",
90 "loc_arthur_ashe_stadium.png",
91 #ifdef NONFREE_LOCATIONS
92 "loc_training_camp.png",
93 "loc_austrian_open.png",
94 "loc_olympic_green_tennis.png"
95 #endif
98 void init_graphics() {
99 int i;
100 char *d;
101 SDL_Surface* data;
102 SDL_Surface* tmp;
103 TennixArchive *tnxar;
104 struct SDL_RWops* rw;
105 char *font_data = NULL;
107 if (TTF_Init() == -1) {
108 fprintf(stderr, "Cannot init TTF: %s\n", TTF_GetError());
109 exit(EXIT_FAILURE);
112 rect_update_cache = (SDL_Rect*)calloc(RECT_UPDATE_CACHE, sizeof(SDL_Rect));
113 rect_update_old = (SDL_Rect*)calloc(RECT_UPDATE_CACHE, sizeof(SDL_Rect));
114 rect_update_cache_current = 0;
115 rect_update_old_count = 0;
117 images = (Image*)calloc( GR_COUNT, sizeof( Image));
118 fonts = (Font*)calloc(FONT_COUNT, sizeof(Font));
120 buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, WIDTH, HEIGHT,
121 screen->format->BitsPerPixel,
122 screen->format->Rmask,
123 screen->format->Gmask,
124 screen->format->Bmask,
125 screen->format->Amask);
127 background = SDL_CreateRGBSurface(SDL_SWSURFACE, WIDTH, HEIGHT,
128 screen->format->BitsPerPixel,
129 screen->format->Rmask,
130 screen->format->Gmask,
131 screen->format->Bmask,
132 screen->format->Amask);
134 if( buffer == NULL) {
135 fprintf( stderr, "Cannot create buffer surface: %s\n", SDL_GetError());
138 tnxar = tnxar_open(ARCHIVE_FILE);
139 if (tnxar == NULL) {
140 /* not found in cwd - try installed... */
141 tnxar = tnxar_open(ARCHIVE_FILE_INSTALLED);
142 assert(tnxar != NULL);
145 font_freelist = freelist_create();
147 /* Load fonts from resource file */
148 for (i=0; i<FONT_COUNT; i++) {
149 if (font_desc[i].filename != NULL) {
150 assert(tnxar_set_current_filename(tnxar, font_desc[i].filename) != 0);
151 font_data = tnxar_read_current(tnxar);
152 freelist_append(font_freelist, font_data);
154 assert(font_data != NULL);
155 rw = SDL_RWFromMem(font_data, tnxar_size_current(tnxar));
156 fonts[i].data = TTF_OpenFontRW(rw, 1, font_desc[i].size);
157 assert(fonts[i].data != NULL);
158 TTF_SetFontStyle(fonts[i].data, font_desc[i].style);
161 draw_button(40, (HEIGHT-40)/2, WIDTH-80, 40, 100, 100, 100, 1);
162 store_screen();
163 updatescr();
165 for( i=0; i<GR_COUNT; i++) {
166 if (tnxar_set_current_filename(tnxar, graphics[i]) != 0) {
167 d = tnxar_read_current(tnxar);
168 tmp = IMG_Load_RW(SDL_RWFromMem(d, tnxar_size_current(tnxar)), 1);
169 free(d);
170 } else {
171 fprintf(stderr, "Cannot find file: %s\n", graphics[i]);
172 exit(EXIT_FAILURE);
174 if( !tmp) {
175 fprintf( stderr, "Error: %s\n", SDL_GetError());
176 continue;
179 /* Convert to RGBA in the display format */
180 data = SDL_DisplayFormatAlpha(tmp);
181 SDL_FreeSurface(tmp);
183 if( !data) {
184 fprintf( stderr, "Error: %s\n", SDL_GetError());
185 continue;
187 images[i].data = data;
189 draw_button(40, (HEIGHT-40)/2, (WIDTH-80)*i/(GR_COUNT+SOUND_MAX), 40, 100, 250, 100, 0);
190 rectangle(40+BUTTON_BORDER*2, (HEIGHT-40)/2+20+BUTTON_BORDER, (WIDTH-80)*i/(GR_COUNT+SOUND_MAX)-BUTTON_BORDER*2, 10, 170, 250, 170);
191 updatescr();
193 tnxar_close(tnxar);
196 void uninit_graphics() {
197 int i;
199 for( i=0; i<GR_COUNT; i++) {
200 SDL_FreeSurface( images[i].data);
203 if( buffer != NULL) {
204 SDL_FreeSurface( buffer);
207 if (background != NULL) {
208 SDL_FreeSurface(background);
211 free(rect_update_cache);
212 free(rect_update_old);
213 free(images);
215 for (i=0; i<FONT_COUNT; i++) {
216 TTF_CloseFont(fonts[i].data);
217 fonts[i].data = NULL;
219 free(fonts);
221 freelist_free_all(font_freelist);
223 TTF_Quit();
227 FreeList* freelist_create()
229 FreeList* list = (FreeList*)malloc(sizeof(FreeList));
230 assert(list != NULL);
232 list->head = NULL;
234 return list;
237 void freelist_append(FreeList* list, char* data)
239 FreeListItem* new_item = (FreeListItem*)malloc(sizeof(FreeListItem));
241 assert(list != NULL);
242 assert(data != NULL);
244 new_item->data = data;
246 /* Insert item at the beginning of list */
247 new_item->next = list->head;
248 list->head = new_item;
251 void freelist_free_all(FreeList* list)
253 FreeListItem* next;
255 assert(list != NULL);
257 while (list->head != NULL) {
258 /* Remove one item from the head of the list */
259 next = list->head->next;
260 free(list->head->data);
261 free(list->head);
262 list->head = next;
265 free(list);
269 int get_image_width( image_id id) {
270 return images[id].data->w;
273 int get_image_height( image_id id) {
274 return images[id].data->h;
277 int get_sprite_width( image_id id, int items) {
278 return images[id].data->w / items;
281 void show_sprite( image_id id, int pos, int items, int x_offset, int y_offset, int opacity) {
282 SDL_Surface *bitmap;
283 SDL_Rect src, dst;
285 bitmap = images[id].data;
287 if( !bitmap) return;
289 SDL_SetAlpha( bitmap, SDL_SRCALPHA | SDL_RLEACCEL, opacity);
291 dst.w = src.w = bitmap->w/items;
292 dst.h = src.h = bitmap->h;
293 src.x = src.w*pos;
294 src.y = 0;
295 dst.x = x_offset;
296 dst.y = y_offset;
298 SDL_BlitSurface( bitmap, &src, screen, &dst);
299 update_rect2(dst);
302 void fill_image_offset( image_id id, int x, int y, int w, int h, int offset_x, int offset_y) {
303 SDL_Surface *bitmap;
304 SDL_Rect src, dst;
305 int dx = 0, dy = 0, cx, cy;
307 if( id >= GR_COUNT) return;
309 bitmap = images[id].data;
311 /* Make negative offsets positive */
312 if (offset_x < 0) {
313 offset_x = (offset_x%bitmap->w)+bitmap->w;
315 if (offset_y < 0) {
316 offset_y = (offset_y%bitmap->h)+bitmap->h;
319 src.y = offset_y % bitmap->h;
320 while( dy < h) {
321 src.h = dst.h = cy = (h-dy > bitmap->h-src.y)?(bitmap->h-src.y):(h-dy);
322 dst.y = y+dy;
324 dx = 0;
325 src.x = offset_x % bitmap->w;
326 while( dx < w) {
327 src.w = dst.w = cx = (w-dx > bitmap->w-src.x)?(bitmap->w-src.x):(w-dx);
328 dst.x = x+dx;
330 SDL_BlitSurface( bitmap, &src, screen, &dst);
331 update_rect2(dst);
333 dx += cx;
334 src.x = 0;
337 dy += cy;
338 src.y = 0;
342 void line_horiz( int y, Uint8 r, Uint8 g, Uint8 b) {
343 rectangle(0, y, screen->w, 1, r, g, b);
346 void line_vert( int x, Uint8 r, Uint8 g, Uint8 b) {
347 rectangle(x, 0, 1, screen->h, r, g, b);
350 void rectangle( int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b) {
351 Uint32 color = SDL_MapRGB( screen->format, r, g, b);
352 SDL_Rect rect;
354 rect.x = x;
355 rect.y = y;
356 rect.w = w;
357 rect.h = h;
359 SDL_FillRect( screen, &rect, color);
360 update_rect(x, y, w, h);
363 void rectangle_alpha(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 opacity)
365 Uint32 color = SDL_MapRGB(screen->format, r, g, b);
366 SDL_Surface *buf;
367 SDL_Rect rect, rect2;
369 buf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
370 screen->format->BitsPerPixel,
371 screen->format->Rmask,
372 screen->format->Gmask,
373 screen->format->Bmask,
374 screen->format->Amask);
376 rect.x = rect.y = 0;
377 rect2.x = x;
378 rect2.y = y;
379 rect.w = rect2.w = w;
380 rect.h = rect2.h = h;
382 SDL_FillRect(buf, &rect, color);
383 SDL_SetAlpha(buf, SDL_RLEACCEL | SDL_SRCALPHA, opacity);
384 SDL_BlitSurface(buf, &rect, screen, &rect2);
385 update_rect(x, y, w, h);
387 SDL_FreeSurface(buf);
390 void draw_button( int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, char pressed) {
391 float diff = (pressed?1.0-BUTTON_HIGHLIGHT:1.0+BUTTON_HIGHLIGHT);
392 rectangle(x, y, w, h, MIN(r*diff, 255), MIN(g*diff, 255), MIN(b*diff, 255));
393 rectangle(x+BUTTON_BORDER, y+BUTTON_BORDER, w-BUTTON_BORDER, h-BUTTON_BORDER, MIN(r/diff, 255), MIN(g/diff, 255), MIN(b/diff, 255));
394 rectangle(x+BUTTON_BORDER, y+BUTTON_BORDER, w-2*BUTTON_BORDER, h-2*BUTTON_BORDER, r, g, b);
397 void draw_button_text(const char* s, int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, char pressed) {
398 int font_x, font_y;
399 float factor = 0.3;
400 pressed = pressed?1:0;
401 draw_button( x, y, w, h, r, g, b, pressed);
402 font_x = x + w/2 - font_get_string_width(FONT_SMALL, s)/2 + pressed*BUTTON_BORDER;
403 font_y = y + h/2 - font_get_height(FONT_SMALL)/2 + pressed*BUTTON_BORDER;
404 font_draw_string_color(FONT_SMALL, s, font_x, font_y, (Uint8)(r*factor), (Uint8)(g*factor), (Uint8)(b*factor));
407 void clear_screen()
409 SDL_Rect rect;
411 rect.x = rect.y = 0;
412 rect.w = WIDTH;
413 rect.h = HEIGHT;
415 SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
418 void store_screen()
420 SDL_BlitSurface(screen, NULL, background, NULL);
421 rect_update_old[0].x = rect_update_old[0].y = 0;
422 rect_update_old[0].w = WIDTH;
423 rect_update_old[0].h = HEIGHT;
424 rect_update_old_count = 1;
425 rect_update_cache_current = 0;
428 void reset_screen()
430 int i;
431 SDL_Rect* tmp;
432 SDL_BlitSurface(background, NULL, screen, NULL);
434 for (i=0; i<rect_update_cache_current; i++) {
435 SDL_BlitSurface(background, &rect_update_cache[i], screen, &rect_update_cache[i]);
438 /* Save rects I've just updated for redraw later */
439 tmp = rect_update_cache;
440 rect_update_cache = rect_update_old;
441 rect_update_old = tmp;
442 rect_update_old_count = rect_update_cache_current;
443 rect_update_cache_current = 0;
446 void update_rect(Sint32 x, Sint32 y, Sint32 w, Sint32 h)
448 SDL_Rect* u = NULL;
449 static int inside = 0;
450 if (inside) return;
451 inside = 1;
453 #ifdef DRAW_UPDATE_RECTANGLE
454 rectangle(x, y, w, h, 50+rand()%200, 50+rand()%200 ,50+rand()%200);
455 #endif
457 if ((x >= WIDTH) | (y >= HEIGHT) | (x+w <= 0) | (y+h <= 0) | (!w) | (!h)) {
458 inside = 0;
459 return;
462 if (rect_update_cache_current == RECT_UPDATE_CACHE) {
463 fprintf(stderr, "Overflow\n");
464 rect_update_cache_current = 0;
467 u = &(rect_update_cache[rect_update_cache_current]);
469 if (x < 0/* && x+w > 0*/) {
470 w += x;
471 x = 0;
473 if (y < 0/* && y+h > 0*/) {
474 h += y;
475 y = 0;
478 if (x+w >= WIDTH) {
479 w -= (x+w-WIDTH+1);
482 if (y+h >= HEIGHT) {
483 h -= (y+h-HEIGHT+1);
486 if (w==0 || h==0) {
487 inside = 0;
488 return;
491 u->x = x;
492 u->y = y;
493 u->w = w;
494 u->h = h;
496 rect_update_cache_current++;
498 inside = 0;
501 void updatescr()
503 Uint32 ticks = SDL_GetTicks();
504 SDL_Rect *r_current = NULL, *end_current = NULL;
505 SDL_Rect *r_old = NULL;
507 static int fading_last_time = 0;
508 unsigned char fading_now = ticks < fading_start+FADE_DURATION;
510 if (fading_now) {
511 SDL_SetAlpha(buffer, SDL_SRCALPHA | SDL_RLEACCEL, 255-255*(ticks-fading_start)/FADE_DURATION);
512 SDL_BlitSurface(buffer, NULL, screen, NULL);
513 SDL_UpdateRect(screen, 0, 0, 0, 0);
514 } else if (fading_last_time && !fading_now) {
515 SDL_UpdateRect(screen, 0, 0, 0, 0);
516 } else {
517 if (rect_update_old_count == rect_update_cache_current) {
518 /* Merge rects into one single rect list */
519 r_old = rect_update_old;
520 r_current = rect_update_cache;
521 end_current = rect_update_cache + rect_update_cache_current;
522 while (r_current != end_current) {
523 r_old->w = MAX(r_current->x+r_current->w, r_old->x+r_old->w);
524 r_old->h = MAX(r_current->y+r_current->h, r_old->y+r_old->h);
525 r_old->x = MIN(r_current->x, r_old->x);
526 r_old->y = MIN(r_current->y, r_old->y);
527 r_old->w -= r_old->x;
528 r_old->h -= r_old->y;
530 r_current++;
531 r_old++;
533 SDL_UpdateRects(screen, rect_update_old_count, rect_update_old);
534 } else {
535 SDL_UpdateRects(screen, rect_update_old_count, rect_update_old);
536 SDL_UpdateRects(screen, rect_update_cache_current, rect_update_cache);
540 reset_screen();
541 fading_last_time = fading_now;
544 void start_fade() {
545 SDL_BlitSurface( screen, NULL, buffer, NULL);
546 fading_start = SDL_GetTicks();
549 void font_draw_string_color(font_id id, const char* s, int x_offset, int y_offset, Uint8 r, Uint8 g, Uint8 b) {
550 SDL_Surface *text;
551 SDL_Rect rect, rect2;
552 SDL_Color color;
554 color.r = r;
555 color.g = g;
556 color.b = b;
558 assert(id < FONT_COUNT);
560 text = TTF_RenderText_Blended(fonts[id].data, s, color);
561 if (text != NULL) {
562 rect.w = rect2.w = text->w;
563 rect.h = rect2.h = text->h;
564 rect.x = rect.y = 0;
565 rect2.x = x_offset;
566 rect2.y = y_offset;
567 SDL_SetAlpha(text, SDL_SRCALPHA | SDL_RLEACCEL, 255);
568 SDL_BlitSurface(text, &rect, screen, &rect2);
569 SDL_FreeSurface(text);
572 update_rect(x_offset, y_offset, rect.w, rect.h);
575 int font_get_string_width(font_id id, const char* s) {
576 int w, h;
578 assert(id < FONT_COUNT);
580 if (TTF_SizeText(fonts[id].data, s, &w, &h) != 0) {
581 return 0;
584 return w;
587 int font_get_height(font_id id) {
588 assert(id < FONT_COUNT);
589 return TTF_FontHeight(fonts[id].data);
592 void draw_line_faded( int x1, int y1, int x2, int y2, int r, int g, int b, int r2, int g2, int b2) {
593 float step, dx, dy, x = x1, y = y1;
594 int i;
595 char fade = (r!=r2 || g!=g2 || b!=b2);
597 step = (float)(abs(x2-x1)>abs(y2-y1)?abs(x2-x1):abs(y2-y1));
598 dx = (float)(x2-x1) / step;
599 dy = (float)(y2-y1) / step;
601 SDL_LockSurface( screen);
602 for( i=0; i<step; i++) {
603 x += dx;
604 y += dy;
605 if( x < 0.0 || x >= WIDTH || y < 0.0 || y >= HEIGHT) {
606 continue;
608 if( fade) {
609 SET_PIXEL_RGB( screen, (int)x, (int)y, (r*(step-i)+r2*i)/step, (g*(step-i)+g2*i)/step, (b*(step-i)+b2*i)/step);
610 } else {
611 SET_PIXEL_RGB( screen, (int)x, (int)y, r, g, b);
614 SDL_UnlockSurface( screen);