统一 Image 和 Sprite 语义到目前的 Image/Sprite 结构体而非之前的类定义

This commit is contained in:
SepComet 2026-06-09 10:28:29 +08:00
parent 56eec9e9d2
commit feb088a854
16 changed files with 166786 additions and 167030 deletions

View File

@ -212,7 +212,7 @@ cmake --build build-win --config Release --target GenerateTomAtlasHeader
src/Apps/Game/generated/tom_atlas.h
```
该头文件包含 `tom_atlas_pixels` 和每张图的 `RenderData::SpriteRegion`,因此板端运行 Tom 游戏时不需要额外部署图片资源文件。尺寸规则、region 名称和 PNG 文件名统一记录在 `src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp` 顶部的 `Sources` 表里CMake 不再重复维护每张 PNG 的路径。
该头文件包含 `tom_atlas_pixels` 和每张图的 `RenderData::Sprite`,因此板端运行 Tom 游戏时不需要额外部署图片资源文件。尺寸规则、region 名称和 PNG 文件名统一记录在 `src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp` 顶部的 `Sources` 表里CMake 不再重复维护每张 PNG 的路径。
`src/Apps/Game/assets/sprites/` 属于旧的 `.sprite` 文件部署流程Tom 主游戏现在不再依赖它。只有旧的手动测试或未迁移工具还可能引用该目录;清理这些旧入口后可以删除这个目录。
@ -307,8 +307,8 @@ IMX6U-Game/
## 模块说明
### Draw2D
- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_sprite_region`、`draw_text`、`draw_tilemap`、`present` 接口
- **SpriteRegion**:描述 atlas 中的子区域,`draw_sprite_region` / `draw_sprite_region_ex` 可直接绘制子图,底层复用 `draw_sprite_ex`
- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_text`、`draw_tilemap`、`present` 接口
- **Sprite**:描述 atlas 中的子区域,是对外 sprite 绘制单位;`draw_sprite` / `draw_sprite_ex` 读取其 atlas 子区域并写入 RGB565 framebuffer
- **Tilemap**:使用 `uint16_t` tile id 引用 atlas 中的固定大小 tile`draw_tilemap` 按视口可见范围遍历 tile并在视口边缘做像素级裁剪
### Core
@ -351,7 +351,7 @@ IMX6U-Game/
- 可旋转立方体的 3D 渲染MVP 变换、背面剔除、扫描线填充、深度测试)
- 双平台显示后端SDL2 / Framebuffer
- 离线资源转换工具PNG sprite -> RGBA5551 C++ 头文件,像素字体 -> bitmap atlas/header
- 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap
- 基础 2D sprite、atlas 子图 sprite、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap
- Core 目录规范化,代码收敛到 `src/Core/`
- `Core::DrawContext` 统一绘制入口,封装现有绘制能力
- C++11 兼容代码

View File

@ -77,7 +77,7 @@ IMX6U-Game/
职责:
- 管理 framebuffer、depthbuffer、渲染上下文。
- 提供基础绘制接口点、线、矩形、四边形、三角形、sprite、SpriteRegion、tilemap、简单文本等。
- 提供基础绘制接口点、线、矩形、四边形、三角形、sprite、atlas 子图 sprite、tilemap、简单文本等。
- 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。
- 封装 SDL2 / framebuffer 显示提交、输入轮询,并通过独立 `ITimeSource` 提供单调整数毫秒时间。
- 提供音频输入、音频输出和按键输入的抽象接口。
@ -231,7 +231,7 @@ ALSA、evdev、SDL2、`/dev/fb0` 等平台细节只能出现在 `src/Core/Platfo
```cpp
Core::DrawContext ctx(width, height);
ctx.clear(RenderData::Color(18, 18, 24, 255));
ctx.draw_sprite(x, y, image);
ctx.draw_sprite(x, y, sprite);
ctx.draw_text(font, x, y, color, "text");
ctx.present(display);
```
@ -281,7 +281,7 @@ poll input -> update current app -> render current app -> present framebuffer
1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~ 未实现,当前直接写主循环。
2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~ **已完成**
3. ~~建立 `Core::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**`Core::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_sprite_region、draw_text、draw_tilemap、present
3. ~~建立 `Core::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**`Core::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_text、draw_tilemap、present
4. ~~底层代码统一放在 `src/Core/`Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成**
5. 新增 Launcher app只做最小菜单和应用切换。
6. 新增 GameA/GameB 空壳,验证三应用切换。
@ -309,10 +309,10 @@ poll input -> update current app -> render current app -> present framebuffer
- 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。
- 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应继续评估 1-bit mask、RLE 或自定义资源包格式。
## 12. SpriteRegion 与 Tilemap 约定
## 12. Sprite 与 Tilemap 约定
- `RenderData::SpriteRegion` 只描述某张 atlas 中的子区域,不拥有像素数据;它通过 `const Image* atlas` 引用源图。
- `DrawContext::draw_sprite_ex` 是底层 sprite 绘制入口负责源区域检查、目标屏幕裁剪、scale 和 flip`draw_sprite_region` 系列只是对 atlas 子区域的语义包装
- `RenderData::Sprite` 只描述某张 atlas 中的子区域,不拥有像素数据;它通过 `const Image* atlas` 引用源图。
- `DrawContext::draw_sprite` 是对外 sprite 绘制入口;`draw_sprite_ex` 只在需要 scale / flip 时使用。两者都以 `RenderData::Sprite` 为渲染单位,内部再按 atlas 子区域读取像素
- `RenderData::Tilemap` 使用 `uint16_t` tile id 保存地图网格,`Tilemap::EmptyTile` (`0xFFFF`) 表示空 tile。
- `Tilemap` 当前只支持一个 atlas、固定 tile 宽高和固定 `atlas_columns`tile id 通过 `tile_id % atlas_columns` / `tile_id / atlas_columns` 映射到 atlas 中的源区域。
- `DrawContext::draw_tilemap` 的裁剪分两层:

