fix(gfx): convert indexed sheets to SNES planar

This commit is contained in:
scawful
2025-12-22 14:29:26 -05:00
parent ea4e1873de
commit ffc3ddd854
4 changed files with 189 additions and 67 deletions

View File

@@ -2,6 +2,7 @@
#include "graphics_editor.h"
// C++ standard library headers
#include <algorithm>
#include <filesystem>
// Third-party library headers
@@ -229,22 +230,6 @@ absl::Status GraphicsEditor::Save() {
compressed = false;
}
// Convert 8BPP bitmap data to SNES indexed format
auto indexed_data = gfx::Bpp8SnesToIndexed(sheet.vector(), bpp);
std::vector<uint8_t> final_data;
if (compressed) {
// Compress using Hyrule Magic LC-LZ2
int compressed_size = 0;
auto compressed_data = gfx::HyruleMagicCompress(
indexed_data.data(), static_cast<int>(indexed_data.size()),
&compressed_size, 1);
final_data.assign(compressed_data.begin(),
compressed_data.begin() + compressed_size);
} else {
final_data = std::move(indexed_data);
}
// Calculate ROM offset for this sheet
// Get version constants from game_data
auto version_constants = zelda3::kVersionConstantsMap.at(game_data()->version);
@@ -254,6 +239,47 @@ absl::Status GraphicsEditor::Save() {
version_constants.kOverworldGfxPtr2,
version_constants.kOverworldGfxPtr3, rom_->size());
// Convert 8BPP bitmap data to SNES planar format
auto snes_tile_data = gfx::IndexedToSnesSheet(sheet.vector(), bpp);
constexpr size_t kDecompressedSheetSize = 0x800;
std::vector<uint8_t> base_data;
if (compressed) {
auto decomp_result = gfx::lc_lz2::DecompressV2(
rom_->data(), offset, static_cast<int>(kDecompressedSheetSize), 1,
rom_->size());
if (!decomp_result.ok()) {
return decomp_result.status();
}
base_data = std::move(*decomp_result);
} else {
auto read_result =
rom_->ReadByteVector(offset, kDecompressedSheetSize);
if (!read_result.ok()) {
return read_result.status();
}
base_data = std::move(*read_result);
}
if (base_data.size() < snes_tile_data.size()) {
base_data.resize(snes_tile_data.size(), 0);
}
std::copy(snes_tile_data.begin(), snes_tile_data.end(),
base_data.begin());
std::vector<uint8_t> final_data;
if (compressed) {
// Compress using Hyrule Magic LC-LZ2
int compressed_size = 0;
auto compressed_data = gfx::HyruleMagicCompress(
base_data.data(), static_cast<int>(base_data.size()),
&compressed_size, 1);
final_data.assign(compressed_data.begin(),
compressed_data.begin() + compressed_size);
} else {
final_data = std::move(base_data);
}
// Write data to ROM buffer
for (size_t i = 0; i < final_data.size(); i++) {
rom_->WriteByte(offset + i, final_data[i]);

View File

@@ -1,9 +1,10 @@
#include "snes_tile.h"
#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <vector>
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace yaze {
namespace gfx {
@@ -128,8 +129,8 @@ std::vector<uint8_t> ConvertBpp(std::span<uint8_t> tiles, uint32_t from_bpp,
return converted;
}
std::vector<uint8_t> SnesTo8bppSheet(std::span<uint8_t> sheet, int bpp,
int num_sheets) {
std::vector<uint8_t> SnesTo8bppSheet(std::span<uint8_t> sheet, int bpp,
int num_sheets) {
int xx = 0; // positions where we are at on the sheet
int yy = 0;
int pos = 0;
@@ -196,11 +197,67 @@ std::vector<uint8_t> SnesTo8bppSheet(std::span<uint8_t> sheet, int bpp,
ypos = 0;
}
}
return sheet_buffer_out;
}
std::vector<uint8_t> Bpp8SnesToIndexed(std::vector<uint8_t> data,
uint64_t bpp) {
return sheet_buffer_out;
}
std::vector<uint8_t> IndexedToSnesSheet(std::span<const uint8_t> sheet, int bpp,
int num_sheets) {
if (sheet.empty()) {
return {};
}
const int tiles_per_row = kTilesheetWidth / 8;
const int default_tile_rows = (bpp == 2) ? 8 : 4;
const int computed_tile_rows =
static_cast<int>(sheet.size()) / (kTilesheetWidth * 8);
const int tile_rows = (computed_tile_rows > 0)
? std::max(default_tile_rows, computed_tile_rows)
: default_tile_rows;
const int tiles_per_sheet = tiles_per_row * tile_rows;
const int total_tiles = tiles_per_sheet * num_sheets;
const int bytes_per_tile = bpp * 8;
const uint8_t max_color =
static_cast<uint8_t>((1u << static_cast<uint8_t>(bpp)) - 1u);
std::vector<uint8_t> output(total_tiles * bytes_per_tile, 0);
int xx = 0;
int yy = 0;
int pos = 0;
int ypos = 0;
for (int i = 0; i < total_tiles; i++) {
snes_tile8 tile = {};
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
const int index =
(x + xx) + (y * kTilesheetWidth) + (yy * kTilesheetWidth * 8);
if (index >= 0 && index < static_cast<int>(sheet.size())) {
tile.data[y * 8 + x] = sheet[index] & max_color;
}
}
}
auto packed_tile = PackBppTile(tile, bpp);
std::copy(packed_tile.begin(), packed_tile.end(),
output.begin() + (pos * bytes_per_tile));
pos++;
ypos++;
xx += 8;
if (ypos >= tiles_per_row) {
yy++;
xx = 0;
ypos = 0;
}
}
return output;
}
std::vector<uint8_t> Bpp8SnesToIndexed(std::vector<uint8_t> data,
uint64_t bpp) {
// 3BPP
// [r0,bp1],[r0,bp2],[r1,bp1],[r1,bp2],[r2,bp1],[r2,bp2],[r3,bp1],[r3,bp2]
// [r4,bp1],[r4,bp2],[r5,bp1],[r5,bp2],[r6,bp1],[r6,bp2],[r7,bp1],[r7,bp2]

View File

@@ -20,10 +20,12 @@ constexpr int kTilesheetDepth = 8;
constexpr uint8_t kGraphicsBitmap[8] = {0x80, 0x40, 0x20, 0x10,
0x08, 0x04, 0x02, 0x01};
std::vector<uint8_t> SnesTo8bppSheet(std::span<uint8_t> sheet, int bpp,
int num_sheets = 1);
std::vector<uint8_t> Bpp8SnesToIndexed(std::vector<uint8_t> data,
uint64_t bpp = 0);
std::vector<uint8_t> SnesTo8bppSheet(std::span<uint8_t> sheet, int bpp,
int num_sheets = 1);
std::vector<uint8_t> IndexedToSnesSheet(std::span<const uint8_t> sheet,
int bpp, int num_sheets = 1);
std::vector<uint8_t> Bpp8SnesToIndexed(std::vector<uint8_t> data,
uint64_t bpp = 0);
snes_tile8 UnpackBppTile(std::span<uint8_t> data, const uint32_t offset,
const uint32_t bpp);