[nostalgia/core/studio] Add scale option to TileSheet export
This commit is contained in:
parent
9b11fa4e91
commit
56f59b29fe
@ -87,6 +87,11 @@ constexpr Color32 color32(uint8_t r, uint8_t g, uint8_t b) noexcept {
|
||||
return static_cast<Color32>(r | (g << 8) | (b << 16));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color32 color32(Color16 c) noexcept {
|
||||
return color32(red32(c), green32(c), blue32(c));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color32 color32(float r, float g, float b) noexcept {
|
||||
return static_cast<Color32>(static_cast<uint8_t>(r * 255) | (static_cast<uint8_t>(g * 255) << 8) | (static_cast<uint8_t>(b * 255) << 16));
|
||||
|
@ -12,33 +12,64 @@
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
template<bool alpha = false>
|
||||
ox::Error toPngFile(
|
||||
ox::CStringView const&path, TileSheet::SubSheet const&s, Palette const&pal, int8_t bpp) noexcept {
|
||||
ox::Vector<uint8_t> pixels;
|
||||
s.readPixelsTo(&pixels, bpp);
|
||||
const unsigned rows = s.rows == -1 ?
|
||||
static_cast<unsigned>(pixels.size()) / PixelsPerTile : static_cast<unsigned>(s.rows);
|
||||
const unsigned cols = s.columns == -1 ? 1 : static_cast<unsigned>(s.columns);
|
||||
const auto width = cols * TileWidth;
|
||||
const auto height = rows * TileHeight;
|
||||
constexpr auto bytesPerPixel = alpha ? 4 : 3;
|
||||
ox::Vector<unsigned char> outData(pixels.size() * bytesPerPixel);
|
||||
for (auto idx = 0; const auto colorIdx : pixels) {
|
||||
const auto pt = idxToPt(idx, static_cast<int>(cols));
|
||||
const auto i = static_cast<unsigned>(pt.y * static_cast<int>(width) + pt.x) * bytesPerPixel;
|
||||
const auto c = pal.colors[colorIdx];
|
||||
outData[i + 0] = red32(c);
|
||||
outData[i + 1] = green32(c);
|
||||
outData[i + 2] = blue32(c);
|
||||
if constexpr(alpha) {
|
||||
outData[i + 3] = colorIdx ? 255 : 0;
|
||||
static ox::Vector<uint32_t> normalizePixelSizes(
|
||||
ox::Vector<uint8_t> const&inPixels,
|
||||
int const bpp) noexcept {
|
||||
uint_t const bytesPerTile = bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
|
||||
ox::Vector<uint32_t> outPixels;
|
||||
if (bytesPerTile == 64) { // 8 BPP
|
||||
outPixels.resize(inPixels.size());
|
||||
for (std::size_t i = 0; i < inPixels.size(); ++i) {
|
||||
outPixels[i] = inPixels[i];
|
||||
}
|
||||
} else { // 4 BPP
|
||||
outPixels.resize(inPixels.size() * 2);
|
||||
for (std::size_t i = 0; i < inPixels.size(); ++i) {
|
||||
outPixels[i * 2 + 0] = inPixels[i] & 0xF;
|
||||
outPixels[i * 2 + 1] = inPixels[i] >> 4;
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
constexpr auto fmt = alpha ? LCT_RGBA : LCT_RGB;
|
||||
return outPixels;
|
||||
}
|
||||
|
||||
static ox::Vector<uint32_t> normalizePixelArrangement(
|
||||
ox::Vector<uint32_t> const&inPixels,
|
||||
int cols,
|
||||
int scale) {
|
||||
auto const scalePt = ox::Point{scale, scale};
|
||||
auto const width = cols * TileWidth;
|
||||
auto const height = static_cast<int>(inPixels.size()) / width;
|
||||
auto const dstWidth = width * scale;
|
||||
ox::Vector<uint32_t> outPixels(static_cast<size_t>((width * scale) * (height * scale)));
|
||||
for (std::size_t dstIdx = 0; dstIdx < outPixels.size(); ++dstIdx) {
|
||||
auto const dstPt = ox::Point{
|
||||
static_cast<int>(dstIdx) % dstWidth,
|
||||
static_cast<int>(dstIdx) / dstWidth};
|
||||
auto const srcPt = dstPt / scalePt;
|
||||
auto const srcIdx = ptToIdx(srcPt, cols);
|
||||
outPixels[dstIdx] = inPixels[srcIdx];
|
||||
}
|
||||
return outPixels;
|
||||
}
|
||||
|
||||
static ox::Error toPngFile(
|
||||
ox::CStringView const&path,
|
||||
ox::Vector<uint32_t> &&pixels,
|
||||
Palette const&pal,
|
||||
unsigned width,
|
||||
unsigned height) noexcept {
|
||||
for (auto &c : pixels) {
|
||||
c = color32(pal.color(c)) | static_cast<Color32>(0XFF << 24);
|
||||
}
|
||||
constexpr auto fmt = LCT_RGBA;
|
||||
return OxError(static_cast<ox::ErrorCode>(
|
||||
lodepng_encode_file(path.c_str(), outData.data(), width, height, fmt, 8)));
|
||||
lodepng_encode_file(
|
||||
path.c_str(),
|
||||
reinterpret_cast<uint8_t const*>(pixels.data()),
|
||||
width,
|
||||
height,
|
||||
fmt,
|
||||
8)));
|
||||
}
|
||||
|
||||
TileSheetEditorImGui::TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringView path):
|
||||
@ -50,11 +81,12 @@ TileSheetEditorImGui::TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringVi
|
||||
// connect signal/slots
|
||||
undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges);
|
||||
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
|
||||
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
|
||||
m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::exportFile() {
|
||||
exportSubhseetToPng();
|
||||
m_exportMenu.show();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::cut() {
|
||||
@ -75,6 +107,7 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
||||
}
|
||||
if (key == turbine::Key::Escape) {
|
||||
m_subsheetEditor.close();
|
||||
m_exportMenu.close();
|
||||
}
|
||||
auto pal = m_model.pal();
|
||||
if (pal) {
|
||||
@ -168,7 +201,7 @@ void TileSheetEditorImGui::draw(turbine::Context&) noexcept {
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Export", ImVec2(51, btnHeight))) {
|
||||
exportSubhseetToPng();
|
||||
m_exportMenu.show();
|
||||
}
|
||||
TileSheet::SubSheetIdx path;
|
||||
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
||||
@ -185,6 +218,7 @@ void TileSheetEditorImGui::draw(turbine::Context&) noexcept {
|
||||
}
|
||||
ImGui::EndChild();
|
||||
m_subsheetEditor.draw();
|
||||
m_exportMenu.draw();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) {
|
||||
@ -244,26 +278,33 @@ ox::Error TileSheetEditorImGui::saveItem() noexcept {
|
||||
|
||||
void TileSheetEditorImGui::showSubsheetEditor() noexcept {
|
||||
const auto sheet = m_model.activeSubSheet();
|
||||
if (sheet->subsheets.size()) {
|
||||
if (!sheet->subsheets.empty()) {
|
||||
m_subsheetEditor.show(sheet->name, -1, -1);
|
||||
} else {
|
||||
m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows);
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::exportSubhseetToPng() noexcept {
|
||||
auto [path, err] = studio::saveFile({{"PNG", "png"}});
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int scale) noexcept {
|
||||
oxRequire(path, studio::saveFile({{"PNG", "png"}}));
|
||||
// subsheet to png
|
||||
const auto &img = m_model.img();
|
||||
const auto &s = *m_model.activeSubSheet();
|
||||
const auto &pal = m_model.pal();
|
||||
err = toPngFile(path, s, *pal, img.bpp);
|
||||
auto const&img = m_model.img();
|
||||
auto const&s = *m_model.activeSubSheet();
|
||||
auto const&pal = m_model.pal();
|
||||
auto const width = s.columns * TileWidth;
|
||||
auto const height = s.rows * TileHeight;
|
||||
auto pixels = normalizePixelSizes(s.pixels, img.bpp);
|
||||
pixels = normalizePixelArrangement(pixels, s.columns, scale);
|
||||
auto const err = toPngFile(
|
||||
path,
|
||||
std::move(pixels),
|
||||
*pal,
|
||||
static_cast<unsigned>(width * scale),
|
||||
static_cast<unsigned>(height * scale));
|
||||
if (err) {
|
||||
oxErrorf("Tilesheet export failed: {}", toStr(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
||||
@ -420,13 +461,13 @@ void TileSheetEditorImGui::SubSheetEditor::draw() noexcept {
|
||||
ImGui::InputInt("Columns", &m_cols);
|
||||
ImGui::InputInt("Rows", &m_rows);
|
||||
}
|
||||
if (ImGui::Button("OK")) {
|
||||
if (ImGui::Button("OK", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
inputSubmitted.emit(m_name, m_cols, m_rows);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
if (ImGui::Button("Cancel", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
}
|
||||
@ -438,4 +479,34 @@ void TileSheetEditorImGui::SubSheetEditor::close() noexcept {
|
||||
m_show = false;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::ExportMenu::draw() noexcept {
|
||||
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
constexpr auto popupName = "Export Tile Sheet";
|
||||
if (!m_show) {
|
||||
return;
|
||||
}
|
||||
ImGui::OpenPopup(popupName);
|
||||
constexpr auto popupHeight = 80.f;
|
||||
ImGui::SetNextWindowSize(ImVec2(235, popupHeight));
|
||||
if (ImGui::BeginPopupModal(popupName, &m_show, modalFlags)) {
|
||||
ImGui::InputInt("Scale", &m_scale);
|
||||
m_scale = ox::clamp(m_scale, 1, 20);
|
||||
if (ImGui::Button("OK", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
inputSubmitted.emit(m_scale);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::ExportMenu::close() noexcept {
|
||||
m_show = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,10 +27,10 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
|
||||
private:
|
||||
class SubSheetEditor {
|
||||
ox::BString<100> m_name;
|
||||
int m_cols = 0;
|
||||
int m_rows = 0;
|
||||
bool m_show = false;
|
||||
ox::BString<100> m_name;
|
||||
int m_cols = 0;
|
||||
int m_rows = 0;
|
||||
bool m_show = false;
|
||||
public:
|
||||
ox::Signal<ox::Error(ox::StringView const&name, int cols, int rows)> inputSubmitted;
|
||||
constexpr void show(ox::StringView const&name, int cols, int rows) noexcept {
|
||||
@ -42,10 +42,23 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
void draw() noexcept;
|
||||
void close() noexcept;
|
||||
};
|
||||
class ExportMenu {
|
||||
int m_scale = 0;
|
||||
bool m_show = false;
|
||||
public:
|
||||
ox::Signal<ox::Error(int scale)> inputSubmitted;
|
||||
constexpr void show() noexcept {
|
||||
m_show = true;
|
||||
m_scale = 5;
|
||||
}
|
||||
void draw() noexcept;
|
||||
void close() noexcept;
|
||||
};
|
||||
std::size_t m_selectedPaletteIdx = 0;
|
||||
turbine::Context &m_ctx;
|
||||
ox::Vector<ox::String> m_paletteList;
|
||||
SubSheetEditor m_subsheetEditor;
|
||||
ExportMenu m_exportMenu;
|
||||
glutils::FrameBuffer m_framebuffer;
|
||||
TileSheetEditorView m_view;
|
||||
TileSheetEditorModel &m_model;
|
||||
@ -81,7 +94,7 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
private:
|
||||
void showSubsheetEditor() noexcept;
|
||||
|
||||
void exportSubhseetToPng() noexcept;
|
||||
ox::Error exportSubhseetToPng(int scale) noexcept;
|
||||
|
||||
void drawTileSheet(ox::Vec2 const&fbSize) noexcept;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
add_executable(nostalgia-studio WIN32 MACOSX_BUNDLE)
|
||||
add_executable(NostalgiaStudio WIN32 MACOSX_BUNDLE)
|
||||
|
||||
target_link_libraries(
|
||||
nostalgia-studio
|
||||
NostalgiaStudio
|
||||
NostalgiaProfile
|
||||
NostalgiaStudioModules
|
||||
NostalgiaKeelModules
|
||||
@ -11,7 +11,7 @@ target_link_libraries(
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
nostalgia-studio
|
||||
NostalgiaStudio
|
||||
RUNTIME DESTINATION
|
||||
${NOSTALGIA_DIST_BIN}
|
||||
BUNDLE DESTINATION .
|
||||
@ -19,11 +19,11 @@ install(
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT WIN32)
|
||||
# enable LTO
|
||||
set_property(TARGET nostalgia-studio PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
set_property(TARGET NostalgiaStudio PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(nostalgia-studio PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||
set_target_properties(NostalgiaStudio PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||
endif()
|
||||
|
||||
install(
|
||||
|
@ -3,17 +3,23 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Nostalgia Studio</string>
|
||||
<string>NostalgiaStudio</string>
|
||||
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Nostalgia Studio</string>
|
||||
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<string>icons/ns_logo128.png</string>
|
||||
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>net.drinkingtea.nostalgia.studio</string>
|
||||
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.0</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user