View File

@ -146,7 +146,7 @@ int main(int argc, char* argv[])
test_sprite_width,
test_sprite_height,
RenderData::PixelFormat::RGBA5551);
RenderData::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height);
RenderData::Sprite sprite(&sprite_img, 0, 0, sprite_img.width, sprite_img.height);
const std::array<uint16_t, 8 * 4> tileIds = {
0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile,
@ -179,9 +179,9 @@ int main(int argc, char* argv[])
ctx.clear_color(clearColor);
// sprite 测试
ctx.draw_sprite(10, 10, sprite_img);
ctx.draw_sprite_region_ex(30, 10, sprite_region, 2, false, false);
ctx.draw_sprite_region_ex(10, 30, sprite_region, 3, true, false);
ctx.draw_sprite(10, 10, sprite);
ctx.draw_sprite_ex(30, 10, sprite, 2, false, false);
ctx.draw_sprite_ex(10, 30, sprite, 3, true, false);
ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast<int32_t>(frame_start_ms / 20u) % 32, 0);
// FPS 计数

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
#include "Color.h"
#include "DrawContext.h"
#include "PointerInput.h"
#include "SpriteRegion.h"
#include "Sprite.h"
#include "tom_atlas.h"
#include <algorithm>
#include <cstddef>
@ -19,7 +19,7 @@ namespace
const int32_t TomBottomPadding = 72;
const int32_t ButtonBottomPadding = 16;
const RenderData::SpriteRegion* const SpeakingFrames[] = {
const RenderData::Sprite* const SpeakingFrames[] = {
&TomAtlas::tom_say1,
&TomAtlas::tom_say2,
&TomAtlas::tom_say3,
@ -28,7 +28,7 @@ namespace
&TomAtlas::tom_say2
};
static const RenderData::SpriteRegion& SelectSpeakingFrame(uint32_t animationMs)
static const RenderData::Sprite& SelectSpeakingFrame(uint32_t animationMs)
{
const size_t frameCount = sizeof(SpeakingFrames) / sizeof(SpeakingFrames[0]);
const size_t frameIndex = (animationMs / SpeakingFrameMs) % frameCount;
@ -113,9 +113,9 @@ namespace Game
void TomGameApp::draw(Core::DrawContext& ctx)
{
ctx.clear_color(RenderData::Color(18, 18, 24, 255));
ctx.draw_sprite_region(0, 0, TomAtlas::background);
ctx.draw_sprite(0, 0, TomAtlas::background);
const RenderData::SpriteRegion* tom = &TomAtlas::tom_stand;
const RenderData::Sprite* tom = &TomAtlas::tom_stand;
if (state == TomGameState::Recording)
{
tom = &TomAtlas::tom_listhen;
@ -132,8 +132,8 @@ namespace Game
tomY = 0;
}
ctx.draw_sprite_region(tomX, tomY, *tom);
ctx.draw_sprite_region(buttonX, buttonY, TomAtlas::ui_record);
ctx.draw_sprite(tomX, tomY, *tom);
ctx.draw_sprite(buttonX, buttonY, TomAtlas::ui_record);
}
void TomGameApp::update_input(bool& recordTrigger)

View File

@ -1,5 +1,5 @@
#include "AnimationSystem.h"
#include "SpriteRasterizer.h"
#include "DrawContext.h"
namespace Game
{
@ -35,7 +35,7 @@ namespace Game
}
}
void AnimationSystem::draw(Rasterizer::SpriteRasterizer& spriteRasterizer) const
void AnimationSystem::draw(Core::DrawContext& drawContext) const
{
for (size_t i = 0; i < objects.size(); ++i)
{
@ -48,7 +48,7 @@ namespace Game
const RenderData::Sprite* sprite = object.animator->get_current_sprite();
if (sprite != nullptr)
{
spriteRasterizer.DrawSprite(*sprite, object.x, object.y);
drawContext.draw_sprite(object.x, object.y, *sprite);
}
}
}

View File

@ -4,9 +4,9 @@
#include <vector>
#include "../components/SpriteAnimator.h"
namespace Rasterizer
namespace Core
{
class SpriteRasterizer;
class DrawContext;
}
namespace Game
@ -38,7 +38,7 @@ namespace Game
void clear();
void update(float deltaTime);
void draw(Rasterizer::SpriteRasterizer& spriteRasterizer) const;
void draw(Core::DrawContext& drawContext) const;
void set_position(size_t index, int32_t x, int32_t y);
void set_visible(size_t index, bool visible);

View File

@ -331,7 +331,7 @@ namespace
const std::vector<uint16_t>& atlas_pixels,
int32_t atlas_height)
{
std::ofstream file(OutputHeaderPath);
std::ofstream file(OutputHeaderPath, std::ios::binary);
if (!file.good())
{
std::cerr << "Open output header failed: " << OutputHeaderPath << std::endl;
@ -342,7 +342,7 @@ namespace
file << "#pragma once\n";
file << "#include <cstdint>\n";
file << "#include \"Image.h\"\n";
file << "#include \"SpriteRegion.h\"\n\n";
file << "#include \"Sprite.h\"\n\n";
file << "namespace TomAtlas\n";
file << "{\n";
file << "\tstatic const int32_t tom_atlas_width = " << AtlasWidth << ";\n";
@ -355,8 +355,15 @@ namespace
const size_t end = std::min(i + 12, atlas_pixels.size());
for (size_t j = i; j < end; ++j)
{
if (j > i)
{
file << " ";
}
WritePixel5551(file, atlas_pixels[j]);
file << ", ";
if (j + 1 < atlas_pixels.size())
{
file << ",";
}
}
file << "\n";
}
@ -369,7 +376,7 @@ namespace
for (size_t i = 0; i < regions.size(); ++i)
{
const AtlasRegion& region = regions[i];
file << "\tstatic const RenderData::SpriteRegion "
file << "\tstatic const RenderData::Sprite "
<< region.source->region_name
<< "(&image, "
<< region.x << ", "

View File

@ -60,53 +60,39 @@ namespace Core
triangleRasterizer->DrawTriangle2D(triangle, color);
}
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img)
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Sprite& sprite)
{
draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height);
draw_sprite_ex(dst_x, dst_y, sprite, 1, false, false);
}
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Sprite& sprite,
int32_t scale, bool flip_h, bool flip_v)
{
draw_sprite_region(dst_x, dst_y, region);
}
if (!sprite.atlas) return;
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_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
{
draw_sprite_region_ex(dst_x, dst_y, region, 1, false, false);
}
void DrawContext::draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region,
int32_t scale, bool flip_h, bool flip_v)
{
if (!region.atlas) return;
draw_sprite_ex(
blit_sprite_pixels(
dst_x,
dst_y,
*region.atlas,
region.x,
region.y,
region.width,
region.height,
*sprite.atlas,
sprite.x,
sprite.y,
sprite.width,
sprite.height,
scale,
flip_h,
flip_v);
}
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)
void DrawContext::blit_sprite_pixels(int32_t dst_x, int32_t dst_y,
const RenderData::Image& image,
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 || src_w <= 0 || src_h <= 0) return;
if (src_x < 0 || src_y < 0 || src_x + src_w > img.width || src_y + src_h > img.height) return;
if (scale < 1 || !image.pixels || src_w <= 0 || src_h <= 0) return;
if (src_x < 0 || src_y < 0 || src_x + src_w > image.width || src_y + src_h > image.height) return;
const int32_t img_w = img.width;
const int32_t img_w = image.width;
const int32_t screen_w = frameBuffer->get_width();
const int32_t screen_h = frameBuffer->get_height();
const int32_t draw_w = src_w * scale;
@ -126,7 +112,7 @@ namespace Core
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width();
const uint16_t* src = static_cast<const uint16_t*>(img.pixels);
const uint16_t* src = static_cast<const uint16_t*>(image.pixels);
if (scale == 1)
{
@ -249,9 +235,9 @@ namespace Core
const int32_t clipped_w = clipped_right - clipped_left;
const int32_t clipped_h = clipped_bottom - clipped_top;
draw_sprite_ex(clipped_left, clipped_top, *tilemap.atlas,
clipped_src_x, clipped_src_y, clipped_w, clipped_h,
1, false, false);
blit_sprite_pixels(clipped_left, clipped_top, *tilemap.atlas,
clipped_src_x, clipped_src_y, clipped_w, clipped_h,
1, false, false);
}
}
}

