From 3e0174c1427a0f6802112d2b8caaef02a5de6aa2 Mon Sep 17 00:00:00 2001 From: Christophe Meneboeuf Date: Sat, 13 Feb 2021 17:59:57 +0100 Subject: [PATCH] Live Preview --- Rgb2Hires_PC/CMakeLists.txt | 2 +- Rgb2Hires_PC/src/App_Picture.cpp | 7 +- Rgb2Hires_PC/src/Display.cpp | 195 ++++++++++++++++++++----------- Rgb2Hires_PC/src/Display.h | 40 ++++++- 4 files changed, 173 insertions(+), 71 deletions(-) diff --git a/Rgb2Hires_PC/CMakeLists.txt b/Rgb2Hires_PC/CMakeLists.txt index 9c8744d..f67693b 100644 --- a/Rgb2Hires_PC/CMakeLists.txt +++ b/Rgb2Hires_PC/CMakeLists.txt @@ -12,7 +12,7 @@ endif(WIN32) # The version number. set (Rgb2Hires_VERSION_MAJOR 1) set (Rgb2Hires_VERSION_MINOR 0) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) # required to use set(CMAKE_VERBOSE_MAKEFILE ON) # dependencies diff --git a/Rgb2Hires_PC/src/App_Picture.cpp b/Rgb2Hires_PC/src/App_Picture.cpp index 4837d06..86a2e16 100644 --- a/Rgb2Hires_PC/src/App_Picture.cpp +++ b/Rgb2Hires_PC/src/App_Picture.cpp @@ -51,9 +51,11 @@ int main( int argc, char *argv[] ) TCLAP::ValueArg imagePath("i", "image", "Source image path", true, "", "path_to_image"); TCLAP::ValueArg outputPath("o", "output", "Output path", true, "", "path_to_output"); TCLAP::SwitchArg assembly("a", "asm", "Output asm format"); + TCLAP::SwitchArg preview("p", "preview", "Open a window to display a preview."); cmd.add(imagePath); cmd.add(outputPath); cmd.add(assembly); + cmd.add(preview); cmd.parse(argc, argv); if (imagePath.getValue().size() == 0 || outputPath.getValue().size() == 0) { @@ -61,7 +63,8 @@ int main( int argc, char *argv[] ) return -1; } - try { + try + { const auto filepath = imagePath.getValue(); if (!exists(filepath)) { throw runtime_error("Cannot read " + filepath); @@ -80,7 +83,7 @@ int main( int argc, char *argv[] ) } const auto bytes = imageHiRes.getBlob(); - Display::Display(bytes->data()); + Display::Window::GetInstance()->display(filepath, bytes->data()); } diff --git a/Rgb2Hires_PC/src/Display.cpp b/Rgb2Hires_PC/src/Display.cpp index be2a179..9914171 100644 --- a/Rgb2Hires_PC/src/Display.cpp +++ b/Rgb2Hires_PC/src/Display.cpp @@ -1,6 +1,9 @@ +#include +#include #include #include +#include "Picture.h" #include "Display.h" @@ -11,6 +14,123 @@ namespace RgbToHires namespace Display { + //! @brief Output the colors from a 14-dot block + void UpdateHiResRGBCell(const int x, const uint8_t* pLineAddr, rgba8Bits_t* pOut); + std::unique_ptr ComputeRgbBuffer(const uint8_t* hires); + + + Window* Window::S_pInstance = nullptr; + + Window::~Window() + { + if (_pTexture != nullptr) { + SDL_DestroyTexture(_pTexture); + } + if (_pRenderer != nullptr) { + SDL_DestroyRenderer(_pRenderer); + } + if (_pWindow != nullptr) { + SDL_DestroyWindow(_pWindow); + } + SDL_Quit(); + } + + Window* Window::GetInstance() + { + if (S_pInstance == nullptr) { + S_pInstance = new Window; + S_pInstance->init(); + } + return S_pInstance; + } + + + bool Window::init() + { + // init + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + sdlError("cannot initialise the preview window."); + } + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); // nearest neighbor + + // window + _pWindow = SDL_CreateWindow("Preview", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 560 * SCALE, + 384 * SCALE, + SDL_WINDOW_SHOWN + ); + if (_pWindow == nullptr) { + sdlError("cannot initialise the preview window."); + } + // renderer + _pRenderer = SDL_CreateRenderer(_pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (_pRenderer == nullptr) { + sdlError("cannot initialise the preview window."); + } + // texture + _pTexture = SDL_CreateTexture(_pRenderer, SDL_PIXELFORMAT_BGR888, SDL_TEXTUREACCESS_STATIC, 560, 384); + if (_pTexture == nullptr) { + sdlError("cannot initialise the preview window."); + } + + return true; + } + + void Window::sdlError(const std::string& msg) + { + throw std::runtime_error{ "Error: " + msg }; + } + + void Window::display(const std::string& path, const uint8_t* hiresblob) + { + bool isImgModified = false; + auto timeModified = std::filesystem::last_write_time(path); + + { + auto pViewport = ComputeRgbBuffer(hiresblob); + SDL_UpdateTexture(_pTexture, nullptr, pViewport->data(), sizeof(rgba8Bits_t) * 560); + SDL_RenderClear(_pRenderer); + SDL_RenderCopy(_pRenderer, _pTexture, NULL, NULL); + SDL_RenderPresent(_pRenderer); + } + + // event loop + SDL_Event e; + while (true) + { + + if (isImgModified) + { + // new modification time + timeModified = std::filesystem::last_write_time(path); + + // update hires image + const auto imageRgb = Magick::Image{ path }; + auto imageQuantized = ImageQuantized{ imageRgb }; + const auto imageHiRes = Picture{ imageQuantized }; + + // rgb conversion from hires data + auto pViewport = ComputeRgbBuffer(imageHiRes.getBlob()->data()); + + // update the display with rgb data + SDL_UpdateTexture(_pTexture, nullptr, pViewport->data(), sizeof(rgba8Bits_t) * 560); + SDL_RenderClear(_pRenderer); + SDL_RenderCopy(_pRenderer, _pTexture, NULL, NULL); + SDL_RenderPresent(_pRenderer); + } + + if (SDL_WaitEventTimeout(&e, 250)) + { + if (e.type == SDL_QUIT) { break; } + } + + isImgModified = (timeModified != std::filesystem::last_write_time(path)); + + } + + } + void SdlError(const std::string& error, SDL_Window* const pWindow = nullptr, SDL_Renderer* const pRenderer = nullptr @@ -29,14 +149,6 @@ namespace RgbToHires exit(1); } - struct rgba8Bits_t - { - uint8_t r = 0; - uint8_t g = 0; - uint8_t b = 0; - uint8_t a = 0xff; - }; - constexpr std::array Palette = { rgba8Bits_t{0x00,0x00,0x00, 0xFF}, // black @@ -51,8 +163,7 @@ namespace RgbToHires //=========================================================================== // RGB videocards HGR - - //! @brief Output the colors from a 14-dot block + //! @details Adapted from AppleWin: https://github.com/AppleWin/AppleWin //! A 14-dot block is used to draw a 7-pixel block (2 * 3.5-pixel blocks) //! Each dot of a 3.5-pixel block can be delayed so we draw on 2 subdots. @@ -60,7 +171,7 @@ namespace RgbToHires //! @param x Vertical position of the 14-dot block //! @param pLineAddr pointer to the start of the line //! @param pOut pointer to the 28-subdot block to draw - void UpdateHiResRGBCell(const int x, uint8_t* pLineAddr, rgba8Bits_t* pOut) + void UpdateHiResRGBCell(const int x, const uint8_t* pLineAddr, rgba8Bits_t* pOut) { const int xpixel = x * 14; int xoffset = x & 1; // offset to start of the 2 bytes @@ -133,13 +244,10 @@ namespace RgbToHires } } - void Display(uint8_t* blob) + + std::unique_ptr ComputeRgbBuffer(const uint8_t* hires) { - - using Block = std::array; - using Line = std::array; - using Screen = std::array; - + // Getting a RGB framebuffer // Converting line per line, pixel-block per pixel-block auto pViewport = make_unique(); auto itLine = std::begin(*pViewport); @@ -147,65 +255,20 @@ namespace RgbToHires { for (const auto lineOffset : LineOffsets) { - uint8_t* pHires = blob + lineBlockOffset + lineOffset; // interleaved HIRES source line + const uint8_t* pHires = hires + lineBlockOffset + lineOffset; // interleaved HIRES source line for (std::size_t x = 0; x < itLine->size(); ++x) { auto& block = (*itLine)[x]; UpdateHiResRGBCell(static_cast(x), pHires++, block.data()); - } + } *(itLine + 1) = *itLine; // Doubling the destination lines itLine += 2; // Next dest line } } - // Display with SDL - constexpr int scale = 1; - // init - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - SdlError("cannot initialise the preview window."); - } - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); // nearest neighbor - - // window - SDL_Window* pWindow = SDL_CreateWindow("Preview", SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - 560 * scale, - 384 * scale, - SDL_WINDOW_SHOWN - ); - if (pWindow == nullptr) { - SdlError("cannot initialise the preview window."); - } - // renderer - SDL_Renderer* pRenderer = SDL_CreateRenderer(pWindow, -1, SDL_RENDERER_ACCELERATED); - if (pRenderer == nullptr) { - SdlError("cannot initialise the preview window.", pWindow); - } - // texture - SDL_Texture* pTexture = SDL_CreateTexture(pRenderer, SDL_PIXELFORMAT_BGR888, SDL_TEXTUREACCESS_STATIC, 560, 384); - if (pTexture == nullptr) { - SdlError("cannot initialise the preview window.", pWindow, pRenderer); - } - // display - SDL_UpdateTexture(pTexture, nullptr, pViewport->data(), sizeof(rgba8Bits_t) * 560); - SDL_RenderClear(pRenderer); - SDL_RenderCopy(pRenderer, pTexture, NULL, NULL); - SDL_RenderPresent(pRenderer); - // event loop - while (true) - { - SDL_Event e; - if (SDL_WaitEvent(&e)) - { - if (e.type == SDL_QUIT) { break; } - } - } - // free - SDL_DestroyTexture(pTexture); - SDL_DestroyRenderer(pRenderer); - SDL_DestroyWindow(pWindow); - SDL_Quit(); + return pViewport; } + } } diff --git a/Rgb2Hires_PC/src/Display.h b/Rgb2Hires_PC/src/Display.h index bcf904d..1d67dff 100644 --- a/Rgb2Hires_PC/src/Display.h +++ b/Rgb2Hires_PC/src/Display.h @@ -21,12 +21,48 @@ #include "ImageQuantized.h" #include "HiRes.h" +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Texture; + namespace RgbToHires { namespace Display - { + { - void Display(uint8_t* blob); + struct rgba8Bits_t + { + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + uint8_t a = 0xff; + }; + + using Block = std::array; + using Line = std::array; + using Screen = std::array; + + class Window + { + public: + Window() = default; + ~Window(); + + static Window* GetInstance(); + + void display(const std::string& path, const uint8_t* hiresblob); + + private: + bool init(); + void sdlError(const std::string& msg); + + static Window* S_pInstance; + static const int SCALE = 2; + + SDL_Window* _pWindow = nullptr; + SDL_Renderer* _pRenderer = nullptr; + SDL_Texture* _pTexture = nullptr; + }; } }