diff --git a/src/nostalgia/core/gfx.hpp b/src/nostalgia/core/gfx.hpp index b0e3a7a2..f13df49c 100644 --- a/src/nostalgia/core/gfx.hpp +++ b/src/nostalgia/core/gfx.hpp @@ -32,13 +32,13 @@ enum class TileSheetSpace { struct NostalgiaPalette { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette"; static constexpr auto TypeVersion = 1; - ox::Vector colors; + ox::Vector colors = {}; }; struct Palette { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette"; static constexpr auto TypeVersion = 1; - ox::Vector colors; + ox::Vector colors = {}; }; // Predecessor to TileSheet, kept for backward compatibility @@ -51,7 +51,7 @@ struct NostalgiaGraphic { int columns = 1; ox::FileAddress defaultPalette; Palette pal; - ox::Vector pixels; + ox::Vector pixels = {}; }; struct TileSheet { @@ -64,7 +64,7 @@ struct TileSheet { int columns = 1; int rows = 1; ox::Vector subsheets; - ox::Vector pixels; + ox::Vector pixels = {}; constexpr SubSheet() noexcept = default; constexpr SubSheet(const SubSheet &other) noexcept { @@ -109,10 +109,34 @@ struct TileSheet { return *this; } + [[nodiscard]] auto idx(const geo::Point &pt) const noexcept { return ptToIdx(pt, columns); } + /** + * Reads all pixels of this sheet or its children into the given pixel list + * @param pixels + */ + void readPixelsTo(ox::Vector *pPixels, int8_t bpp) const noexcept { + if (subsheets.size()) { + for (auto &s: subsheets) { + s.readPixelsTo(pPixels); + } + } else { + if (bpp == 4) { + for (auto p: this->pixels) { + pPixels->emplace_back(p & 0b1111); + pPixels->emplace_back(p >> 4); + } + } else { + for (auto p: this->pixels) { + pPixels->emplace_back(p); + } + } + } + } + /** * Reads all pixels of this sheet or its children into the given pixel list * @param pixels @@ -158,8 +182,8 @@ struct TileSheet { } [[nodiscard]] - constexpr auto getPixel(int8_t bpp, std::size_t idx) const noexcept { - if (bpp == 4) { + constexpr auto getPixel(int8_t pBpp, std::size_t idx) const noexcept { + if (pBpp == 4) { return getPixel4Bpp(idx); } else { return getPixel8Bpp(idx); @@ -179,13 +203,13 @@ struct TileSheet { } [[nodiscard]] - constexpr auto getPixel(int8_t bpp, const geo::Point &pt) const noexcept { + constexpr auto getPixel(int8_t pBpp, const geo::Point &pt) const noexcept { const auto idx = ptToIdx(pt, columns); - return getPixel(bpp, idx); + return getPixel(pBpp, idx); } - constexpr auto walkPixels(int8_t bpp, auto callback) const noexcept { - if (bpp == 4) { + constexpr auto walkPixels(int8_t pBpp, auto callback) const noexcept { + if (pBpp == 4) { for (std::size_t i = 0; i < pixels.size(); ++i) { const auto colorIdx1 = pixels[i] & 0xF; const auto colorIdx2 = pixels[i] >> 4; @@ -200,9 +224,9 @@ struct TileSheet { } } - constexpr void setPixel(int8_t bpp, uint64_t idx, uint8_t palIdx) noexcept { + constexpr void setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept { auto &pixel = this->pixels[idx / 2]; - if (bpp == 4) { + if (pBpp == 4) { if (idx & 1) { pixel = (pixel & 0b0000'1111) | (palIdx << 4); } else { @@ -213,13 +237,13 @@ struct TileSheet { } } - constexpr void setPixel(int8_t bpp, const geo::Point &pt, uint8_t palIdx) noexcept { + constexpr void setPixel(int8_t pBpp, const geo::Point &pt, uint8_t palIdx) noexcept { const auto idx = ptToIdx(pt, columns); - setPixel(bpp, idx, palIdx); + setPixel(pBpp, idx, palIdx); } - constexpr auto setPixelCount(int8_t bpp, std::size_t cnt) noexcept { - switch (bpp) { + constexpr auto setPixelCount(int8_t pBpp, std::size_t cnt) noexcept { + switch (pBpp) { case 4: pixels.resize(cnt / 2); return OxError(0); @@ -227,17 +251,17 @@ struct TileSheet { pixels.resize(cnt); return OxError(0); default: - return OxError(1, "Invalid bpp used for TileSheet::SubSheet::setPixelCount"); + return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount"); } } /** * Gets a count of the pixels in this sheet, and not that of its children. - * @param bpp bits per pixel, need for knowing how to count the pixels + * @param pBpp bits per pixel, need for knowing how to count the pixels * @return a count of the pixels in this sheet */ - constexpr auto pixelCnt(int8_t bpp) const noexcept { - return bpp == 4 ? pixels.size() * 2 : pixels.size(); + constexpr auto pixelCnt(int8_t pBpp) const noexcept { + return pBpp == 4 ? pixels.size() * 2 : pixels.size(); } }; @@ -386,7 +410,7 @@ struct CompactTileSheet { static constexpr auto TypeVersion = 1; int8_t bpp = 0; ox::FileAddress defaultPalette; - ox::Vector pixels; + ox::Vector pixels = {}; }; oxModelBegin(NostalgiaPalette) diff --git a/src/nostalgia/core/studio/CMakeLists.txt b/src/nostalgia/core/studio/CMakeLists.txt index 5c1157f5..b4783ea0 100644 --- a/src/nostalgia/core/studio/CMakeLists.txt +++ b/src/nostalgia/core/studio/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries( NostalgiaStudio NostalgiaCore NostalgiaGlUtils + lodepng ) #target_compile_definitions(NostalgiaCore-Studio PRIVATE QT_QML_DEBUG) diff --git a/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp b/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp index 17a96819..9af5b022 100644 --- a/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp @@ -3,6 +3,7 @@ */ #include +#include #include #include @@ -11,10 +12,29 @@ namespace nostalgia::core { +static ox::Error toPngFile(const ox::String &path, const TileSheet::SubSheet &s, const Palette &pal, int8_t bpp) noexcept { + ox::Vector pixels; + s.readPixelsTo(&pixels, bpp); + const unsigned rows = s.rows == -1 ? pixels.size() / PixelsPerTile : static_cast(s.rows); + const unsigned cols = s.columns == -1 ? 1 : static_cast(s.columns); + const auto width = cols * TileWidth; + const auto height = rows * TileHeight; + ox::Vector outData(pixels.size() * 3); + for (auto idx = 0; const auto colorIdx : pixels) { + const auto pt = idxToPt(idx, static_cast(cols)); + const auto i = static_cast(pt.y * static_cast(width) + pt.x) * 3; + const auto c = pal.colors[colorIdx]; + outData[i + 0] = (red32(c)); + outData[i + 1] = (green32(c)); + outData[i + 2] = (blue32(c)); + ++idx; + } + return OxError(lodepng_encode24_file(path.c_str(), outData.data(), width, height)); +} TileSheetEditorImGui::TileSheetEditorImGui(Context *ctx, const ox::String &path): m_tileSheetEditor(ctx, path) { m_ctx = ctx; m_itemPath = path; - const auto lastSlash = std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset(); + const auto lastSlash = ox::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset(); m_itemName = m_itemPath.substr(lastSlash + 1); // init palette idx const auto &palPath = model()->palPath(); @@ -41,6 +61,7 @@ const ox::String &TileSheetEditorImGui::itemDisplayName() const noexcept { } void TileSheetEditorImGui::exportFile() { + exportSubhseetToPng(); } void TileSheetEditorImGui::cut() { @@ -125,9 +146,13 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept { } } ImGui::SameLine(); - if (ImGui::Button("Edit", ImVec2(36, btnHeight))) { + if (ImGui::Button("Edit", ImVec2(51, btnHeight))) { showSubsheetEditor(); } + ImGui::SameLine(); + if (ImGui::Button("Export", ImVec2(51, btnHeight))) { + exportSubhseetToPng(); + } TileSheet::SubSheetIdx path; static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; if (ImGui::BeginTable("Subsheets", 3, flags)) { @@ -214,6 +239,21 @@ void TileSheetEditorImGui::showSubsheetEditor() noexcept { } } +void TileSheetEditorImGui::exportSubhseetToPng() noexcept { + auto [path, err] = studio::saveFile({{"PNG", "png"}}); + if (err) { + return; + } + // subsheet to png + const auto &img = model()->img(); + const auto &s = *model()->activeSubSheet(); + const auto &pal = model()->pal(); + err = toPngFile(path, s, pal, img.bpp); + if (err) { + oxErrorf("Tilesheet export failed: {}", toStr(err)); + } +} + void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { const auto winPos = ImGui::GetWindowPos(); const auto fbSizei = geo::Size(static_cast(fbSize.x), static_cast(fbSize.y)); @@ -230,10 +270,10 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { m_tileSheetEditor.draw(); glBindFramebuffer(GL_FRAMEBUFFER, 0); ImGui::Image( - reinterpret_cast(m_framebuffer.color.id), - static_cast(fbSize), - ImVec2(0, 1), - ImVec2(1, 0)); + reinterpret_cast(m_framebuffer.color.id), + static_cast(fbSize), + ImVec2(0, 1), + ImVec2(1, 0)); // handle input, this must come after drawing const auto &io = ImGui::GetIO(); const auto mousePos = geo::Vec2(io.MousePos); @@ -242,7 +282,7 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { const auto wheelh = io.MouseWheelH; if (wheel != 0) { const auto zoomMod = ox::defines::OS == ox::OS::Darwin ? - io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl); + io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl); m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod); } if (wheelh != 0) { diff --git a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp index 8a2378b5..9043320e 100644 --- a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp +++ b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp @@ -43,7 +43,7 @@ class TileSheetEditorImGui: public studio::BaseEditor { }; std::size_t m_selectedPaletteIdx = 0; Context *m_ctx = nullptr; - ox::Vector m_paletteList; + ox::Vector m_paletteList{}; SubSheetEditor m_subsheetEditor; ox::String m_itemPath; ox::String m_itemName; @@ -87,6 +87,8 @@ class TileSheetEditorImGui: public studio::BaseEditor { private: void showSubsheetEditor() noexcept; + void exportSubhseetToPng() noexcept; + [[nodiscard]] constexpr auto model() const noexcept { return m_tileSheetEditor.model(); diff --git a/src/nostalgia/core/studio/tilesheeteditormodel.cpp b/src/nostalgia/core/studio/tilesheeteditormodel.cpp index 8cc172ee..7d73b706 100644 --- a/src/nostalgia/core/studio/tilesheeteditormodel.cpp +++ b/src/nostalgia/core/studio/tilesheeteditormodel.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -119,7 +120,7 @@ class DrawCommand: public TileSheetCommand { auto &subsheet = m_img->getSubSheet(m_subSheetIdx); if (m_changes.back().value.idx != idx && subsheet.getPixel(m_img->bpp, idx) != m_palIdx) { // duplicate entries are bad - auto existing = std::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) { + auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) { return c.idx == idx; }); if (existing == m_changes.cend()) { @@ -501,7 +502,7 @@ class PaletteChangeCommand: public TileSheetCommand { ox::FileAddress m_newPalette; public: - constexpr PaletteChangeCommand(const TileSheet::SubSheetIdx &idx, TileSheet *img, const ox::String &newPalette) noexcept { + PaletteChangeCommand(const TileSheet::SubSheetIdx &idx, TileSheet *img, const ox::String &newPalette) noexcept { m_idx = idx; m_img = img; m_oldPalette = m_img->defaultPalette; diff --git a/src/nostalgia/core/studio/tilesheeteditormodel.hpp b/src/nostalgia/core/studio/tilesheeteditormodel.hpp index 7e34576d..2c37e911 100644 --- a/src/nostalgia/core/studio/tilesheeteditormodel.hpp +++ b/src/nostalgia/core/studio/tilesheeteditormodel.hpp @@ -73,16 +73,19 @@ class TileSheetEditorModel: public ox::SignalHandler { void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept; + [[nodiscard]] constexpr const TileSheet::SubSheet *activeSubSheet() const noexcept { auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx); return &activeSubSheet; } + [[nodiscard]] constexpr TileSheet::SubSheet *activeSubSheet() noexcept { auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx); return &activeSubSheet; } + [[nodiscard]] constexpr const TileSheet::SubSheetIdx &activeSubSheetIdx() const noexcept { return m_activeSubsSheetIdx; } @@ -106,6 +109,7 @@ class TileSheetEditorModel: public ox::SignalHandler { ox::Error saveFile() noexcept; + [[nodiscard]] constexpr studio::UndoStack *undoStack() noexcept; bool pixelSelected(std::size_t idx) const noexcept;