View File

@ -3,7 +3,7 @@
#include "Vector2.h"
#include "Triangle.h"
#include "Image.h"
#include "SpriteRegion.h"
#include "Sprite.h"
#include "Tilemap.h"
#include "BitmapFont.h"
#include <cstdint>
@ -41,6 +41,12 @@ namespace Core
int32_t dst_x, int32_t dst_y,
uint16_t pixel);
void blit_sprite_pixels(int32_t dst_x, int32_t dst_y,
const RenderData::Image& image,
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);
public:
DrawContext(int32_t width, int32_t height);
~DrawContext();
@ -58,15 +64,8 @@ namespace Core
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(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region);
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_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region);
void draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region,
int32_t scale, bool flip_h, bool flip_v);
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,
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Sprite& sprite);
void draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Sprite& sprite,
int32_t scale, bool flip_h, bool flip_v);
void draw_tilemap(const RenderData::Tilemap& tilemap,
int32_t screen_x, int32_t screen_y,

View File

@ -4,7 +4,7 @@
namespace RenderData
{
struct SpriteRegion
struct Sprite
{
const Image* atlas;
int32_t x;
@ -12,10 +12,10 @@ namespace RenderData
int32_t width;
int32_t height;
SpriteRegion()
Sprite()
: atlas(nullptr), x(0), y(0), width(0), height(0) {}
SpriteRegion(const Image* atlas, int32_t x, int32_t y, int32_t width, int32_t height)
Sprite(const Image* atlas, int32_t x, int32_t y, int32_t width, int32_t height)
: atlas(atlas), x(x), y(y), width(width), height(height) {}
};
}

View File

@ -1,65 +0,0 @@
#include "SpriteRasterizer.h"
#include "FrameBuffer.h"
#include "../RenderData/Image.h"
#include "../RenderData/Sprite.h"
namespace Rasterizer
{
namespace
{
inline uint16_t rgba5551_to_rgb565(uint16_t c)
{
const uint16_t r = static_cast<uint16_t>((c >> 11) & 0x1Fu);
const uint16_t g = static_cast<uint16_t>((c >> 6) & 0x1Fu);
const uint16_t b = static_cast<uint16_t>((c >> 1) & 0x1Fu);
const uint16_t g6 = static_cast<uint16_t>((g << 1) | (g >> 4));
return static_cast<uint16_t>((r << 11) | (g6 << 5) | b);
}
inline bool rgba5551_is_opaque(uint16_t c)
{
return (c & 0x1u) != 0;
}
}
void SpriteRasterizer::DrawImage(const RenderData::Image& image, int32_t x, int32_t y)
{
DrawSprite(RenderData::Sprite(&image), x, y);
}
void SpriteRasterizer::DrawSprite(const RenderData::Sprite& sprite, int32_t x, int32_t y)
{
if (frameBuffer == nullptr || !sprite.is_valid())
{
return;
}
const int32_t minX = x < 0 ? -x : 0;
const int32_t minY = y < 0 ? -y : 0;
const int32_t maxX = x + sprite.width > frameBuffer->get_width() ? frameBuffer->get_width() - x : sprite.width;
const int32_t maxY = y + sprite.height > frameBuffer->get_height() ? frameBuffer->get_height() - y : sprite.height;
if (minX >= maxX || minY >= maxY)
{
return;
}
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width();
for (int32_t sy = minY; sy < maxY; ++sy)
{
Core::FramePixel* dst_row = dst + (y + sy) * fb_w;
for (int32_t sx = minX; sx < maxX; ++sx)
{
const uint16_t color = sprite.get_pixel_fast(sx, sy);
if (!rgba5551_is_opaque(color))
{
continue;
}
dst_row[x + sx] = rgba5551_to_rgb565(color);
}
}
}
}

View File

@ -1,28 +0,0 @@
#pragma once
#include <cstdint>
namespace Core
{
class FrameBuffer;
}
namespace RenderData
{
class Image;
struct Sprite;
}
namespace Rasterizer
{
class SpriteRasterizer
{
private:
Core::FrameBuffer* frameBuffer;
public:
explicit SpriteRasterizer(Core::FrameBuffer* frameBuffer) : frameBuffer(frameBuffer) {}
void DrawImage(const RenderData::Image& image, int32_t x, int32_t y);
void DrawSprite(const RenderData::Sprite& sprite, int32_t x, int32_t y);
};
}

View File

@ -1,105 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>
namespace RenderData
{
class Image
{
private:
int32_t width;
int32_t height;
std::vector<uint16_t> pixels;
static bool calculate_pixel_count(int32_t width, int32_t height, size_t& count)
{
if (width <= 0 || height <= 0)
{
count = 0;
return false;
}
const size_t safeWidth = static_cast<size_t>(width);
const size_t safeHeight = static_cast<size_t>(height);
if (safeWidth > std::numeric_limits<size_t>::max() / safeHeight)
{
count = 0;
return false;
}
count = safeWidth * safeHeight;
return true;
}
public:
Image() : width(0), height(0) {}
Image(int32_t width, int32_t height) : width(0), height(0)
{
size_t pixelCount = 0;
if (calculate_pixel_count(width, height, pixelCount))
{
this->width = width;
this->height = height;
pixels.assign(pixelCount, 0);
}
}
Image(int32_t width, int32_t height, const std::vector<uint16_t>& pixels) : width(0), height(0)
{
size_t pixelCount = 0;
if (calculate_pixel_count(width, height, pixelCount) && pixels.size() == pixelCount)
{
this->width = width;
this->height = height;
this->pixels = pixels;
}
}
int32_t get_width() const { return width; }
int32_t get_height() const { return height; }
size_t total_pixels() const { return pixels.size(); }
const uint16_t* data() const { return pixels.data(); }
uint16_t* data() { return pixels.data(); }
bool is_valid() const
{
size_t pixelCount = 0;
return calculate_pixel_count(width, height, pixelCount) && pixels.size() == pixelCount;
}
uint16_t get_pixel(int32_t x, int32_t y) const
{
if (x < 0 || x >= width || y < 0 || y >= height)
{
return 0;
}
size_t index = static_cast<size_t>(y) * width + x;
return pixels[index];
}
uint16_t get_pixel_fast(int32_t x, int32_t y) const
{
size_t index = static_cast<size_t>(y) * width + x;
return pixels[index];
}
void set_pixel(int32_t x, int32_t y, uint16_t color)
{
if (x < 0 || x >= width || y < 0 || y >= height)
{
return;
}
size_t index = static_cast<size_t>(y) * width + x;
pixels.at(index) = color;
}
};
}

View File

@ -1,63 +0,0 @@
#pragma once
#include <cstdint>
#include "Image.h"
namespace RenderData
{
struct Sprite
{
const Image* image;
int32_t x;
int32_t y;
int32_t width;
int32_t height;
Sprite() : image(nullptr), x(0), y(0), width(0), height(0) {}
Sprite(const Image* image) :
image(image),
x(0),
y(0),
width(image ? image->get_width() : 0),
height(image ? image->get_height() : 0)
{}
Sprite(const Image* image, int32_t x, int32_t y, int32_t width, int32_t height) :
image(image),
x(x),
y(y),
width(width),
height(height)
{}
bool is_valid() const
{
if (image == nullptr || !image->is_valid())
{
return false;
}
if (x < 0 || y < 0 || width <= 0 || height <= 0)
{
return false;
}
return x + width <= image->get_width() && y + height <= image->get_height();
}
uint16_t get_pixel(int32_t localX, int32_t localY) const
{
if (!is_valid() || localX < 0 || localX >= width || localY < 0 || localY >= height)
{
return 0;
}
return image->get_pixel(x + localX, y + localY);
}
uint16_t get_pixel_fast(int32_t localX, int32_t localY) const
{
return image->get_pixel_fast(x + localX, y + localY);
}
};
}

View File

@ -78,11 +78,12 @@ namespace
Core::DrawContext ctx(2, 1);
ctx.clear_color(RenderData::Color(0, 0, 255, 255));
RenderData::Image sprite(
RenderData::Image image(
sprite_pixels,
2,
1,
RenderData::PixelFormat::RGBA5551);
RenderData::Sprite sprite(&image, 0, 0, image.width, image.height);
ctx.draw_sprite(0, 0, sprite);
CaptureDisplay display;
@ -95,6 +96,29 @@ namespace
assert(pixels[1] == 0x07E0u);
}
void TestDrawSpriteUsesAtlasSubrect()
{
const uint16_t atlas_pixels[] = {
0xF801u,
0x07C1u
};
RenderData::Image atlas(
atlas_pixels,
2,
1,
RenderData::PixelFormat::RGBA5551);
RenderData::Sprite sprite(&atlas, 1, 0, 1, 1);
Core::DrawContext ctx(1, 1);
ctx.clear_color(RenderData::Color::Blue());
ctx.draw_sprite(0, 0, sprite);
CaptureDisplay display;
const Core::FramePixel* pixels = PresentPixels(ctx, display);
assert(pixels[0] == 0x07E0u);
}
void TestSpriteAssetLoaderRoundTripsRgba5551()
{
const std::string path = "render_pipeline_test.sprite";
@ -216,6 +240,7 @@ int main()
TestFrameBufferStoresRgb565();
TestImageDefaultsToRgba5551AndReturnsRawPixels();
TestDrawSpriteUsesOneBitAlpha();
TestDrawSpriteUsesAtlasSubrect();
TestSpriteAssetLoaderRoundTripsRgba5551();
TestDrawTextUsesBitMaskAndColor();
TestDrawTextColorChangesPerCallAndBackgroundFill();