7#include "absl/status/status.h"
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_join.h"
20constexpr int kSheetWidth = 128;
21constexpr int kSheetHeight = 32;
29 return {color.r, color.g, color.b};
34 return absl::InvalidArgumentError(
35 absl::StrFormat(
"Room 0x%X is out of range", room));
38 return absl::FailedPreconditionError(
"Dungeon palettes are not loaded");
49 for (
int i = 0; i < 8; ++i) {
50 const auto level =
static_cast<uint8_t
>(i * 255 / 7);
51 palette[i] = {level, level, level};
59 if (row < 0 || row > 7) {
60 return absl::InvalidArgumentError(
"Background palette row must be 0-7");
66 if (row >=
static_cast<int>(group.size()) ||
67 group.palette_ref(row).size() < 8) {
68 return absl::FailedPreconditionError(absl::StrFormat(
69 "Room 0x%X has no background palette row %d", room, row));
72 for (
int i = 0; i < 8; ++i) {
73 const auto rgb = group.palette_ref(row)[i].rgb();
74 palette[i] = {
static_cast<uint8_t
>(rgb.x),
static_cast<uint8_t
>(rgb.y),
75 static_cast<uint8_t
>(rgb.z)};
98 if (row < 0 || row > 7) {
99 return absl::InvalidArgumentError(
"Sprite palette row must be 0-7");
104 for (
int i = 0; i < 8; ++i) {
105 palette[i] = ToSheetColor(cgram[(8 + row) * 16 + (high_half ? 8 : 0) + i]);
111 const std::vector<uint8_t>& snes_3bpp,
const SheetPalette& palette,
112 int first_block,
int block_count) {
114 return absl::InvalidArgumentError(
"A 3bpp sheet is 0x600 bytes");
116 if (first_block < 0 || block_count <= 0 ||
118 return absl::InvalidArgumentError(
119 absl::StrFormat(
"Blocks %d-%d are outside the sheet's 16 blocks",
120 first_block, first_block + block_count - 1));
123 const int columns = std::min(block_count, 8);
124 const int rows = (block_count + 7) / 8;
125 const int width = columns * kBlockSize;
126 const int height = rows * kBlockSize;
127 std::vector<uint8_t> indices(
static_cast<size_t>(width) * height, 0);
128 for (
int k = 0; k < block_count; ++k) {
129 const auto [sx, sy] = BlockOrigin(first_block + k);
130 const int dx = (k % 8) * kBlockSize;
131 const int dy = (k / 8) * kBlockSize;
132 for (
int y = 0; y < kBlockSize; ++y) {
133 for (
int x = 0; x < kBlockSize; ++x) {
134 indices[(dy + y) * width + dx + x] =
135 sheet[(sy + y) * kSheetWidth + sx + x] & 0x07;
139 std::vector<std::array<uint8_t, 4>> png_palette;
140 for (
int i = 0; i < 8; ++i) {
141 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
142 static_cast<uint8_t
>(i == 0 ? 0 : 255)});
148 const std::vector<uint8_t>& before_3bpp,
149 const std::vector<uint8_t>& after_3bpp,
const SheetPalette& palette) {
152 return absl::InvalidArgumentError(
"A 3bpp sheet is 0x600 bytes");
154 constexpr int kPanel = kSheetWidth * kSheetHeight;
155 constexpr uint8_t kHighlight = 8;
158 std::vector<uint8_t> indices(
static_cast<size_t>(kPanel) * 3, 0);
159 for (
int i = 0; i < kPanel; ++i) {
160 indices[i] = before[i] & 0x07;
161 indices[kPanel + i] = after[i] & 0x07;
162 if (indices[i] != indices[kPanel + i])
163 indices[2 * kPanel + i] = kHighlight;
165 std::vector<std::array<uint8_t, 4>> png_palette;
166 for (
int i = 0; i < 8; ++i) {
167 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
168 static_cast<uint8_t
>(i == 0 ? 0 : 255)});
170 png_palette.push_back({255, 0, 255, 255});
179 return absl::InvalidArgumentError(
"A 3bpp sheet is 0x600 bytes");
181 if (png.
width % kBlockSize != 0 || png.
height % kBlockSize != 0) {
182 return absl::InvalidArgumentError(absl::StrFormat(
183 "PNG is %dx%d; width and height must be multiples of 16", png.
width,
186 const int columns = png.
width / kBlockSize;
187 const int block_count = columns * (png.
height / kBlockSize);
189 return absl::InvalidArgumentError(absl::StrFormat(
190 "The PNG holds %d 16x16 blocks; starting at block %d it runs past "
192 block_count, first_block));
196 std::vector<uint8_t> values(
static_cast<size_t>(png.
width) * png.
height);
197 std::vector<std::string> offenders;
199 for (
int y = 0; y < png.
height; ++y) {
200 for (
int x = 0; x < png.
width; ++x) {
201 const size_t i =
static_cast<size_t>(y) * png.
width + x;
206 seen = absl::StrFormat(
"index %d", value);
211 const uint8_t* p = &png.
rgba[i * 4];
212 seen = absl::StrFormat(
"#%02X%02X%02X", p[0], p[1], p[2]);
215 }
else if (p[3] == 255) {
216 for (
int c = 0; c < 8; ++c) {
217 if (palette[c][0] == p[0] && palette[c][1] == p[1] &&
218 palette[c][2] == p[2]) {
227 if (offenders.size() < 8) {
228 offenders.push_back(absl::StrFormat(
"(%d,%d) %s", x, y, seen));
232 values[i] =
static_cast<uint8_t
>(value);
235 if (bad_pixels > 0) {
236 return absl::InvalidArgumentError(absl::StrFormat(
237 "%d pixel(s) are not 3bpp indices 0-7 of the sheet palette; nothing "
238 "was imported. First: %s",
239 bad_pixels, absl::StrJoin(offenders,
", ")));
243 std::vector<uint8_t> sheet = old_sheet;
244 for (
int k = 0; k < block_count; ++k) {
245 const auto [dx, dy] = BlockOrigin(first_block + k);
246 const int sx = (k % columns) * kBlockSize;
247 const int sy = (k / columns) * kBlockSize;
248 for (
int y = 0; y < kBlockSize; ++y) {
249 for (
int x = 0; x < kBlockSize; ++x) {
250 sheet[(dy + y) * kSheetWidth + dx + x] =
251 values[(sy + y) * png.
width + sx + x];
260 const auto [bx, by] = BlockOrigin(block);
261 bool changed =
false;
262 for (
int y = 0; y < kBlockSize && !changed; ++y) {
263 for (
int x = 0; x < kBlockSize; ++x) {
264 const size_t i = (by + y) * kSheetWidth + bx + x;
265 if ((sheet[i] & 7) != (old_sheet[i] & 7)) {
275 for (
int tile = 0; tile < 64; ++tile) {
276 if (!std::equal(result.
snes_3bpp.begin() + tile * 24,
277 result.
snes_3bpp.begin() + tile * 24 + 24,
278 snes_3bpp.begin() + tile * 24)) {
282 for (
size_t i = 0; i < snes_3bpp.size();) {
283 if (result.
snes_3bpp[i] == snes_3bpp[i]) {
288 while (end < snes_3bpp.size() && result.
snes_3bpp[end] != snes_3bpp[end]) {
292 static_cast<int>(end));
299 uint8_t header_blockset) {
305 for (
int i = 0; i < 8; ++i) {
309 if (room_sheet != 0) {
310 set.
sheets[i] = room_sheet;
319 Rom& rom,
GameData& data,
int room,
int slot,
int row) {
320 if (slot < 0 || slot >= 8 || row < 0 || row >= 8) {
321 return absl::InvalidArgumentError(
"Background slot and row must be 0-7");
339 if (row >=
static_cast<int>(group.size()) ||
340 group.palette_ref(row).size() < 16) {
341 return absl::FailedPreconditionError(
342 "Room background palette is incomplete");
344 for (
int index = 0; index < 8; ++index) {
345 const int column = index == 0 ? 0 : index + result.
palette_offset;
347 const auto rgb = group.palette_ref(row)[column].rgb();
348 result.
colors[index] = {
static_cast<uint8_t
>(rgb.x),
349 static_cast<uint8_t
>(rgb.y),
350 static_cast<uint8_t
>(rgb.z)};
357 std::vector<RoomBackgroundSet> sets;
366 constexpr int kHeight = kSheetHeight * 8;
367 std::vector<uint8_t> indices(
static_cast<size_t>(kSheetWidth) * kHeight, 0);
368 for (
int slot = 0; slot < 8; ++slot) {
369 const uint16_t sheet = set.
sheets[slot];
376 for (
int y = 0; y < kSheetHeight; ++y) {
377 for (
int x = 0; x < kSheetWidth; ++x) {
378 indices[(slot * kSheetHeight + y) * kSheetWidth + x] =
379 pixels[y * kSheetWidth + x] & 0x07;
383 std::vector<std::array<uint8_t, 4>> png_palette;
384 for (
int i = 0; i < 8; ++i) {
385 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
386 static_cast<uint8_t
>(i == 0 ? 0 : 255)});
394 if (png.
width != kSheetWidth || png.
height != kSheetHeight * 8) {
395 return absl::InvalidArgumentError(absl::StrFormat(
396 "A room background PNG is 128x256 (8 stacked sheets); got %dx%d",
399 std::vector<RoomSheetImport> imports;
400 std::map<uint16_t, std::pair<int, std::vector<uint8_t>>> seen;
401 for (
int slot = 0; slot < 8; ++slot) {
402 const uint16_t sheet = set.
sheets[slot];
405 band.
width = kSheetWidth;
406 band.
height = kSheetHeight;
409 const size_t begin =
static_cast<size_t>(slot) * kSheetHeight * kSheetWidth;
410 const size_t count =
static_cast<size_t>(kSheetHeight) * kSheetWidth;
413 png.
indices.begin() + begin + count);
415 band.
rgba.assign(png.
rgba.begin() + begin * 4,
416 png.
rgba.begin() + (begin + count) * 4);
419 const bool writable =
428 return absl::Status(result.status().code(),
429 absl::StrFormat(
"Slot %d (sheet 0x%02X): %s", slot,
430 sheet, result.status().message()));
433 if (!result->changed_blocks.empty()) {
434 return absl::FailedPreconditionError(absl::StrFormat(
435 "Slot %d shows 2bpp sheet 0x%02X, which cannot be edited here",
442 auto first = seen.find(sheet);
443 if (first != seen.end()) {
444 if (first->second.second != result->snes_3bpp) {
445 return absl::InvalidArgumentError(absl::StrFormat(
446 "Sheet 0x%02X appears in slots %d and %d with different pixels",
447 sheet, first->second.first, slot));
450 seen.emplace(sheet, std::make_pair(slot, result->snes_3bpp));
452 if (result->changed_blocks.empty()) {
455 auto existing = std::find_if(
456 imports.begin(), imports.end(),
457 [sheet](
const RoomSheetImport& other) { return other.sheet == sheet; });
458 if (existing != imports.end()) {
459 existing->slots.push_back(slot);
462 imports.push_back({sheet, {slot}, std::move(*result)});
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
int ResolveDungeonPaletteId() const
void SetGameData(GameData *data)
Which graphics sheets exist, which tables use them, and which 16x16 blocks are free.
3bpp graphics sheets to and from indexed PNGs.
Locates graphics sheets in the ROM and writes edited sheets back.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
std::vector< uint8_t > IndexedToSnesSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
absl::StatusOr< std::vector< uint8_t > > EncodeIndexedPng(int width, int height, const std::vector< uint8_t > &indices, const std::vector< std::array< uint8_t, 4 > > &palette)
std::pair< int, int > BlockOrigin(int block)
SheetColor ToSheetColor(const SDL_Color &color)
absl::StatusOr< Room > LoadRoomWithPalettes(Rom &rom, GameData &data, int room)
Zelda 3 specific classes and functions.
std::vector< RoomBackgroundSet > ResolveAllRoomBackgroundSets(const Rom &rom, const GameData &data)
All 296 rooms' background sets (header bytes read from the ROM).
absl::StatusOr< SheetPalette > RoomSpriteSheetPalette(Rom &rom, GameData &data, int room, int row, bool high_half)
absl::StatusOr< RoomSheetPaletteContext > ResolveRoomSheetPalette(Rom &rom, GameData &data, int room, int slot, int row)
absl::StatusOr< std::vector< uint8_t > > ExportRoomBackgroundPng(const Rom &rom, const RoomBackgroundSet &set, const SheetPalette &palette)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
absl::StatusOr< SheetImportResult > ImportSheetBlocksPng(const std::vector< uint8_t > &snes_3bpp, const util::PngImage &png, int first_block, const SheetPalette &palette)
Places a PNG's 16x16 blocks over a sheet from first_block.
constexpr size_t kGfxSheet3bppBytes
RoomBackgroundSet ResolveRoomBackgroundSet(const GameData &data, int room, uint8_t header_blockset)
bool IsHighHalfSpriteSheet(int sheet)
std::map< int, RoomGfxInfo > CollectRoomGfx(const Rom &rom)
gfx::PaletteGroup BuildDungeonRenderPaletteGroupFromGameData(const gfx::SnesPalette &dungeon_palette, const GameData *game_data)
absl::StatusOr< std::vector< uint8_t > > ReadGfxSheetData(const Rom &rom, uint16_t sheet_id, const GfxSheetPointerTables &tables)
Returns the sheet's decoded bytes (SNES planar data).
constexpr uint16_t kSpriteSheetBase
std::array< uint8_t, 3 > SheetColor
int DungeonBackgroundPaletteOffset(uint8_t main_blockset, int destination_slot)
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
constexpr size_t kGfxSheetCount
constexpr int kGfxSheetBlockCount
16x16 blocks per sheet: 8 columns x 2 rows over the 128x32 sheet.
RoomMainBlocksetResolution ResolveRoomMainBlockset(int room_id, uint8_t header_blockset, uint8_t requested_main_blockset, const GameData &game_data)
std::array< SheetColor, 8 > SheetPalette
Colors for 3bpp indices 0-7. Index 0 is exported as transparent.
absl::StatusOr< std::vector< uint8_t > > ExportSheetBlocksPng(const std::vector< uint8_t > &snes_3bpp, const SheetPalette &palette, int first_block, int block_count)
SheetPalette GrayscaleSheetPalette()
absl::StatusOr< SheetPalette > RoomBackgroundSheetPalette(Rom &rom, GameData &data, int room, int row)
constexpr int kNumberOfRooms
absl::StatusOr< std::vector< uint8_t > > ExportSheetDiffPng(const std::vector< uint8_t > &before_3bpp, const std::vector< uint8_t > &after_3bpp, const SheetPalette &palette)
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
absl::StatusOr< std::vector< RoomSheetImport > > ImportRoomBackgroundPng(const Rom &rom, const RoomBackgroundSet &set, const util::PngImage &png, const SheetPalette &palette)
PaletteGroup dungeon_main
const SnesPalette & palette_ref(int i) const
A decoded PNG: palette indices for indexed files, RGBA otherwise.
std::vector< std::array< uint8_t, 4 > > palette
std::vector< uint8_t > indices
std::vector< uint8_t > rgba
std::vector< RoomDefaultEntrance > room_default_entrances
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
gfx::PaletteGroupMap palette_groups
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
std::array< uint8_t, 8 > sheets
std::array< bool, 8 > from_room_blockset
RoomMainBlocksetSource source
std::array< uint8_t, 8 > cgram_indices
RoomDefaultEntrance entrance
RoomMainBlocksetResolution main
RoomBackgroundSet background
std::vector< std::pair< int, int > > changed_ranges
std::vector< uint8_t > snes_3bpp
std::vector< int > changed_tiles
std::vector< int > changed_blocks