6#include "absl/strings/str_format.h"
28void CopySheet(
const std::vector<uint8_t>& sheet, std::vector<uint8_t>& dst,
31 std::copy(sheet.begin(), sheet.end(), dst.begin() + block *
kSheetBytes);
40 if (game_data ==
nullptr) {
43 for (uint16_t
id : ids) {
45 if (!pixels.empty()) {
46 out[id] = std::vector<uint8_t>(pixels.begin(), pixels.end());
56 if (game_data !=
nullptr && arena.gfx_sheets_owner() !=
nullptr &&
57 arena.gfx_sheets_owner() != game_data) {
60 const auto& sheets = arena.gfx_sheets();
61 for (
const uint16_t
id : ids) {
62 if (
id >= sheets.size()) {
65 const auto& sheet = sheets[id];
66 if (sheet.width() == 128 && sheet.height() == 32 &&
68 out[id] = sheet.vector();
77 std::vector<uint8_t> rgba(
static_cast<size_t>(
width) *
height * 4, 0);
79 std::copy_n(
pixels.begin(), std::min(
pixels.size(), rgba.size()),
83 for (
size_t i = 0; i <
pixels.size() && i * 4 + 3 < rgba.size(); ++i) {
84 const uint8_t index =
pixels[i];
88 const SDL_Color& c =
colors[index];
89 rgba[i * 4 + 0] = c.r;
90 rgba[i * 4 + 1] = c.g;
91 rgba[i * 4 + 2] = c.b;
92 rgba[i * 4 + 3] = c.a;
101 const auto b = other.
ToRgba();
103 for (
size_t i = 0; i < a.size(); i += 4) {
104 count += !std::equal(a.begin() + i, a.begin() + i + 4, b.begin() + i);
123 for (
int i = 0; i < palette->ncolors && i < 256; ++i) {
124 image.
colors[i] = palette->colors[i];
129 SDL_GetColorKey(bitmap.
surface(), &color_key) == 0 &&
130 color_key < image.
colors.size()) {
131 image.
colors[color_key].a = 0;
142 const std::map<int, zelda3::RoomGfxInfo>& rooms) {
143 std::map<int, std::array<uint8_t, 16>> out;
144 if (rom ==
nullptr || game_data ==
nullptr) {
147 for (
const auto& [room_id, info] : rooms) {
152 std::array<uint8_t, 16> blocks{};
153 const auto& room_blocks = room.
blocks();
154 std::copy_n(room_blocks.begin(), 16, blocks.begin());
155 out[room_id] = blocks;
161 const std::map<
int, std::array<uint8_t, 16>>& room_blocks, uint16_t sheet,
162 bool include_sprites) {
163 std::vector<int> rooms;
164 for (
const auto& [room_id, blocks] : room_blocks) {
165 const int end = include_sprites ? 16 : 8;
166 if (std::find(blocks.begin(), blocks.begin() + end, sheet) !=
167 blocks.begin() + end) {
168 rooms.push_back(room_id);
176 return "ROM · Default entrance";
178 return "Live · Default entrance";
179 return absl::StrFormat(
"Live · Entrance $%02X", live->
entrance_slot);
183 const std::vector<RoomTileReference>& references,
184 const std::vector<GraphicsEditTarget>& targets,
186 std::map<size_t, SourceObjectUse> matches;
187 for (
const auto& reference : references) {
189 bool palette =
false;
190 for (
const auto& target : targets) {
191 pixels |= target.sheet_id == reference.sheet &&
192 target.tile_index == (reference.tile_word & 0x3F);
193 if (!owner || !target.writable_palette ||
194 target.writable_palette->owner != owner)
196 for (
size_t raw = 1; raw < reference.palette_colors.size(); ++raw) {
197 if (!reference.palette_colors[raw])
199 for (
size_t source = 1; source < 8; ++source) {
200 palette |= target.writable_palette->colors[source] &&
201 reference.palette_colors[raw] ==
202 target.writable_palette->colors[source];
206 if (!pixels && !palette)
208 auto& match = matches[reference.object_index];
209 match.object_index = reference.object_index;
210 match.object_id = reference.object_id;
211 match.shares_pixels |= pixels;
212 match.shares_palette |= palette;
213 match.references.push_back(reference);
215 std::vector<SourceObjectUse> out;
216 for (
auto& [index, match] : matches)
217 out.push_back(std::move(match));
224 if (!rom || !rom->
is_loaded() || !game_data || !room || room->
rom() != rom ||
226 return absl::FailedPreconditionError(
"Room usage session mismatch");
230 return absl::OkStatus();
235 const std::vector<GraphicsEditTarget>& targets) {
236 std::vector<LoadedRoomSourceUses> out;
238 for (
const auto& [
id, context] : rooms) {
242 context.room && context.room->id() ==
id
244 : absl::FailedPreconditionError(
"Room ID mismatch");
246 result.
error = std::string(status.message());
252 out.push_back(std::move(result));
262 if (!rom || !rom->
is_loaded() || !game_data) {
263 return absl::FailedPreconditionError(
"ROM not loaded");
266 return absl::InvalidArgumentError(
"Invalid room ID");
269 (live_room->
rom() != rom || live_room->
game_data() != game_data ||
271 return absl::FailedPreconditionError(
"Room usage session mismatch");
276 room_->game_data() != game_data) {
279 room_->SetGameData(game_data);
281 room_->SetGraphicsSheetOverrides(sheets);
282 rendered =
room_.get();
300 return absl::InternalError(
"Room composite is empty after render");
303 return absl::OkStatus();
318 std::set<size_t> attributed;
319 std::set<std::pair<size_t, uint16_t>> seen;
320 for (
int layer = 0; layer < 2; ++layer) {
321 const auto& words = layer == 0 ? maps.bg1 : maps.bg2;
322 const auto& indices = layer == 0 ? owners.bg1 : owners.bg2;
323 for (
size_t i = 0; i < indices.size(); ++i) {
324 const int index = indices[i];
325 if (index < 0 || index >=
static_cast<int>(objects.size()))
327 const uint16_t word = words[i];
328 const int tile = word & 0x3FF;
330 if (tile >= 512 || (tile >= 0x1B0 && tile < 0x1D0))
332 attributed.insert(index);
333 if (!seen.emplace(index, word).second)
335 const auto&
object = objects[index];
337 {
static_cast<size_t>(index),
static_cast<uint16_t
>(
object.id_), word,
338 blocks_[tile / kTilesPerSheet], tile / kTilesPerSheet});
341 const int row = (word >> 10) & 7;
342 for (
int raw = 1; raw < 8; ++raw) {
343 reference.palette_colors[raw] =
346 const auto [x, y, w, h] =
357 uint16_t sheet)
const {
358 std::vector<SDL_Rect> rects;
359 std::set<size_t> seen;
361 if (use.sheet == sheet && seen.insert(use.object_index).second)
368 size_t index)
const {
370 return found ==
object_rects_.end() ? std::vector<SDL_Rect>{}
371 : std::vector<SDL_Rect>{found->second};
389 int spriteset,
int palette_room,
390 std::string hack_name) {
391 if (rom ==
nullptr || !rom->
is_loaded() || game_data ==
nullptr) {
392 return absl::FailedPreconditionError(
"ROM not loaded");
395 spriteset >=
static_cast<int>(game_data->
spriteset_ids.size())) {
396 return absl::InvalidArgumentError(
397 absl::StrFormat(
"Invalid spriteset 0x%02X", spriteset));
406 if (palette_room >= 0) {
412 groups.global_sprites, groups.sprites_aux1, groups.sprites_aux2,
413 groups.sprites_aux3);
414 for (
size_t row = 0; row < palettes.size() && row < 8; ++row) {
415 const auto& palette = palettes.palette_ref(row);
416 for (
size_t i = 0; i < palette.size() && i < 16; ++i) {
417 const auto rgb = palette[i].rgb();
419 SDL_Color{
static_cast<uint8_t
>(rgb.x),
static_cast<uint8_t
>(rgb.y),
420 static_cast<uint8_t
>(rgb.z), 255};
424 return absl::OkStatus();
438 std::max<int>(0,
static_cast<int>(zsm.
animations.size()) - 1));
444 if (!
zsm_.has_value()) {
447 return static_cast<int>(
zsm_->editor.Frames.size());
456 if (!
zsm_.has_value() ||
zsm_->animations.empty()) {
469 uint16_t sheet)
const {
470 if (
const auto it = sheets.find(sheet); it != sheets.end()) {
475 const size_t offset =
static_cast<size_t>(sheet) *
kSheetBytes;
477 return {buffer.begin() + offset, buffer.begin() + offset +
kSheetBytes};
491 if (
zsm_.has_value()) {
494 std::vector<uint8_t> buffer(0x10000, 0);
495 for (
size_t slot = 0; slot <
slots_.size(); ++slot) {
498 constexpr int kCanvas = 128;
500 std::vector<uint8_t>(kCanvas * kCanvas, 0));
506 image.
width = kCanvas;
510 for (
auto& pixel : image.
pixels) {
512 pixel =
static_cast<uint8_t
>(pixel | 0x80);
521 std::vector<uint8_t> buffer(0x10000, 0);
522 for (
size_t slot = 0; slot <
slots_.size(); ++slot) {
531 if (pixels ==
nullptr || bounds.w <= 0 || bounds.h <= 0 ||
532 pixels->size() <
static_cast<size_t>(bounds.w * bounds.h)) {
535 image.
width = bounds.w;
537 image.
pixels.assign(pixels->begin(), pixels->begin() + bounds.w * bounds.h);
548 if (rom ==
nullptr || !rom->
is_loaded() || game_data ==
nullptr) {
549 return absl::FailedPreconditionError(
"ROM not loaded");
552 return absl::InvalidArgumentError(
553 absl::StrFormat(
"Invalid overworld area 0x%02X",
area));
558 return tiles16.status();
565 map_ = std::make_unique<zelda3::OverworldMap>(
area, rom, game_data);
566 map_->set_game_state(0);
567 map_->LoadAreaGraphics();
568 if (
auto status =
map_->LoadPalette(); !status.ok()) {
574 return absl::OkStatus();
578 int tile_index)
const {
579 std::vector<int> ids;
580 if (!
map_ || tile_index < 0 || tile_index >= kTilesPerSheet) {
585 for (
int slot = 0; slot < 8; ++slot) {
586 if (
map_->static_graphics(slot) == sheet) {
587 ids.push_back(slot * kTilesPerSheet + tile_index);
594 int tile_index)
const {
595 std::vector<int> result;
596 const auto ids =
TileIdsFor(sheet, tile_index);
600 for (
size_t i = 0; i <
tiles16_.size(); ++i) {
601 const auto& info =
tiles16_[i].tiles_info;
602 if (std::any_of(info.begin(), info.end(), [&](
const gfx::TileInfo& t) {
603 return std::find(ids.begin(), ids.end(), t.id_) != ids.end();
605 result.push_back(
static_cast<int>(i));
612 const std::vector<int>& tile16s,
613 uint16_t sheet,
int tile_index,
614 std::vector<SDL_Rect>* highlights) {
617 if (!
map_ || tile16s.empty()) {
620 map_->SetGraphicsSheetOverrides(sheets);
621 if (!
map_->BuildTileset().ok() ||
626 const auto& blockset =
map_->current_tile16_blockset();
627 const auto& palette =
map_->current_palette();
628 for (
size_t i = 0; i < palette.size() && i < 256; ++i) {
629 const auto rgb = palette[i].rgb();
631 SDL_Color{
static_cast<uint8_t
>(rgb.x),
static_cast<uint8_t
>(rgb.y),
632 static_cast<uint8_t
>(rgb.z), 255};
635 constexpr int kPerRow = 8;
636 const int rows = (
static_cast<int>(tile16s.size()) + kPerRow - 1) / kPerRow;
637 image.
width = kPerRow * 16;
640 const auto ids =
TileIdsFor(sheet, tile_index);
641 constexpr int kQuadrantX[] = {0, 8, 0, 8};
642 constexpr int kQuadrantY[] = {0, 0, 8, 8};
643 for (
size_t n = 0; n < tile16s.size(); ++n) {
644 const int tile16 = tile16s[n];
646 const int src_x = (tile16 % kPerRow) * 16;
647 const int src_y = (tile16 / kPerRow) * 16;
648 const int dst_x =
static_cast<int>(n % kPerRow) * 16;
649 const int dst_y =
static_cast<int>(n / kPerRow) * 16;
650 for (
int y = 0; y < 16; ++y) {
651 const size_t src =
static_cast<size_t>(src_y + y) * 128 + src_x;
652 if (src + 16 > blockset.size()) {
655 std::copy_n(blockset.begin() + src, 16,
656 image.
pixels.begin() + (dst_y + y) * image.
width + dst_x);
658 if (highlights !=
nullptr && tile16 >= 0 &&
659 tile16 <
static_cast<int>(
tiles16_.size())) {
660 for (
int q = 0; q < 4; ++q) {
661 const int id =
tiles16_[tile16].tiles_info[q].id_;
662 if (std::find(ids.begin(), ids.end(),
id) != ids.end()) {
663 highlights->push_back(
664 {dst_x + kQuadrantX[q], dst_y + kQuadrantY[q], 8, 8});
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Draws sprite OAM tiles to bitmaps for preview rendering.
void DrawFrame(gfx::Bitmap &bitmap, const zsprite::Frame &frame, int origin_x, int origin_y)
Draw all tiles in a ZSM frame.
size_t unknown_objects() const
std::map< size_t, SDL_Rect > object_rects_
std::vector< RoomTileReference > tile_references_
absl::Status TraceLoadedRoom(Rom *rom, zelda3::GameData *game_data, zelda3::Room *room)
std::array< uint8_t, 16 > blocks_
std::unique_ptr< zelda3::Room > room_
absl::Status Render(Rom *rom, zelda3::GameData *game_data, int room_id, const SheetPixels &sheets, bool objects_only, zelda3::Room *live_room=nullptr)
void CaptureUsage(const zelda3::Room &room)
std::array< uint8_t, 16 > blocks() const
Room graphics blocks (0-7 background, 8-15 sprites) of the loaded room.
std::vector< SDL_Rect > ObjectRectsUsingSheet(uint16_t sheet) const
const std::vector< RoomTileReference > & tile_references() const
void Reset()
Clears the loaded room so the next Render() reloads it from the ROM.
std::vector< SDL_Rect > ObjectRectsForObject(size_t index) const
std::array< SDL_Color, 256 > colors_
zelda3::GameData * game_data_
absl::Status Configure(Rom *rom, zelda3::GameData *game_data, int spriteset, int palette_room, std::string hack_name={})
std::optional< zsprite::ZSprite > zsm_
void SetVanillaSprite(uint8_t sprite_id)
Plays a vanilla sprite (single pose from the sprite catalog).
PreviewImage RenderFrame(const SheetPixels &sheets) const
Draws the current frame with sheets (edited pixels) over the ROM's.
bool UsesSheet(uint16_t sheet) const
std::vector< uint8_t > SheetOrRom(const SheetPixels &sheets, uint16_t sheet) const
std::array< uint8_t, 8 > slots_
void SetZsm(const zsprite::ZSprite &zsm, int animation)
Plays animation animation of a .zsm (the preview keeps a copy).
bool Advance(float seconds)
int frame_count() const
Frames in the current animation (1 for vanilla sprites).
PreviewImage Render(const SheetPixels &sheets, const std::vector< int > &tile16s, uint16_t sheet, int tile_index, std::vector< SDL_Rect > *highlights=nullptr)
std::vector< int > TileIdsFor(uint16_t sheet, int tile_index) const
std::vector< gfx::Tile16 > tiles16_
absl::Status Configure(Rom *rom, zelda3::GameData *game_data, int area)
Loads overworld area area (graphics and palette) and the tile16 table.
std::unique_ptr< zelda3::OverworldMap > map_
zelda3::GameData * game_data_
std::vector< int > Tile16sUsingTile(uint16_t sheet, int tile_index) const
Represents a bitmap image optimized for SNES ROM hacking.
const std::vector< uint8_t > & vector() const
SDL_Surface * surface() const
SNES 16-bit tile metadata container.
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
absl::Span< const uint8_t > Sheet(uint16_t sheet) const
The sheet's 4096 pixels, or an empty span when it is not held.
RoomLayerManager - Manages layer visibility and compositing.
void ApplyGameLayerRegisters(const RoomLayerRegisters ®isters)
void ApplyLayerMerging(const LayerMergeType &merge_type)
void SetLayerVisible(LayerType layer, bool visible)
void ApplyRoomEffect(EffectKey effect)
void RenderComposite(const RoomLayerManager &layer_mgr, gfx::Bitmap &output)
RoomLayerRegisters GameLayerRegisters(uint16_t room_flags=0) const
void LoadRoomGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
void PrepareForRender(std::optional< uint8_t > entrance_blockset=std::nullopt)
const std::vector< RoomObject > & GetTileObjects() const
const LayerMergeType & layer_merging() const
int BackgroundPaletteOffset(int slot) const
const PaletteAddressMap & rendered_palette_addresses() const
void SetSpriteset(uint8_t ss)
void SetBlockset(uint8_t bs)
static const SpriteOamLayout * GetPreviewOverride(uint8_t sprite_id, std::string_view hack_name)
A class for managing sprites in the overworld and underworld.
void RenderPreviewGraphics(std::span< const uint8_t > graphics, const SpriteOamLayout *layout_override=nullptr, std::span< const uint8_t > graphics_resource={})
auto preview_graphics() const
SDL_Rect preview_bounds() const
gfx::PaletteGroup DefaultSpritePreviewPalettes(const gfx::PaletteGroup &global, const gfx::PaletteGroup &aux1, const gfx::PaletteGroup &aux2, const gfx::PaletteGroup &aux3)
bool Advance(const zsprite::AnimationGroup &animation, size_t frame_count, float delta, int &frame, float &remainder)
constexpr int kTilesPerSheet
void CopySheet(const std::vector< uint8_t > &sheet, std::vector< uint8_t > &dst, size_t block)
SheetPixels SnapshotSessionSheets(const std::set< uint16_t > &ids, const zelda3::GameData *game_data)
std::vector< LoadedRoomSourceUses > FindLoadedRoomSourceUses(Rom *rom, zelda3::GameData *game_data, const LiveRoomContexts &rooms, const std::vector< GraphicsEditTarget > &targets)
std::string RoomContextLabel(const LiveRoomContext *live)
SheetPixels SnapshotArenaSheets(const std::set< uint16_t > &ids, const zelda3::GameData *game_data)
std::map< uint16_t, std::vector< uint8_t > > SheetPixels
Sheet id -> 4096 8bpp pixels (the GameData::graphics_buffer layout).
constexpr size_t kSheetBytes
std::map< int, LiveRoomContext > LiveRoomContexts
PreviewImage SnapshotBitmap(const gfx::Bitmap &bitmap)
Copies a palettized Bitmap (for example a room composite).
std::vector< int > RoomsUsingSheet(const std::map< int, std::array< uint8_t, 16 > > &room_blocks, uint16_t sheet, bool include_sprites)
std::map< int, std::array< uint8_t, 16 > > CollectRoomSheetBlocks(Rom *rom, zelda3::GameData *game_data, const std::map< int, zelda3::RoomGfxInfo > &rooms)
std::vector< SourceObjectUse > FindSourceObjectUses(const std::vector< RoomTileReference > &references, const std::vector< GraphicsEditTarget > &targets, const zelda3::GameData *owner)
RoomTilemaps ComposeYazeRoomTilemaps(const Room &room)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
absl::StatusOr< std::vector< gfx::Tile16 > > ReadMap16Tiles(const Rom &rom, bool *expanded)
Represents the full Overworld data, light and dark world.
Room LoadRoomFromRom(Rom *rom, int room_id)
constexpr int kNumOverworldAreas
constexpr bool IsUnderworldSpriteset(int spriteset)
Dungeon rooms use spritesets 0x40-0x8F (room header value + 0x40).
TileOwners ComputeObjectTileOwners(Rom *rom, int room_id, const std::vector< RoomObject > &objects, const RoomTilemaps &yaze, const std::vector< bool > &hidden)
constexpr int kNumberOfRooms
std::array< uint8_t, 8 > SpriteSheetSlots(const std::array< uint8_t, 4 > &spriteset_values, bool underworld)
The eight sheets for a spriteset's four values.
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
SDL2/SDL3 compatibility layer.
Which graphics sheets back sprite OAM tiles 0x000-0x1FF.
std::vector< SourceObjectUse > objects
std::array< SDL_Color, 256 > colors
std::vector< uint8_t > ToRgba() const
RGBA8 rows; transparent pixels get alpha 0.
int CountDifferentPixels(const PreviewImage &other) const
std::vector< uint8_t > pixels
std::vector< AnimationGroup > animations
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
gfx::PaletteGroupMap palette_groups
std::vector< uint8_t > & graphics_buffer
Legacy name for sheet_store.mutable_pixels().
GraphicsSheetStore sheet_store
Renders a graphics sheet the way the game uses it, from unsaved sheet pixels, without writing the ROM...