yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
bitmap.cc
Go to the documentation of this file.
1#include "bitmap.h"
2
3#include <SDL.h>
4
5#include <cstdint>
6#include <cstring> // for memcpy
7#include <span>
8#include <stdexcept>
9
13#include "util/log.h"
14
15namespace yaze {
16namespace gfx {
17
18
19class BitmapError : public std::runtime_error {
20 public:
21 using std::runtime_error::runtime_error;
22};
23
34Uint32 GetSnesPixelFormat(int format) {
35 switch (format) {
36 case 0:
37 return SDL_PIXELFORMAT_INDEX8;
38 case 1:
40 case 2:
42 default:
43 return SDL_PIXELFORMAT_INDEX8;
44 }
45}
46
47Bitmap::Bitmap(int width, int height, int depth,
48 const std::vector<uint8_t> &data)
49 : width_(width), height_(height), depth_(depth), data_(data) {
51}
52
53Bitmap::Bitmap(int width, int height, int depth,
54 const std::vector<uint8_t> &data, const SnesPalette &palette)
55 : width_(width),
56 height_(height),
57 depth_(depth),
58 palette_(palette),
59 data_(data) {
62}
63
65 : width_(other.width_),
66 height_(other.height_),
67 depth_(other.depth_),
68 active_(other.active_),
69 modified_(other.modified_),
70 palette_(other.palette_),
71 data_(other.data_) {
72 // Copy the data and recreate surface/texture with simple assignment
73 pixel_data_ = data_.data();
74 if (active_ && !data_.empty()) {
77 if (surface_) {
79 memcpy(surface_->pixels, pixel_data_, data_.size());
81 }
82 }
83}
84
86 if (this != &other) {
88 height_ = other.height_;
89 depth_ = other.depth_;
90 active_ = other.active_;
91 modified_ = other.modified_;
92 palette_ = other.palette_;
93 data_ = other.data_;
94
95 // Copy the data and recreate surface/texture
96 pixel_data_ = data_.data();
97 if (active_ && !data_.empty()) {
100 if (surface_) {
102 memcpy(surface_->pixels, pixel_data_, data_.size());
104 }
105 }
106 }
107 return *this;
108}
109
110Bitmap::Bitmap(Bitmap&& other) noexcept
111 : width_(other.width_),
112 height_(other.height_),
113 depth_(other.depth_),
114 active_(other.active_),
115 modified_(other.modified_),
116 texture_pixels(other.texture_pixels),
117 pixel_data_(other.pixel_data_),
118 palette_(std::move(other.palette_)),
119 data_(std::move(other.data_)),
120 surface_(other.surface_),
121 texture_(other.texture_) {
122 // Reset the moved-from object
123 other.width_ = 0;
124 other.height_ = 0;
125 other.depth_ = 0;
126 other.active_ = false;
127 other.modified_ = false;
128 other.texture_pixels = nullptr;
129 other.pixel_data_ = nullptr;
130 other.surface_ = nullptr;
131 other.texture_ = nullptr;
132}
133
134Bitmap& Bitmap::operator=(Bitmap&& other) noexcept {
135 if (this != &other) {
136 width_ = other.width_;
137 height_ = other.height_;
138 depth_ = other.depth_;
139 active_ = other.active_;
140 modified_ = other.modified_;
141 texture_pixels = other.texture_pixels;
142 pixel_data_ = other.pixel_data_;
143 palette_ = std::move(other.palette_);
144 data_ = std::move(other.data_);
145 surface_ = other.surface_;
146 texture_ = other.texture_;
147
148 // Reset the moved-from object
149 other.width_ = 0;
150 other.height_ = 0;
151 other.depth_ = 0;
152 other.active_ = false;
153 other.modified_ = false;
154 other.texture_pixels = nullptr;
155 other.pixel_data_ = nullptr;
156 other.surface_ = nullptr;
157 other.texture_ = nullptr;
158 }
159 return *this;
160}
161
162void Bitmap::Create(int width, int height, int depth, std::span<uint8_t> data) {
163 data_ = std::vector<uint8_t>(data.begin(), data.end());
165}
166
167void Bitmap::Create(int width, int height, int depth,
168 const std::vector<uint8_t> &data) {
169 Create(width, height, depth, static_cast<int>(BitmapFormat::kIndexed), data);
170}
171
186void Bitmap::Create(int width, int height, int depth, int format,
187 const std::vector<uint8_t> &data) {
188 if (data.empty()) {
189 SDL_Log("Bitmap data is empty\n");
190 active_ = false;
191 return;
192 }
193 active_ = true;
194 width_ = width;
195 height_ = height;
196 depth_ = depth;
197 if (data.empty()) {
198 SDL_Log("Data provided to Bitmap is empty.\n");
199 return;
200 }
201 data_.reserve(data.size());
202 data_ = data;
203 pixel_data_ = data_.data();
205 GetSnesPixelFormat(format));
206 if (surface_ == nullptr) {
207 SDL_Log("Bitmap::Create.SDL_CreateRGBSurfaceWithFormat failed: %s\n",
208 SDL_GetError());
209 active_ = false;
210 return;
211 }
212
213 // CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment
214 // Direct assignment breaks SDL's memory management and causes malloc errors on shutdown
215 if (surface_ && data_.size() > 0) {
217 memcpy(surface_->pixels, pixel_data_, data_.size());
219 }
220 active_ = true;
221
222 // Apply the stored palette if one exists
223 if (!palette_.empty()) {
225 }
226}
227
228void Bitmap::Reformat(int format) {
230 GetSnesPixelFormat(format));
231
232 // CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment
233 if (surface_ && data_.size() > 0) {
235 memcpy(surface_->pixels, pixel_data_, data_.size());
237 }
238 active_ = true;
240}
241
245
249
250
251
269 if (surface_ == nullptr) {
270 return; // Can't apply without surface
271 }
272 if (surface_->format == nullptr) {
273 return; // Invalid surface format
274 }
275 if (palette_.empty()) {
276 return; // No palette to apply
277 }
278
279 // Invalidate palette cache when palette changes
281
282 // For indexed surfaces, ensure palette exists
283 SDL_Palette *sdl_palette = surface_->format->palette;
284 if (sdl_palette == nullptr) {
285 // Non-indexed surface or palette not created - can't apply palette
286 SDL_Log("Warning: Bitmap surface has no palette (non-indexed format?)\n");
287 return;
288 }
289
291
292 // Build SDL color array from SnesPalette
293 // Only set the colors that exist in the palette - don't fill unused entries
294 std::vector<SDL_Color> colors(palette_.size());
295 for (size_t i = 0; i < palette_.size(); ++i) {
296 const auto& pal_color = palette_[i];
297
298 // Get RGB values - stored as 0-255 in ImVec4 (unconventional!)
299 ImVec4 rgb_255 = pal_color.rgb();
300
301 colors[i].r = static_cast<Uint8>(rgb_255.x);
302 colors[i].g = static_cast<Uint8>(rgb_255.y);
303 colors[i].b = static_cast<Uint8>(rgb_255.z);
304
305 // Only apply transparency if explicitly set
306 if (pal_color.is_transparent()) {
307 colors[i].a = 0; // Fully transparent
308 } else {
309 colors[i].a = 255; // Fully opaque
310 }
311 }
312
313 // Apply palette to surface using SDL_SetPaletteColors
314 // Only set the colors we have - leave rest of palette unchanged
315 // This prevents breaking systems that use small palettes (8-16 colors)
316 SDL_SetPaletteColors(sdl_palette, colors.data(), 0, static_cast<int>(palette_.size()));
317
319}
320
322 if (!surface_ || data_.empty()) {
323 return;
324 }
325
326 // Copy pixel data from data_ vector to SDL surface
328 if (surface_->pixels && data_.size() > 0) {
329 memcpy(surface_->pixels, data_.data(), std::min(data_.size(), static_cast<size_t>(surface_->pitch * surface_->h)));
330 }
332}
333
334void Bitmap::SetPalette(const SnesPalette &palette) {
335 // Store palette even if surface isn't ready yet
337
338 // Apply it immediately if surface is ready
340
341 // Mark as modified to trigger texture update
342 modified_ = true;
343}
344
360void Bitmap::ApplyPaletteByMetadata(const SnesPalette& palette, int sub_palette_index) {
361 if (metadata_.palette_format == 1) {
362 // Sub-palette: need transparent black + 7 colors from palette
363 // Common for 3BPP graphics sheets (title screen, etc.)
365 } else {
366 // Full palette application
367 // Used for 4BPP, Mode 7, and other full-color formats
369 }
370}
371
403void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
404 int length) {
405 // Store the full palette for reference (not modified)
407
408 // If surface isn't created yet, just store the palette for later
409 if (surface_ == nullptr) {
410 return; // Palette will be applied when surface is created
411 }
412
413 // Validate parameters
414 if (index >= palette.size()) {
415 throw std::invalid_argument("Invalid palette index");
416 }
417
419 throw std::invalid_argument("Invalid palette length (must be 0-7 for SNES palettes)");
420 }
421
422 if (index + length > palette.size()) {
423 throw std::invalid_argument("Palette index + length exceeds size");
424 }
425
426 // Build 8-color SNES sub-palette
427 std::vector<ImVec4> colors;
428
429 // Color 0: Transparent (SNES hardware requirement)
430 colors.push_back(ImVec4(0, 0, 0, 0)); // Transparent black
431
432 // Colors 1-7: Extract from source palette
433 // NOTE: palette[i].rgb() returns 0-255 values in ImVec4 (unconventional!)
434 for (size_t i = 0; i < 7 && (index + i) < palette.size(); ++i) {
435 const auto &pal_color = palette[index + i];
436 ImVec4 rgb_255 = pal_color.rgb(); // 0-255 range (unconventional storage)
437
438 // Convert to standard ImVec4 0-1 range for SDL
439 colors.push_back(ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f,
440 rgb_255.z / 255.0f, 1.0f)); // Always opaque
441 }
442
443 // Ensure we have exactly 8 colors
444 while (colors.size() < 8) {
445 colors.push_back(ImVec4(0, 0, 0, 1.0f)); // Fill with opaque black
446 }
447
448 // Update palette cache with full palette (for color lookup)
450
451 // Apply the 8-color SNES sub-palette to SDL surface
453 for (int color_index = 0; color_index < 8 && color_index < static_cast<int>(colors.size()); ++color_index) {
454 if (color_index < surface_->format->palette->ncolors) {
455 surface_->format->palette->colors[color_index].r = static_cast<Uint8>(colors[color_index].x * 255.0f);
456 surface_->format->palette->colors[color_index].g = static_cast<Uint8>(colors[color_index].y * 255.0f);
457 surface_->format->palette->colors[color_index].b = static_cast<Uint8>(colors[color_index].z * 255.0f);
458 surface_->format->palette->colors[color_index].a = static_cast<Uint8>(colors[color_index].w * 255.0f);
459 }
460 }
462}
463
464void Bitmap::SetPalette(const std::vector<SDL_Color> &palette) {
466 for (size_t i = 0; i < palette.size(); ++i) {
467 surface_->format->palette->colors[i].r = palette[i].r;
468 surface_->format->palette->colors[i].g = palette[i].g;
469 surface_->format->palette->colors[i].b = palette[i].b;
470 surface_->format->palette->colors[i].a = palette[i].a;
471 }
473}
474
475void Bitmap::WriteToPixel(int position, uint8_t value) {
476 // Bounds checking to prevent crashes
477 if (position < 0 || position >= static_cast<int>(data_.size())) {
478 SDL_Log("ERROR: WriteToPixel - position %d out of bounds (size: %zu)",
479 position, data_.size());
480 return;
481 }
482
483 // Safety check: ensure bitmap is active and has valid data
484 if (!active_ || data_.empty()) {
485 SDL_Log("ERROR: WriteToPixel - bitmap not active or data empty (active=%s, size=%zu)",
486 active_ ? "true" : "false", data_.size());
487 return;
488 }
489
490 if (pixel_data_ == nullptr) {
491 pixel_data_ = data_.data();
492 }
493
494 // Safety check: ensure surface exists and is valid
495 if (!surface_ || !surface_->pixels) {
496 SDL_Log("ERROR: WriteToPixel - surface or pixels are null (surface=%p, pixels=%p)",
497 surface_, surface_ ? surface_->pixels : nullptr);
498 return;
499 }
500
501 // Additional validation: ensure pixel_data_ is valid
502 if (pixel_data_ == nullptr) {
503 SDL_Log("ERROR: WriteToPixel - pixel_data_ is null after assignment");
504 return;
505 }
506
507 // CRITICAL FIX: Update both data_ and surface_ properly
508 data_[position] = value;
509 pixel_data_[position] = value;
510
511 // Update surface if it exists
512 if (surface_) {
514 static_cast<uint8_t*>(surface_->pixels)[position] = value;
516 }
517
518 // Mark as modified for traditional update path
519 modified_ = true;
520}
521
522void Bitmap::WriteColor(int position, const ImVec4 &color) {
523 // Bounds checking to prevent crashes
524 if (position < 0 || position >= static_cast<int>(data_.size())) {
525 return;
526 }
527
528 // Safety check: ensure bitmap is active and has valid data
529 if (!active_ || data_.empty()) {
530 return;
531 }
532
533 // Safety check: ensure surface exists and is valid
534 if (!surface_ || !surface_->pixels || !surface_->format) {
535 return;
536 }
537
538 // Convert ImVec4 (RGBA) to SDL_Color (RGBA)
540 sdl_color.r = static_cast<Uint8>(color.x * 255);
541 sdl_color.g = static_cast<Uint8>(color.y * 255);
542 sdl_color.b = static_cast<Uint8>(color.z * 255);
543 sdl_color.a = static_cast<Uint8>(color.w * 255);
544
545 // Map SDL_Color to the nearest color index in the surface's palette
546 Uint8 index =
548
549 // CRITICAL FIX: Update both data_ and surface_ properly
550 if (pixel_data_ == nullptr) {
551 pixel_data_ = data_.data();
552 }
553 data_[position] = ConvertRgbToSnes(color);
554 pixel_data_[position] = index;
555
556 // Update surface if it exists
557 if (surface_) {
559 static_cast<uint8_t*>(surface_->pixels)[position] = index;
561 }
562
563 modified_ = true;
564}
565
566void Bitmap::Get8x8Tile(int tile_index, int x, int y,
567 std::vector<uint8_t> &tile_data,
568 int &tile_data_offset) {
570 int tile_x = (x * 8) % width_;
571 int tile_y = (y * 8) % height_;
572 for (int i = 0; i < 8; i++) {
573 for (int j = 0; j < 8; j++) {
574 int pixel_offset = tile_offset + (tile_y + i) * width_ + tile_x + j;
576 tile_data[tile_data_offset] = pixel_value;
578 }
579 }
580}
581
582void Bitmap::Get16x16Tile(int tile_x, int tile_y,
583 std::vector<uint8_t> &tile_data,
584 int &tile_data_offset) {
585 for (int ty = 0; ty < 16; ty++) {
586 for (int tx = 0; tx < 16; tx++) {
587 // Calculate the pixel position in the bitmap
588 int pixel_x = tile_x + tx;
589 int pixel_y = tile_y + ty;
590 int pixel_offset = (pixel_y * width_) + pixel_x;
592
593 // Store the pixel value in the tile data
595 tile_data[tile_data_offset] = pixel_value;
596 }
597 }
598}
599
600
617void Bitmap::SetPixel(int x, int y, const SnesColor& color) {
619 return; // Bounds check
620 }
621
622 int position = y * width_ + x;
623 if (position >= 0 && position < static_cast<int>(data_.size())) {
624 uint8_t color_index = FindColorIndex(color);
625 data_[position] = color_index;
626
627 // Update pixel_data_ to maintain consistency
628 if (pixel_data_) {
629 pixel_data_[position] = color_index;
630 }
631
632 // Update surface if it exists
633 if (surface_) {
635 static_cast<uint8_t*>(surface_->pixels)[position] = color_index;
637 }
638
639 // Update dirty region for efficient texture updates
641 modified_ = true;
642 }
643}
644
645void Bitmap::Resize(int new_width, int new_height) {
646 if (new_width <= 0 || new_height <= 0) {
647 return; // Invalid dimensions
648 }
649
650 std::vector<uint8_t> new_data(new_width * new_height, 0);
651
652 // Copy existing data, handling size changes
653 if (!data_.empty()) {
654 for (int y = 0; y < std::min(height_, new_height); y++) {
655 for (int x = 0; x < std::min(width_, new_width); x++) {
656 int old_pos = y * width_ + x;
657 int new_pos = y * new_width + x;
658 if (old_pos < (int)data_.size() && new_pos < (int)new_data.size()) {
660 }
661 }
662 }
663 }
664
667 data_ = std::move(new_data);
668 pixel_data_ = data_.data();
669
670 // Recreate surface with new dimensions
673 if (surface_) {
675 memcpy(surface_->pixels, pixel_data_, data_.size());
677 active_ = true;
678 } else {
679 active_ = false;
680 }
681
682 modified_ = true;
683}
684
695uint32_t Bitmap::HashColor(const ImVec4& color) {
696 // Convert float values to integers for consistent hashing
697 uint32_t r = static_cast<uint32_t>(color.x * 255.0F) & 0xFF;
698 uint32_t g = static_cast<uint32_t>(color.y * 255.0F) & 0xFF;
699 uint32_t b = static_cast<uint32_t>(color.z * 255.0F) & 0xFF;
700 uint32_t a = static_cast<uint32_t>(color.w * 255.0F) & 0xFF;
701
702 // Simple hash combining all components
703 return (r << 24) | (g << 16) | (b << 8) | a;
704}
705
716 color_to_index_cache_.clear();
717
718 // Rebuild cache with current palette
719 for (size_t i = 0; i < palette_.size(); i++) {
721 color_to_index_cache_[color_hash] = static_cast<uint8_t>(i);
722 }
723}
724
736uint8_t Bitmap::FindColorIndex(const SnesColor& color) {
737 ScopedTimer timer("palette_lookup_optimized");
738 uint32_t hash = HashColor(color.rgb());
739 auto it = color_to_index_cache_.find(hash);
740 return (it != color_to_index_cache_.end()) ? it->second : 0;
741}
742
743void Bitmap::set_data(const std::vector<uint8_t> &data) {
744 // Validate input data
745 if (data.empty()) {
746 SDL_Log("Warning: set_data called with empty data vector");
747 return;
748 }
749
750 data_ = data;
751 pixel_data_ = data_.data();
752
753 // CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment
754 if (surface_ && !data_.empty()) {
756 memcpy(surface_->pixels, pixel_data_, data_.size());
758 }
759
760 modified_ = true;
761}
762
764 if (!surface_ || !surface_->pixels || data_.empty()) {
765 SDL_Log("ValidateDataSurfaceSync: surface or data is null/empty");
766 return false;
767 }
768
769 // Check if data and surface are synchronized
770 size_t surface_size = static_cast<size_t>(surface_->h * surface_->pitch);
771 size_t data_size = data_.size();
772 size_t compare_size = std::min(data_size, surface_size);
773
774 if (compare_size == 0) {
775 SDL_Log("ValidateDataSurfaceSync: invalid sizes - surface: %zu, data: %zu",
776 surface_size, data_size);
777 return false;
778 }
779
780 // Compare first few bytes to check synchronization
781 if (memcmp(surface_->pixels, data_.data(), compare_size) != 0) {
782 SDL_Log("ValidateDataSurfaceSync: data and surface are not synchronized");
783 return false;
784 }
785
786 return true;
787}
788
789} // namespace gfx
790} // namespace yaze
SDL_Surface * AllocateSurface(int width, int height, int depth, int format)
Definition arena.cc:125
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
static Arena & Get()
Definition arena.cc:15
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
const uint8_t * data() const
Definition bitmap.h:286
const SnesPalette & palette() const
Definition bitmap.h:277
SDL_Surface * surface_
Definition bitmap.h:324
Bitmap & operator=(const Bitmap &other)
Copy assignment operator.
Definition bitmap.cc:85
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
Definition bitmap.cc:475
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:162
bool ValidateDataSurfaceSync()
Validate that bitmap data and surface pixels are synchronized.
Definition bitmap.cc:763
const std::vector< uint8_t > & vector() const
Definition bitmap.h:290
void UpdateSurfacePixels()
Update SDL surface with current pixel data from data_ vector Call this after modifying pixel data via...
Definition bitmap.cc:321
std::unordered_map< uint32_t, uint8_t > color_to_index_cache_
Definition bitmap.h:330
void Reformat(int format)
Reformat the bitmap to use a different pixel format.
Definition bitmap.cc:228
uint8_t * pixel_data_
Definition bitmap.h:312
static uint32_t HashColor(const ImVec4 &color)
Hash a color for cache lookup.
Definition bitmap.cc:695
void Get8x8Tile(int tile_index, int x, int y, std::vector< uint8_t > &tile_data, int &tile_data_offset)
Extract an 8x8 tile from the bitmap (SNES standard tile size)
Definition bitmap.cc:566
void CreateTexture()
Creates the underlying SDL_Texture to be displayed.
Definition bitmap.cc:242
void WriteColor(int position, const ImVec4 &color)
Write a color to a pixel at the given position.
Definition bitmap.cc:522
int height() const
Definition bitmap.h:283
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:743
void Resize(int new_width, int new_height)
Resize the bitmap to new dimensions (preserves existing data)
Definition bitmap.cc:645
void SetPixel(int x, int y, const SnesColor &color)
Set a pixel at the given x,y coordinates with SNES color.
Definition bitmap.cc:617
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
Definition bitmap.cc:334
int width() const
Definition bitmap.h:282
BitmapMetadata metadata_
Definition bitmap.h:318
void ApplyStoredPalette()
Apply the stored palette to the surface (internal helper)
Definition bitmap.cc:268
std::vector< uint8_t > data_
Definition bitmap.h:321
int depth() const
Definition bitmap.h:284
void InvalidatePaletteCache()
Invalidate the palette lookup cache (call when palette changes)
Definition bitmap.cc:715
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
Definition bitmap.cc:403
struct yaze::gfx::Bitmap::DirtyRegion dirty_region_
void ApplyPaletteByMetadata(const SnesPalette &palette, int sub_palette_index=0)
Apply palette using metadata-driven strategy Chooses between SetPalette and SetPaletteWithTransparent...
Definition bitmap.cc:360
void Get16x16Tile(int tile_x, int tile_y, std::vector< uint8_t > &tile_data, int &tile_data_offset)
Extract a 16x16 tile from the bitmap (SNES metatile size)
Definition bitmap.cc:582
uint8_t FindColorIndex(const SnesColor &color)
Find color index in palette using optimized hash map lookup.
Definition bitmap.cc:736
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Definition bitmap.cc:246
gfx::SnesPalette palette_
Definition bitmap.h:315
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:109
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:182
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
@ kIndexed
Definition bitmap.h:35
uint16_t ConvertRgbToSnes(const snes_color &color)
Convert RGB (0-255) to SNES 15-bit color.
Definition snes_color.cc:33
constexpr Uint32 SNES_PIXELFORMAT_8BPP
Definition bitmap.h:30
constexpr Uint32 SNES_PIXELFORMAT_4BPP
Definition bitmap.h:26
Uint32 GetSnesPixelFormat(int format)
Convert bitmap format enum to SDL pixel format.
Definition bitmap.cc:34
Main namespace for the application.
Definition controller.cc:20
void AddPoint(int x, int y)
Definition bitmap.h:342