加入离线 sprite 与位图字体转换流程
Demo 现在通过生成的 C++ 头文件使用测试 sprite 和像素风 bitmap font,避免在运行时解码 PNG/TTF。同步补充资源转换文档,明确源资源、生成脚本和生成头文件需要一起提交,保证后续可追溯和可重新生成。 Constraint: IMX6U 运行时路径应尽量轻量,避免图片/字体解码开销 Constraint: 生成像素沿用当前 framebuffer 的 uint32_t RGBA 格式 Rejected: 运行时加载 PNG/TTF | 会增加解码依赖和运行时成本 Confidence: high Scope-risk: moderate Directive: 修改 sprite 或字体时,必须同步提交源资源、转换脚本和生成头文件 Tested: cmake -B build-win .; cmake --build build-win --config Release Not-tested: 尚未在 IMX6U 真机上验证 ARM framebuffer/SDL 后端
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include "Vector2.h"
|
||||
#include "Vector3.h"
|
||||
#include "Vector4.h"
|
||||
@@ -12,6 +13,8 @@
|
||||
#include <cstdlib>
|
||||
#include "Vertex.h"
|
||||
#include "DrawContext.h"
|
||||
#include "test_sprite.h"
|
||||
#include "font_atlas.h"
|
||||
|
||||
#include "Display.h"
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
@@ -116,6 +119,13 @@ int main(int argc, char *argv[])
|
||||
|
||||
Gfx::DrawContext ctx(width, height);
|
||||
|
||||
RenderData::BitmapFont font;
|
||||
font.atlas = RenderData::Image(font_atlas_pixels, font_atlas_width, font_atlas_height);
|
||||
font.char_w = font_char_w;
|
||||
font.char_h = font_char_h;
|
||||
font.columns = font_columns;
|
||||
font.first_char = font_first_char;
|
||||
|
||||
Scene::Camera camera;
|
||||
camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f);
|
||||
camera.transform.rotation = Math::Vector3(0.0f, 3.1415926535f, 0.0f);
|
||||
@@ -147,8 +157,15 @@ int main(int argc, char *argv[])
|
||||
|
||||
const RenderData::Color clearColor(18, 18, 24, 255);
|
||||
const RenderData::Color cubeColor(240, 240, 240, 255);
|
||||
const RenderData::Color fpsColor(0, 255, 80, 255);
|
||||
const RenderData::Color fpsBg(0, 0, 0, 200);
|
||||
const float aspectRatio = static_cast<float>(width) / static_cast<float>(height);
|
||||
|
||||
int32_t fps = 0;
|
||||
int32_t frame_count = 0;
|
||||
uint32_t last_fps_time = 0;
|
||||
char fps_text[32];
|
||||
|
||||
bool isRunning = true;
|
||||
while (isRunning)
|
||||
{
|
||||
@@ -235,6 +252,25 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
// sprite 测试
|
||||
RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000);
|
||||
|
||||
ctx.draw_sprite(10, 10, sprite_img);
|
||||
ctx.draw_sprite_ex(30, 10, sprite_img, 0, 0, sprite_img.width, sprite_img.height, 2, false, false);
|
||||
ctx.draw_sprite_ex(10, 30, sprite_img, 0, 0, sprite_img.width, sprite_img.height, 3, true, false);
|
||||
|
||||
// FPS 计数
|
||||
++frame_count;
|
||||
const uint32_t now = display->get_time_ms();
|
||||
if (now - last_fps_time >= 1000)
|
||||
{
|
||||
fps = frame_count;
|
||||
frame_count = 0;
|
||||
last_fps_time = now;
|
||||
}
|
||||
std::snprintf(fps_text, sizeof(fps_text), "FPS: %d", fps);
|
||||
ctx.draw_text(font, 4, 4, fpsColor, fpsBg, fps_text);
|
||||
|
||||
ctx.present(display);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Rasterizer.h"
|
||||
#include "TriangleRasterizer.h"
|
||||
#include "Display.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace Gfx
|
||||
{
|
||||
@@ -54,6 +55,122 @@ namespace Gfx
|
||||
triangleRasterizer->DrawTriangle2D(triangle, color);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img)
|
||||
{
|
||||
draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h)
|
||||
{
|
||||
draw_sprite_ex(dst_x, dst_y, img, src_x, src_y, src_w, src_h, 1, false, false);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
||||
int32_t scale, bool flip_h, bool flip_v)
|
||||
{
|
||||
if (scale < 1 || !img.pixels) return;
|
||||
|
||||
const int32_t img_w = img.width;
|
||||
const uint32_t* src = img.pixels;
|
||||
const bool has_key = img.has_color_key;
|
||||
const uint32_t key = img.color_key;
|
||||
|
||||
for (int32_t sy = 0; sy < src_h; ++sy)
|
||||
{
|
||||
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
|
||||
const uint32_t* row = src + read_y * img_w;
|
||||
|
||||
for (int32_t sx = 0; sx < src_w; ++sx)
|
||||
{
|
||||
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
|
||||
const uint32_t pixel = row[read_x];
|
||||
|
||||
if (has_key && pixel == key) continue;
|
||||
|
||||
const int32_t base_x = dst_x + sx * scale;
|
||||
const int32_t base_y = dst_y + sy * scale;
|
||||
|
||||
if (scale == 1)
|
||||
{
|
||||
frameBuffer->set_pixel(base_x, base_y, pixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32_t dy = 0; dy < scale; ++dy)
|
||||
{
|
||||
for (int32_t dx = 0; dx < scale; ++dx)
|
||||
{
|
||||
frameBuffer->set_pixel(base_x + dx, base_y + dy, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawContext::fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color)
|
||||
{
|
||||
const uint32_t rgba = color.to_rgba();
|
||||
for (int32_t row = y; row < y + h; ++row)
|
||||
{
|
||||
for (int32_t col = x; col < x + w; ++col)
|
||||
{
|
||||
frameBuffer->set_pixel(col, row, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawContext::draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const char* text)
|
||||
{
|
||||
if (!font.atlas.pixels || !text) return;
|
||||
|
||||
const uint32_t rgba = color.to_rgba();
|
||||
const int32_t cw = font.char_w;
|
||||
const int32_t ch = font.char_h;
|
||||
const int32_t atlas_w = font.atlas.width;
|
||||
const uint32_t* src = font.atlas.pixels;
|
||||
|
||||
int32_t cursor_x = x;
|
||||
for (const char* p = text; *p; ++p)
|
||||
{
|
||||
const int32_t index = static_cast<int32_t>(*p) - font.first_char;
|
||||
if (index < 0) { cursor_x += cw; continue; }
|
||||
|
||||
const int32_t col = index % font.columns;
|
||||
const int32_t row = index / font.columns;
|
||||
const int32_t src_x = col * cw;
|
||||
const int32_t src_y = row * ch;
|
||||
|
||||
for (int32_t sy = 0; sy < ch; ++sy)
|
||||
{
|
||||
const uint32_t* atlas_row = src + (src_y + sy) * atlas_w;
|
||||
const int32_t dst_y_abs = y + sy;
|
||||
for (int32_t sx = 0; sx < cw; ++sx)
|
||||
{
|
||||
const uint32_t pixel = atlas_row[src_x + sx];
|
||||
if ((pixel & 0xFF) == 0) continue;
|
||||
frameBuffer->set_pixel(cursor_x + sx, dst_y_abs, rgba);
|
||||
}
|
||||
}
|
||||
cursor_x += cw;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawContext::draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const RenderData::Color& bg_color, const char* text)
|
||||
{
|
||||
if (!text) return;
|
||||
|
||||
int32_t len = 0;
|
||||
for (const char* p = text; *p; ++p) ++len;
|
||||
|
||||
fill_rect(x, y, len * font.char_w, font.char_h, bg_color);
|
||||
draw_text(font, x, y, color, text);
|
||||
}
|
||||
|
||||
void DrawContext::present(Platform::IDisplay* display)
|
||||
{
|
||||
display->present(frameBuffer);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "Color.h"
|
||||
#include "Vector2.h"
|
||||
#include "Triangle.h"
|
||||
#include "Image.h"
|
||||
#include "BitmapFont.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace Core
|
||||
@@ -47,6 +49,19 @@ namespace Gfx
|
||||
void draw_line(const Math::Vector2Int& from, const Math::Vector2Int& to, const RenderData::Color& color);
|
||||
void draw_triangle(const RenderData::Triangle& triangle, const RenderData::Color& color);
|
||||
|
||||
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img);
|
||||
void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h);
|
||||
void draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
||||
int32_t scale, bool flip_h, bool flip_v);
|
||||
|
||||
void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color);
|
||||
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const char* text);
|
||||
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const RenderData::Color& bg_color, const char* text);
|
||||
|
||||
void present(Platform::IDisplay* display);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Image.h"
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
struct BitmapFont
|
||||
{
|
||||
Image atlas;
|
||||
int32_t char_w;
|
||||
int32_t char_h;
|
||||
int32_t columns;
|
||||
int32_t first_char;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Color.h"
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
struct Image
|
||||
{
|
||||
const uint32_t* pixels;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint32_t color_key;
|
||||
bool has_color_key;
|
||||
|
||||
Image() : pixels(nullptr), width(0), height(0), color_key(0), has_color_key(false) {}
|
||||
|
||||
Image(const uint32_t* pixels, int32_t width, int32_t height)
|
||||
: pixels(pixels), width(width), height(height), color_key(0), has_color_key(false) {}
|
||||
|
||||
Image(const uint32_t* pixels, int32_t width, int32_t height, uint32_t color_key)
|
||||
: pixels(pixels), width(width), height(height), color_key(color_key), has_color_key(true) {}
|
||||
|
||||
uint32_t get_pixel(int32_t x, int32_t y) const
|
||||
{
|
||||
return pixels[y * width + x];
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user