From 3b7e1eaef85bbd18f7abb40acd03488cd310ab6f Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Fri, 22 Dec 2023 00:03:52 +0800 Subject: [PATCH] example: implement font_outline This still needs to be cleaned up, particularly to properly pass the texture size around--there are a few unnecessary '128x256' magic numbers scattered in the code. --- common.mk | 2 +- dejavusansmono.data | Bin 0 -> 34700 bytes dejavusansmono.hpp | 5 + example/example.mk | 13 ++ example/font_bitmap.cpp | 2 +- example/font_outline.cpp | 280 +++++++++++++++++++++++++++++++++++++++ font/font.hpp | 39 ++++++ font/font_draw.cpp | 31 +++++ tools/2d-pack.cpp | 113 ++++++++++++++++ tools/2d-pack.hpp | 5 + tools/Makefile | 23 ++++ tools/insertion_sort.hpp | 17 +++ tools/rect.hpp | 16 +++ tools/sizes.py | 98 ++++++++++++++ tools/ttf-outline.cpp | 254 +++++++++++++++++++++++++++++++++++ tools/z-curve-test.py | 172 ++++++++++++++++++++++++ twiddle.hpp | 40 +++--- 17 files changed, 1090 insertions(+), 20 deletions(-) create mode 100644 dejavusansmono.data create mode 100644 dejavusansmono.hpp create mode 100644 example/font_outline.cpp create mode 100644 font/font.hpp create mode 100644 font/font_draw.cpp create mode 100644 tools/2d-pack.cpp create mode 100644 tools/2d-pack.hpp create mode 100644 tools/Makefile create mode 100644 tools/insertion_sort.hpp create mode 100644 tools/rect.hpp create mode 100644 tools/sizes.py create mode 100644 tools/ttf-outline.cpp create mode 100644 tools/z-curve-test.py diff --git a/common.mk b/common.mk index 3145c60..8bbee2b 100644 --- a/common.mk +++ b/common.mk @@ -12,7 +12,7 @@ AFLAGS = --fatal-warnings CARCH = -m4-single-only -ml CFLAGS += -falign-functions=4 -ffunction-sections -fdata-sections -fshort-enums -ffreestanding -nostdlib CFLAGS += -Wall -Werror -Wfatal-errors -CFLAGS += -Wno-error=narrowing -Wno-error=unused-variable +CFLAGS += -Wno-error=narrowing -Wno-error=unused-variable -Wno-error=array-bounds= -Wno-array-bounds CFLAGS += -mfsca -funsafe-math-optimizations CFLAGS += -I$(dir $(MAKEFILE_PATH)) DEPFLAGS = -MMD -E diff --git a/dejavusansmono.data b/dejavusansmono.data new file mode 100644 index 0000000000000000000000000000000000000000..fff4ab5a59301fdbba93432f50663e4e609e74a6 GIT binary patch literal 34700 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 literal 0 HcmV?d00001 diff --git a/dejavusansmono.hpp b/dejavusansmono.hpp new file mode 100644 index 0000000..9a2fb08 --- /dev/null +++ b/dejavusansmono.hpp @@ -0,0 +1,5 @@ +#include + +extern uint32_t _binary_dejavusansmono_data_start __asm("_binary_dejavusansmono_data_start"); +extern uint32_t _binary_dejavusansmono_data_end __asm("_binary_dejavusansmono_data_end"); +extern uint32_t _binary_dejavusansmono_data_size __asm("_binary_dejavusansmono_data_size"); diff --git a/example/example.mk b/example/example.mk index 014c764..c3c942c 100644 --- a/example/example.mk +++ b/example/example.mk @@ -46,6 +46,19 @@ FONT_BITMAP_OBJ = \ example/font_bitmap.elf: LDSCRIPT = $(LIB)/alt.lds example/font_bitmap.elf: $(START_OBJ) $(FONT_BITMAP_OBJ) +FONT_OUTLINE_OBJ = \ + example/font_outline.o \ + vga.o \ + holly/core.o \ + holly/region_array.o \ + holly/background.o \ + holly/ta_fifo_polygon_converter.o \ + serial.o \ + dejavusansmono.data.o + +example/font_outline.elf: LDSCRIPT = $(LIB)/alt.lds +example/font_outline.elf: $(START_OBJ) $(FONT_OUTLINE_OBJ) + MACAW_MULTIPASS_OBJ = \ example/macaw_multipass.o \ vga.o \ diff --git a/example/font_bitmap.cpp b/example/font_bitmap.cpp index b1feb1c..0065951 100644 --- a/example/font_bitmap.cpp +++ b/example/font_bitmap.cpp @@ -189,7 +189,7 @@ inline void inflate_character(const uint8_t * src, const uint8_t c) } */ - twiddle::texture2<4>(&texture[offset / 4], temp, 8, 8); + twiddle::texture2<4>(&texture[offset / 4], temp, 8, 8, 0, 0); } void inflate_font(const uint8_t * src) diff --git a/example/font_outline.cpp b/example/font_outline.cpp new file mode 100644 index 0000000..aa7ea42 --- /dev/null +++ b/example/font_outline.cpp @@ -0,0 +1,280 @@ +#include + +#include "align.hpp" + +#include "vga.hpp" +#include "holly.hpp" +#include "holly/core.hpp" +#include "holly/core_bits.hpp" +#include "holly/ta_parameter.hpp" +#include "holly/ta_fifo_polygon_converter.hpp" +#include "holly/texture_memory_alloc.hpp" +#include "memorymap.hpp" +#include "holly/background.hpp" +#include "holly/region_array.hpp" +#include "holly/ta_bits.hpp" +#include "twiddle.hpp" +#include "serial.hpp" + +#include "font/font.hpp" +#include "dejavusansmono.hpp" + +#include "sperrypc.hpp" + +struct vertex { + float x; + float y; + float z; + float u; + float v; +}; + +/* +// screen space coordinates +const struct vertex quad_verticies[4] = { + { 0.f, 64.f, 0.01f, 0.f, 1.f }, + { 0.f, 0.f, 0.01f, 0.f, 0.f }, + { 64.f, 0.f, 0.01f, 1.f, 0.f }, + { 64.f, 64.f, 0.01f, 1.f, 1.f, }, +}; + +uint32_t transform(uint32_t * ta_parameter_buf) +{ + auto parameter = ta_parameter_writer(ta_parameter_buf); + uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture)); + constexpr uint32_t base_color = 0xffffffff; + auto sprite = global_sprite(base_color); + sprite.parameter_control_word = para_control::para_type::sprite + | para_control::list_type::opaque + | obj_control::col_type::packed_color + | obj_control::texture + | obj_control::_16bit_uv; + sprite.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog + | tsp_instruction_word::texture_u_size::_8 // 8px + | tsp_instruction_word::texture_v_size::_8; // 8px + sprite.texture_control_word = texture_control_word::pixel_format::_565 + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address(texture_address / 8); + parameter.append() = sprite; + + parameter.append() = + vertex_sprite_type_1(quad_verticies[0].x, + quad_verticies[0].y, + quad_verticies[0].z, + quad_verticies[1].x, + quad_verticies[1].y, + quad_verticies[1].z, + quad_verticies[2].x, + quad_verticies[2].y, + quad_verticies[2].z, + quad_verticies[3].x, + quad_verticies[3].y, + uv_16bit(quad_verticies[0].u, quad_verticies[0].v), + uv_16bit(quad_verticies[1].u, quad_verticies[1].v), + uv_16bit(quad_verticies[2].u, quad_verticies[2].v)); + // curiously, there is no `dz` in vertex_sprite_type_1 + // curiously, there is no `du_dv` in vertex_sprite_type_1 + + parameter.append() = global_end_of_list(); + + return parameter.offset; +} +*/ + +const struct vertex strip_vertices[4] = { + // [ position ] [ uv coordinates ] + { 0.f, 1.f, 0.f, 0.f, 1.f, }, + { 0.f, 0.f, 0.f, 0.f, 0.f, }, + { 1.f, 1.f, 0.f, 1.f, 1.f, }, + { 1.f, 0.f, 0.f, 1.f, 0.f, }, +}; +constexpr uint32_t strip_length = (sizeof (strip_vertices)) / (sizeof (struct vertex)); + +uint32_t transform(ta_parameter_writer& parameter, + const uint32_t first_char_code, const glyph * glyphs, + const char * s, const uint32_t len, + const uint32_t y_offset) +{ + uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture)); + + uint32_t advance = 0; // in 26.6 fixed-point + + for (uint32_t string_ix = 0; string_ix < len; string_ix++) { + auto polygon = global_polygon_type_0(texture_address); + polygon.parameter_control_word = para_control::para_type::polygon_or_modifier_volume + | para_control::list_type::opaque + | obj_control::col_type::packed_color + | obj_control::texture; + + polygon.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog + | tsp_instruction_word::texture_u_size::_128 + | tsp_instruction_word::texture_v_size::_256; + + polygon.texture_control_word = texture_control_word::pixel_format::_8bpp_palette + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address(texture_address / 8); + parameter.append() = polygon; + + char c = s[string_ix]; + auto& glyph = glyphs[c - first_char_code]; + + for (uint32_t i = 0; i < strip_length; i++) { + bool end_of_strip = i == strip_length - 1; + + float x = strip_vertices[i].x; + float y = strip_vertices[i].y; + float z = strip_vertices[i].z; + + x *= glyph.bitmap.width; + y *= glyph.bitmap.height; + x += 100.f + ((advance + glyph.metrics.horiBearingX) >> 6); + y += 200.f - ((glyph.metrics.horiBearingY) >> 6); + y += y_offset >> 6; + z = 1.f / (z + 10.f); + + float u = strip_vertices[i].u; + float v = strip_vertices[i].v; + u *= glyph.bitmap.width; + v *= glyph.bitmap.height; + u += glyph.bitmap.x; + v += glyph.bitmap.y; + u = u / 128.f; + v = v / 256.f; + + parameter.append() = + vertex_polygon_type_3(x, y, z, + u, v, + 0x00000000, // base_color + end_of_strip); + } + + advance += glyph.metrics.horiAdvance; + } + + return parameter.offset; +} + + +void init_texture_memory(const struct opb_size& opb_size) +{ + auto mem = reinterpret_cast(texture_memory32); + + background_parameter(mem->background, 0xff0000ff); + + region_array2(mem->region_array, + (offsetof (struct texture_memory_alloc, object_list)), + 640 / 32, // width + 480 / 32, // height + opb_size + ); +} + +void inflate_font(const uint32_t * src, const uint32_t size) +{ + auto mem = reinterpret_cast(texture_memory64); + auto texture = reinterpret_cast(mem->texture); + + for (uint32_t i = 0; i < (size / 4); i++) { + texture[i] = src[i]; + } +} + +void palette_data() +{ + holly.PAL_RAM_CTRL = pal_ram_ctrl::pixel_format::rgb565; + + // palette of 256 greys + for (int i = 0; i < 256; i++) { + holly.PALETTE_RAM[i] = ((i >> 3) << 11) + | ((i >> 2) << 5) + | ((i >> 3) << 0); + } +} + +uint32_t _ta_parameter_buf[((32 * 10 * 17) + 32) / 4]; + +void main() +{ + vga(); + + auto font = reinterpret_cast(&_binary_dejavusansmono_data_start); + auto glyphs = reinterpret_cast(&font[1]); + auto texture = reinterpret_cast(&glyphs[font->glyph_count]); + + serial::integer(font->first_char_code); + serial::integer(font->glyph_count); + serial::integer(font->glyph_height); + serial::integer(font->texture_width); + serial::integer(font->texture_height); + serial::character('\n'); + serial::integer(((uint32_t)glyphs) - ((uint32_t)font)); + serial::integer(((uint32_t)texture) - ((uint32_t)font)); + + uint32_t texture_size = font->texture_width * font->texture_height; + inflate_font(texture, texture_size); + palette_data(); + + // The address of `ta_parameter_buf` must be a multiple of 32 bytes. + // This is mandatory for ch2-dma to the ta fifo polygon converter. + uint32_t * ta_parameter_buf = align_32byte(_ta_parameter_buf); + + constexpr uint32_t ta_alloc = ta_alloc_ctrl::pt_opb::no_list + | ta_alloc_ctrl::tm_opb::no_list + | ta_alloc_ctrl::t_opb::no_list + | ta_alloc_ctrl::om_opb::no_list + | ta_alloc_ctrl::o_opb::_16x4byte; + + constexpr struct opb_size opb_size = { .opaque = 16 * 4 + , .opaque_modifier = 0 + , .translucent = 0 + , .translucent_modifier = 0 + , .punch_through = 0 + }; + + constexpr uint32_t tiles = (640 / 32) * (320 / 32); + + holly.SOFTRESET = softreset::pipeline_soft_reset + | softreset::ta_soft_reset; + holly.SOFTRESET = 0; + + core_init(); + init_texture_memory(opb_size); + + uint32_t frame_ix = 0; + constexpr uint32_t num_frames = 1; + + const char ana[18] = "A from ana i know"; + const char cabal[27] = "where is this secret cabal"; + + while (true) { + ta_polygon_converter_init(opb_size.total() * tiles, ta_alloc, + 640, 480); + + auto parameter = ta_parameter_writer(ta_parameter_buf); + + transform(parameter, font->first_char_code, glyphs, + ana, 17, + 0); + + transform(parameter, font->first_char_code, glyphs, + cabal, 26, + font->glyph_height); + + parameter.append() = global_end_of_list(); + + ta_polygon_converter_transfer(ta_parameter_buf, parameter.offset); + ta_wait_opaque_list(); + + core_start_render(frame_ix, num_frames); + + v_sync_out(); + v_sync_in(); + core_wait_end_of_render_video(frame_ix, num_frames); + + frame_ix++; + } +} diff --git a/font/font.hpp b/font/font.hpp new file mode 100644 index 0000000..27fb418 --- /dev/null +++ b/font/font.hpp @@ -0,0 +1,39 @@ +// this file is designed to be platform-agnostic +#pragma once + +#include + +// metrics are 26.6 fixed point +struct glyph_metrics { + int32_t horiBearingX; + int32_t horiBearingY; + int32_t horiAdvance; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph_metrics)) == ((sizeof (int32_t)) * 3)); + +struct glyph_bitmap { + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph_bitmap)) == ((sizeof (uint16_t)) * 4)); + +struct glyph { + glyph_bitmap bitmap; + glyph_metrics metrics; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph)) == ((sizeof (glyph_bitmap)) + (sizeof (glyph_metrics)))); + +struct font { + uint32_t first_char_code; + uint16_t glyph_count; + uint16_t glyph_height; + uint16_t texture_width; + uint16_t texture_height; +} __attribute__ ((packed)); + +static_assert((sizeof (font)) == ((sizeof (uint32_t)) * 3)); diff --git a/font/font_draw.cpp b/font/font_draw.cpp new file mode 100644 index 0000000..a159dbc --- /dev/null +++ b/font/font_draw.cpp @@ -0,0 +1,31 @@ + + +uint32_t pixel_data(const uint32_t * dest, const uint8_t * glyph_bitmaps, const uint32_t size) +{ + const uint8_t temp[size]; + + const uint32_t * buf = reinterpret_cast(&glyph_bitmaps[0]); + + copy(table, buf, size); + + return table_address; +} + +uint32_t font_data(const uint32_t * buf, state& state) +{ + constexpr uint32_t font_offset = 0; + constexpr uint32_t glyphs_offset = (sizeof (struct font)); + const uint32_t glyph_bitmaps_offset = (sizeof (struct font)) + (sizeof (struct glyph)) * font->glyph_index; + + auto font = reinterpret_cast(&buf[font_offset / 4]); + auto glyphs = reinterpret_cast(&buf[glyphs_offset / 4]); + auto glyph_bitmaps = &(reinterpret_cast(buf))[glyph_bitmaps_offset]; + + for (uint32_t glyph_ix = 0; glyph_ix < font->glyph_index; glyph_ix++) { + auto& glyph_bitmap = glyphs[glyph_ix].bitmap; + + auto bitmap = &glyph_bitmaps[glyph_bitmap.offset]; + // bitmap.pitch may be zero; bitmap.pitch is a multiple of 8 pixels + SIZE__X(bitmap.pitch) | SIZE__Y(bitmap.rows); + } +} diff --git a/tools/2d-pack.cpp b/tools/2d-pack.cpp new file mode 100644 index 0000000..1b6edd8 --- /dev/null +++ b/tools/2d-pack.cpp @@ -0,0 +1,113 @@ +#include +#include +#include + +#include "insertion_sort.hpp" +#include "rect.hpp" +#include "../twiddle.hpp" +#include + +struct size { + uint32_t width; + uint32_t height; +}; + +constexpr struct size max_texture = {1024, 1024}; + +inline bool area_valid(const uint8_t texture[max_texture.height][max_texture.width], + const uint32_t x_offset, + const uint32_t y_offset, + const struct rect& rect, + const struct size& window) +{ + for (uint32_t yi = 0; yi < rect.height; yi++) { + for (uint32_t xi = 0; xi < rect.width; xi++) { + uint32_t x = x_offset + xi; + uint32_t y = y_offset + yi; + + if (texture[y][x] != 0) + return false; + + if (x >= window.width || y >= window.height) + return false; + } + } + + return true; +} + +bool pack_into(uint8_t texture[max_texture.height][max_texture.width], + struct size& window, + struct rect& rect) +{ + uint32_t z_curve_ix = 0; + + if (rect.width == 0 || rect.height == 0) { + rect.x = 0; + rect.y = 0; + return false; + } + + while (true) { + auto [x_offset, y_offset] = twiddle::from_ix(z_curve_ix); + + if (x_offset >= window.width and y_offset >= window.height) { + std::cerr << z_curve_ix << ' ' << window.width << ' ' << window.height << '\n'; + assert(window.width < max_texture.width || window.height < max_texture.height); + if (window.width == window.height) { window.height *= 2; } + else { window.width *= 2; } + + // when the window changes; start again from the beginning and + // re-check earlier locations that might have been skipped due + // to window size + z_curve_ix = 0; + } + + if (area_valid(texture, x_offset, y_offset, rect, window)) { + for (uint32_t yi = 0; yi < rect.height; yi++) { + for (uint32_t xi = 0; xi < rect.width; xi++) { + uint32_t x = x_offset + xi; + uint32_t y = y_offset + yi; + + texture[y][x] = 1; + } + } + + rect.x = x_offset; + rect.y = y_offset; + + return true; + } else { + z_curve_ix += 1; + continue; + } + } +} + +uint32_t pack_all(struct rect * rects, const uint32_t num_rects) +{ + uint8_t texture[max_texture.height][max_texture.width] = { 0 }; + size window = {1, 1}; + + // sort all rectangles by size + insertion_sort(rects, num_rects); + + uint32_t max_x = 0; + uint32_t max_y = 0; + + for (uint32_t i = 0; i < num_rects; i++) { + std::cerr << "pack " << i << '\n'; + bool packed = pack_into(texture, window, rects[i]); + if (packed) { + const uint32_t x = rects[i].x + rects[i].width - 1; + const uint32_t y = rects[i].y + rects[i].height - 1; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; + } + } + + const uint32_t curve_ix = twiddle::from_xy(max_x, max_y); + std::cerr << "max xy " << max_x << ' ' << max_y << '\n'; + std::cerr << "curve_ix " << curve_ix << '\n'; + return curve_ix; +} diff --git a/tools/2d-pack.hpp b/tools/2d-pack.hpp new file mode 100644 index 0000000..33f3ea4 --- /dev/null +++ b/tools/2d-pack.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +uint32_t pack_all(struct rect * rects, const uint32_t num_rects); diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..0f38c97 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,23 @@ +CFLAGS = -Og -g -gdwarf-4 -Wall -Wextra -Werror -Wfatal-errors -ggdb -Wno-error=unused-parameter -Wno-error=unused-variable -fstack-protector-strong +CXXFLAGS = -std=c++23 + +CFLAGS += $(shell pkg-config --cflags freetype2) +LDFLAGS = $(shell pkg-config --libs freetype2) + +all: ttf-outline + +%.o: %.cpp + $(CXX) $(CFLAGS) $(CXXFLAGS) -c $< -o $@ + +%: %.o + $(CXX) $(LDFLAGS) $^ -o $@ + +ttf-outline: ttf-outline.o 2d-pack.o + +clean: + rm -f *.o ttf-convert ttf-bitmap + +.SUFFIXES: +.INTERMEDIATE: +.SECONDARY: +.PHONY: all clean diff --git a/tools/insertion_sort.hpp b/tools/insertion_sort.hpp new file mode 100644 index 0000000..26bef9d --- /dev/null +++ b/tools/insertion_sort.hpp @@ -0,0 +1,17 @@ +#include +#include +#include + +template +void insertion_sort(T * arr, int len) +{ + int i = 1; + while (i < len) { + int j = i; + while (j > 0 && arr[j - 1] < arr[j]) { + std::swap(arr[j - 1], arr[j]); + j -= 1; + } + i += 1; + } +} diff --git a/tools/rect.hpp b/tools/rect.hpp new file mode 100644 index 0000000..1df0be7 --- /dev/null +++ b/tools/rect.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct rect { + uint32_t char_code; + uint32_t width; + uint32_t height; + int32_t x; + int32_t y; + + std::strong_ordering operator<=>(const rect& b) const + { + return (width * height) <=> (b.width * b.height); + } +}; diff --git a/tools/sizes.py b/tools/sizes.py new file mode 100644 index 0000000..d56af84 --- /dev/null +++ b/tools/sizes.py @@ -0,0 +1,98 @@ +sizes = [ +(0, 0), +(23, 4), +(9, 9), +(23, 20), +(30, 16), +(23, 19), +(23, 20), +(9, 3), +(29, 8), +(29, 8), +(14, 15), +(17, 17), +(9, 6), +(3, 9), +(5, 5), +(26, 16), +(23, 16), +(23, 15), +(23, 15), +(23, 15), +(23, 17), +(23, 15), +(23, 16), +(23, 15), +(23, 16), +(23, 17), +(17, 5), +(21, 6), +(16, 17), +(10, 17), +(16, 17), +(23, 14), +(27, 19), +(23, 19), +(23, 16), +(23, 15), +(23, 16), +(23, 15), +(23, 15), +(23, 17), +(23, 16), +(23, 14), +(23, 14), +(23, 18), +(23, 15), +(23, 17), +(23, 16), +(23, 17), +(23, 15), +(27, 17), +(23, 18), +(23, 16), +(23, 19), +(23, 15), +(23, 19), +(23, 20), +(23, 19), +(23, 19), +(23, 17), +(29, 7), +(26, 16), +(29, 8), +(9, 18), +(3, 20), +(6, 8), +(18, 15), +(24, 15), +(18, 14), +(24, 16), +(18, 17), +(24, 14), +(25, 16), +(24, 14), +(24, 16), +(31, 11), +(24, 16), +(24, 15), +(18, 17), +(18, 14), +(18, 16), +(25, 16), +(25, 15), +(18, 14), +(18, 14), +(23, 15), +(18, 14), +(18, 17), +(18, 20), +(18, 18), +(25, 18), +(18, 14), +(30, 13), +(32, 3), +(30, 13), +(5, 17), +(28, 17), +] diff --git a/tools/ttf-outline.cpp b/tools/ttf-outline.cpp new file mode 100644 index 0000000..11740e0 --- /dev/null +++ b/tools/ttf-outline.cpp @@ -0,0 +1,254 @@ +#include +#include +#include + +#include +#include + +#include +#include FT_FREETYPE_H + +#include "../font/font.hpp" +#include "rect.hpp" +#include "2d-pack.hpp" +#include "../twiddle.hpp" + +std::endian _target_endian; + +uint32_t byteswap(const uint32_t n) +{ + if (std::endian::native != _target_endian) { + return std::byteswap(n); + } else { + return n; + } +} + +int32_t +load_outline_char_bitmap_rect(const FT_Face face, + const FT_ULong char_code, + struct rect& rect) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + rect.char_code = char_code; + rect.height = face->glyph->bitmap.rows; + rect.width = face->glyph->bitmap.width; + rect.x = -1; + rect.y = -1; + + return 0; +} + +int32_t +load_outline_char(const FT_Face face, + const FT_ULong char_code, + glyph * glyph, + uint8_t * texture, + struct rect& rect) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + std::cerr << "size " << face->glyph->bitmap.rows << ' ' << face->glyph->bitmap.width << '\n'; + + //assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE); + + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if (error) { + std::cerr << "FT_Render_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + if (!(face->glyph->bitmap.pitch > 0)) { + assert(face->glyph->bitmap.width == 0); + assert(face->glyph->bitmap.rows == 0); + } + + assert(face->glyph->bitmap.width == rect.width); + assert(face->glyph->bitmap.rows == rect.height); + + std::cerr << "num_grays " << face->glyph->bitmap.num_grays << '\n'; + switch (face->glyph->bitmap.num_grays) { + case 2: + assert(false); + break; + case 256: + std::cerr << "rxy " << rect.x << ' ' << rect.y << '\n'; + std::cerr << "rwh " << rect.width << ' ' << rect.height << '\n'; + + for (uint32_t y = 0; y < rect.height; y++) { + for (uint32_t x = 0; x < rect.width; x++) { + uint32_t texture_ix = (rect.y + y) * 1024 + (rect.x + x); + assert(texture_ix < 1024 * 1024); + texture[texture_ix] = face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + x]; + } + } + + break; + default: + assert(face->glyph->bitmap.num_grays == -1); + } + + glyph_bitmap& bitmap = glyph->bitmap; + bitmap.x = byteswap(rect.x); + bitmap.y = byteswap(rect.y); + bitmap.width = byteswap(rect.width); + bitmap.height = byteswap(rect.height); + + glyph_metrics& metrics = glyph->metrics; + metrics.horiBearingX = byteswap(face->glyph->metrics.horiBearingX); + metrics.horiBearingY = byteswap(face->glyph->metrics.horiBearingY); + metrics.horiAdvance = byteswap(face->glyph->metrics.horiAdvance); + + return 0; +} + +enum { + start_hex = 1, + end_hex = 2, + pixel_size = 3, + target_endian = 4, + font_file_path = 5, + output_file_path = 6, + argv_length = 7 +}; + +void load_all_positions(const FT_Face face, + const uint32_t start, + const uint32_t end, + glyph * glyphs, + uint32_t * texture + ) +{ + const uint32_t num_glyphs = (end - start) + 1; + struct rect rects[num_glyphs]; + + uint8_t temp[1024 * 1024]; + + // first, load all rectangles + for (uint32_t char_code = start; char_code <= end; char_code++) { + load_outline_char_bitmap_rect(face, char_code, rects[char_code - start]); + } + + // calculate a 2-dimensional packing for the rectangles + pack_all(rects, num_glyphs); + + // asdf + for (uint32_t i = 0; i < num_glyphs; i++) { + const uint32_t char_code = rects[i].char_code; + int32_t err = load_outline_char(face, + char_code, + &glyphs[char_code - start], + temp, + rects[i]); + if (err < 0) assert(false); + } + + twiddle::texture2<8>(texture, temp, + 128, 256, + 1024); +} + +int main(int argc, char *argv[]) +{ + FT_Library library; + FT_Face face; + FT_Error error; + + if (argc != argv_length) { + std::cerr << "usage: " << argv[0] << " [start-hex] [end-hex] [pixel-size] [target-endian] [font-file-path] [output-file-path]\n\n"; + std::cerr << "ex. 1: " << argv[0] << " 3000 30ff 30 little ipagp.ttf font.bin\n"; + std::cerr << "ex. 2: " << argv[0] << " 20 7f 30 big DejaVuSans.ttf font.bin\n"; + return -1; + } + + error = FT_Init_FreeType(&library); + if (error) { + std::cerr << "FT_Init_FreeType\n"; + return -1; + } + + error = FT_New_Face(library, argv[font_file_path], 0, &face); + if (error) { + std::cerr << "FT_New_Face\n"; + return -1; + } + + std::stringstream ss3; + int font_size; + ss3 << std::dec << argv[pixel_size]; + ss3 >> font_size; + std::cerr << "font_size: " << font_size << '\n'; + + error = FT_Set_Pixel_Sizes(face, 0, font_size); + if (error) { + std::cerr << "FT_Set_Pixel_Sizes: " << FT_Error_String(error) << error << '\n'; + return -1; + } + + if (std::string(argv[target_endian]).compare("little") == 0) { + _target_endian = std::endian::little; + } else if (std::string(argv[target_endian]).compare("big") == 0) { + _target_endian = std::endian::big; + } else { + std::cerr << "unknown endian: " << argv[target_endian] << '\n'; + std::cerr << "expected one of: big, little\n"; + return -1; + } + + std::cerr << "here\n"; + + uint32_t start; + uint32_t end; + + std::stringstream ss1; + ss1 << std::hex << argv[start_hex]; + ss1 >> start; + std::stringstream ss2; + ss2 << std::hex << argv[end_hex]; + ss2 >> end; + + uint32_t num_glyphs = (end - start) + 1; + glyph glyphs[num_glyphs]; + uint32_t texture[(1024 * 1024) / 4]; + memset(texture, 0x00, 1024 * 1024); + + font font; + font.first_char_code = byteswap(start); + font.glyph_count = byteswap(num_glyphs); + font.glyph_height = byteswap(face->size->metrics.height); + font.texture_width = 128; + font.texture_height = 256; + + load_all_positions(face, start, end, glyphs, texture); + + std::cerr << "start: 0x" << std::hex << start << '\n'; + std::cerr << "end: 0x" << std::hex << end << '\n'; + + FILE * out = fopen(argv[output_file_path], "w"); + if (out == NULL) { + perror("fopen(w)"); + return -1; + } + + uint32_t texture_size = 128 * 256; + fwrite(reinterpret_cast(&font), (sizeof (font)), 1, out); + fwrite(reinterpret_cast(&glyphs[0]), (sizeof (glyph)), num_glyphs, out); + fwrite(reinterpret_cast(&texture[0]), (sizeof (uint8_t)), texture_size, out); + + fclose(out); +} diff --git a/tools/z-curve-test.py b/tools/z-curve-test.py new file mode 100644 index 0000000..58b10a4 --- /dev/null +++ b/tools/z-curve-test.py @@ -0,0 +1,172 @@ +import sys + +def from_xy(x: int, y: int) -> int: + # maximum texture size : 1024x1024 + # maximum 1-dimensional index: 0xfffff + # bits : 19-0 + + twiddle_ix = 0 + for i in range(0, (19 // 2) + 1): + twiddle_ix |= ((y >> i) & 1) << (i * 2 + 0) + twiddle_ix |= ((x >> i) & 1) << (i * 2 + 1) + + return twiddle_ix + +assert from_xy(0b000, 0b000) == 0 +assert from_xy(0b001, 0b000) == 2 +assert from_xy(0b010, 0b000) == 8 +assert from_xy(0b011, 0b000) == 10 +assert from_xy(0b100, 0b000) == 32 +assert from_xy(0b101, 0b000) == 34 +assert from_xy(0b110, 0b000) == 40 +assert from_xy(0b111, 0b000) == 42 + +assert from_xy(0b000, 0b001) == 1 +assert from_xy(0b000, 0b010) == 4 +assert from_xy(0b000, 0b011) == 5 +assert from_xy(0b000, 0b100) == 16 +assert from_xy(0b000, 0b101) == 17 +assert from_xy(0b000, 0b110) == 20 +assert from_xy(0b000, 0b111) == 21 + +def from_ix(z_curve_ix: int) -> tuple[int, int]: + x_y = [0, 0] + xyi = 0 + while z_curve_ix != 0: + x_y[(xyi + 1) % 2] |= (z_curve_ix & 1) << (xyi // 2) + z_curve_ix >>= 1 + xyi += 1 + return tuple(x_y) + +assert from_ix(17) == (0b000, 0b101) +assert from_ix(21) == (0b000, 0b111) +assert from_ix(42) == (0b111, 0b000) + +""" +def texture(src: list[int], + width: int, height: int) -> list[int]: + dst = [0] * (width * height) + + for y in range(0, height): + for x in range(0, width): + twiddle_ix = from_xy(x, y) + value = src[y * width + x] + dst[twiddle_ix] = value + + return dst +""" + +import random +from sizes import sizes as _sizes +from colorsys import hsv_to_rgb + +def all_colors(num): + for i in range(num): + hue = i / (num - 1) + rgb = hsv_to_rgb(hue, 1.0, 1.0) + def color(): + for i in rgb: + yield int(i * 255) + yield tuple(color()) + +def random_colors(num): + l = list(all_colors(num)) + random.shuffle(l) + return l + +def area_pixels(x_off, y_off, width, height): + for x in range(height): + for y in range(width): + px_ix = (x_off + x, y_off + y) + yield px_ix + +max_size = (1, 1) + +def pack_into(texture: dict[tuple[int, int], int], + width_height: tuple[int, int], + z_curve_ix: int): + global max_size + # ignore passed z_curve_ix + z_curve_ix = 0 + + width, height = width_height + if width == 0 or height == 0: + return (0, 0), z_curve_ix + + while True: + x_off, y_off = from_ix(z_curve_ix) + if x_off >= max_size[0] and y_off >= max_size[0]: + if max_size[0] == max_size[1]: + max_size = (max_size[0], max_size[1] * 2) + else: + max_size = (max_size[0] * 2, max_size[1]) + z_curve_ix = 0 + + if all((pixel not in texture) and (pixel[0] < max_size[0] and pixel[1] < max_size[1]) + for pixel in area_pixels(x_off, y_off, width, height)): + #x, y = x_off + width - 1, y_off + height - 1 + return (x_off, y_off), z_curve_ix + else: + z_curve_ix += 1 + +def sort_by_area(width_height): + width, height = width_height + area = width * height + return area + +max_ix = 0 + +def insert_into_texture(texture, color_ix, x_off__y_off, width_height): + global max_ix + x_off, y_off = x_off__y_off + width, height = width_height + for px_ix in area_pixels(x_off, y_off, width, height): + assert px_ix not in texture, px_ix + ix = from_xy(*px_ix) + if ix > max_ix: + max_ix = ix + texture[px_ix] = color_ix + +def pack_all(sizes): + global max_size + max_size = (1, 1) + global max_ix + max_ix = 0 + + sorted_sizes = sorted(sizes, key=sort_by_area, reverse=True) + z_curve_ix = 0 + texture = dict() + for color_ix, width_height in enumerate(sorted_sizes): + x_off__y_off, z_curve_ix = pack_into(texture, width_height, z_curve_ix) + insert_into_texture(texture, color_ix, x_off__y_off, width_height) + #if color_ix == 2: + # break + return texture + +def ppm(texture: dict, num_colors): + colors = random_colors(num_colors) + max_x = max(px[0] for px in texture.keys()) + max_y = max(px[1] for px in texture.keys()) + width = max_x + 1 + height = max_y + 1 + print(" max xy:", max_x, max_y, file=sys.stderr) + print("max curve ix:", from_xy(max_x, max_y), file=sys.stderr) + + yield "P3" + yield f"{width} {height}" + yield "255" + for y in range(0, height): + for x in range(0, width): + if (x, y) in texture: + i = texture[(x, y)] + color = colors[i] + yield " ".join(map(str, color)) + else: + yield "0 0 0" + +if __name__ == '__main__': + texture = pack_all((x, y) for (y, x) in _sizes) + print("ideal size", sum(x * y for x, y in _sizes), file=sys.stderr) + image = list(ppm(texture, len(_sizes))) + print("\n".join(image)) + pass diff --git a/twiddle.hpp b/twiddle.hpp index 48eaaa7..9ccedd1 100644 --- a/twiddle.hpp +++ b/twiddle.hpp @@ -93,22 +93,23 @@ from_ix(uint32_t curve_ix) return {x, y}; } -static_assert(from_ix(0) == std::tuple{0b000, 0b000}); -static_assert(from_ix(2) == std::tuple{0b001, 0b000}); -static_assert(from_ix(8) == std::tuple{0b010, 0b000}); -static_assert(from_ix(10) == std::tuple{0b011, 0b000}); -static_assert(from_ix(32) == std::tuple{0b100, 0b000}); -static_assert(from_ix(34) == std::tuple{0b101, 0b000}); -static_assert(from_ix(40) == std::tuple{0b110, 0b000}); -static_assert(from_ix(42) == std::tuple{0b111, 0b000}); +using xy_type = std::tuple; +static_assert(from_ix(0) == xy_type{0b000, 0b000}); +static_assert(from_ix(2) == xy_type{0b001, 0b000}); +static_assert(from_ix(8) == xy_type{0b010, 0b000}); +static_assert(from_ix(10) == xy_type{0b011, 0b000}); +static_assert(from_ix(32) == xy_type{0b100, 0b000}); +static_assert(from_ix(34) == xy_type{0b101, 0b000}); +static_assert(from_ix(40) == xy_type{0b110, 0b000}); +static_assert(from_ix(42) == xy_type{0b111, 0b000}); -static_assert(from_ix(1) == std::tuple{0b000, 0b001}); -static_assert(from_ix(4) == std::tuple{0b000, 0b010}); -static_assert(from_ix(5) == std::tuple{0b000, 0b011}); -static_assert(from_ix(16) == std::tuple{0b000, 0b100}); -static_assert(from_ix(17) == std::tuple{0b000, 0b101}); -static_assert(from_ix(20) == std::tuple{0b000, 0b110}); -static_assert(from_ix(21) == std::tuple{0b000, 0b111}); +static_assert(from_ix(1) == xy_type{0b000, 0b001}); +static_assert(from_ix(4) == xy_type{0b000, 0b010}); +static_assert(from_ix(5) == xy_type{0b000, 0b011}); +static_assert(from_ix(16) == xy_type{0b000, 0b100}); +static_assert(from_ix(17) == xy_type{0b000, 0b101}); +static_assert(from_ix(20) == xy_type{0b000, 0b110}); +static_assert(from_ix(21) == xy_type{0b000, 0b111}); template void texture(volatile T * dst, const T * src, const uint32_t width, const uint32_t height) @@ -137,7 +138,9 @@ void texture_4bpp(volatile T * dst, const T * src, const uint32_t width, const u } template -void texture2(volatile T * dst, const U * src, const uint32_t width, const uint32_t height) +void texture2(volatile T * dst, const U * src, + const uint32_t width, const uint32_t height, + const uint32_t stride) { constexpr uint32_t t_bits = (sizeof (T)) * 8; constexpr uint32_t bits_per_pixel = B; @@ -147,9 +150,10 @@ void texture2(volatile T * dst, const U * src, const uint32_t width, const uint3 static_assert(pixels_per_t == 1 || pixels_per_t == 2 || pixels_per_t == 4 || pixels_per_t == 8); T dst_val = 0; - for (uint32_t curve_ix = 0; curve_ix < (width * height); curve_ix++) { + const uint32_t end_ix = from_xy(width - 1, height - 1); + for (uint32_t curve_ix = 0; curve_ix <= end_ix; curve_ix++) { auto [x, y] = from_ix(curve_ix); - const U src_val = src[y * width + x]; + const U src_val = src[y * stride + x]; if constexpr (pixels_per_t == 1) { dst[curve_ix] = src_val; } else {