26fc5565 [nostalgia/gfx] Make dangling reference warning suppressions check for GCC 13 388541ce [nostalgia/player] Cleanup 6c194667 [nostalgia] Fix NostalgiaGfx lib name, stub out sound package 62d0579f [ox/fs] Restructure stat error handling to make easier to debug 202595b2 [keel] Fix loading assets by path cb21ff3f Merge commit 'a6b9657268eb3fe139b0c22df27c2cb2efc0013c' 8459d3ba Merge commit 'c42adc290cd8a27d01bb6d9877032dd2c963a4b7' 8d04af69 Merge commit 'ab760b064fd6a302bad13274e0e02b2b2c957b67' 6c34198f Merge commit '897a59cdad66e593fd45eece9414d8414fa7f1ae' f63c5816 [studio] Add filepickerpopup.hpp to studio.hpp git-subtree-dir: deps/nostalgia git-subtree-split: 26fc5565e86e09c6c51a615683fd9003816a24ac
429 lines
12 KiB
C++
429 lines
12 KiB
C++
/*
|
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
*/
|
|
|
|
#include <ox/std/size.hpp>
|
|
#include <ox/std/vector.hpp>
|
|
|
|
#include <nostalgia/gfx/ptidxconv.hpp>
|
|
#include <nostalgia/gfx/tilesheet.hpp>
|
|
|
|
namespace nostalgia::gfx {
|
|
|
|
std::size_t idx(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
|
|
return ptToIdx(pt, ss.columns);
|
|
}
|
|
|
|
[[nodiscard]]
|
|
static TileSheet::SubSheet const *getSubsheet(TileSheet::SubSheet const&ss, SubSheetId const id) noexcept {
|
|
if (ss.id == id) {
|
|
return &ss;
|
|
}
|
|
for (auto const&child: ss.subsheets) {
|
|
if (auto out = getSubsheet(child, id)) {
|
|
return out;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]]
|
|
static size_t getTileCnt(TileSheet::SubSheet const&ss) noexcept {
|
|
if (ss.subsheets.empty()) {
|
|
return ss.pixels.size() / PixelsPerTile;
|
|
} else {
|
|
size_t out{};
|
|
for (auto const&child: ss.subsheets) {
|
|
out += getTileCnt(child);
|
|
}
|
|
return out;
|
|
}
|
|
}
|
|
|
|
size_t getTileCnt(TileSheet const&ts) noexcept {
|
|
return getTileCnt(ts.subsheet);
|
|
}
|
|
|
|
TileSheet::SubSheet const *getSubsheet(TileSheet const&ts, SubSheetId const id) noexcept {
|
|
return getSubsheet(ts.subsheet, id);
|
|
}
|
|
|
|
static ox::Optional<size_t> getPixelIdx(
|
|
TileSheet::SubSheet const&ss,
|
|
SubSheetId const id,
|
|
size_t &idx) noexcept {
|
|
for (auto const&child: ss.subsheets) {
|
|
if (child.id == id) {
|
|
return ox::Optional<size_t>(ox::in_place, idx);
|
|
}
|
|
if (auto out = getPixelIdx(child, id, idx)) {
|
|
return out;
|
|
}
|
|
idx += pixelCnt(child);
|
|
}
|
|
return ox::Optional<size_t>{};
|
|
}
|
|
|
|
ox::Optional<size_t> getTileIdx(TileSheet const&ts, SubSheetId const id) noexcept {
|
|
size_t idx{};
|
|
auto const out = getPixelIdx(ts.subsheet, id, idx);
|
|
return out ? ox::Optional<size_t>{ox::in_place, *out / PixelsPerTile} : out;
|
|
}
|
|
|
|
uint8_t getPixel(TileSheet::SubSheet const&ss, std::size_t const idx) noexcept {
|
|
return ss.pixels[idx];
|
|
}
|
|
|
|
uint8_t getPixel(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
|
|
auto const idx = ptToIdx(pt, ss.columns);
|
|
return getPixel(ss, idx);
|
|
}
|
|
|
|
static void setPixel(
|
|
ox::Vector<uint8_t> &pixels,
|
|
int const columns,
|
|
ox::Point const&pt,
|
|
uint8_t const palIdx) noexcept {
|
|
const auto idx = ptToIdx(pt, columns);
|
|
pixels[idx] = palIdx;
|
|
}
|
|
|
|
void setPixel(TileSheet::SubSheet &ss, ox::Point const&pt, uint8_t const palIdx) noexcept {
|
|
const auto idx = ptToIdx(pt, ss.columns);
|
|
ss.pixels[idx] = palIdx;
|
|
}
|
|
|
|
static ox::Error setPixelCount(ox::Vector<uint8_t> &pixels, std::size_t const cnt) noexcept {
|
|
pixels.reserve(cnt);
|
|
pixels.resize(cnt);
|
|
return {};
|
|
}
|
|
|
|
ox::Error setPixelCount(TileSheet::SubSheet &ss, std::size_t const cnt) noexcept {
|
|
return setPixelCount(ss.pixels, cnt);
|
|
}
|
|
|
|
void flipX(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept {
|
|
auto const w = (b.x - a.x) / 2 + 1;
|
|
auto const pixCols = ss.columns * TileWidth;
|
|
for (int32_t yi = a.y; yi <= b.y; ++yi) {
|
|
for (int32_t xi = 0; xi < w; ++xi) {
|
|
auto const aIdx = ptToIdx(a.x + xi, yi, pixCols);
|
|
auto const bIdx = ptToIdx(b.x - xi, yi, pixCols);
|
|
std::swap(ss.pixels[aIdx], ss.pixels[bIdx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void flipY(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept {
|
|
auto const h = (b.y - a.y) / 2 + 1;
|
|
auto const pixCols = ss.columns * TileWidth;
|
|
for (int32_t yi = 0; yi < h; ++yi) {
|
|
for (int32_t xi = a.x; xi <= b.x; ++xi) {
|
|
auto const aIdx = ptToIdx(xi, a.y + yi, pixCols);
|
|
auto const bIdx = ptToIdx(xi, b.y - yi, pixCols);
|
|
std::swap(ss.pixels[aIdx], ss.pixels[bIdx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned pixelCnt(TileSheet::SubSheet const&ss) noexcept {
|
|
return static_cast<unsigned>(ss.pixels.size());
|
|
}
|
|
|
|
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, ox::Size const&sz) noexcept {
|
|
ox::Vector<uint8_t> out;
|
|
OX_RETURN_ERROR(setPixelCount(out, static_cast<size_t>(sz.width * sz.height) * PixelsPerTile));
|
|
auto const w = ox::min<int32_t>(ss.columns, sz.width) * TileWidth;
|
|
auto const h = ox::min<int32_t>(ss.rows, sz.height) * TileHeight;
|
|
for (auto x = 0; x < w; ++x) {
|
|
for (auto y = 0; y < h; ++y) {
|
|
auto const palIdx = getPixel(ss, {x, y});
|
|
setPixel(out, sz.width, {x, y}, palIdx);
|
|
}
|
|
}
|
|
ss.columns = sz.width;
|
|
ss.rows = sz.height;
|
|
ss.pixels = std::move(out);
|
|
return {};
|
|
}
|
|
|
|
ox::Result<ox::StringView> getNameFor(TileSheet::SubSheet const&ss, SubSheetId const pId) noexcept {
|
|
if (ss.id == pId) {
|
|
return ox::StringView(ss.name);
|
|
}
|
|
for (const auto &sub : ss.subsheets) {
|
|
const auto [name, err] = getNameFor(sub, pId);
|
|
if (!err) {
|
|
return name;
|
|
}
|
|
}
|
|
return ox::Error(1, "SubSheet not found");
|
|
}
|
|
|
|
|
|
TileSheet::SubSheetIdx validateSubSheetIdx(
|
|
TileSheet::SubSheetIdx &&pIdx,
|
|
std::size_t const pIdxIt,
|
|
TileSheet::SubSheet const&pSubsheet) noexcept {
|
|
if (pIdxIt >= pIdx.size()) {
|
|
return std::move(pIdx);
|
|
}
|
|
auto ¤tIdx = pIdx[pIdxIt];
|
|
if (pSubsheet.subsheets.size() <= currentIdx) {
|
|
if (pSubsheet.subsheets.empty()) {
|
|
// currentIdx could not be repaired, remove
|
|
// this and all succeeding idxs and return
|
|
pIdx.resize(pIdxIt);
|
|
return std::move(pIdx);
|
|
} else {
|
|
currentIdx = static_cast<uint32_t>(pSubsheet.subsheets.size() - 1);
|
|
}
|
|
}
|
|
return validateSubSheetIdx(std::move(pIdx), pIdxIt + 1, pSubsheet.subsheets[currentIdx]);
|
|
}
|
|
|
|
TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubSheetIdx idx) noexcept {
|
|
return validateSubSheetIdx(std::move(idx), 0, ts.subsheet);
|
|
}
|
|
|
|
#if defined(__GNUC__) && __GNUC__ >= 13
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdangling-reference"
|
|
#endif
|
|
static TileSheet::SubSheet const&getSubSheet(
|
|
ox::SpanView<uint32_t> const &idx,
|
|
std::size_t const idxIt,
|
|
TileSheet::SubSheet const &pSubsheet) noexcept {
|
|
if (idxIt == idx.size()) {
|
|
return pSubsheet;
|
|
}
|
|
const auto currentIdx = idx[idxIt];
|
|
if (pSubsheet.subsheets.size() < currentIdx) {
|
|
return pSubsheet;
|
|
}
|
|
return getSubSheet(idx, idxIt + 1, pSubsheet.subsheets[currentIdx]);
|
|
}
|
|
#if defined(__GNUC__) && __GNUC__ >= 13
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
TileSheet::SubSheet &getSubSheet(
|
|
ox::SpanView<uint32_t> const &idx,
|
|
std::size_t const idxIt,
|
|
TileSheet::SubSheet &pSubsheet) noexcept {
|
|
if (idxIt == idx.size()) {
|
|
return pSubsheet;
|
|
}
|
|
return getSubSheet(idx, idxIt + 1, pSubsheet.subsheets[idx[idxIt]]);
|
|
}
|
|
|
|
#if defined(__GNUC__) && __GNUC__ >= 13
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdangling-reference"
|
|
#endif
|
|
TileSheet::SubSheet const&getSubSheet(TileSheet const &ts, ox::SpanView<uint32_t> const &idx) noexcept {
|
|
return gfx::getSubSheet(idx, 0, ts.subsheet);
|
|
}
|
|
|
|
TileSheet::SubSheet &getSubSheet(TileSheet &ts, ox::SpanView<uint32_t> const &idx) noexcept {
|
|
return gfx::getSubSheet(idx, 0, ts.subsheet);
|
|
}
|
|
#if defined(__GNUC__) && __GNUC__ >= 13
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
ox::Error addSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept {
|
|
auto &parent = getSubSheet(ts, idx);
|
|
if (parent.subsheets.size() < 2) {
|
|
parent.subsheets.emplace_back(++ts.idIt, ox::sfmt("Subsheet {}", parent.subsheets.size()), 1, 1);
|
|
} else {
|
|
parent.subsheets.emplace_back(++ts.idIt, "Subsheet 0", parent.columns, parent.rows);
|
|
parent.subsheets.emplace_back(++ts.idIt, "Subsheet 1", 1, 1);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ox::Error insertSubSheet(TileSheet &ts, ox::SpanView<uint32_t> const&idx, TileSheet::SubSheet ss) noexcept {
|
|
if (idx.empty()) {
|
|
return ox::Error{1, "invalid insert idx"};
|
|
}
|
|
auto &parent = getSubSheet(ts, {idx.data(), idx.size() - 1});
|
|
auto const insertIdx = idx[idx.size() - 1];
|
|
if (insertIdx > parent.subsheets.size()) {
|
|
return ox::Error{1, "invalid insert idx"};
|
|
}
|
|
parent.subsheets.emplace(insertIdx, std::move(ss));
|
|
return {};
|
|
}
|
|
|
|
ox::Error rmSubSheet(
|
|
TileSheet &ts,
|
|
TileSheet::SubSheetIdx const&idx,
|
|
std::size_t const idxIt,
|
|
TileSheet::SubSheet &pSubsheet) noexcept {
|
|
if (idxIt == idx.size() - 1) {
|
|
return pSubsheet.subsheets.erase(idx[idxIt]).error;
|
|
}
|
|
return rmSubSheet(ts, idx, idxIt + 1, pSubsheet.subsheets[idx[idxIt]]);
|
|
}
|
|
|
|
ox::Error rmSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept {
|
|
return rmSubSheet(ts, idx, 0, ts.subsheet);
|
|
}
|
|
|
|
uint8_t getPixel(
|
|
TileSheet const&ts,
|
|
ox::Point const&pt,
|
|
TileSheet::SubSheetIdx const&subsheetIdx) noexcept {
|
|
auto &s = getSubSheet(ts, subsheetIdx);
|
|
auto const idx = ptToIdx(pt, s.columns);
|
|
return getPixel(s, idx);
|
|
}
|
|
|
|
uint8_t getPixel4Bpp(
|
|
CompactTileSheet const&ts,
|
|
size_t const idx) noexcept {
|
|
oxAssert(ts.bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
|
|
if (idx & 1) {
|
|
return ts.pixels[idx / 2] >> 4;
|
|
} else {
|
|
return ts.pixels[idx / 2] & 0b0000'1111;
|
|
}
|
|
}
|
|
|
|
uint8_t getPixel8Bpp(
|
|
CompactTileSheet const&ts,
|
|
size_t const idx) noexcept {
|
|
oxAssert(ts.bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
|
|
return ts.pixels[idx];
|
|
}
|
|
|
|
ox::Pair<uint8_t> get2Pixels4Bpp(
|
|
CompactTileSheet const&ts,
|
|
size_t const idx) noexcept {
|
|
oxAssert(ts.bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
|
|
auto const out = ts.pixels[idx / 2];
|
|
return {
|
|
static_cast<uint8_t>(out & 0x0f),
|
|
static_cast<uint8_t>(out >> 4),
|
|
};
|
|
}
|
|
|
|
ox::Pair<uint8_t> get2Pixels8Bpp(
|
|
CompactTileSheet const&ts,
|
|
size_t const idx) noexcept {
|
|
oxAssert(ts.bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
|
|
return {
|
|
static_cast<uint8_t>(ts.pixels[idx]),
|
|
static_cast<uint8_t>(ts.pixels[idx + 1]),
|
|
};
|
|
}
|
|
|
|
static ox::Result<SubSheetId> getIdFor(
|
|
TileSheet::SubSheet const&ss,
|
|
ox::SpanView<ox::StringView> const&pNamePath,
|
|
std::size_t const pIt = 0) noexcept {
|
|
for (auto &sub : ss.subsheets) {
|
|
if (sub.name == pNamePath[pIt]) {
|
|
if (pIt == pNamePath.size()) {
|
|
return ss.id;
|
|
}
|
|
return getIdFor(ss, pNamePath, pIt + 1);
|
|
}
|
|
}
|
|
return ox::Error(1, "SubSheet not found");
|
|
}
|
|
|
|
ox::Result<SubSheetId> getIdFor(TileSheet const&ts, ox::StringViewCR path) noexcept {
|
|
return getIdFor(ts.subsheet, ox::split<8>(path, '.'));
|
|
}
|
|
|
|
/**
|
|
* Gets the offset in tiles of the desired subsheet.
|
|
*/
|
|
static ox::Result<uint32_t> getTileOffset(
|
|
TileSheet::SubSheet const&ss,
|
|
ox::SpanView<ox::StringView> const&pNamePath,
|
|
std::size_t const pIt = 0,
|
|
uint32_t pCurrentTotal = 0) noexcept {
|
|
// pIt == pNamePath.size() - 1 &&
|
|
if (ss.name != pNamePath[pIt]) {
|
|
return ox::Error(2, "Wrong branch");
|
|
}
|
|
if (pIt == pNamePath.size() - 1) {
|
|
return pCurrentTotal;
|
|
}
|
|
for (auto &sub : ss.subsheets) {
|
|
auto [offset, err] = getTileOffset(
|
|
sub, pNamePath, pIt + 1, pCurrentTotal);
|
|
if (!err) {
|
|
return offset;
|
|
}
|
|
// Possible bug? Should this be using a recursive version of
|
|
// pixelCnt will count pixels in subsheets of sub as well.
|
|
pCurrentTotal += pixelCnt(sub) / PixelsPerTile;
|
|
}
|
|
return ox::Error(1, "SubSheet not found");
|
|
}
|
|
|
|
ox::Result<uint32_t> getTileOffset(TileSheet const&ts, ox::StringViewCR pNamePath) noexcept {
|
|
return gfx::getTileOffset(ts.subsheet, ox::split<8>(pNamePath, '.'));
|
|
}
|
|
|
|
ox::Result<ox::StringView> getNameFor(TileSheet &ts, SubSheetId const pId) noexcept {
|
|
return gfx::getNameFor(ts.subsheet, pId);
|
|
}
|
|
|
|
ox::Result<ox::StringView> getNameFor(TileSheet const&ts, SubSheetId const pId) noexcept {
|
|
return gfx::getNameFor(ts.subsheet, pId);
|
|
}
|
|
|
|
static void readPixelsTo(TileSheet::SubSheet &ss, int const pBpp, ox::Vector<uint8_t> &pPixels) noexcept {
|
|
if (!ss.subsheets.empty()) {
|
|
for (auto &s: ss.subsheets) {
|
|
readPixelsTo(s, pBpp, pPixels);
|
|
}
|
|
} else {
|
|
if (pBpp == 4) {
|
|
for (size_t i{}; i < ss.pixels.size() - 1; i += 2) {
|
|
auto const p1 = ss.pixels[i];
|
|
auto const p2 = ss.pixels[i + 1];
|
|
pPixels.emplace_back(static_cast<uint8_t>(p1 | (p2 << 4)));
|
|
}
|
|
} else {
|
|
for (auto const p : ss.pixels) {
|
|
pPixels.emplace_back(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ox::Vector<uint8_t> pixels(TileSheet &ts) noexcept {
|
|
ox::Vector<uint8_t> out;
|
|
readPixelsTo(ts.subsheet, ts.bpp, out);
|
|
return out;
|
|
}
|
|
|
|
|
|
ox::Vector<uint32_t> resizeTileSheetData(
|
|
ox::Vector<uint32_t> const&srcPixels,
|
|
ox::Size const&srcSize,
|
|
int const scale) noexcept {
|
|
ox::Vector<uint32_t> dst;
|
|
auto dstWidth = srcSize.width * scale;
|
|
auto dstHeight = srcSize.height * scale;
|
|
const auto pixelCnt = dstWidth * dstHeight;
|
|
dst.resize(static_cast<std::size_t>(pixelCnt));
|
|
for (auto i = 0; i < pixelCnt; ++i) {
|
|
const auto dstPt = idxToPt(i, 1, scale);
|
|
const auto srcPt = dstPt / ox::Point{scale, scale};
|
|
const auto srcIdx = ptToIdx(srcPt, 1);
|
|
const auto srcPixel = srcPixels[srcIdx];
|
|
dst[static_cast<std::size_t>(i)] = srcPixel;
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
}
|