yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_rendering.cc
Go to the documentation of this file.
1#include "canvas_rendering.h"
2
3#include <algorithm>
4#include <cmath>
5
8#include "util/log.h"
9
10namespace yaze {
11namespace gui {
12
13namespace {
14
15// Diagnostic for the silent guard inside RenderBitmapOnCanvas. The renderer
16// drops a draw when the bitmap has no SDL texture (typical for a first-frame
17// composite whose CREATE command is queued but not yet processed). Without
18// this log the symptom is a blank canvas with no breadcrumb. The metadata
19// purpose tag pinned in slice 3 is what makes this log actionable: the call
20// site already declared what the bitmap IS, so the log can name it.
21inline const char* PurposeLabel(gfx::Bitmap::BitmapPurpose purpose) {
22 switch (purpose) {
24 return "preview";
26 return "editable";
28 return "selection-source";
30 return "composite-output";
31 default:
32 return "unspecified";
33 }
34}
35
36inline void LogBitmapDropIfDebug(const gfx::Bitmap& bitmap, const char* site) {
37 LOG_DEBUG("CanvasRender",
38 "Skipped %s: bitmap purpose=%s active=%d size=%dx%d (no texture)",
39 site, PurposeLabel(bitmap.metadata().purpose), bitmap.is_active(),
40 bitmap.width(), bitmap.height());
41}
42
43} // namespace
44
45void RenderCanvasBackground(ImDrawList* draw_list,
46 const CanvasGeometry& geometry) {
47 const auto& theme = ThemeManager::Get().GetCurrentTheme();
48
49 // Draw primary background color from theme
50 draw_list->AddRectFilled(
51 geometry.canvas_p0, geometry.canvas_p1,
52 ImGui::GetColorU32(ConvertColorToImVec4(theme.editor_background)));
53
54 // Add a subtle pattern to the background (e.g., very faint dots)
55 if (theme.enable_glow_effects) {
56 const float dot_spacing = 32.0f;
57 const uint32_t dot_color =
58 ImGui::GetColorU32(ConvertColorToImVec4(theme.editor_grid));
59
60 for (float x =
61 geometry.canvas_p0.x + fmodf(geometry.scrolling.x, dot_spacing);
62 x < geometry.canvas_p1.x; x += dot_spacing) {
63 for (float y =
64 geometry.canvas_p0.y + fmodf(geometry.scrolling.y, dot_spacing);
65 y < geometry.canvas_p1.y; y += dot_spacing) {
66 draw_list->AddCircleFilled(ImVec2(x, y), 1.0f, dot_color);
67 }
68 }
69 }
70
71 // Draw theme-aware border
72 draw_list->AddRect(
73 geometry.canvas_p0, geometry.canvas_p1,
74 ImGui::GetColorU32(ConvertColorToImVec4(theme.border)), 0.0f, 0,
75 theme.window_border_size > 0 ? theme.window_border_size : 1.0f);
76}
77
78void RenderCanvasGrid(ImDrawList* draw_list, const CanvasGeometry& geometry,
79 const CanvasConfig& config, int highlight_tile_id) {
80 if (!config.enable_grid) {
81 return;
82 }
83
84 // Create render context for utility functions (extracted from
85 // Canvas::DrawGrid)
87 .draw_list = draw_list,
88 .canvas_p0 = geometry.canvas_p0,
89 .canvas_p1 = geometry.canvas_p1,
90 .scrolling = geometry.scrolling,
91 .global_scale = config.global_scale,
92 .enable_grid = config.enable_grid,
93 .enable_hex_labels = config.enable_hex_labels,
94 .grid_step = config.grid_step};
95
96 // Use high-level utility function
97 CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id);
98}
99
100void RenderCanvasOverlay(ImDrawList* draw_list, const CanvasGeometry& geometry,
101 const CanvasConfig& config,
102 const ImVector<ImVec2>& points,
103 const ImVector<ImVec2>& selected_points) {
104 // Create render context for utility functions (extracted from
105 // Canvas::DrawOverlay)
107 .draw_list = draw_list,
108 .canvas_p0 = geometry.canvas_p0,
109 .canvas_p1 = geometry.canvas_p1,
110 .scrolling = geometry.scrolling,
111 .global_scale = config.global_scale,
112 .enable_grid = config.enable_grid,
113 .enable_hex_labels = config.enable_hex_labels,
114 .grid_step = config.grid_step};
115
116 // Use high-level utility function
117 CanvasUtils::DrawCanvasOverlay(ctx, points, selected_points);
118}
119
120void RenderCanvasLabels(ImDrawList* draw_list, const CanvasGeometry& geometry,
121 const CanvasConfig& config,
122 const ImVector<ImVector<std::string>>& labels,
123 int current_labels, int tile_id_offset) {
124 if (!config.enable_custom_labels || current_labels >= labels.size()) {
125 return;
126 }
127
128 // Push clip rect to prevent drawing outside canvas
129 draw_list->PushClipRect(geometry.canvas_p0, geometry.canvas_p1, true);
130
131 // Create render context for utility functions
133 .draw_list = draw_list,
134 .canvas_p0 = geometry.canvas_p0,
135 .canvas_p1 = geometry.canvas_p1,
136 .scrolling = geometry.scrolling,
137 .global_scale = config.global_scale,
138 .enable_grid = config.enable_grid,
139 .enable_hex_labels = config.enable_hex_labels,
140 .grid_step = config.grid_step};
141
142 // Use high-level utility function (extracted from Canvas::DrawInfoGrid)
143 CanvasUtils::DrawCanvasLabels(ctx, labels, current_labels, tile_id_offset);
144
145 draw_list->PopClipRect();
146}
147
148void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry,
149 gfx::Bitmap& bitmap, int /*border_offset*/,
150 float scale) {
151 if (!bitmap.is_active() || !bitmap.texture()) {
152 LogBitmapDropIfDebug(bitmap, "RenderBitmapOnCanvas(border)");
153 return;
154 }
155
156 // Extracted from Canvas::DrawBitmap (border offset variant)
157 draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
158 ImVec2(geometry.canvas_p0.x, geometry.canvas_p0.y),
159 ImVec2(geometry.canvas_p0.x + (bitmap.width() * scale),
160 geometry.canvas_p0.y + (bitmap.height() * scale)));
161 // NOTE: Canvas border is drawn once by RenderCanvasBackground().
162 // Do NOT draw border here - this function is called per-bitmap, which would
163 // cause N stacked border rectangles (e.g., 64 in overworld editor).
164}
165
166void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry,
167 gfx::Bitmap& bitmap, int x_offset, int y_offset,
168 float scale, int alpha) {
169 if (!bitmap.is_active() || !bitmap.texture()) {
170 LogBitmapDropIfDebug(bitmap, "RenderBitmapOnCanvas(offset)");
171 return;
172 }
173
174 // Calculate the actual rendered size including scale and offsets
175 // CRITICAL: Use scale parameter (NOT global_scale_) for per-bitmap scaling
176 // Extracted from Canvas::DrawBitmap (x/y offset variant)
177 ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
178
179 // CRITICAL FIX: Draw bitmap WITHOUT additional global_scale multiplication
180 // The scale parameter already contains the correct scale factor
181 // The scrolling should NOT be scaled - it's already in screen space
182 draw_list->AddImage(
183 (ImTextureID)(intptr_t)bitmap.texture(),
184 ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x,
185 geometry.canvas_p0.y + y_offset + geometry.scrolling.y),
186 ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x +
187 rendered_size.x,
188 geometry.canvas_p0.y + y_offset + geometry.scrolling.y +
189 rendered_size.y),
190 ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
191}
192
193void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry,
194 gfx::Bitmap& bitmap, ImVec2 dest_pos,
195 ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) {
196 if (!bitmap.is_active() || !bitmap.texture()) {
197 LogBitmapDropIfDebug(bitmap, "RenderBitmapOnCanvas(custom-uv)");
198 return;
199 }
200
201 // Extracted from Canvas::DrawBitmap (custom source/dest regions variant)
202 draw_list->AddImage(
203 (ImTextureID)(intptr_t)bitmap.texture(),
204 ImVec2(geometry.canvas_p0.x + dest_pos.x,
205 geometry.canvas_p0.y + dest_pos.y),
206 ImVec2(geometry.canvas_p0.x + dest_pos.x + dest_size.x,
207 geometry.canvas_p0.y + dest_pos.y + dest_size.y),
208 ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()),
209 ImVec2((src_pos.x + src_size.x) / bitmap.width(),
210 (src_pos.y + src_size.y) / bitmap.height()));
211}
212
213void RenderBitmapGroup(ImDrawList* draw_list, const CanvasGeometry& geometry,
214 std::vector<int>& group, gfx::Tilemap& tilemap,
215 int tile_size, float scale, int local_map_size,
216 ImVec2 total_map_size) {
217 // Extracted from Canvas::DrawBitmapGroup (lines 1148-1264)
218 // This is used for multi-tile selection preview in overworld editor
219
220 if (group.empty()) {
221 return;
222 }
223
224 // OPTIMIZATION: Use optimized rendering for large groups to improve
225 // performance
226 bool use_optimized_rendering = group.size() > 128;
227
228 // Pre-calculate common values to avoid repeated computation
229 const float tile_scale = tile_size * scale;
230 const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
231
232 // Get selected points (note: this assumes selected_points are available in
233 // context) For now, we'll just render tiles at their grid positions The full
234 // implementation would need the selected_points passed in
235
236 int i = 0;
237 for (const auto tile_id : group) {
238 // Calculate grid position for this tile
239 int tiles_per_row = 32; // Default for standard maps
240 int x = i % tiles_per_row;
241 int y = i / tiles_per_row;
242
243 int tile_pos_x = x * tile_size * scale;
244 int tile_pos_y = y * tile_size * scale;
245
246 // Check if tile_id is within the range
247 auto tilemap_size = tilemap.map_size.x;
248 if (tile_id >= 0 && tile_id < tilemap_size) {
249 if (tilemap.atlas.is_active() && tilemap.atlas.texture() &&
250 atlas_tiles_per_row > 0) {
251 int atlas_tile_x =
252 (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x;
253 int atlas_tile_y =
254 (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y;
255
256 // Simple bounds check
257 if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() &&
258 atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) {
259 // Calculate UV coordinates once for efficiency
260 const float atlas_width = static_cast<float>(tilemap.atlas.width());
261 const float atlas_height = static_cast<float>(tilemap.atlas.height());
262 ImVec2 uv0 =
263 ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
264 ImVec2 uv1 =
265 ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width,
266 (atlas_tile_y + tilemap.tile_size.y) / atlas_height);
267
268 // Calculate screen positions
269 float screen_x =
270 geometry.canvas_p0.x + geometry.scrolling.x + tile_pos_x;
271 float screen_y =
272 geometry.canvas_p0.y + geometry.scrolling.y + tile_pos_y;
273 float screen_w = tilemap.tile_size.x * scale;
274 float screen_h = tilemap.tile_size.y * scale;
275
276 // Use higher alpha for large selections to make them more visible
277 uint32_t alpha_color = use_optimized_rendering
278 ? IM_COL32(255, 255, 255, 200)
279 : IM_COL32(255, 255, 255, 150);
280
281 // Draw from atlas texture with optimized parameters
282 draw_list->AddImage((ImTextureID)(intptr_t)tilemap.atlas.texture(),
283 ImVec2(screen_x, screen_y),
284 ImVec2(screen_x + screen_w, screen_y + screen_h),
285 uv0, uv1, alpha_color);
286 }
287 }
288 }
289 i++;
290 }
291}
292
293} // namespace gui
294} // namespace yaze
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
TextureHandle texture() const
Definition bitmap.h:401
BitmapPurpose
How a bitmap is used in the editor pipeline.
Definition bitmap.h:360
BitmapMetadata & metadata()
Definition bitmap.h:391
bool is_active() const
Definition bitmap.h:405
int height() const
Definition bitmap.h:395
int width() const
Definition bitmap.h:394
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
#define LOG_DEBUG(category, format,...)
Definition log.h:103
void DrawCanvasLabels(const CanvasRenderContext &ctx, const ImVector< ImVector< std::string > > &labels, int current_labels, int tile_id_offset)
void DrawCanvasOverlay(const CanvasRenderContext &ctx, const ImVector< ImVec2 > &points, const ImVector< ImVec2 > &selected_points)
void DrawCanvasGrid(const CanvasRenderContext &ctx, int highlight_tile_id)
void LogBitmapDropIfDebug(const gfx::Bitmap &bitmap, const char *site)
const char * PurposeLabel(gfx::Bitmap::BitmapPurpose purpose)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
void RenderBitmapGroup(ImDrawList *draw_list, const CanvasGeometry &geometry, std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale, int local_map_size, ImVec2 total_map_size)
Render group of bitmaps from tilemap.
void RenderCanvasLabels(ImDrawList *draw_list, const CanvasGeometry &geometry, const CanvasConfig &config, const ImVector< ImVector< std::string > > &labels, int current_labels, int tile_id_offset)
Render canvas labels on grid.
void RenderCanvasBackground(ImDrawList *draw_list, const CanvasGeometry &geometry)
Render canvas background and border.
void RenderCanvasGrid(ImDrawList *draw_list, const CanvasGeometry &geometry, const CanvasConfig &config, int highlight_tile_id)
Render canvas grid with optional highlighting.
void RenderBitmapOnCanvas(ImDrawList *draw_list, const CanvasGeometry &geometry, gfx::Bitmap &bitmap, int, float scale)
Render bitmap on canvas (border offset variant)
void RenderCanvasOverlay(ImDrawList *draw_list, const CanvasGeometry &geometry, const CanvasConfig &config, const ImVector< ImVec2 > &points, const ImVector< ImVec2 > &selected_points)
Render canvas overlay (hover and selection points)
int y
Y coordinate or height.
Definition tilemap.h:21
int x
X coordinate or width.
Definition tilemap.h:20
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:118
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:123
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:124
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
Unified configuration for canvas display and interaction.
Canvas geometry calculated per-frame.