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
10#include "app/gfx/arena.h"
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
253 if (surface_ == nullptr) {
254 return; // Can't apply without surface
255 }
256 if (surface_->format == nullptr || surface_->format->palette == nullptr) {
257 return; // Can't apply palette to this surface format
258 }
259 if (palette_.empty()) {
260 return; // No palette to apply
261 }
262
263 // Invalidate palette cache when palette changes
265
266 SDL_Palette *sdl_palette = surface_->format->palette;
267 if (sdl_palette == nullptr) {
268 return;
269 }
270
272
273 // Apply all palette colors from the SnesPalette
274 for (size_t i = 0; i < palette_.size() && i < 256; ++i) {
275 const auto& pal_color = palette_[i];
276 // NOTE: rgb() stores 0-255 values directly in ImVec4 (unconventional but intentional)
277 sdl_palette->colors[i].r = static_cast<Uint8>(pal_color.rgb().x);
278 sdl_palette->colors[i].g = static_cast<Uint8>(pal_color.rgb().y);
279 sdl_palette->colors[i].b = static_cast<Uint8>(pal_color.rgb().z);
280
281 // CRITICAL: Transparency for color 0 of each sub-palette
282 if (pal_color.is_transparent()) {
283 sdl_palette->colors[i].a = 0; // Fully transparent
284 } else {
285 sdl_palette->colors[i].a = 255; // Fully opaque
286 }
287 }
288
290}
291
292void Bitmap::SetPalette(const SnesPalette &palette) {
293 // Store palette even if surface isn't ready yet
295
296 // Apply it immediately if surface is ready
298
299 // Mark as modified to trigger texture update
300 modified_ = true;
301}
302
303void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
304 int length) {
305 // Store palette even if surface isn't ready yet
307
308 // If surface isn't created yet, just store the palette for later
309 if (surface_ == nullptr) {
310 return; // Palette will be applied when surface is created
311 }
312
313 // CRITICAL FIX: Use index directly as palette slot, not index * 7
314 // For 8-color palettes, index should be 0-7, not 0-49
315 if (index >= palette.size()) {
316 throw std::invalid_argument("Invalid palette index");
317 }
318
320 throw std::invalid_argument("Invalid palette length (must be 0-8 for 8-color palettes)");
321 }
322
323 if (index + length > palette.size()) {
324 throw std::invalid_argument("Palette index + length exceeds size");
325 }
326
327 // Extract 8-color sub-palette starting at the specified index
328 // This correctly handles both 256-color overworld palettes and smaller palettes
329 std::vector<ImVec4> colors;
330
331 // Always start with transparent color (index 0)
332 colors.push_back(ImVec4(0, 0, 0, 0));
333
334 // Extract up to 7 colors from the palette starting at index
335 for (size_t i = 0; i < 7 && (index + i) < palette.size(); ++i) {
336 auto &pal_color = palette[index + i];
337 colors.push_back(pal_color.rgb());
338 }
339
340 // Ensure we have exactly 8 colors (transparent + 7 data colors)
341 while (colors.size() < 8) {
342 colors.push_back(ImVec4(0, 0, 0, 1.0f)); // Fill with black if needed
343 }
344
345 // CRITICAL FIX: Keep the original complete palette in palette_ member
346 // Only update the SDL surface palette for display purposes
347 // This prevents breaking other editors that expect the complete palette
348 if (palette_.size() != palette.size()) {
349 palette_ = palette; // Store complete palette
350 InvalidatePaletteCache(); // Update cache with complete palette
351 }
352
353 // Apply the 8-color sub-palette to SDL surface for display
355 for (int color_index = 0; color_index < 8 && color_index < static_cast<int>(colors.size()); ++color_index) {
356 if (color_index < surface_->format->palette->ncolors) {
357 surface_->format->palette->colors[color_index].r = static_cast<Uint8>(colors[color_index].x * 255);
358 surface_->format->palette->colors[color_index].g = static_cast<Uint8>(colors[color_index].y * 255);
359 surface_->format->palette->colors[color_index].b = static_cast<Uint8>(colors[color_index].z * 255);
360 surface_->format->palette->colors[color_index].a = static_cast<Uint8>(colors[color_index].w * 255);
361 }
362 }
364}
365
366void Bitmap::SetPalette(const std::vector<SDL_Color> &palette) {
368 for (size_t i = 0; i < palette.size(); ++i) {
369 surface_->format->palette->colors[i].r = palette[i].r;
370 surface_->format->palette->colors[i].g = palette[i].g;
371 surface_->format->palette->colors[i].b = palette[i].b;
372 surface_->format->palette->colors[i].a = palette[i].a;
373 }
375}
376
377void Bitmap::WriteToPixel(int position, uint8_t value) {
378 // Bounds checking to prevent crashes
379 if (position < 0 || position >= static_cast<int>(data_.size())) {
380 SDL_Log("ERROR: WriteToPixel - position %d out of bounds (size: %zu)",
381 position, data_.size());
382 return;
383 }
384
385 // Safety check: ensure bitmap is active and has valid data
386 if (!active_ || data_.empty()) {
387 SDL_Log("ERROR: WriteToPixel - bitmap not active or data empty (active=%s, size=%zu)",
388 active_ ? "true" : "false", data_.size());
389 return;
390 }
391
392 if (pixel_data_ == nullptr) {
393 pixel_data_ = data_.data();
394 }
395
396 // Safety check: ensure surface exists and is valid
397 if (!surface_ || !surface_->pixels) {
398 SDL_Log("ERROR: WriteToPixel - surface or pixels are null (surface=%p, pixels=%p)",
399 surface_, surface_ ? surface_->pixels : nullptr);
400 return;
401 }
402
403 // Additional validation: ensure pixel_data_ is valid
404 if (pixel_data_ == nullptr) {
405 SDL_Log("ERROR: WriteToPixel - pixel_data_ is null after assignment");
406 return;
407 }
408
409 // CRITICAL FIX: Update both data_ and surface_ properly
410 data_[position] = value;
411 pixel_data_[position] = value;
412
413 // Update surface if it exists
414 if (surface_) {
416 static_cast<uint8_t*>(surface_->pixels)[position] = value;
418 }
419
420 // Mark as modified for traditional update path
421 modified_ = true;
422}
423
424void Bitmap::WriteColor(int position, const ImVec4 &color) {
425 // Bounds checking to prevent crashes
426 if (position < 0 || position >= static_cast<int>(data_.size())) {
427 return;
428 }
429
430 // Safety check: ensure bitmap is active and has valid data
431 if (!active_ || data_.empty()) {
432 return;
433 }
434
435 // Safety check: ensure surface exists and is valid
436 if (!surface_ || !surface_->pixels || !surface_->format) {
437 return;
438 }
439
440 // Convert ImVec4 (RGBA) to SDL_Color (RGBA)
442 sdl_color.r = static_cast<Uint8>(color.x * 255);
443 sdl_color.g = static_cast<Uint8>(color.y * 255);
444 sdl_color.b = static_cast<Uint8>(color.z * 255);
445 sdl_color.a = static_cast<Uint8>(color.w * 255);
446
447 // Map SDL_Color to the nearest color index in the surface's palette
448 Uint8 index =
450
451 // CRITICAL FIX: Update both data_ and surface_ properly
452 if (pixel_data_ == nullptr) {
453 pixel_data_ = data_.data();
454 }
455 data_[position] = ConvertRgbToSnes(color);
456 pixel_data_[position] = index;
457
458 // Update surface if it exists
459 if (surface_) {
461 static_cast<uint8_t*>(surface_->pixels)[position] = index;
463 }
464
465 modified_ = true;
466}
467
468void Bitmap::Get8x8Tile(int tile_index, int x, int y,
469 std::vector<uint8_t> &tile_data,
470 int &tile_data_offset) {
472 int tile_x = (x * 8) % width_;
473 int tile_y = (y * 8) % height_;
474 for (int i = 0; i < 8; i++) {
475 for (int j = 0; j < 8; j++) {
476 int pixel_offset = tile_offset + (tile_y + i) * width_ + tile_x + j;
478 tile_data[tile_data_offset] = pixel_value;
480 }
481 }
482}
483
484void Bitmap::Get16x16Tile(int tile_x, int tile_y,
485 std::vector<uint8_t> &tile_data,
486 int &tile_data_offset) {
487 for (int ty = 0; ty < 16; ty++) {
488 for (int tx = 0; tx < 16; tx++) {
489 // Calculate the pixel position in the bitmap
490 int pixel_x = tile_x + tx;
491 int pixel_y = tile_y + ty;
492 int pixel_offset = (pixel_y * width_) + pixel_x;
494
495 // Store the pixel value in the tile data
497 tile_data[tile_data_offset] = pixel_value;
498 }
499 }
500}
501
502
519void Bitmap::SetPixel(int x, int y, const SnesColor& color) {
521 return; // Bounds check
522 }
523
524 int position = y * width_ + x;
525 if (position >= 0 && position < static_cast<int>(data_.size())) {
526 uint8_t color_index = FindColorIndex(color);
527 data_[position] = color_index;
528
529 // Update pixel_data_ to maintain consistency
530 if (pixel_data_) {
531 pixel_data_[position] = color_index;
532 }
533
534 // Update surface if it exists
535 if (surface_) {
537 static_cast<uint8_t*>(surface_->pixels)[position] = color_index;
539 }
540
541 // Update dirty region for efficient texture updates
543 modified_ = true;
544 }
545}
546
547void Bitmap::Resize(int new_width, int new_height) {
548 if (new_width <= 0 || new_height <= 0) {
549 return; // Invalid dimensions
550 }
551
552 std::vector<uint8_t> new_data(new_width * new_height, 0);
553
554 // Copy existing data, handling size changes
555 if (!data_.empty()) {
556 for (int y = 0; y < std::min(height_, new_height); y++) {
557 for (int x = 0; x < std::min(width_, new_width); x++) {
558 int old_pos = y * width_ + x;
559 int new_pos = y * new_width + x;
560 if (old_pos < (int)data_.size() && new_pos < (int)new_data.size()) {
562 }
563 }
564 }
565 }
566
569 data_ = std::move(new_data);
570 pixel_data_ = data_.data();
571
572 // Recreate surface with new dimensions
575 if (surface_) {
577 memcpy(surface_->pixels, pixel_data_, data_.size());
579 active_ = true;
580 } else {
581 active_ = false;
582 }
583
584 modified_ = true;
585}
586
597uint32_t Bitmap::HashColor(const ImVec4& color) {
598 // Convert float values to integers for consistent hashing
599 uint32_t r = static_cast<uint32_t>(color.x * 255.0F) & 0xFF;
600 uint32_t g = static_cast<uint32_t>(color.y * 255.0F) & 0xFF;
601 uint32_t b = static_cast<uint32_t>(color.z * 255.0F) & 0xFF;
602 uint32_t a = static_cast<uint32_t>(color.w * 255.0F) & 0xFF;
603
604 // Simple hash combining all components
605 return (r << 24) | (g << 16) | (b << 8) | a;
606}
607
618 color_to_index_cache_.clear();
619
620 // Rebuild cache with current palette
621 for (size_t i = 0; i < palette_.size(); i++) {
623 color_to_index_cache_[color_hash] = static_cast<uint8_t>(i);
624 }
625}
626
638uint8_t Bitmap::FindColorIndex(const SnesColor& color) {
639 ScopedTimer timer("palette_lookup_optimized");
640 uint32_t hash = HashColor(color.rgb());
641 auto it = color_to_index_cache_.find(hash);
642 return (it != color_to_index_cache_.end()) ? it->second : 0;
643}
644
645void Bitmap::set_data(const std::vector<uint8_t> &data) {
646 // Validate input data
647 if (data.empty()) {
648 SDL_Log("Warning: set_data called with empty data vector");
649 return;
650 }
651
652 data_ = data;
653 pixel_data_ = data_.data();
654
655 // CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment
656 if (surface_ && !data_.empty()) {
658 memcpy(surface_->pixels, pixel_data_, data_.size());
660 }
661
662 modified_ = true;
663}
664
666 if (!surface_ || !surface_->pixels || data_.empty()) {
667 SDL_Log("ValidateDataSurfaceSync: surface or data is null/empty");
668 return false;
669 }
670
671 // Check if data and surface are synchronized
672 size_t surface_size = static_cast<size_t>(surface_->h * surface_->pitch);
673 size_t data_size = data_.size();
674 size_t compare_size = std::min(data_size, surface_size);
675
676 if (compare_size == 0) {
677 SDL_Log("ValidateDataSurfaceSync: invalid sizes - surface: %zu, data: %zu",
678 surface_size, data_size);
679 return false;
680 }
681
682 // Compare first few bytes to check synchronization
683 if (memcmp(surface_->pixels, data_.data(), compare_size) != 0) {
684 SDL_Log("ValidateDataSurfaceSync: data and surface are not synchronized");
685 return false;
686 }
687
688 return true;
689}
690
691} // namespace gfx
692} // 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:257
const SnesPalette & palette() const
Definition bitmap.h:251
SDL_Surface * surface_
Definition bitmap.h:292
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:377
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:665
const std::vector< uint8_t > & vector() const
Definition bitmap.h:261
std::unordered_map< uint32_t, uint8_t > color_to_index_cache_
Definition bitmap.h:298
void Reformat(int format)
Reformat the bitmap to use a different pixel format.
Definition bitmap.cc:228
uint8_t * pixel_data_
Definition bitmap.h:283
static uint32_t HashColor(const ImVec4 &color)
Hash a color for cache lookup.
Definition bitmap.cc:597
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:468
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:424
int height() const
Definition bitmap.h:254
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:645
void Resize(int new_width, int new_height)
Resize the bitmap to new dimensions (preserves existing data)
Definition bitmap.cc:547
void SetPixel(int x, int y, const SnesColor &color)
Set a pixel at the given x,y coordinates with SNES color.
Definition bitmap.cc:519
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
Definition bitmap.cc:292
int width() const
Definition bitmap.h:253
void ApplyStoredPalette()
Apply the stored palette to the surface (internal helper)
Definition bitmap.cc:252
std::vector< uint8_t > data_
Definition bitmap.h:289
int depth() const
Definition bitmap.h:255
void InvalidatePaletteCache()
Invalidate the palette lookup cache (call when palette changes)
Definition bitmap.cc:617
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
Definition bitmap.cc:303
struct yaze::gfx::Bitmap::DirtyRegion dirty_region_
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:484
uint8_t FindColorIndex(const SnesColor &color)
Find color index in palette using optimized hash map lookup.
Definition bitmap.cc:638
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Definition bitmap.cc:246
gfx::SnesPalette palette_
Definition bitmap.h:286
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:38
constexpr ImVec4 rgb() const
Definition snes_color.h:74
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
@ kIndexed
Definition bitmap.h:35
uint16_t ConvertRgbToSnes(const snes_color &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.
void AddPoint(int x, int y)
Definition bitmap.h:310