yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
bpp_format_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <cstring>
6#include <sstream>
7
9#include "util/log.h"
10
11namespace yaze {
12namespace gfx {
13
15 static BppFormatManager instance;
16 return instance;
17}
18
22 max_cache_size_ = 64 * 1024 * 1024; // 64MB cache limit
23}
24
27 BppFormat::kBpp2, "2BPP", 2, 4, 16, 2048, true,
28 "2 bits per pixel - 4 colors, used for simple graphics and UI elements"
29 );
30
32 BppFormat::kBpp3, "3BPP", 3, 8, 24, 3072, true,
33 "3 bits per pixel - 8 colors, common for SNES sprites and tiles"
34 );
35
37 BppFormat::kBpp4, "4BPP", 4, 16, 32, 4096, true,
38 "4 bits per pixel - 16 colors, standard for SNES backgrounds"
39 );
40
42 BppFormat::kBpp8, "8BPP", 8, 256, 64, 8192, false,
43 "8 bits per pixel - 256 colors, high-color graphics and converted formats"
44 );
45}
46
48 auto it = format_info_.find(format);
49 if (it == format_info_.end()) {
50 throw std::invalid_argument("Unknown BPP format");
51 }
52 return it->second;
53}
54
58
59std::vector<uint8_t> BppFormatManager::ConvertFormat(const std::vector<uint8_t>& data,
60 BppFormat from_format, BppFormat to_format,
61 int width, int height) {
62 if (from_format == to_format) {
63 return data; // No conversion needed
64 }
65
66 ScopedTimer timer("bpp_format_conversion");
67
68 // Check cache first
69 std::string cache_key = GenerateCacheKey(data, from_format, to_format, width, height);
70 auto cache_iter = conversion_cache_.find(cache_key);
71 if (cache_iter != conversion_cache_.end()) {
72 conversion_stats_["cache_hits"]++;
73 return cache_iter->second;
74 }
75
76 std::vector<uint8_t> result;
77
78 // Convert to 8BPP as intermediate format if needed
79 std::vector<uint8_t> intermediate_data = data;
80 if (from_format != BppFormat::kBpp8) {
81 switch (from_format) {
83 intermediate_data = Convert2BppTo8Bpp(data, width, height);
84 break;
86 intermediate_data = Convert3BppTo8Bpp(data, width, height);
87 break;
89 intermediate_data = Convert4BppTo8Bpp(data, width, height);
90 break;
91 default:
92 intermediate_data = data;
93 break;
94 }
95 }
96
97 // Convert from 8BPP to target format
98 if (to_format != BppFormat::kBpp8) {
99 switch (to_format) {
100 case BppFormat::kBpp2:
101 result = Convert8BppTo2Bpp(intermediate_data, width, height);
102 break;
103 case BppFormat::kBpp3:
104 result = Convert8BppTo3Bpp(intermediate_data, width, height);
105 break;
106 case BppFormat::kBpp4:
107 result = Convert8BppTo4Bpp(intermediate_data, width, height);
108 break;
109 default:
110 result = intermediate_data;
111 break;
112 }
113 } else {
114 result = intermediate_data;
115 }
116
117 // Cache the result
118 if (cache_memory_usage_ + result.size() < max_cache_size_) {
119 conversion_cache_[cache_key] = result;
120 cache_memory_usage_ += result.size();
121 }
122
123 conversion_stats_["conversions"]++;
124 conversion_stats_["cache_misses"]++;
125
126 return result;
127}
128
130 int sheet_id,
131 const SnesPalette& palette) {
132 // Check analysis cache
133 auto cache_it = analysis_cache_.find(sheet_id);
134 if (cache_it != analysis_cache_.end()) {
135 return cache_it->second;
136 }
137
138 ScopedTimer timer("graphics_sheet_analysis");
139
140 GraphicsSheetAnalysis analysis;
141 analysis.sheet_id = sheet_id;
142 analysis.original_size = sheet_data.size();
143 analysis.current_size = sheet_data.size();
144
145 // Detect current format
146 analysis.current_format = DetectFormat(sheet_data, 128, 32); // Standard sheet size
147
148 // Analyze color usage
149 analysis.palette_entries_used = CountUsedColors(sheet_data, palette.size());
150
151 // Determine if this was likely converted from a lower BPP format
152 if (analysis.current_format == BppFormat::kBpp8 && analysis.palette_entries_used <= 16) {
153 if (analysis.palette_entries_used <= 4) {
155 } else if (analysis.palette_entries_used <= 8) {
157 } else {
159 }
160 analysis.was_converted = true;
161 } else {
162 analysis.original_format = analysis.current_format;
163 analysis.was_converted = false;
164 }
165
166 // Generate conversion history
167 if (analysis.was_converted) {
168 std::ostringstream history;
169 history << "Originally " << GetFormatInfo(analysis.original_format).name
170 << " (" << analysis.palette_entries_used << " colors used)";
171 history << " -> Converted to " << GetFormatInfo(analysis.current_format).name;
172 analysis.conversion_history = history.str();
173 } else {
174 analysis.conversion_history = "No conversion - original format";
175 }
176
177 // Analyze tile usage pattern
178 analysis.tile_usage_pattern = AnalyzeTileUsagePattern(sheet_data, 128, 32, 8);
179
180 // Calculate compression ratio (simplified)
181 analysis.compression_ratio = 1.0f; // Would need original compressed data for accurate calculation
182
183 // Cache the analysis
184 analysis_cache_[sheet_id] = analysis;
185
186 return analysis;
187}
188
189BppFormat BppFormatManager::DetectFormat(const std::vector<uint8_t>& data, int width, int height) {
190 if (data.empty()) {
191 return BppFormat::kBpp8; // Default
192 }
193
194 // Analyze color depth
195 return AnalyzeColorDepth(data, width, height);
196}
197
199 BppFormat target_format,
200 const std::vector<int>& used_colors) {
201 const auto& format_info = GetFormatInfo(target_format);
202
203 // Create optimized palette with target format size
204 SnesPalette optimized_palette;
205
206 // Add used colors first
207 for (int color_index : used_colors) {
208 if (color_index < static_cast<int>(palette.size()) &&
209 static_cast<int>(optimized_palette.size()) < format_info.max_colors) {
210 optimized_palette.AddColor(palette[color_index]);
211 }
212 }
213
214 // Fill remaining slots with unused colors or transparent
215 while (static_cast<int>(optimized_palette.size()) < format_info.max_colors) {
216 if (static_cast<int>(optimized_palette.size()) < static_cast<int>(palette.size())) {
217 optimized_palette.AddColor(palette[optimized_palette.size()]);
218 } else {
219 // Add transparent color
220 optimized_palette.AddColor(SnesColor(ImVec4(0, 0, 0, 0)));
221 }
222 }
223
224 return optimized_palette;
225}
226
227std::unordered_map<std::string, int> BppFormatManager::GetConversionStats() const {
228 return conversion_stats_;
229}
230
237
238std::pair<size_t, size_t> BppFormatManager::GetMemoryStats() const {
240}
241
242// Helper method implementations
243
244std::string BppFormatManager::GenerateCacheKey(const std::vector<uint8_t>& data,
245 BppFormat from_format, BppFormat to_format,
246 int width, int height) {
247 std::ostringstream key;
248 key << static_cast<int>(from_format) << "_" << static_cast<int>(to_format)
249 << "_" << width << "x" << height << "_" << data.size();
250
251 // Add hash of data for uniqueness
252 size_t hash = 0;
253 for (size_t i = 0; i < std::min(data.size(), size_t(1024)); ++i) {
254 hash = hash * 31 + data[i];
255 }
256 key << "_" << hash;
257
258 return key.str();
259}
260
261BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector<uint8_t>& data, int /*width*/, int /*height*/) {
262 if (data.empty()) {
263 return BppFormat::kBpp8;
264 }
265
266 // Find maximum color index used
267 uint8_t max_color = 0;
268 for (uint8_t pixel : data) {
269 max_color = std::max(max_color, pixel);
270 }
271
272 // Determine BPP based on color usage
273 if (max_color < 4) {
274 return BppFormat::kBpp2;
275 }
276 if (max_color < 8) {
277 return BppFormat::kBpp3;
278 }
279 if (max_color < 16) {
280 return BppFormat::kBpp4;
281 }
282 return BppFormat::kBpp8;
283}
284
285std::vector<uint8_t> BppFormatManager::Convert2BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height) {
286 std::vector<uint8_t> result(width * height);
287
288 for (int row = 0; row < height; ++row) {
289 for (int col = 0; col < width; col += 4) { // 4 pixels per byte in 2BPP
290 if (col / 4 < static_cast<int>(data.size())) {
291 uint8_t byte = data[row * (width / 4) + (col / 4)];
292
293 // Extract 4 pixels from the byte
294 for (int i = 0; i < 4 && (col + i) < width; ++i) {
295 uint8_t pixel = (byte >> (6 - i * 2)) & 0x03;
296 result[row * width + col + i] = pixel;
297 }
298 }
299 }
300 }
301
302 return result;
303}
304
305std::vector<uint8_t> BppFormatManager::Convert3BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height) {
306 // 3BPP is more complex - typically stored as 4BPP with unused bits
307 return Convert4BppTo8Bpp(data, width, height);
308}
309
310std::vector<uint8_t> BppFormatManager::Convert4BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height) {
311 std::vector<uint8_t> result(width * height);
312
313 for (int row = 0; row < height; ++row) {
314 for (int col = 0; col < width; col += 2) { // 2 pixels per byte in 4BPP
315 if (col / 2 < static_cast<int>(data.size())) {
316 uint8_t byte = data[row * (width / 2) + (col / 2)];
317
318 // Extract 2 pixels from the byte
319 uint8_t pixel1 = byte & 0x0F;
320 uint8_t pixel2 = (byte >> 4) & 0x0F;
321
322 result[row * width + col] = pixel1;
323 if (col + 1 < width) {
324 result[row * width + col + 1] = pixel2;
325 }
326 }
327 }
328 }
329
330 return result;
331}
332
333std::vector<uint8_t> BppFormatManager::Convert8BppTo2Bpp(const std::vector<uint8_t>& data, int width, int height) {
334 std::vector<uint8_t> result((width * height) / 4); // 4 pixels per byte
335
336 for (int row = 0; row < height; ++row) {
337 for (int col = 0; col < width; col += 4) {
338 uint8_t byte = 0;
339
340 // Pack 4 pixels into one byte
341 for (int i = 0; i < 4 && (col + i) < width; ++i) {
342 uint8_t pixel = data[row * width + col + i] & 0x03; // Clamp to 2 bits
343 byte |= (pixel << (6 - i * 2));
344 }
345
346 result[row * (width / 4) + (col / 4)] = byte;
347 }
348 }
349
350 return result;
351}
352
353std::vector<uint8_t> BppFormatManager::Convert8BppTo3Bpp(const std::vector<uint8_t>& data, int width, int height) {
354 // Convert to 4BPP first, then optimize
355 auto result_4bpp = Convert8BppTo4Bpp(data, width, height);
356 // Note: 3BPP conversion would require more sophisticated palette optimization
357 return result_4bpp;
358}
359
360std::vector<uint8_t> BppFormatManager::Convert8BppTo4Bpp(const std::vector<uint8_t>& data, int width, int height) {
361 std::vector<uint8_t> result((width * height) / 2); // 2 pixels per byte
362
363 for (int row = 0; row < height; ++row) {
364 for (int col = 0; col < width; col += 2) {
365 uint8_t pixel1 = data[row * width + col] & 0x0F; // Clamp to 4 bits
366 uint8_t pixel2 = (col + 1 < width) ? (data[row * width + col + 1] & 0x0F) : 0;
367
368 uint8_t byte = pixel1 | (pixel2 << 4);
369 result[row * (width / 2) + (col / 2)] = byte;
370 }
371 }
372
373 return result;
374}
375
376int BppFormatManager::CountUsedColors(const std::vector<uint8_t>& data, int max_colors) {
377 std::vector<bool> used_colors(max_colors, false);
378
379 for (uint8_t pixel : data) {
380 if (pixel < max_colors) {
381 used_colors[pixel] = true;
382 }
383 }
384
385 int count = 0;
386 for (bool used : used_colors) {
387 if (used) count++;
388 }
389
390 return count;
391}
392
393float BppFormatManager::CalculateCompressionRatio(const std::vector<uint8_t>& original,
394 const std::vector<uint8_t>& compressed) {
395 if (compressed.empty()) return 1.0f;
396 return static_cast<float>(original.size()) / static_cast<float>(compressed.size());
397}
398
399std::vector<int> BppFormatManager::AnalyzeTileUsagePattern(const std::vector<uint8_t>& data,
400 int width, int height, int tile_size) {
401 std::vector<int> usage_pattern;
402 int tiles_x = width / tile_size;
403 int tiles_y = height / tile_size;
404
405 for (int tile_row = 0; tile_row < tiles_y; ++tile_row) {
406 for (int tile_col = 0; tile_col < tiles_x; ++tile_col) {
407 int non_zero_pixels = 0;
408
409 // Count non-zero pixels in this tile
410 for (int row = 0; row < tile_size; ++row) {
411 for (int col = 0; col < tile_size; ++col) {
412 int pixel_x = tile_col * tile_size + col;
413 int pixel_y = tile_row * tile_size + row;
414 int pixel_index = pixel_y * width + pixel_x;
415
416 if (pixel_index < static_cast<int>(data.size()) && data[pixel_index] != 0) {
417 non_zero_pixels++;
418 }
419 }
420 }
421
422 usage_pattern.push_back(non_zero_pixels);
423 }
424 }
425
426 return usage_pattern;
427}
428
429// BppConversionScope implementation
430
432 int width, int height)
433 : from_format_(from_format), to_format_(to_format), width_(width), height_(height),
434 timer_("bpp_convert_scope") {
435 std::ostringstream op_name;
436 op_name << "bpp_convert_" << static_cast<int>(from_format)
437 << "_to_" << static_cast<int>(to_format);
438 operation_name_ = op_name.str();
439}
440
442 // Timer automatically ends in destructor
443}
444
445std::vector<uint8_t> BppConversionScope::Convert(const std::vector<uint8_t>& data) {
447}
448
449} // namespace gfx
450} // namespace yaze
BppConversionScope(BppFormat from_format, BppFormat to_format, int width, int height)
std::vector< uint8_t > Convert(const std::vector< uint8_t > &data)
Comprehensive BPP format management system for SNES ROM hacking.
std::vector< uint8_t > Convert8BppTo3Bpp(const std::vector< uint8_t > &data, int width, int height)
SnesPalette OptimizePaletteForFormat(const SnesPalette &palette, BppFormat target_format, const std::vector< int > &used_colors)
Optimize palette for specific BPP format.
std::unordered_map< std::string, int > GetConversionStats() const
Get conversion statistics.
std::unordered_map< BppFormat, BppFormatInfo > format_info_
std::vector< uint8_t > Convert8BppTo2Bpp(const std::vector< uint8_t > &data, int width, int height)
std::pair< size_t, size_t > GetMemoryStats() const
Get memory usage statistics.
std::vector< int > AnalyzeTileUsagePattern(const std::vector< uint8_t > &data, int width, int height, int tile_size)
std::string GenerateCacheKey(const std::vector< uint8_t > &data, BppFormat from_format, BppFormat to_format, int width, int height)
BppFormat DetectFormat(const std::vector< uint8_t > &data, int width, int height)
Detect BPP format from bitmap data.
std::unordered_map< int, GraphicsSheetAnalysis > analysis_cache_
std::vector< BppFormat > GetAvailableFormats() const
Get all available BPP formats.
GraphicsSheetAnalysis AnalyzeGraphicsSheet(const std::vector< uint8_t > &sheet_data, int sheet_id, const SnesPalette &palette)
Analyze graphics sheet to determine original and current BPP formats.
std::vector< uint8_t > Convert3BppTo8Bpp(const std::vector< uint8_t > &data, int width, int height)
std::vector< uint8_t > ConvertFormat(const std::vector< uint8_t > &data, BppFormat from_format, BppFormat to_format, int width, int height)
Convert bitmap data between BPP formats.
int CountUsedColors(const std::vector< uint8_t > &data, int max_colors)
void Initialize()
Initialize the BPP format manager.
std::vector< uint8_t > Convert2BppTo8Bpp(const std::vector< uint8_t > &data, int width, int height)
std::unordered_map< std::string, std::vector< uint8_t > > conversion_cache_
float CalculateCompressionRatio(const std::vector< uint8_t > &original, const std::vector< uint8_t > &compressed)
static BppFormatManager & Get()
BppFormat AnalyzeColorDepth(const std::vector< uint8_t > &data, int width, int height)
std::vector< uint8_t > Convert8BppTo4Bpp(const std::vector< uint8_t > &data, int width, int height)
void ClearCache()
Clear conversion cache.
std::vector< uint8_t > Convert4BppTo8Bpp(const std::vector< uint8_t > &data, int width, int height)
const BppFormatInfo & GetFormatInfo(BppFormat format) const
Get BPP format information.
std::unordered_map< std::string, int > conversion_stats_
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:38
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
BppFormat
BPP format enumeration for SNES graphics.
@ kBpp4
4 bits per pixel (16 colors)
@ kBpp3
3 bits per pixel (8 colors)
@ kBpp2
2 bits per pixel (4 colors)
@ kBpp8
8 bits per pixel (256 colors)
Main namespace for the application.
BPP format metadata and conversion information.
Graphics sheet analysis result.