refactor: Organize Canvas Utilities and BPP Format Management

- Moved canvas utility functions into a dedicated `canvas_utils` module for better structure and maintainability.
- Introduced a new `BppFormatUI` class for managing BPP format selection and conversion, enhancing the user interface for format management.
- Updated CMake configuration to include new source and header files for the canvas utilities and BPP format UI.
- Adjusted file paths in the project structure to reflect the new organization, ensuring proper integration across components.
This commit is contained in:
scawful
2025-10-10 12:02:33 -04:00
parent 33335782e7
commit f538775954
9 changed files with 16 additions and 9 deletions

View File

@@ -0,0 +1,620 @@
#include "bpp_format_ui.h"
#include <algorithm>
#include <sstream>
#include "app/gfx/bpp_format_manager.h"
#include "app/gfx/bitmap.h"
#include "app/gui/ui_helpers.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
BppFormatUI::BppFormatUI(const std::string& id)
: id_(id), selected_format_(gfx::BppFormat::kBpp8), preview_format_(gfx::BppFormat::kBpp8),
show_analysis_(false), show_preview_(false), show_sheet_analysis_(false),
format_changed_(false), last_analysis_sheet_("") {
}
bool BppFormatUI::RenderFormatSelector(gfx::Bitmap* bitmap, const gfx::SnesPalette& palette,
std::function<void(gfx::BppFormat)> on_format_changed) {
if (!bitmap) return false;
format_changed_ = false;
ImGui::BeginGroup();
ImGui::Text("BPP Format Selection");
ImGui::Separator();
// Current format detection
gfx::BppFormat current_format = gfx::BppFormatManager::Get().DetectFormat(
bitmap->vector(), bitmap->width(), bitmap->height());
ImGui::Text("Current Format: %s",
gfx::BppFormatManager::Get().GetFormatInfo(current_format).name.c_str());
// Format selection
ImGui::Text("Target Format:");
ImGui::SameLine();
const char* format_names[] = {"2BPP", "3BPP", "4BPP", "8BPP"};
int current_selection = static_cast<int>(selected_format_) - 2; // Convert to 0-based index
if (ImGui::Combo("##BppFormat", &current_selection, format_names, 4)) {
selected_format_ = static_cast<gfx::BppFormat>(current_selection + 2);
format_changed_ = true;
}
// Format information
const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(selected_format_);
ImGui::Text("Max Colors: %d", format_info.max_colors);
ImGui::Text("Bytes per Tile: %d", format_info.bytes_per_tile);
ImGui::Text("Description: %s", format_info.description.c_str());
// Conversion efficiency
if (current_format != selected_format_) {
int efficiency = GetConversionEfficiency(current_format, selected_format_);
ImGui::Text("Conversion Efficiency: %d%%", efficiency);
ImVec4 efficiency_color;
if (efficiency >= 80) {
efficiency_color = GetSuccessColor(); // Green
} else if (efficiency >= 60) {
efficiency_color = GetWarningColor(); // Yellow
} else {
efficiency_color = GetErrorColor(); // Red
}
ImGui::TextColored(efficiency_color, "Quality: %s",
efficiency >= 80 ? "Excellent" :
efficiency >= 60 ? "Good" : "Poor");
}
// Action buttons
ImGui::Separator();
if (ImGui::Button("Convert Format")) {
if (on_format_changed) {
on_format_changed(selected_format_);
}
format_changed_ = true;
}
ImGui::SameLine();
if (ImGui::Button("Show Analysis")) {
show_analysis_ = !show_analysis_;
}
ImGui::SameLine();
if (ImGui::Button("Preview Conversion")) {
show_preview_ = !show_preview_;
preview_format_ = selected_format_;
}
ImGui::EndGroup();
// Analysis panel
if (show_analysis_) {
RenderAnalysisPanel(*bitmap, palette);
}
// Preview panel
if (show_preview_) {
RenderConversionPreview(*bitmap, preview_format_, palette);
}
return format_changed_;
}
void BppFormatUI::RenderAnalysisPanel(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette) {
ImGui::Begin("BPP Format Analysis", &show_analysis_);
// Basic analysis
gfx::BppFormat detected_format = gfx::BppFormatManager::Get().DetectFormat(
bitmap.vector(), bitmap.width(), bitmap.height());
ImGui::Text("Detected Format: %s",
gfx::BppFormatManager::Get().GetFormatInfo(detected_format).name.c_str());
// Color usage analysis
std::vector<int> color_usage(256, 0);
for (uint8_t pixel : bitmap.vector()) {
color_usage[pixel]++;
}
int used_colors = 0;
for (int count : color_usage) {
if (count > 0) used_colors++;
}
ImGui::Text("Colors Used: %d / %d", used_colors, static_cast<int>(palette.size()));
ImGui::Text("Color Efficiency: %.1f%%",
(static_cast<float>(used_colors) / palette.size()) * 100.0f);
// Color usage chart
if (ImGui::CollapsingHeader("Color Usage Chart")) {
RenderColorUsageChart(color_usage);
}
// Format recommendations
ImGui::Separator();
ImGui::Text("Format Recommendations:");
if (used_colors <= 4) {
ImGui::TextColored(GetSuccessColor(), "✓ 2BPP format would be optimal");
} else if (used_colors <= 8) {
ImGui::TextColored(GetSuccessColor(), "✓ 3BPP format would be optimal");
} else if (used_colors <= 16) {
ImGui::TextColored(GetSuccessColor(), "✓ 4BPP format would be optimal");
} else {
ImGui::TextColored(GetWarningColor(), "⚠ 8BPP format is necessary");
}
// Memory usage comparison
if (ImGui::CollapsingHeader("Memory Usage Comparison")) {
const auto& current_info = gfx::BppFormatManager::Get().GetFormatInfo(detected_format);
int current_bytes = (bitmap.width() * bitmap.height() * current_info.bits_per_pixel) / 8;
ImGui::Text("Current Format (%s): %d bytes", current_info.name.c_str(), current_bytes);
for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) {
if (format == detected_format) continue;
const auto& info = gfx::BppFormatManager::Get().GetFormatInfo(format);
int format_bytes = (bitmap.width() * bitmap.height() * info.bits_per_pixel) / 8;
float ratio = static_cast<float>(format_bytes) / current_bytes;
ImGui::Text("%s: %d bytes (%.1fx)", info.name.c_str(), format_bytes, ratio);
}
}
ImGui::End();
}
void BppFormatUI::RenderConversionPreview(const gfx::Bitmap& bitmap, gfx::BppFormat target_format,
const gfx::SnesPalette& palette) {
ImGui::Begin("BPP Conversion Preview", &show_preview_);
gfx::BppFormat current_format = gfx::BppFormatManager::Get().DetectFormat(
bitmap.vector(), bitmap.width(), bitmap.height());
if (current_format == target_format) {
ImGui::Text("No conversion needed - formats are identical");
ImGui::End();
return;
}
// Convert the bitmap
auto converted_data = gfx::BppFormatManager::Get().ConvertFormat(
bitmap.vector(), current_format, target_format, bitmap.width(), bitmap.height());
// Create preview bitmap
gfx::Bitmap preview_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(),
converted_data, palette);
// Render side-by-side comparison
ImGui::Text("Original (%s) vs Converted (%s)",
gfx::BppFormatManager::Get().GetFormatInfo(current_format).name.c_str(),
gfx::BppFormatManager::Get().GetFormatInfo(target_format).name.c_str());
ImGui::Columns(2, "PreviewColumns");
// Original
ImGui::Text("Original");
if (bitmap.texture()) {
ImGui::Image((ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(256, 256 * bitmap.height() / bitmap.width()));
}
ImGui::NextColumn();
// Converted
ImGui::Text("Converted");
if (preview_bitmap.texture()) {
ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(),
ImVec2(256, 256 * preview_bitmap.height() / preview_bitmap.width()));
}
ImGui::Columns(1);
// Conversion statistics
ImGui::Separator();
ImGui::Text("Conversion Statistics:");
const auto& from_info = gfx::BppFormatManager::Get().GetFormatInfo(current_format);
const auto& to_info = gfx::BppFormatManager::Get().GetFormatInfo(target_format);
int from_bytes = (bitmap.width() * bitmap.height() * from_info.bits_per_pixel) / 8;
int to_bytes = (bitmap.width() * bitmap.height() * to_info.bits_per_pixel) / 8;
ImGui::Text("Size: %d bytes -> %d bytes", from_bytes, to_bytes);
ImGui::Text("Compression Ratio: %.2fx", static_cast<float>(from_bytes) / to_bytes);
ImGui::End();
}
void BppFormatUI::RenderSheetAnalysis(const std::vector<uint8_t>& sheet_data, int sheet_id,
const gfx::SnesPalette& palette) {
std::string analysis_key = "sheet_" + std::to_string(sheet_id);
// Check if we need to update analysis
if (last_analysis_sheet_ != analysis_key) {
auto analysis = gfx::BppFormatManager::Get().AnalyzeGraphicsSheet(sheet_data, sheet_id, palette);
UpdateAnalysisCache(sheet_id, analysis);
last_analysis_sheet_ = analysis_key;
}
auto it = cached_analysis_.find(sheet_id);
if (it == cached_analysis_.end()) return;
const auto& analysis = it->second;
ImGui::Begin("Graphics Sheet Analysis", &show_sheet_analysis_);
ImGui::Text("Sheet ID: %d", analysis.sheet_id);
ImGui::Text("Original Format: %s",
gfx::BppFormatManager::Get().GetFormatInfo(analysis.original_format).name.c_str());
ImGui::Text("Current Format: %s",
gfx::BppFormatManager::Get().GetFormatInfo(analysis.current_format).name.c_str());
if (analysis.was_converted) {
ImGui::TextColored(GetWarningColor(), "⚠ This sheet was converted");
ImGui::Text("Conversion History: %s", analysis.conversion_history.c_str());
} else {
ImGui::TextColored(GetSuccessColor(), "✓ Original format preserved");
}
ImGui::Separator();
ImGui::Text("Color Usage: %d / %d colors used",
analysis.palette_entries_used, static_cast<int>(palette.size()));
ImGui::Text("Compression Ratio: %.2fx", analysis.compression_ratio);
ImGui::Text("Size: %zu -> %zu bytes", analysis.original_size, analysis.current_size);
// Tile usage pattern
if (ImGui::CollapsingHeader("Tile Usage Pattern")) {
int total_tiles = analysis.tile_usage_pattern.size();
int used_tiles = 0;
int empty_tiles = 0;
for (int usage : analysis.tile_usage_pattern) {
if (usage > 0) {
used_tiles++;
} else {
empty_tiles++;
}
}
ImGui::Text("Total Tiles: %d", total_tiles);
ImGui::Text("Used Tiles: %d (%.1f%%)", used_tiles,
(static_cast<float>(used_tiles) / total_tiles) * 100.0f);
ImGui::Text("Empty Tiles: %d (%.1f%%)", empty_tiles,
(static_cast<float>(empty_tiles) / total_tiles) * 100.0f);
}
// Recommendations
ImGui::Separator();
ImGui::Text("Recommendations:");
if (analysis.was_converted && analysis.palette_entries_used <= 16) {
ImGui::TextColored(GetSuccessColor(),
"✓ Consider reverting to %s format for better compression",
gfx::BppFormatManager::Get().GetFormatInfo(analysis.original_format).name.c_str());
}
if (analysis.palette_entries_used < static_cast<int>(palette.size()) / 2) {
ImGui::TextColored(GetWarningColor(),
"⚠ Palette is underutilized - consider optimization");
}
ImGui::End();
}
bool BppFormatUI::IsConversionAvailable(gfx::BppFormat from_format, gfx::BppFormat to_format) const {
// All conversions are available in our implementation
return from_format != to_format;
}
int BppFormatUI::GetConversionEfficiency(gfx::BppFormat from_format, gfx::BppFormat to_format) const {
// Calculate efficiency based on format compatibility
if (from_format == to_format) return 100;
// Higher BPP to lower BPP conversions may lose quality
if (static_cast<int>(from_format) > static_cast<int>(to_format)) {
int bpp_diff = static_cast<int>(from_format) - static_cast<int>(to_format);
return std::max(20, 100 - (bpp_diff * 20)); // Reduce efficiency by 20% per BPP level
}
// Lower BPP to higher BPP conversions are lossless
return 100;
}
void BppFormatUI::RenderFormatInfo(const gfx::BppFormatInfo& info) {
ImGui::Text("Format: %s", info.name.c_str());
ImGui::Text("Bits per Pixel: %d", info.bits_per_pixel);
ImGui::Text("Max Colors: %d", info.max_colors);
ImGui::Text("Bytes per Tile: %d", info.bytes_per_tile);
ImGui::Text("Compressed: %s", info.is_compressed ? "Yes" : "No");
ImGui::Text("Description: %s", info.description.c_str());
}
void BppFormatUI::RenderColorUsageChart(const std::vector<int>& color_usage) {
// Find maximum usage for scaling
int max_usage = *std::max_element(color_usage.begin(), color_usage.end());
if (max_usage == 0) return;
// Render simple bar chart
ImGui::Text("Color Usage Distribution:");
for (size_t i = 0; i < std::min(color_usage.size(), size_t(16)); ++i) {
if (color_usage[i] > 0) {
float usage_ratio = static_cast<float>(color_usage[i]) / max_usage;
ImGui::Text("Color %zu: %d pixels (%.1f%%)", i, color_usage[i],
(static_cast<float>(color_usage[i]) / (16 * 16)) * 100.0f);
ImGui::SameLine();
ImGui::ProgressBar(usage_ratio, ImVec2(100, 0));
}
}
}
void BppFormatUI::RenderConversionHistory(const std::string& history) {
ImGui::Text("Conversion History:");
ImGui::TextWrapped("%s", history.c_str());
}
std::string BppFormatUI::GetFormatDescription(gfx::BppFormat format) const {
return gfx::BppFormatManager::Get().GetFormatInfo(format).description;
}
ImVec4 BppFormatUI::GetFormatColor(gfx::BppFormat format) const {
switch (format) {
case gfx::BppFormat::kBpp2: return ImVec4(1, 0, 0, 1); // Red
case gfx::BppFormat::kBpp3: return ImVec4(1, 1, 0, 1); // Yellow
case gfx::BppFormat::kBpp4: return ImVec4(0, 1, 0, 1); // Green
case gfx::BppFormat::kBpp8: return ImVec4(0, 0, 1, 1); // Blue
default: return ImVec4(1, 1, 1, 1); // White
}
}
void BppFormatUI::UpdateAnalysisCache(int sheet_id, const gfx::GraphicsSheetAnalysis& analysis) {
cached_analysis_[sheet_id] = analysis;
}
// BppConversionDialog implementation
BppConversionDialog::BppConversionDialog(const std::string& id)
: id_(id), is_open_(false), target_format_(gfx::BppFormat::kBpp8),
preserve_palette_(true), preview_valid_(false), show_preview_(true), preview_scale_(1.0f) {
}
void BppConversionDialog::Show(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette,
std::function<void(gfx::BppFormat, bool)> on_convert) {
source_bitmap_ = bitmap;
source_palette_ = palette;
convert_callback_ = on_convert;
is_open_ = true;
preview_valid_ = false;
}
bool BppConversionDialog::Render() {
if (!is_open_) return false;
ImGui::OpenPopup("BPP Format Conversion");
if (ImGui::BeginPopupModal("BPP Format Conversion", &is_open_,
ImGuiWindowFlags_AlwaysAutoResize)) {
RenderFormatSelector();
ImGui::Separator();
RenderOptions();
ImGui::Separator();
if (show_preview_) {
RenderPreview();
ImGui::Separator();
}
RenderButtons();
ImGui::EndPopup();
}
return is_open_;
}
void BppConversionDialog::UpdatePreview() {
if (preview_valid_) return;
gfx::BppFormat current_format = gfx::BppFormatManager::Get().DetectFormat(
source_bitmap_.vector(), source_bitmap_.width(), source_bitmap_.height());
if (current_format == target_format_) {
preview_bitmap_ = source_bitmap_;
preview_valid_ = true;
return;
}
auto converted_data = gfx::BppFormatManager::Get().ConvertFormat(
source_bitmap_.vector(), current_format, target_format_,
source_bitmap_.width(), source_bitmap_.height());
preview_bitmap_ = gfx::Bitmap(source_bitmap_.width(), source_bitmap_.height(),
source_bitmap_.depth(), converted_data, source_palette_);
preview_valid_ = true;
}
void BppConversionDialog::RenderFormatSelector() {
ImGui::Text("Convert to BPP Format:");
const char* format_names[] = {"2BPP", "3BPP", "4BPP", "8BPP"};
int current_selection = static_cast<int>(target_format_) - 2;
if (ImGui::Combo("##TargetFormat", &current_selection, format_names, 4)) {
target_format_ = static_cast<gfx::BppFormat>(current_selection + 2);
preview_valid_ = false; // Invalidate preview
}
const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(target_format_);
ImGui::Text("Max Colors: %d", format_info.max_colors);
ImGui::Text("Description: %s", format_info.description.c_str());
}
void BppConversionDialog::RenderPreview() {
if (ImGui::Button("Update Preview")) {
preview_valid_ = false;
}
UpdatePreview();
if (preview_valid_ && preview_bitmap_.texture()) {
ImGui::Text("Preview:");
ImGui::Image((ImTextureID)(intptr_t)preview_bitmap_.texture(),
ImVec2(128 * preview_scale_, 128 * preview_scale_));
ImGui::SliderFloat("Scale", &preview_scale_, 0.5f, 3.0f);
}
}
void BppConversionDialog::RenderOptions() {
ImGui::Checkbox("Preserve Palette", &preserve_palette_);
ImGui::SameLine();
ImGui::Checkbox("Show Preview", &show_preview_);
}
void BppConversionDialog::RenderButtons() {
if (ImGui::Button("Convert")) {
if (convert_callback_) {
convert_callback_(target_format_, preserve_palette_);
}
is_open_ = false;
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
is_open_ = false;
}
}
// BppComparisonTool implementation
BppComparisonTool::BppComparisonTool(const std::string& id)
: id_(id), is_open_(false), has_source_(false), comparison_scale_(1.0f),
show_metrics_(true), selected_comparison_(gfx::BppFormat::kBpp8) {
}
void BppComparisonTool::SetSource(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette) {
source_bitmap_ = bitmap;
source_palette_ = palette;
has_source_ = true;
GenerateComparisons();
}
void BppComparisonTool::Render() {
if (!is_open_ || !has_source_) return;
ImGui::Begin("BPP Format Comparison", &is_open_);
RenderFormatSelector();
ImGui::Separator();
RenderComparisonGrid();
if (show_metrics_) {
ImGui::Separator();
RenderMetrics();
}
ImGui::End();
}
void BppComparisonTool::GenerateComparisons() {
gfx::BppFormat source_format = gfx::BppFormatManager::Get().DetectFormat(
source_bitmap_.vector(), source_bitmap_.width(), source_bitmap_.height());
for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) {
if (format == source_format) {
comparison_bitmaps_[format] = source_bitmap_;
comparison_palettes_[format] = source_palette_;
comparison_valid_[format] = true;
continue;
}
try {
auto converted_data = gfx::BppFormatManager::Get().ConvertFormat(
source_bitmap_.vector(), source_format, format,
source_bitmap_.width(), source_bitmap_.height());
comparison_bitmaps_[format] = gfx::Bitmap(source_bitmap_.width(), source_bitmap_.height(),
source_bitmap_.depth(), converted_data, source_palette_);
comparison_palettes_[format] = source_palette_;
comparison_valid_[format] = true;
} catch (...) {
comparison_valid_[format] = false;
}
}
}
void BppComparisonTool::RenderComparisonGrid() {
ImGui::Text("Format Comparison (Scale: %.1fx)", comparison_scale_);
ImGui::SliderFloat("##Scale", &comparison_scale_, 0.5f, 3.0f);
ImGui::Columns(2, "ComparisonColumns");
for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) {
auto it = comparison_bitmaps_.find(format);
if (it == comparison_bitmaps_.end() || !comparison_valid_[format]) continue;
const auto& bitmap = it->second;
const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(format);
ImGui::Text("%s", format_info.name.c_str());
if (bitmap.texture()) {
ImGui::Image((ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(128 * comparison_scale_, 128 * comparison_scale_));
}
ImGui::NextColumn();
}
ImGui::Columns(1);
}
void BppComparisonTool::RenderMetrics() {
ImGui::Text("Format Metrics:");
for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) {
if (!comparison_valid_[format]) continue;
const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(format);
std::string metrics = CalculateMetrics(format);
ImGui::Text("%s: %s", format_info.name.c_str(), metrics.c_str());
}
}
void BppComparisonTool::RenderFormatSelector() {
ImGui::Text("Selected for Analysis: ");
ImGui::SameLine();
const char* format_names[] = {"2BPP", "3BPP", "4BPP", "8BPP"};
int selection = static_cast<int>(selected_comparison_) - 2;
if (ImGui::Combo("##SelectedFormat", &selection, format_names, 4)) {
selected_comparison_ = static_cast<gfx::BppFormat>(selection + 2);
}
ImGui::SameLine();
ImGui::Checkbox("Show Metrics", &show_metrics_);
}
std::string BppComparisonTool::CalculateMetrics(gfx::BppFormat format) const {
const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(format);
int bytes = (source_bitmap_.width() * source_bitmap_.height() * format_info.bits_per_pixel) / 8;
std::ostringstream metrics;
metrics << bytes << " bytes, " << format_info.max_colors << " colors";
return metrics.str();
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,248 @@
#ifndef YAZE_APP_GUI_BPP_FORMAT_UI_H
#define YAZE_APP_GUI_BPP_FORMAT_UI_H
#include <string>
#include <vector>
#include <functional>
#include "app/gfx/bpp_format_manager.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
namespace yaze {
namespace gui {
/**
* @brief BPP format selection and conversion UI component
*
* Provides a comprehensive UI for BPP format management in the YAZE ROM hacking editor.
* Includes format selection, conversion preview, and analysis tools.
*/
class BppFormatUI {
public:
/**
* @brief Constructor
* @param id Unique identifier for this UI component
*/
explicit BppFormatUI(const std::string& id);
/**
* @brief Render the BPP format selection UI
* @param bitmap Current bitmap being edited
* @param palette Current palette
* @param on_format_changed Callback when format is changed
* @return True if format was changed
*/
bool RenderFormatSelector(gfx::Bitmap* bitmap, const gfx::SnesPalette& palette,
std::function<void(gfx::BppFormat)> on_format_changed);
/**
* @brief Render format analysis panel
* @param bitmap Bitmap to analyze
* @param palette Palette to analyze
*/
void RenderAnalysisPanel(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette);
/**
* @brief Render conversion preview
* @param bitmap Source bitmap
* @param target_format Target BPP format
* @param palette Source palette
*/
void RenderConversionPreview(const gfx::Bitmap& bitmap, gfx::BppFormat target_format,
const gfx::SnesPalette& palette);
/**
* @brief Render graphics sheet analysis
* @param sheet_data Graphics sheet data
* @param sheet_id Sheet identifier
* @param palette Sheet palette
*/
void RenderSheetAnalysis(const std::vector<uint8_t>& sheet_data, int sheet_id,
const gfx::SnesPalette& palette);
/**
* @brief Get currently selected BPP format
* @return Selected BPP format
*/
gfx::BppFormat GetSelectedFormat() const { return selected_format_; }
/**
* @brief Set the selected BPP format
* @param format BPP format to select
*/
void SetSelectedFormat(gfx::BppFormat format) { selected_format_ = format; }
/**
* @brief Check if format conversion is available
* @param from_format Source format
* @param to_format Target format
* @return True if conversion is available
*/
bool IsConversionAvailable(gfx::BppFormat from_format, gfx::BppFormat to_format) const;
/**
* @brief Get conversion efficiency score
* @param from_format Source format
* @param to_format Target format
* @return Efficiency score (0-100)
*/
int GetConversionEfficiency(gfx::BppFormat from_format, gfx::BppFormat to_format) const;
private:
std::string id_;
gfx::BppFormat selected_format_;
gfx::BppFormat preview_format_;
bool show_analysis_;
bool show_preview_;
bool show_sheet_analysis_;
// Analysis cache
std::unordered_map<int, gfx::GraphicsSheetAnalysis> cached_analysis_;
// UI state
bool format_changed_;
std::string last_analysis_sheet_;
// Helper methods
void RenderFormatInfo(const gfx::BppFormatInfo& info);
void RenderColorUsageChart(const std::vector<int>& color_usage);
void RenderConversionHistory(const std::string& history);
std::string GetFormatDescription(gfx::BppFormat format) const;
ImVec4 GetFormatColor(gfx::BppFormat format) const;
void UpdateAnalysisCache(int sheet_id, const gfx::GraphicsSheetAnalysis& analysis);
};
/**
* @brief BPP format conversion dialog
*/
class BppConversionDialog {
public:
/**
* @brief Constructor
* @param id Unique identifier
*/
explicit BppConversionDialog(const std::string& id);
/**
* @brief Show the conversion dialog
* @param bitmap Bitmap to convert
* @param palette Palette to use
* @param on_convert Callback when conversion is confirmed
*/
void Show(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette,
std::function<void(gfx::BppFormat, bool)> on_convert);
/**
* @brief Render the dialog
* @return True if dialog should remain open
*/
bool Render();
/**
* @brief Check if dialog is open
* @return True if dialog is open
*/
bool IsOpen() const { return is_open_; }
/**
* @brief Close the dialog
*/
void Close() { is_open_ = false; }
private:
std::string id_;
bool is_open_;
gfx::Bitmap source_bitmap_;
gfx::SnesPalette source_palette_;
gfx::BppFormat target_format_;
bool preserve_palette_;
std::function<void(gfx::BppFormat, bool)> convert_callback_;
// Preview data
std::vector<uint8_t> preview_data_;
gfx::Bitmap preview_bitmap_;
bool preview_valid_;
// UI state
bool show_preview_;
float preview_scale_;
// Helper methods
void UpdatePreview();
void RenderFormatSelector();
void RenderPreview();
void RenderOptions();
void RenderButtons();
};
/**
* @brief BPP format comparison tool
*/
class BppComparisonTool {
public:
/**
* @brief Constructor
* @param id Unique identifier
*/
explicit BppComparisonTool(const std::string& id);
/**
* @brief Set source bitmap for comparison
* @param bitmap Source bitmap
* @param palette Source palette
*/
void SetSource(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette);
/**
* @brief Render the comparison tool
*/
void Render();
/**
* @brief Check if tool is open
* @return True if tool is open
*/
bool IsOpen() const { return is_open_; }
/**
* @brief Open the tool
*/
void Open() { is_open_ = true; }
/**
* @brief Close the tool
*/
void Close() { is_open_ = false; }
private:
std::string id_;
bool is_open_;
// Source data
gfx::Bitmap source_bitmap_;
gfx::SnesPalette source_palette_;
bool has_source_;
// Comparison data
std::unordered_map<gfx::BppFormat, gfx::Bitmap> comparison_bitmaps_;
std::unordered_map<gfx::BppFormat, gfx::SnesPalette> comparison_palettes_;
std::unordered_map<gfx::BppFormat, bool> comparison_valid_;
// UI state
float comparison_scale_;
bool show_metrics_;
gfx::BppFormat selected_comparison_;
// Helper methods
void GenerateComparisons();
void RenderComparisonGrid();
void RenderMetrics();
void RenderFormatSelector();
std::string CalculateMetrics(gfx::BppFormat format) const;
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_BPP_FORMAT_UI_H

View File

@@ -3,19 +3,23 @@
# Canvas core components
set(CANVAS_SOURCES
bpp_format_ui.cc
canvas_modals.cc
canvas_context_menu.cc
canvas_usage_tracker.cc
canvas_performance_integration.cc
canvas_interaction_handler.cc
canvas_utils.cc
)
set(CANVAS_HEADERS
bpp_format_ui.h
canvas_modals.h
canvas_context_menu.h
canvas_usage_tracker.h
canvas_performance_integration.h
canvas_interaction_handler.h
canvas_utils.h
)
# Create canvas library
@@ -43,6 +47,9 @@ target_include_directories(yaze_canvas PUBLIC
target_link_libraries(yaze_canvas PUBLIC
yaze_gfx
yaze_gui_common
absl::status
absl::statusor
absl::strings
imgui
SDL2::SDL2
)

View File

@@ -0,0 +1,389 @@
#include "canvas_utils.h"
#include <cmath>
#include "app/gfx/arena.h"
#include "app/gfx/snes_palette.h"
#include "util/log.h"
namespace yaze {
namespace gui {
namespace CanvasUtils {
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
std::floor(pos.y / grid_step) * grid_step);
}
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size,
float global_scale) {
if (content_size.x <= 0 || content_size.y <= 0)
return global_scale;
float scale_x = (canvas_size.x * global_scale) / content_size.x;
float scale_y = (canvas_size.y * global_scale) / content_size.y;
return std::min(scale_x, scale_y);
}
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale,
int tiles_per_row) {
float scaled_tile_size = tile_size * scale;
int tile_x = static_cast<int>(mouse_pos.x / scaled_tile_size);
int tile_y = static_cast<int>(mouse_pos.y / scaled_tile_size);
return tile_x + (tile_y * tiles_per_row);
}
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) {
if (!rom || palette_manager.palettes_loaded) {
return palette_manager.palettes_loaded;
}
try {
const auto& palette_groups = rom->palette_group();
palette_manager.rom_palette_groups.clear();
palette_manager.palette_group_names.clear();
// Overworld palettes
if (palette_groups.overworld_main.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.overworld_main[0]);
palette_manager.palette_group_names.push_back("Overworld Main");
}
if (palette_groups.overworld_aux.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.overworld_aux[0]);
palette_manager.palette_group_names.push_back("Overworld Aux");
}
if (palette_groups.overworld_animated.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.overworld_animated[0]);
palette_manager.palette_group_names.push_back("Overworld Animated");
}
// Dungeon palettes
if (palette_groups.dungeon_main.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.dungeon_main[0]);
palette_manager.palette_group_names.push_back("Dungeon Main");
}
// Sprite palettes
if (palette_groups.global_sprites.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.global_sprites[0]);
palette_manager.palette_group_names.push_back("Global Sprites");
}
if (palette_groups.armors.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.armors[0]);
palette_manager.palette_group_names.push_back("Armor");
}
if (palette_groups.swords.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.swords[0]);
palette_manager.palette_group_names.push_back("Swords");
}
palette_manager.palettes_loaded = true;
LOG_DEBUG("Canvas", "Loaded %zu ROM palette groups",
palette_manager.rom_palette_groups.size());
return true;
} catch (const std::exception& e) {
LOG_ERROR("Canvas", "Failed to load ROM palette groups");
return false;
}
}
bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index) {
if (!bitmap) return false;
if (group_index < 0 || group_index >= palette_manager.rom_palette_groups.size()) {
return false;
}
const auto& palette = palette_manager.rom_palette_groups[group_index];
// Apply the full palette or use SetPaletteWithTransparent if palette_index is specified
if (palette_index == 0) {
bitmap->SetPalette(palette);
} else {
bitmap->SetPaletteWithTransparent(palette, palette_index);
}
bitmap->set_modified(true);
// Queue texture update via Arena's deferred system
if (renderer) {
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, bitmap);
}
return true;
}
// Drawing utility functions
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color,
float global_scale) {
// Apply global scale to position and size
float scaled_x = x * global_scale;
float scaled_y = y * global_scale;
float scaled_w = w * global_scale;
float scaled_h = h * global_scale;
ImVec2 origin(canvas_p0.x + scrolling.x + scaled_x,
canvas_p0.y + scrolling.y + scaled_y);
ImVec2 size(canvas_p0.x + scrolling.x + scaled_x + scaled_w,
canvas_p0.y + scrolling.y + scaled_y + scaled_h);
uint32_t color_u32 = IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
draw_list->AddRectFilled(origin, size, color_u32);
// Add a black outline
ImVec2 outline_origin(origin.x - 1, origin.y - 1);
ImVec2 outline_size(size.x + 1, size.y + 1);
draw_list->AddRect(outline_origin, outline_size, IM_COL32(0, 0, 0, 255));
}
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
const std::string& text, int x, int y, float global_scale) {
// Apply global scale to text position
float scaled_x = x * global_scale;
float scaled_y = y * global_scale;
ImVec2 text_pos(canvas_p0.x + scrolling.x + scaled_x,
canvas_p0.y + scrolling.y + scaled_y);
// Draw text with black shadow for better visibility
draw_list->AddText(ImVec2(text_pos.x + 1, text_pos.y + 1),
IM_COL32(0, 0, 0, 255), text.c_str());
draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), text.c_str());
}
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, int x, int y, int w, int h,
uint32_t color) {
ImVec2 origin(canvas_p0.x + scrolling.x + x, canvas_p0.y + scrolling.y + y);
ImVec2 size(canvas_p0.x + scrolling.x + x + w,
canvas_p0.y + scrolling.y + y + h);
draw_list->AddRect(origin, size, color, 0, 0, 1.5f);
}
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, int x, int y, int w, int h,
ImVec4 color) {
uint32_t color_u32 =
IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
DrawCanvasOutline(draw_list, canvas_p0, scrolling, x, y, w, h, color_u32);
}
// Grid utility functions
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 canvas_p1, ImVec2 scrolling, float grid_step,
float global_scale) {
const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
const float grid_thickness = 0.5f;
float scaled_grid_step = grid_step * global_scale;
for (float x = fmodf(scrolling.x, scaled_grid_step);
x < (canvas_p1.x - canvas_p0.x); x += scaled_grid_step) {
draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y),
ImVec2(canvas_p0.x + x, canvas_p1.y), grid_color,
grid_thickness);
}
for (float y = fmodf(scrolling.y, scaled_grid_step);
y < (canvas_p1.y - canvas_p0.y); y += scaled_grid_step) {
draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y),
ImVec2(canvas_p1.x, canvas_p0.y + y), grid_color,
grid_thickness);
}
}
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, int highlight_tile_id,
float grid_step) {
if (highlight_tile_id == -1)
return;
int tile_x = highlight_tile_id % 8;
int tile_y = highlight_tile_id / 8;
ImVec2 tile_pos(canvas_p0.x + scrolling.x + tile_x * grid_step,
canvas_p0.y + scrolling.y + tile_y * grid_step);
ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
draw_list->AddRectFilled(tile_pos, tile_pos_end, IM_COL32(255, 0, 255, 255));
}
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, ImVec2 canvas_sz, float grid_step,
float global_scale) {
float scaled_grid_step = grid_step * global_scale;
for (float x = fmodf(scrolling.x, scaled_grid_step);
x < canvas_sz.x * global_scale; x += scaled_grid_step) {
for (float y = fmodf(scrolling.y, scaled_grid_step);
y < canvas_sz.y * global_scale; y += scaled_grid_step) {
int tile_x = (x - scrolling.x) / scaled_grid_step;
int tile_y = (y - scrolling.y) / scaled_grid_step;
int tile_id = tile_x + (tile_y * 16);
char hex_id[8];
snprintf(hex_id, sizeof(hex_id), "%02X", tile_id);
draw_list->AddText(ImVec2(canvas_p0.x + x + (scaled_grid_step / 2) - 4,
canvas_p0.y + y + (scaled_grid_step / 2) - 4),
IM_COL32(255, 255, 255, 255), hex_id);
}
}
}
// Layout and interaction utilities
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size,
bool use_custom) {
return use_custom ? custom_size : content_region;
}
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale) {
return ImVec2(canvas_size.x * global_scale, canvas_size.y * global_scale);
}
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1) {
return point.x >= canvas_p0.x && point.x <= canvas_p1.x &&
point.y >= canvas_p0.y && point.y <= canvas_p1.y;
}
// Size reporting for ImGui table integration
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale,
float padding) {
// Calculate minimum size needed to display content with padding
ImVec2 min_size = ImVec2(content_size.x * global_scale + padding * 2,
content_size.y * global_scale + padding * 2);
// Ensure minimum practical size
min_size.x = std::max(min_size.x, 64.0f);
min_size.y = std::max(min_size.y, 64.0f);
return min_size;
}
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale,
float min_scale) {
// Calculate preferred size with minimum scale constraint
float effective_scale = std::max(global_scale, min_scale);
ImVec2 preferred_size = ImVec2(content_size.x * effective_scale + 8.0f,
content_size.y * effective_scale + 8.0f);
// Cap to reasonable maximum sizes for table integration
preferred_size.x = std::min(preferred_size.x, 800.0f);
preferred_size.y = std::min(preferred_size.y, 600.0f);
return preferred_size;
}
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label) {
// Reserve space in ImGui layout so tables know the size
if (!label.empty()) {
ImGui::Text("%s", label.c_str());
}
ImGui::Dummy(canvas_size);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() -
canvas_size.x); // Move back to start
}
void SetNextCanvasSize(ImVec2 size, bool auto_resize) {
if (auto_resize) {
// Use auto-sizing child window for table integration
ImGui::SetNextWindowContentSize(size);
} else {
// Fixed size
ImGui::SetNextWindowSize(size);
}
}
// High-level composite operations
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id) {
if (!ctx.enable_grid)
return;
ctx.draw_list->PushClipRect(ctx.canvas_p0, ctx.canvas_p1, true);
// Draw grid lines
DrawCanvasGridLines(ctx.draw_list, ctx.canvas_p0, ctx.canvas_p1,
ctx.scrolling, ctx.grid_step, ctx.global_scale);
// Draw highlight if specified
if (highlight_tile_id != -1) {
DrawCustomHighlight(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
highlight_tile_id, ctx.grid_step * ctx.global_scale);
}
// Draw hex labels if enabled
if (ctx.enable_hex_labels) {
DrawHexTileLabels(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
ImVec2(ctx.canvas_p1.x - ctx.canvas_p0.x,
ctx.canvas_p1.y - ctx.canvas_p0.y),
ctx.grid_step, ctx.global_scale);
}
ctx.draw_list->PopClipRect();
}
void DrawCanvasOverlay(const CanvasRenderContext& ctx,
const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points) {
const ImVec2 origin(ctx.canvas_p0.x + ctx.scrolling.x,
ctx.canvas_p0.y + ctx.scrolling.y);
// Draw hover points
for (int n = 0; n < points.Size; n += 2) {
ctx.draw_list->AddRect(
ImVec2(origin.x + points[n].x, origin.y + points[n].y),
ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y),
IM_COL32(255, 255, 255, 255), 1.0f);
}
// Draw selection rectangles
if (!selected_points.empty()) {
for (int n = 0; n < selected_points.size(); n += 2) {
ctx.draw_list->AddRect(ImVec2(origin.x + selected_points[n].x,
origin.y + selected_points[n].y),
ImVec2(origin.x + selected_points[n + 1].x + 0x10,
origin.y + selected_points[n + 1].y + 0x10),
IM_COL32(255, 255, 255, 255), 1.0f);
}
}
}
void DrawCanvasLabels(const CanvasRenderContext& ctx,
const ImVector<ImVector<std::string>>& labels,
int current_labels, int tile_id_offset) {
if (current_labels >= labels.size())
return;
float scaled_grid_step = ctx.grid_step * ctx.global_scale;
for (float x = fmodf(ctx.scrolling.x, scaled_grid_step);
x < (ctx.canvas_p1.x - ctx.canvas_p0.x); x += scaled_grid_step) {
for (float y = fmodf(ctx.scrolling.y, scaled_grid_step);
y < (ctx.canvas_p1.y - ctx.canvas_p0.y); y += scaled_grid_step) {
int tile_x = (x - ctx.scrolling.x) / scaled_grid_step;
int tile_y = (y - ctx.scrolling.y) / scaled_grid_step;
int tile_id = tile_x + (tile_y * tile_id_offset);
if (tile_id >= labels[current_labels].size()) {
break;
}
const std::string& label = labels[current_labels][tile_id];
ctx.draw_list->AddText(
ImVec2(ctx.canvas_p0.x + x + (scaled_grid_step / 2) - tile_id_offset,
ctx.canvas_p0.y + y + (scaled_grid_step / 2) - tile_id_offset),
IM_COL32(255, 255, 255, 255), label.c_str());
}
}
}
} // namespace CanvasUtils
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,148 @@
#ifndef YAZE_APP_GUI_CANVAS_UTILS_H
#define YAZE_APP_GUI_CANVAS_UTILS_H
#include <string>
#include <vector>
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Configuration for canvas display and interaction
*/
struct CanvasConfig {
bool enable_grid = true;
bool enable_hex_labels = false;
bool enable_custom_labels = false;
bool enable_context_menu = true;
bool is_draggable = false;
bool auto_resize = false;
bool clamp_rect_to_local_maps = true; // NEW: Prevent rectangle wrap across 512x512 boundaries
float grid_step = 32.0f;
float global_scale = 1.0f;
ImVec2 canvas_size = ImVec2(0, 0);
ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.)
bool custom_canvas_size = false;
};
/**
* @brief Selection state for canvas interactions
*/
struct CanvasSelection {
std::vector<ImVec2> selected_tiles;
std::vector<ImVec2> selected_points;
ImVec2 selected_tile_pos = ImVec2(-1, -1);
bool select_rect_active = false;
void Clear() {
selected_tiles.clear();
selected_points.clear();
selected_tile_pos = ImVec2(-1, -1);
select_rect_active = false;
}
};
/**
* @brief Palette management state for canvas
*/
struct CanvasPaletteManager {
std::vector<gfx::SnesPalette> rom_palette_groups;
std::vector<std::string> palette_group_names;
gfx::SnesPalette original_palette;
bool palettes_loaded = false;
int current_group_index = 0;
int current_palette_index = 0;
void Clear() {
rom_palette_groups.clear();
palette_group_names.clear();
original_palette.clear();
palettes_loaded = false;
current_group_index = 0;
current_palette_index = 0;
}
};
/**
* @brief Context menu item configuration
*/
struct CanvasContextMenuItem {
std::string label;
std::string shortcut;
std::function<void()> callback;
std::function<bool()> enabled_condition = []() { return true; };
std::vector<CanvasContextMenuItem> subitems;
};
/**
* @brief Utility functions for canvas operations
*/
namespace CanvasUtils {
// Core utility functions
ImVec2 AlignToGrid(ImVec2 pos, float grid_step);
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale);
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row);
// Palette management utilities
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager);
bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index);
// Drawing utility functions (moved from Canvas class)
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color, float global_scale);
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
const std::string& text, int x, int y, float global_scale);
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, uint32_t color = IM_COL32(255, 255, 255, 200));
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color);
// Grid utility functions
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1,
ImVec2 scrolling, float grid_step, float global_scale);
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int highlight_tile_id, float grid_step);
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
ImVec2 canvas_sz, float grid_step, float global_scale);
// Layout and interaction utilities
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, bool use_custom);
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale);
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1);
// Size reporting for ImGui table integration
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding = 4.0f);
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale = 1.0f);
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label = "");
void SetNextCanvasSize(ImVec2 size, bool auto_resize = false);
// High-level canvas operations
struct CanvasRenderContext {
ImDrawList* draw_list;
ImVec2 canvas_p0;
ImVec2 canvas_p1;
ImVec2 scrolling;
float global_scale;
bool enable_grid;
bool enable_hex_labels;
float grid_step;
};
// Composite drawing operations
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id = -1);
void DrawCanvasOverlay(const CanvasRenderContext& ctx, const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points);
void DrawCanvasLabels(const CanvasRenderContext& ctx, const ImVector<ImVector<std::string>>& labels,
int current_labels, int tile_id_offset);
} // namespace CanvasUtils
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_UTILS_H