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
31 BppFormat::kBpp3, "3BPP", 3, 8, 24, 3072, true,
32 "3 bits per pixel - 8 colors, common for SNES sprites and tiles");
33
35 BppFormat::kBpp4, "4BPP", 4, 16, 32, 4096, true,
36 "4 bits per pixel - 16 colors, standard for SNES backgrounds");
37
39 BppFormatInfo(BppFormat::kBpp8, "8BPP", 8, 256, 64, 8192, false,
40 "8 bits per pixel - 256 colors, high-color graphics and "
41 "converted formats");
42}
43
45 auto it = format_info_.find(format);
46 if (it == format_info_.end()) {
47 throw std::invalid_argument("Unknown BPP format");
48 }
49 return it->second;
50}
51
56
58 const std::vector<uint8_t>& data, BppFormat from_format,
59 BppFormat to_format, int width, int height) {
60 if (from_format == to_format) {
61 return data; // No conversion needed
62 }
63
64 ScopedTimer timer("bpp_format_conversion");
65
66 // Check cache first
67 std::string cache_key =
68 GenerateCacheKey(data, from_format, to_format, width, height);
69 auto cache_iter = conversion_cache_.find(cache_key);
70 if (cache_iter != conversion_cache_.end()) {
71 conversion_stats_["cache_hits"]++;
72 return cache_iter->second;
73 }
74
75 std::vector<uint8_t> result;
76
77 // Convert to 8BPP as intermediate format if needed
78 std::vector<uint8_t> intermediate_data = data;
79 if (from_format != BppFormat::kBpp8) {
80 switch (from_format) {
82 intermediate_data = Convert2BppTo8Bpp(data, width, height);
83 break;
85 intermediate_data = Convert3BppTo8Bpp(data, width, height);
86 break;
88 intermediate_data = Convert4BppTo8Bpp(data, width, height);
89 break;
90 default:
91 intermediate_data = data;
92 break;
93 }
94 }
95
96 // Convert from 8BPP to target format
97 if (to_format != BppFormat::kBpp8) {
98 switch (to_format) {
100 result = Convert8BppTo2Bpp(intermediate_data, width, height);
101 break;
102 case BppFormat::kBpp3:
103 result = Convert8BppTo3Bpp(intermediate_data, width, height);
104 break;
105 case BppFormat::kBpp4:
106 result = Convert8BppTo4Bpp(intermediate_data, width, height);
107 break;
108 default:
109 result = intermediate_data;
110 break;
111 }
112 } else {
113 result = intermediate_data;
114 }
115
116 // Cache the result
117 if (cache_memory_usage_ + result.size() < max_cache_size_) {
118 conversion_cache_[cache_key] = result;
119 cache_memory_usage_ += result.size();
120 }
121
122 conversion_stats_["conversions"]++;
123 conversion_stats_["cache_misses"]++;
124
125 return result;
126}
127
129 const std::vector<uint8_t>& sheet_data, int sheet_id,
130 const SnesPalette& palette) {
131 // Check analysis cache
132 auto cache_it = analysis_cache_.find(sheet_id);
133 if (cache_it != analysis_cache_.end()) {
134 return cache_it->second;
135 }
136
137 ScopedTimer timer("graphics_sheet_analysis");
138
139 GraphicsSheetAnalysis analysis;
140 analysis.sheet_id = sheet_id;
141 analysis.original_size = sheet_data.size();
142 analysis.current_size = sheet_data.size();
143
144 // Detect current format
145 analysis.current_format =
146 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 &&
153 analysis.palette_entries_used <= 16) {
154 if (analysis.palette_entries_used <= 4) {
156 } else if (analysis.palette_entries_used <= 8) {
158 } else {
160 }
161 analysis.was_converted = true;
162 } else {
163 analysis.original_format = analysis.current_format;
164 analysis.was_converted = false;
165 }
166
167 // Generate conversion history
168 if (analysis.was_converted) {
169 std::ostringstream history;
170 history << "Originally " << GetFormatInfo(analysis.original_format).name
171 << " (" << analysis.palette_entries_used << " colors used)";
172 history << " -> Converted to "
173 << GetFormatInfo(analysis.current_format).name;
174 analysis.conversion_history = history.str();
175 } else {
176 analysis.conversion_history = "No conversion - original format";
177 }
178
179 // Analyze tile usage pattern
180 analysis.tile_usage_pattern = AnalyzeTileUsagePattern(sheet_data, 128, 32, 8);
181
182 // Calculate compression ratio (simplified)
183 analysis.compression_ratio =
184 1.0f; // Would need original compressed data for accurate calculation
185
186 // Cache the analysis
187 analysis_cache_[sheet_id] = analysis;
188
189 return analysis;
190}
191
192BppFormat BppFormatManager::DetectFormat(const std::vector<uint8_t>& data,
193 int width, int height) {
194 if (data.empty()) {
195 return BppFormat::kBpp8; // Default
196 }
197
198 // Analyze color depth
199 return AnalyzeColorDepth(data, width, height);
200}
201
203 const SnesPalette& palette, BppFormat target_format,
204 const std::vector<int>& used_colors) {
205 const auto& format_info = GetFormatInfo(target_format);
206
207 // Create optimized palette with target format size
208 SnesPalette optimized_palette;
209
210 // Add used colors first
211 for (int color_index : used_colors) {
212 if (color_index < static_cast<int>(palette.size()) &&
213 static_cast<int>(optimized_palette.size()) < format_info.max_colors) {
214 optimized_palette.AddColor(palette[color_index]);
215 }
216 }
217
218 // Fill remaining slots with unused colors or transparent
219 while (static_cast<int>(optimized_palette.size()) < format_info.max_colors) {
220 if (static_cast<int>(optimized_palette.size()) <
221 static_cast<int>(palette.size())) {
222 optimized_palette.AddColor(palette[optimized_palette.size()]);
223 } else {
224 // Add transparent color
225 optimized_palette.AddColor(SnesColor(ImVec4(0, 0, 0, 0)));
226 }
227 }
228
229 return optimized_palette;
230}
231
232std::unordered_map<std::string, int> BppFormatManager::GetConversionStats()
233 const {
234 return conversion_stats_;
235}
236
243
244std::pair<size_t, size_t> BppFormatManager::GetMemoryStats() const {
246}
247
248// Helper method implementations
249
250std::string BppFormatManager::GenerateCacheKey(const std::vector<uint8_t>& data,
251 BppFormat from_format,
252 BppFormat to_format, int width,
253 int height) {
254 std::ostringstream key;
255 key << static_cast<int>(from_format) << "_" << static_cast<int>(to_format)
256 << "_" << width << "x" << height << "_" << data.size();
257
258 // Add hash of data for uniqueness
259 size_t hash = 0;
260 for (size_t i = 0; i < std::min(data.size(), size_t(1024)); ++i) {
261 hash = hash * 31 + data[i];
262 }
263 key << "_" << hash;
264
265 return key.str();
266}
267
268BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector<uint8_t>& data,
269 int /*width*/, int /*height*/) {
270 if (data.empty()) {
271 return BppFormat::kBpp8;
272 }
273
274 // Find maximum color index used
275 uint8_t max_color = 0;
276 for (uint8_t pixel : data) {
277 max_color = std::max(max_color, pixel);
278 }
279
280 // Determine BPP based on color usage
281 if (max_color < 4) {
282 return BppFormat::kBpp2;
283 }
284 if (max_color < 8) {
285 return BppFormat::kBpp3;
286 }
287 if (max_color < 16) {
288 return BppFormat::kBpp4;
289 }
290 return BppFormat::kBpp8;
291}
292
294 const std::vector<uint8_t>& data, int width, int height) {
295 std::vector<uint8_t> result(width * height);
296
297 for (int row = 0; row < height; ++row) {
298 for (int col = 0; col < width; col += 4) { // 4 pixels per byte in 2BPP
299 if (col / 4 < static_cast<int>(data.size())) {
300 uint8_t byte = data[row * (width / 4) + (col / 4)];
301
302 // Extract 4 pixels from the byte
303 for (int i = 0; i < 4 && (col + i) < width; ++i) {
304 uint8_t pixel = (byte >> (6 - i * 2)) & 0x03;
305 result[row * width + col + i] = pixel;
306 }
307 }
308 }
309 }
310
311 return result;
312}
313
315 const std::vector<uint8_t>& data, int width, int height) {
316 // 3BPP is more complex - typically stored as 4BPP with unused bits
317 return Convert4BppTo8Bpp(data, width, height);
318}
319
321 const std::vector<uint8_t>& data, int width, int height) {
322 std::vector<uint8_t> result(width * height);
323
324 for (int row = 0; row < height; ++row) {
325 for (int col = 0; col < width; col += 2) { // 2 pixels per byte in 4BPP
326 if (col / 2 < static_cast<int>(data.size())) {
327 uint8_t byte = data[row * (width / 2) + (col / 2)];
328
329 // Extract 2 pixels from the byte
330 uint8_t pixel1 = byte & 0x0F;
331 uint8_t pixel2 = (byte >> 4) & 0x0F;
332
333 result[row * width + col] = pixel1;
334 if (col + 1 < width) {
335 result[row * width + col + 1] = pixel2;
336 }
337 }
338 }
339 }
340
341 return result;
342}
343
345 const std::vector<uint8_t>& data, int width, int height) {
346 std::vector<uint8_t> result((width * height) / 4); // 4 pixels per byte
347
348 for (int row = 0; row < height; ++row) {
349 for (int col = 0; col < width; col += 4) {
350 uint8_t byte = 0;
351
352 // Pack 4 pixels into one byte
353 for (int i = 0; i < 4 && (col + i) < width; ++i) {
354 uint8_t pixel = data[row * width + col + i] & 0x03; // Clamp to 2 bits
355 byte |= (pixel << (6 - i * 2));
356 }
357
358 result[row * (width / 4) + (col / 4)] = byte;
359 }
360 }
361
362 return result;
363}
364
366 const std::vector<uint8_t>& data, int width, int height) {
367 // Convert to 4BPP first, then optimize
368 auto result_4bpp = Convert8BppTo4Bpp(data, width, height);
369 // Note: 3BPP conversion would require more sophisticated palette optimization
370 return result_4bpp;
371}
372
374 const std::vector<uint8_t>& data, int width, int height) {
375 std::vector<uint8_t> result((width * height) / 2); // 2 pixels per byte
376
377 for (int row = 0; row < height; ++row) {
378 for (int col = 0; col < width; col += 2) {
379 uint8_t pixel1 = data[row * width + col] & 0x0F; // Clamp to 4 bits
380 uint8_t pixel2 =
381 (col + 1 < width) ? (data[row * width + col + 1] & 0x0F) : 0;
382
383 uint8_t byte = pixel1 | (pixel2 << 4);
384 result[row * (width / 2) + (col / 2)] = byte;
385 }
386 }
387
388 return result;
389}
390
391int BppFormatManager::CountUsedColors(const std::vector<uint8_t>& data,
392 int max_colors) {
393 std::vector<bool> used_colors(max_colors, false);
394
395 for (uint8_t pixel : data) {
396 if (pixel < max_colors) {
397 used_colors[pixel] = true;
398 }
399 }
400
401 int count = 0;
402 for (bool used : used_colors) {
403 if (used)
404 count++;
405 }
406
407 return count;
408}
409
411 const std::vector<uint8_t>& original,
412 const std::vector<uint8_t>& compressed) {
413 if (compressed.empty())
414 return 1.0f;
415 return static_cast<float>(original.size()) /
416 static_cast<float>(compressed.size());
417}
418
420 const std::vector<uint8_t>& data, int width, int height, int tile_size) {
421 std::vector<int> usage_pattern;
422 int tiles_x = width / tile_size;
423 int tiles_y = height / tile_size;
424
425 for (int tile_row = 0; tile_row < tiles_y; ++tile_row) {
426 for (int tile_col = 0; tile_col < tiles_x; ++tile_col) {
427 int non_zero_pixels = 0;
428
429 // Count non-zero pixels in this tile
430 for (int row = 0; row < tile_size; ++row) {
431 for (int col = 0; col < tile_size; ++col) {
432 int pixel_x = tile_col * tile_size + col;
433 int pixel_y = tile_row * tile_size + row;
434 int pixel_index = pixel_y * width + pixel_x;
435
436 if (pixel_index < static_cast<int>(data.size()) &&
437 data[pixel_index] != 0) {
438 non_zero_pixels++;
439 }
440 }
441 }
442
443 usage_pattern.push_back(non_zero_pixels);
444 }
445 }
446
447 return usage_pattern;
448}
449
450// BppConversionScope implementation
451
453 BppFormat to_format, int width,
454 int height)
455 : from_format_(from_format),
456 to_format_(to_format),
457 width_(width),
458 height_(height),
459 timer_("bpp_convert_scope") {
460 std::ostringstream op_name;
461 op_name << "bpp_convert_" << static_cast<int>(from_format) << "_to_"
462 << static_cast<int>(to_format);
463 operation_name_ = op_name.str();
464}
465
467 // Timer automatically ends in destructor
468}
469
470std::vector<uint8_t> BppConversionScope::Convert(
471 const std::vector<uint8_t>& data) {
473 width_, height_);
474}
475
476} // namespace gfx
477} // 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:110
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)
BPP format metadata and conversion information.
Graphics sheet analysis result.