tuxanci2-engine
Game engine for tuxanci2
Log | Files | Refs | README | LICENSE
commit 106e2181f50e476d4c99ec649bfaf93a71295c40
parent ef8efbb66c24fc07fbb3e71acfb8f209b6de1f9c
Author: Connor Thomson <connor@gnufault.org>
Date: Mon, 19 Oct 2026 08:24:53 -0700
Optimize it
Diffstat:
7 files changed, 423 insertions(+), 170 deletions(-)
diff --git a/include/ta_image.h b/include/ta_image.h
@@ -3,7 +3,8 @@
#include "ta_types.h"
-ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, float x, float y, float width, float height, ta_uint16 frame);
+ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fixed y, ta_fixed width, ta_fixed height, ta_uint16 frame);
+ta_bool ta_image_prepare(const ta_uint8 *data, ta_uint32 length, ta_fixed width, ta_fixed height, ta_uint16 frame);
ta_bool ta_image_init(const ta_uint8 *data, ta_uint32 length);
ta_bool ta_image_get_size(const ta_uint8 *data, ta_uint32 length, ta_uint32 *width, ta_uint32 *height);
void ta_image_release(void);
diff --git a/include/ta_types.h b/include/ta_types.h
@@ -30,4 +30,9 @@ typedef _Bool ta_bool;
#define ta_true 1
#define ta_false 0
+typedef ta_uint32 ta_fixed;
+
+#define TA_FIXED_ONE 65536u
+#define TA_FIXED(value) ((ta_fixed)((value) * 65536.0 + 0.5))
+
#endif // TA_STDINT_H
diff --git a/src/screens/ta_screen_loading.c b/src/screens/ta_screen_loading.c
@@ -3,11 +3,12 @@
#include "ta_fb.h"
#include "ta_image.h"
-#define TA_LOADING_LOGO_SIZE 0.3f
+#define TA_LOADING_LOGO_SIZE TA_FIXED(0.3)
static ta_bool logo_loaded;
static ta_bool logo_shown;
static ta_bool background_loaded;
+static ta_bool background_scaled;
static void draw_loading_logo(void) {
ta_uint32 width;
@@ -17,13 +18,21 @@ static void draw_loading_logo(void) {
return;
}
- float scale_x = (float)ta_fb.width * TA_LOADING_LOGO_SIZE / width;
- float scale_y = (float)ta_fb.height * TA_LOADING_LOGO_SIZE / height;
- float scale = scale_x < scale_y ? scale_x : scale_y;
- float draw_width = (float)width * scale / ta_fb.width;
- float draw_height = (float)height * scale / ta_fb.height;
- float x = (1.0f - draw_width) * 0.5f;
- float y = (1.0f - draw_height) * 0.5f;
+ ta_uint64 frame_width = ta_fb.width;
+ ta_uint64 frame_height = ta_fb.height;
+ ta_fixed draw_width;
+ ta_fixed draw_height;
+
+ if (frame_width * height <= frame_height * width) {
+ draw_width = TA_LOADING_LOGO_SIZE;
+ draw_height = (ta_fixed)((TA_LOADING_LOGO_SIZE * (ta_uint64)height * frame_width) / ((ta_uint64)width * frame_height));
+ } else {
+ draw_height = TA_LOADING_LOGO_SIZE;
+ draw_width = (ta_fixed)((TA_LOADING_LOGO_SIZE * (ta_uint64)width * frame_height) / ((ta_uint64)height * frame_width));
+ }
+
+ ta_fixed x = (TA_FIXED_ONE - draw_width) / 2;
+ ta_fixed y = (TA_FIXED_ONE - draw_height) / 2;
ta_image_draw(ta_file_loading_logo, ta_file_loading_logo_len, x, y, draw_width, draw_height, 0);
}
@@ -32,6 +41,7 @@ void ta_screen_loading_init(void) {
logo_loaded = ta_false;
logo_shown = ta_false;
background_loaded = ta_false;
+ background_scaled = ta_false;
}
ta_bool ta_screen_loading_update(void) {
@@ -54,6 +64,11 @@ ta_bool ta_screen_loading_update(void) {
return ta_true;
}
+ if (!background_scaled) {
+ background_scaled = ta_image_prepare(ta_file_bg, ta_file_bg_len, TA_FIXED_ONE, TA_FIXED_ONE, 0);
+ return ta_true;
+ }
+
ta_screen_current = TA_SCEEEN_MAIN_MENU;
return ta_true;
diff --git a/src/screens/ta_screen_main_menu.c b/src/screens/ta_screen_main_menu.c
@@ -27,7 +27,7 @@ ta_bool ta_screen_main_menu_update(void) {
needs_redraw = ta_false;
- if (!ta_image_draw(ta_file_bg, ta_file_bg_len, 0.0f, 0.0f, 1.0f, 1.0f, 0)) {
+ if (!ta_image_draw(ta_file_bg, ta_file_bg_len, 0, 0, TA_FIXED_ONE, TA_FIXED_ONE, 0)) {
ta_fb_clear(&ta_fb);
}
diff --git a/src/ta_button.c b/src/ta_button.c
@@ -113,11 +113,16 @@ void ta_button_lists_draw(const ta_button_lists *list) {
return;
}
- double width_scale = (double)ta_fb.width / 800.0;
- double height_scale = (double)ta_fb.height / 600.0;
- double scale = width_scale < height_scale ? width_scale : height_scale;
- ta_uint32 font_size = (ta_uint32)(TA_BUTTON_FONT_SIZE * scale + 0.5);
- ta_int32 spacing = (ta_int32)(TA_BUTTON_SPACING * scale + 0.5);
+ ta_uint32 numerator = (ta_uint32)ta_fb.width;
+ ta_uint32 denominator = 800;
+
+ if ((ta_uint64)ta_fb.width * 600 >= (ta_uint64)ta_fb.height * 800) {
+ numerator = (ta_uint32)ta_fb.height;
+ denominator = 600;
+ }
+
+ ta_uint32 font_size = (TA_BUTTON_FONT_SIZE * numerator * 2 + denominator) / (denominator * 2);
+ ta_int32 spacing = (ta_int32)((TA_BUTTON_SPACING * numerator * 2 + denominator) / (denominator * 2));
if (font_size == 0) {
font_size = 1;
@@ -127,10 +132,10 @@ void ta_button_lists_draw(const ta_button_lists *list) {
spacing = 1;
}
- double progress = 1.0;
+ ta_int32 progress = 512;
if (list->animation_active) {
- progress = (double)list->animation_frame / TA_BUTTON_ANIMATION_FRAMES;
- progress = progress * progress * (3.0 - 2.0 * progress);
+ ta_int32 frame = (ta_int32)list->animation_frame;
+ progress = frame * frame * (24 - 2 * frame);
}
ta_int32 center_y = (ta_int32)(ta_fb.height / 2);
@@ -143,13 +148,13 @@ void ta_button_lists_draw(const ta_button_lists *list) {
ta_text_measure(list->buttons[i].label, font_size, &text_width, &text_height);
ta_int32 row_from = (ta_int32)i - (ta_int32)list->animation_from;
- ta_int32 y = center_y + (ta_int32)(row_from * spacing - selected_shift * spacing * progress) - (ta_int32)(text_height / 2);
+ ta_int32 y = center_y + (row_from * spacing * 512 - selected_shift * spacing * progress) / 512 - (ta_int32)(text_height / 2);
if (y < 0 || (ta_size)y >= ta_fb.height || text_width > ta_fb.width) {
continue;
}
- double selection = i == list->animation_from ? 1.0 - progress : 0.0;
+ ta_int32 selection = i == list->animation_from ? 512 - progress : 0;
if (i == list->selected) {
selection += progress;
}
@@ -157,9 +162,9 @@ void ta_button_lists_draw(const ta_button_lists *list) {
ta_uint32 pulse_position = list->pulse_frame;
ta_uint32 pulse_distance = pulse_position <= TA_BUTTON_PULSE_HALF_FRAMES ? pulse_position : TA_BUTTON_PULSE_HALF_FRAMES * 2 - pulse_position;
ta_uint8 pulse_blue = (ta_uint8)(TA_BUTTON_COLOR_SELECTED - ((TA_BUTTON_COLOR_SELECTED - TA_BUTTON_COLOR_PULSE_BLUE_MIN) * pulse_distance / TA_BUTTON_PULSE_HALF_FRAMES));
- ta_uint8 red = (ta_uint8)(TA_BUTTON_COLOR_IDLE + (TA_BUTTON_COLOR_SELECTED - TA_BUTTON_COLOR_IDLE) * selection + 0.5);
+ ta_uint8 red = (ta_uint8)((TA_BUTTON_COLOR_IDLE * 512 + (TA_BUTTON_COLOR_SELECTED - TA_BUTTON_COLOR_IDLE) * selection + 256) / 512);
ta_uint8 green = red;
- ta_uint8 blue = (ta_uint8)(TA_BUTTON_COLOR_IDLE + (pulse_blue - TA_BUTTON_COLOR_IDLE) * selection + 0.5);
+ ta_uint8 blue = (ta_uint8)((TA_BUTTON_COLOR_IDLE * 512 + ((ta_int32)pulse_blue - TA_BUTTON_COLOR_IDLE) * selection + 256) / 512);
ta_uint32 x = (ta_uint32)(ta_fb.width - text_width) / 2;
ta_text_draw_outline(list->buttons[i].label, x, (ta_uint32)y, text_width, text_height, red, green, blue, 1, 0, 0, 0);
diff --git a/src/ta_image.c b/src/ta_image.c
@@ -5,6 +5,17 @@
#define TAIF_FLAG_ANIMATED 0x04
#define TAIF_WINDOW_SIZE 4096
+typedef struct {
+ ta_uint8 window[TAIF_WINDOW_SIZE];
+ ta_uint32 stream_position;
+ ta_uint32 window_position;
+ ta_uint32 filled;
+ ta_uint32 match_distance;
+ ta_uint32 match_remaining;
+ ta_uint8 item_flags;
+ ta_uint8 flags_remaining;
+} ta_image_decoder;
+
typedef struct ta_image_cache {
const ta_uint8 *data;
ta_uint32 length;
@@ -13,10 +24,13 @@ typedef struct ta_image_cache {
ta_uint32 stride;
ta_uint32 frame_size;
ta_uint16 frame_count;
- ta_uint16 requested_frame;
ta_uint16 palette_entries;
+ ta_uint32 frames_decoded;
ta_uint8 flags;
ta_uint8 palette[256][3];
+ const ta_uint8 *stream;
+ ta_uint32 stream_length;
+ ta_image_decoder decoder;
ta_uint8 *pixels;
ta_uint8 *scaled;
ta_uint32 scaled_width;
@@ -31,7 +45,7 @@ static ta_uint16 read_le16(const ta_uint8 *data) {
return (ta_uint16)((ta_uint16)data[0] | ((ta_uint16)data[1] << 8));
}
-static ta_bool decode_image(const ta_uint8 *data, ta_uint32 length, ta_uint16 requested_frame, ta_image_cache *image) {
+static ta_bool parse_image(const ta_uint8 *data, ta_uint32 length, ta_image_cache *image) {
if (data == 0 || length < 11 || data[0] != 'T' || data[1] != 'A' || data[2] != 'I' || data[3] != 'F' || data[4] != 1) {
return ta_false;
}
@@ -67,174 +81,160 @@ static ta_bool decode_image(const ta_uint8 *data, ta_uint32 length, ta_uint16 re
header_size = 14;
}
- if (requested_frame >= frame_count) {
- return ta_false;
- }
-
ta_uint64 stride = eight_bpp ? width : (width + 1) / 2;
ta_uint64 frame_size = stride * height;
ta_uint64 max_size = (ta_uint64)(ta_size)-1;
ta_uint64 palette_size = (ta_uint64)palette_entries * 3;
- if (frame_size == 0 || frame_size > max_size || header_size + palette_size > length) {
+ if (frame_size == 0 || frame_size > max_size || frame_size > 0xffffffffu || header_size + palette_size > length) {
return ta_false;
}
const ta_uint8 *palette = data + header_size;
- const ta_uint8 *stream = palette + palette_size;
- ta_uint32 stream_length = length - (ta_uint32)(header_size + palette_size);
- ta_uint8 *previous = (ta_uint8 *)ta_malloc((ta_size)frame_size);
- ta_uint8 *current = (ta_uint8 *)ta_malloc((ta_size)frame_size);
- ta_uint8 *decoded_frame = (ta_uint8 *)ta_malloc((ta_size)frame_size);
-
- if (previous == 0 || current == 0 || decoded_frame == 0) {
- ta_free(previous);
- ta_free(current);
- ta_free(decoded_frame);
+
+ image->pixels = (ta_uint8 *)ta_malloc((ta_size)frame_size);
+ if (image->pixels == 0) {
return ta_false;
}
for (ta_uint32 i = 0; i < (ta_uint32)frame_size; i++) {
- previous[i] = 0;
+ image->pixels[i] = 0;
}
- ta_uint8 window[TAIF_WINDOW_SIZE];
- ta_uint32 stream_position = 0;
- ta_uint32 window_position = 0;
- ta_uint64 produced = 0;
- ta_uint64 total_size = frame_size * frame_count;
- ta_bool valid = ta_true;
- ta_uint32 frame_position = 0;
- ta_uint32 frame_index = 0;
- ta_uint32 match_distance = 0;
- ta_uint32 match_remaining = 0;
- ta_uint8 item_flags = 0;
- ta_uint8 flags_remaining = 0;
-
- while (produced < total_size && valid) {
+ image->data = data;
+ image->length = length;
+ image->width = width;
+ image->height = height;
+ image->stride = (ta_uint32)stride;
+ image->frame_size = (ta_uint32)frame_size;
+ image->frame_count = frame_count;
+ image->palette_entries = (ta_uint16)palette_entries;
+ image->flags = flags;
+ image->stream = palette + palette_size;
+ image->stream_length = length - (ta_uint32)(header_size + palette_size);
+
+ for (ta_uint32 i = 0; i < palette_entries; i++) {
+ for (ta_uint32 channel = 0; channel < 3; channel++) {
+ image->palette[i][channel] = palette[i * 3 + channel];
+ }
+ }
+
+ return ta_true;
+}
+
+static void decoder_reset(ta_image_cache *image) {
+ ta_image_decoder *decoder = &image->decoder;
+
+ decoder->stream_position = 0;
+ decoder->window_position = 0;
+ decoder->filled = 0;
+ decoder->match_distance = 0;
+ decoder->match_remaining = 0;
+ decoder->item_flags = 0;
+ decoder->flags_remaining = 0;
+
+ for (ta_uint32 i = 0; i < image->frame_size; i++) {
+ image->pixels[i] = 0;
+ }
+
+ ta_free(image->scaled);
+
+ image->scaled = 0;
+ image->frames_decoded = 0;
+}
+
+static ta_bool decode_next_frame(ta_image_cache *image) {
+ ta_image_decoder *decoder = &image->decoder;
+ const ta_uint8 *stream = image->stream;
+ ta_uint32 stream_length = image->stream_length;
+ ta_uint32 frame_size = image->frame_size;
+ ta_uint32 position = 0;
+
+ while (position < frame_size) {
ta_uint8 value;
- if (match_remaining != 0) {
- ta_uint32 source_position = (window_position + TAIF_WINDOW_SIZE - match_distance) & (TAIF_WINDOW_SIZE - 1);
- value = window[source_position];
- match_remaining--;
+
+ if (decoder->match_remaining != 0) {
+ ta_uint32 source_position = (decoder->window_position + TAIF_WINDOW_SIZE - decoder->match_distance) & (TAIF_WINDOW_SIZE - 1);
+
+ value = decoder->window[source_position];
+ decoder->match_remaining--;
} else {
- if (flags_remaining == 0) {
- if (stream_position >= stream_length) {
- valid = ta_false;
- break;
+ if (decoder->flags_remaining == 0) {
+ if (decoder->stream_position >= stream_length) {
+ return ta_false;
}
- item_flags = stream[stream_position++];
- flags_remaining = 8;
+ decoder->item_flags = stream[decoder->stream_position++];
+ decoder->flags_remaining = 8;
}
- ta_bool literal = (item_flags & 1) != 0;
- item_flags >>= 1;
- flags_remaining--;
+ ta_bool literal = (decoder->item_flags & 1) != 0;
+
+ decoder->item_flags >>= 1;
+ decoder->flags_remaining--;
if (literal) {
- if (stream_position >= stream_length) {
- valid = ta_false;
-
- break;
+ if (decoder->stream_position >= stream_length) {
+ return ta_false;
}
- value = stream[stream_position++];
+ value = stream[decoder->stream_position++];
} else {
- if (stream_length - stream_position < 2) {
- valid = ta_false;
- break;
+ if (stream_length - decoder->stream_position < 2) {
+ return ta_false;
}
- ta_uint8 b0 = stream[stream_position++];
- ta_uint8 b1 = stream[stream_position++];
- match_distance = ((((ta_uint32)b1 >> 4) << 8) | b0) + 1;
- match_remaining = (b1 & 0x0f) + 3;
-
- if (match_distance > produced) {
- valid = ta_false;
- break;
+ ta_uint8 b0 = stream[decoder->stream_position++];
+ ta_uint8 b1 = stream[decoder->stream_position++];
+
+ decoder->match_distance = ((((ta_uint32)b1 >> 4) << 8) | b0) + 1;
+ decoder->match_remaining = (b1 & 0x0f) + 3;
+
+ if (decoder->match_distance > decoder->filled) {
+ return ta_false;
}
-
+
continue;
}
}
- current[frame_position++] = value;
- window[window_position] = value;
- window_position = (window_position + 1) & (TAIF_WINDOW_SIZE - 1);
- produced++;
+ image->pixels[position++] ^= value;
+ decoder->window[decoder->window_position] = value;
+ decoder->window_position = (decoder->window_position + 1) & (TAIF_WINDOW_SIZE - 1);
- if (frame_position == (ta_uint32)frame_size) {
- for (ta_uint32 i = 0; i < (ta_uint32)frame_size; i++) {
- if ((flags & TAIF_FLAG_ANIMATED) != 0) {
- current[i] ^= previous[i];
- }
- }
+ if (decoder->filled < TAIF_WINDOW_SIZE) {
+ decoder->filled++;
+ }
+ }
- for (ta_uint32 row = 0; row < height && valid; row++) {
- for (ta_uint32 column = 0; column < width; column++) {
- ta_uint8 index;
-
- if (eight_bpp) {
- index = current[row * (ta_uint32)stride + column];
- } else {
- ta_uint8 packed = current[row * (ta_uint32)stride + column / 2];
- index = (column & 1) == 0 ? packed >> 4 : packed & 0x0f;
- }
-
- if ((transparent && index != 0 && index > palette_entries) || (!transparent && index >= palette_entries)) {
- valid = ta_false;
-
- break;
- }
- }
- }
+ ta_bool eight_bpp = (image->flags & TAIF_FLAG_8BPP) != 0;
+ ta_bool transparent = (image->flags & TAIF_FLAG_TRANSPARENT) != 0;
+ ta_uint32 max_index = transparent ? image->palette_entries : (ta_uint32)image->palette_entries - 1;
+ ta_uint32 full_bytes = eight_bpp ? image->width : image->width / 2;
+ ta_bool odd_column = !eight_bpp && (image->width & 1) != 0;
- if (!valid) {
- break;
- }
+ for (ta_uint32 row = 0; row < image->height; row++) {
+ const ta_uint8 *line = image->pixels + (ta_size)row * image->stride;
- if (frame_index == requested_frame) {
- for (ta_uint32 i = 0; i < (ta_uint32)frame_size; i++) {
- decoded_frame[i] = current[i];
+ for (ta_uint32 i = 0; i < full_bytes; i++) {
+ if (eight_bpp) {
+ if (line[i] > max_index) {
+ return ta_false;
}
+ } else if ((ta_uint32)(line[i] >> 4) > max_index || (ta_uint32)(line[i] & 0x0f) > max_index) {
+ return ta_false;
}
-
- ta_uint8 *swap = previous;
- previous = current;
- current = swap;
- frame_index++;
- frame_position = 0;
}
- }
-
- ta_free(previous);
- ta_free(current);
- if (!valid || produced != total_size) {
- ta_free(decoded_frame);
-
- return ta_false;
+ if (odd_column && (ta_uint32)(line[full_bytes] >> 4) > max_index) {
+ return ta_false;
+ }
}
- image->data = data;
- image->length = length;
- image->width = width;
- image->height = height;
- image->stride = (ta_uint32)stride;
- image->frame_size = (ta_uint32)frame_size;
- image->frame_count = frame_count;
- image->requested_frame = requested_frame;
- image->palette_entries = (ta_uint16)palette_entries;
- image->flags = flags;
- image->pixels = decoded_frame;
+ ta_free(image->scaled);
- for (ta_uint32 i = 0; i < palette_entries; i++) {
- for (ta_uint32 channel = 0; channel < 3; channel++) {
- image->palette[i][channel] = palette[i * 3 + channel];
- }
- }
+ image->scaled = 0;
+ image->frames_decoded++;
return ta_true;
}
@@ -260,7 +260,14 @@ ta_bool ta_image_init(const ta_uint8 *data, ta_uint32 length) {
}
*image = (ta_image_cache){0};
- if (!decode_image(data, length, 0, image)) {
+
+ if (!parse_image(data, length, image)) {
+ ta_free(image);
+ return ta_false;
+ }
+
+ if (!decode_next_frame(image)) {
+ ta_free(image->pixels);
ta_free(image);
return ta_false;
}
@@ -288,23 +295,22 @@ static ta_image_cache *get_image(const ta_uint8 *data, ta_uint32 length, ta_uint
}
ta_image_cache *image = find_image(data, length);
- if (image->requested_frame == frame) {
- return image;
- }
-
- ta_image_cache decoded = {0};
- if (!decode_image(data, length, frame, &decoded)) {
+ if (frame >= image->frame_count) {
return 0;
}
- ta_uint8 *old_pixels = image->pixels;
- ta_uint8 *old_scaled = image->scaled;
- ta_image_cache *next = image->next;
- *image = decoded;
- image->next = next;
+ ta_uint32 wanted = (ta_uint32)frame + 1;
- ta_free(old_pixels);
- ta_free(old_scaled);
+ if (image->frames_decoded > wanted) {
+ decoder_reset(image);
+ }
+
+ while (image->frames_decoded < wanted) {
+ if (!decode_next_frame(image)) {
+ decoder_reset(image);
+ return 0;
+ }
+ }
return image;
}
@@ -476,15 +482,39 @@ static ta_bool prepare_scaled(ta_image_cache *image, ta_uint32 width, ta_uint32
return ta_true;
}
-ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, float x, float y, float width, float height, ta_uint16 frame) {
- if (data == 0 || length == 0 || x != x || y != y || width != width || height != height || x < 0.0f || y < 0.0f || x > 1.0f || y > 1.0f || width <= 0.0f || height <= 0.0f || width > 1.0f || height > 1.0f || ta_fb.buffers[1] == 0 || (ta_fb.bpp != 8 && ta_fb.bpp != 32)) {
+ta_bool ta_image_prepare(const ta_uint8 *data, ta_uint32 length, ta_fixed width, ta_fixed height, ta_uint16 frame) {
+ if (data == 0 || length == 0 || width == 0 || height == 0 || width > TA_FIXED_ONE || height > TA_FIXED_ONE || ta_fb.buffers[1] == 0 || (ta_fb.bpp != 8 && ta_fb.bpp != 32)) {
+ return ta_false;
+ }
+
+ ta_uint32 pixel_width = (ta_uint32)(((ta_uint64)width * ta_fb.width) >> 16);
+ ta_uint32 pixel_height = (ta_uint32)(((ta_uint64)height * ta_fb.height) >> 16);
+
+ if (pixel_width == 0 || pixel_height == 0) {
+ return ta_false;
+ }
+
+ ta_image_cache *image = get_image(data, length, frame);
+ if (image == 0) {
+ return ta_false;
+ }
+
+ if ((image->flags & TAIF_FLAG_TRANSPARENT) != 0) {
+ return ta_true;
+ }
+
+ return prepare_scaled(image, pixel_width, pixel_height);
+}
+
+ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fixed y, ta_fixed width, ta_fixed height, ta_uint16 frame) {
+ if (data == 0 || length == 0 || x > TA_FIXED_ONE || y > TA_FIXED_ONE || width == 0 || height == 0 || width > TA_FIXED_ONE || height > TA_FIXED_ONE || ta_fb.buffers[1] == 0 || (ta_fb.bpp != 8 && ta_fb.bpp != 32)) {
return ta_false;
}
- ta_uint32 pixel_x = (ta_uint32)((double)x * ta_fb.width);
- ta_uint32 pixel_y = (ta_uint32)((double)y * ta_fb.height);
- ta_uint32 pixel_width = (ta_uint32)((double)width * ta_fb.width);
- ta_uint32 pixel_height = (ta_uint32)((double)height * ta_fb.height);
+ ta_uint32 pixel_x = (ta_uint32)(((ta_uint64)x * ta_fb.width) >> 16);
+ ta_uint32 pixel_y = (ta_uint32)(((ta_uint64)y * ta_fb.height) >> 16);
+ ta_uint32 pixel_width = (ta_uint32)(((ta_uint64)width * ta_fb.width) >> 16);
+ ta_uint32 pixel_height = (ta_uint32)(((ta_uint64)height * ta_fb.height) >> 16);
if (pixel_width == 0 || pixel_height == 0) {
return ta_false;
diff --git a/src/ta_text.c b/src/ta_text.c
@@ -24,6 +24,10 @@ typedef struct {
ta_uint8 border_red;
ta_uint8 border_green;
ta_uint8 border_blue;
+ ta_uint8 *mask;
+ ta_int32 mask_x0;
+ ta_int32 mask_y0;
+ ta_int32 mask_stride;
} ta_text_ctx;
typedef struct {
@@ -534,6 +538,19 @@ static void put_pixel(const ta_text_ctx *ctx, ta_int32 pixel_x, ta_int32 pixel_y
}
}
+static void emit_pixel(const ta_text_ctx *ctx, ta_int32 pixel_x, ta_int32 pixel_y, ta_uint32 alpha) {
+ if (ctx->mask != 0) {
+ ta_uint8 *target = ctx->mask + (ta_size)(pixel_y - ctx->mask_y0) * (ta_size)ctx->mask_stride + (ta_size)(pixel_x - ctx->mask_x0);
+ ta_uint32 old = *target;
+
+ *target = (ta_uint8)(old + alpha - DIV255(old * alpha));
+
+ return;
+ }
+
+ put_pixel(ctx, pixel_x, pixel_y, alpha);
+}
+
static ta_text_pt transform_point(ta_int32 font_x, ta_int32 font_y, ta_int32 origin_x, ta_int32 origin_y, ta_int32 scale) {
ta_text_pt point;
@@ -790,7 +807,7 @@ static void draw_glyph(const ta_text_ctx *ctx, ta_uint32 glyph_id, ta_int32 pen_
ta_uint32 alpha = ((ta_uint32)amount * 255 + 0x8000) >> 16;
if (alpha) {
- put_pixel(ctx, region_x0 + column, region_y0 + row_index, alpha);
+ emit_pixel(ctx, region_x0 + column, region_y0 + row_index, alpha);
}
}
}
@@ -874,7 +891,7 @@ static void draw_text_pass(const char *text, ta_int32 x, ta_int32 y, const ta_te
}
}
-static void draw_text(const char *text, ta_int32 x, ta_int32 y, const ta_text_ctx *ctx) {
+static void draw_text_direct(const char *text, ta_int32 x, ta_int32 y, const ta_text_ctx *ctx) {
if (ctx->border_thickness != 0) {
ta_text_ctx border_ctx = *ctx;
@@ -897,6 +914,184 @@ static void draw_text(const char *text, ta_int32 x, ta_int32 y, const ta_text_ct
draw_text_pass(text, x, y, ctx);
}
+#define TEXT_CACHE_ENTRIES 8
+#define TEXT_CACHE_MAX_TEXT 64
+#define TEXT_CACHE_MAX_MASK 262144
+
+typedef struct {
+ char text[TEXT_CACHE_MAX_TEXT + 1];
+ ta_int32 scale;
+ ta_int32 x0;
+ ta_int32 y0;
+ ta_int32 width;
+ ta_int32 height;
+ ta_uint8 *mask;
+} ta_text_entry;
+
+static ta_text_entry text_cache[TEXT_CACHE_ENTRIES];
+static ta_uint32 text_cache_next;
+
+static ta_bool same_text(const char *a, const char *b) {
+ while (*a && *a == *b) {
+ a++;
+ b++;
+ }
+
+ return *a == *b;
+}
+
+static ta_text_entry *text_cache_build(const char *text, ta_int32 scale) {
+ ta_uint32 length = 0;
+
+ while (text[length]) {
+ if (++length > TEXT_CACHE_MAX_TEXT) {
+ return 0;
+ }
+ }
+
+ ta_uint32 units;
+ ta_uint32 lines;
+
+ measure_units(text, &units, &lines);
+
+ ta_int32 padding = (ta_int32)(((ta_uint64)font.units_per_em * (ta_uint64)scale) >> 16) / 2 + 2;
+ ta_int32 text_width = (ta_int32)(((ta_uint64)units * (ta_uint64)scale + (ONE - 1)) >> 16);
+ ta_int32 text_height = (ta_int32)(((ta_uint64)lines * line_height_units() * (ta_uint64)scale + (ONE - 1)) >> 16);
+ ta_int32 width = text_width + padding * 2;
+ ta_int32 height = text_height + padding * 2;
+
+ if (width <= 0 || height <= 0 || (ta_uint64)width * (ta_uint64)height > TEXT_CACHE_MAX_MASK) {
+ return 0;
+ }
+
+ ta_size mask_size = (ta_size)width * (ta_size)height;
+ ta_uint8 *mask = (ta_uint8 *)ta_malloc(mask_size);
+
+ if (mask == 0) {
+ return 0;
+ }
+
+ for (ta_size i = 0; i < mask_size; i++) {
+ mask[i] = 0;
+ }
+
+ ta_text_ctx ctx;
+
+ ctx.scale = scale;
+ ctx.clip_x0 = -padding;
+ ctx.clip_y0 = -padding;
+ ctx.clip_x1 = -padding + width;
+ ctx.clip_y1 = -padding + height;
+ ctx.red = 0;
+ ctx.green = 0;
+ ctx.blue = 0;
+ ctx.border_thickness = 0;
+ ctx.border_red = 0;
+ ctx.border_green = 0;
+ ctx.border_blue = 0;
+ ctx.mask = mask;
+ ctx.mask_x0 = -padding;
+ ctx.mask_y0 = -padding;
+ ctx.mask_stride = width;
+
+ draw_text_pass(text, 0, 0, &ctx);
+
+ ta_text_entry *entry = &text_cache[text_cache_next];
+
+ text_cache_next = (text_cache_next + 1) % TEXT_CACHE_ENTRIES;
+
+ ta_free(entry->mask);
+
+ for (ta_uint32 i = 0; i <= length; i++) {
+ entry->text[i] = text[i];
+ }
+
+ entry->scale = scale;
+ entry->x0 = -padding;
+ entry->y0 = -padding;
+ entry->width = width;
+ entry->height = height;
+ entry->mask = mask;
+
+ return entry;
+}
+
+static ta_text_entry *text_cache_get(const char *text, ta_int32 scale) {
+ for (ta_uint32 i = 0; i < TEXT_CACHE_ENTRIES; i++) {
+ if (text_cache[i].mask != 0 && text_cache[i].scale == scale && same_text(text_cache[i].text, text)) {
+ return &text_cache[i];
+ }
+ }
+
+ return text_cache_build(text, scale);
+}
+
+static void composite_mask(const ta_text_entry *entry, const ta_text_ctx *ctx, ta_int32 x, ta_int32 y) {
+ ta_int32 destination_x = x + entry->x0;
+ ta_int32 destination_y = y + entry->y0;
+ ta_int32 column_start = ctx->clip_x0 - destination_x;
+ ta_int32 column_end = ctx->clip_x1 - destination_x;
+ ta_int32 row_start = ctx->clip_y0 - destination_y;
+ ta_int32 row_end = ctx->clip_y1 - destination_y;
+
+ if (column_start < 0) {
+ column_start = 0;
+ }
+
+ if (row_start < 0) {
+ row_start = 0;
+ }
+
+ if (column_end > entry->width) {
+ column_end = entry->width;
+ }
+
+ if (row_end > entry->height) {
+ row_end = entry->height;
+ }
+
+ for (ta_int32 row = row_start; row < row_end; row++) {
+ const ta_uint8 *source = entry->mask + (ta_size)row * (ta_size)entry->width;
+
+ for (ta_int32 column = column_start; column < column_end; column++) {
+ if (source[column] != 0) {
+ put_pixel(ctx, destination_x + column, destination_y + row, source[column]);
+ }
+ }
+ }
+}
+
+static void draw_text(const char *text, ta_int32 x, ta_int32 y, const ta_text_ctx *ctx) {
+ const ta_text_entry *entry = text_cache_get(text, ctx->scale);
+
+ if (entry == 0) {
+ draw_text_direct(text, x, y, ctx);
+
+ return;
+ }
+
+ if (ctx->border_thickness != 0) {
+ ta_text_ctx border_ctx = *ctx;
+
+ border_ctx.red = ctx->border_red;
+ border_ctx.green = ctx->border_green;
+ border_ctx.blue = ctx->border_blue;
+ ta_int32 thickness = ctx->border_thickness;
+
+ for (ta_int32 offset_y = -thickness; offset_y <= thickness; offset_y++) {
+ for (ta_int32 offset_x = -thickness; offset_x <= thickness; offset_x++) {
+ if (offset_x == 0 && offset_y == 0) {
+ continue;
+ }
+
+ composite_mask(entry, &border_ctx, x + offset_x, y + offset_y);
+ }
+ }
+ }
+
+ composite_mask(entry, ctx, x, y);
+}
+
static void draw_text_fitted(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 width, ta_uint32 height, ta_uint8 red, ta_uint8 green, ta_uint8 blue, ta_uint8 border_thickness, ta_uint8 border_red, ta_uint8 border_green, ta_uint8 border_blue) {
if (!text || width == 0 || height == 0 || !fb_usable() || !font_ready()) {
return;
@@ -945,6 +1140,7 @@ static void draw_text_fitted(const char *text, ta_uint32 x, ta_uint32 y, ta_uint
ctx.border_red = border_red;
ctx.border_green = border_green;
ctx.border_blue = border_blue;
+ ctx.mask = 0;
draw_text(text, (ta_int32)x, (ta_int32)y, &ctx);
}
@@ -990,6 +1186,7 @@ void ta_text_draw_size(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 siz
ctx.border_red = 0;
ctx.border_green = 0;
ctx.border_blue = 0;
+ ctx.mask = 0;
draw_text(text, (ta_int32)x, (ta_int32)y, &ctx);
}