Compare commits
6 Commits
823c3dd370
...
7ce9055272
Author | SHA1 | Date |
---|---|---|
Christophe Meneboeuf | 7ce9055272 | |
Christophe Meneboeuf | 7239fff361 | |
Christophe Meneboeuf | a33c95d6c0 | |
Christophe Meneboeuf | ff08549ef1 | |
Christophe Meneboeuf | ed03b6a48f | |
Christophe Meneboeuf | 20f85a2dc5 |
Binary file not shown.
49
Readme.md
49
Readme.md
|
@ -22,7 +22,7 @@ This repository contains three "PC" projects:
|
|||
* **Picture**: a program to convert a RGB png to a binary or an ASM excerpt, that can be loaded to the HIRES memory pages of an Apple II. An optional **live preview** can be displayed: its window will simulate an RGB monitor and will show the result, including color clashing and artifacts.
|
||||
* Source image must be 140x192. Pixels are anamorphic: they will be displayed twice wider than tall.
|
||||
* Source image must contains six colors : BLACK, WHITE, ORANGE, GREEN, BLUE and PURPLE. The color may be approximation of the Apple II 6 colors. Please refer to the provided pic.
|
||||
* **Tile**: given a RGB png tile sheet that satisfies the same requirements as above, it extracts a 14x16 tile and converts it to ASM data. This data **is not interleaved**.
|
||||
* **Tile**: given a RGB png tile sheet that satisfies the same requirements as above, it extracts a 14x16 tile and converts it to ASM data. The lines forming the tile data **are not interleaved** as in a HIRES framebuffer.
|
||||
|
||||
And one Apple II project:
|
||||
* **Loader**: a program that will load and display a picture generated by the *Picture* program. A bash scripts is provided as an exemple to load the picture and the loader on an image disk. [AppleCommander](https://applecommander.github.io/) is required to do so. Once in ProDOS, just type *-DISPLAY*.
|
||||
|
@ -32,40 +32,19 @@ __Note:__ For more information about the "Hires" format and its limitations, you
|
|||
|
||||
## Build from sources
|
||||
|
||||
This project has been tested with VisualStudio 2019, Gcc 9 (Ubuntu 20.04) and Gcc8.3.0 (Debian 10).
|
||||
### Dependencies
|
||||
|
||||
* **Magick++** from ImageMagick 6 and **SDL2**
|
||||
* On **Windows**
|
||||
* ImageMagick
|
||||
* Download ImageMagick 6 from the [official website](https://legacy.imagemagick.org/script/install-source.php).
|
||||
* Compile it in *Dynamic Multithreaded*.
|
||||
* Provide an environment variable called *MAGICK_HOME* and pointing to the root ImageMagick folder.
|
||||
* Copy *ImageMagick-config* from the *script/* folder to *MAGICK_HOME*.
|
||||
* SDL2
|
||||
* Download the latest version of the *development library* from the [official website](https://www.libsdl.org/download-2.0.php) and unarchive it.
|
||||
* Provide an environment variable called *SDL2_HOME* and pointing to the root of the SDL2 library.
|
||||
|
||||
* On **Linux**, install libmagick++-dev for version 6 and libsdl2-dev
|
||||
|
||||
> sudo apt install libmagick++-6.q16-dev libsdl2-dev
|
||||
|
||||
Depending on your Linux distribution, you may have to set an environment variable *sdl2_DIR*, pointing to the directory hosting SDL2's cmake configuration, before running cmake.
|
||||
|
||||
Example on Debian:
|
||||
|
||||
> export sdl2_DIR=/usr/lib/x86_64-linux-gnu/cmake/SDL2/
|
||||
The dependencies are managed by [conan.io](https://conan.io/). Please refer to (their documentation)[https://docs.conan.io/en/latest/installation.html] for installation instructions.
|
||||
|
||||
The build system is managed by CMake.
|
||||
### How to build
|
||||
|
||||
* **Linux** a CMakeList is provided:
|
||||
> mkdir build && cd build
|
||||
|
||||
> cmake ..
|
||||
|
||||
> cmake --build . --config release
|
||||
|
||||
* **Windows** a VisualStudio solution is provided
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
conan install ..
|
||||
cmake .. -DCMAKE_BUILD_TYPE=RELEASE
|
||||
cmake --build . --config release -j
|
||||
```
|
||||
|
||||
## Apple II project (Loader)
|
||||
|
||||
|
@ -73,13 +52,17 @@ This project has been tested with VisualStudio 2019, Gcc 9 (Ubuntu 20.04) and Gc
|
|||
* [CC65](https://cc65.github.io/cc65/)
|
||||
* The crosscompiler suit. Please provide an environment variable, *CC65_HOME* pointing to your CC65 folder.
|
||||
|
||||
### Build
|
||||
### How to build
|
||||
|
||||
> make
|
||||
A make file is provided. Just run
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
## Test
|
||||
|
||||
A correct source image, *test.png*, is provided as an example. Convert it using Picture, then copy it on a dsk image along with the Apple II loader. You can use or refer to the provided script in the *Loader_Apple2* folder.
|
||||
* A correct source image, *testPiceture.png*, is provided as an example. Convert it using the application *Picture*, then copy it on a dsk image along with the Apple II loader. You can use or refer to the provided script in the *Loader_Apple2* folder.
|
||||
* Another source image *testTile.png* is provided to test that the correct tile is converted and extracted using the application *Tile*.
|
||||
|
||||
### Demo
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required (VERSION 3.12)
|
||||
cmake_minimum_required (VERSION 3.19)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
|
||||
project (Rgb2Hires)
|
||||
|
@ -16,71 +16,60 @@ set(CMAKE_CXX_STANDARD 17) # required to use <filesystem>
|
|||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
|
||||
# dependencies
|
||||
if(WIN32)
|
||||
# sdl2
|
||||
if(NOT DEFINED ENV{SDL2_HOME})
|
||||
message(FATAL_ERROR "Please set a SDL2_HOME environment variable to the root directory of SDL2")
|
||||
endif()
|
||||
set(sdl2_DIR $ENV{SDL2_HOME})
|
||||
# imagemagik
|
||||
if(NOT DEFINED ENV{MAGICK_HOME})
|
||||
message(FATAL_ERROR "Please set a MAGICK_HOME environment variable to the root directory of ImageMagick6")
|
||||
endif()
|
||||
find_package(ImageMagick HINTS $ENV{MAGICK_HOME} REQUIRED COMPONENTS Magick++)
|
||||
else()
|
||||
find_package(ImageMagick REQUIRED COMPONENTS Magick++ )
|
||||
endif(WIN32)
|
||||
find_package(sdl2 REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
# Conan init
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
conan_basic_setup(TARGETS)
|
||||
|
||||
# directories
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/bin/debug)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/bin/release)
|
||||
|
||||
# library
|
||||
add_library(${PROJECT_NAME} src/Common.h
|
||||
# Library
|
||||
add_library(${PROJECT_NAME}
|
||||
src/HiRes.cpp
|
||||
src/HiRes.h
|
||||
src/ImageQuantized.cpp
|
||||
src/ImageQuantized.h
|
||||
src/Picture.h
|
||||
src/Picture.cpp
|
||||
src/Tile.h
|
||||
src/Tile.cpp
|
||||
src/Display.h
|
||||
src/Display.cpp
|
||||
)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE MAGICKCORE_QUANTUM_DEPTH=16 MAGICKCORE_HDRI_ENABLE=0)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${ImageMagick_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS})
|
||||
## dependencies
|
||||
conan_set_find_library_paths(${PROJECT_NAME})
|
||||
conan_target_link_libraries(${PROJECT_NAME})
|
||||
|
||||
|
||||
# Application Picture
|
||||
add_executable(Picture src/App_Picture.cpp)
|
||||
add_executable( Picture src/App_Picture.cpp
|
||||
src/Display.h
|
||||
src/Display.cpp
|
||||
)
|
||||
if(NOT WIN32)
|
||||
set_target_properties(Picture PROPERTIES COMPILE_FLAGS -pthread LINK_FLAGS -pthread)
|
||||
endif()
|
||||
target_compile_definitions(Picture PUBLIC MAGICKCORE_QUANTUM_DEPTH=16 MAGICKCORE_HDRI_ENABLE=0)
|
||||
target_include_directories(Picture PRIVATE src ${ImageMagick_INCLUDE_DIRS})
|
||||
target_link_libraries(Picture ${ImageMagick_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${PROJECT_NAME} ${SDL2_LIBRARIES}) # SDL2 must be at the end
|
||||
## dependencies
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "9")
|
||||
target_link_libraries(Picture stdc++fs) # filesystem lib not included in stdc++ for gcc < 9
|
||||
endif()
|
||||
set_property(TARGET Picture PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG})
|
||||
conan_set_find_library_paths(Picture)
|
||||
conan_target_link_libraries(Picture)
|
||||
target_link_libraries(Picture ${PROJECT_NAME})
|
||||
## output
|
||||
set_property(TARGET Picture PROPERTY
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG})
|
||||
|
||||
|
||||
# Application Tile
|
||||
add_executable(Tile src/App_Tile.cpp)
|
||||
target_compile_definitions(Tile PUBLIC MAGICKCORE_QUANTUM_DEPTH=16 MAGICKCORE_HDRI_ENABLE=0)
|
||||
target_include_directories(Tile PRIVATE src ${ImageMagick_INCLUDE_DIRS})
|
||||
target_link_libraries(Tile ${ImageMagick_LIBRARIES} ${PROJECT_NAME})
|
||||
set_property(TARGET Tile PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG})
|
||||
## custom definitions
|
||||
target_compile_definitions(Tile PRIVATE cimg_use_png)
|
||||
## dependencies
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "9")
|
||||
target_link_libraries(Tile stdc++fs) # filesystem lib not included in stdc++ for gcc < 9
|
||||
endif()
|
||||
conan_set_find_library_paths(Tile)
|
||||
conan_target_link_libraries(Tile)
|
||||
target_link_libraries(Tile ${PROJECT_NAME})
|
||||
## output
|
||||
set_property(TARGET Tile PROPERTY
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}
|
||||
)
|
||||
|
||||
|
||||
# Windows only: copy dlls
|
||||
if(WIN32)
|
||||
set(DEBUG_EXE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copy_im_db_dlls.bat")
|
||||
set(RELEASE_EXE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copy_im_rl_dlls.bat")
|
||||
string (REPLACE "/" "\\" WIN_BIN_DIR "$<$<CONFIG:debug>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<CONFIG:release>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}>")
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND "$<$<CONFIG:debug>:${DEBUG_EXE_PATH}>$<$<CONFIG:release>:${RELEASE_EXE_PATH}>" ${WIN_BIN_DIR})
|
||||
endif(WIN32)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
[requires]
|
||||
sdl/2.0.20
|
||||
sdl_image/2.0.5
|
||||
tclap/1.2.4
|
||||
|
||||
[generators]
|
||||
cmake
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/* Rgb2Hires
|
||||
* Copyright (C) 2016 Christophe Meneboeuf <christophe@xtof.info>
|
||||
* Copyright (C) 2016-2022 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
|
@ -23,12 +24,10 @@
|
|||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <Magick++.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <tclap/CmdLine.h>
|
||||
|
||||
#include "ImageQuantized.h"
|
||||
#include "Picture.h"
|
||||
#include "Tile.h"
|
||||
#include "Display.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -41,60 +40,113 @@ inline bool exists(const std::string& path)
|
|||
return (stat(path.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
void ExitOnError(const std::string& message,
|
||||
std::vector<SDL_Surface*> surfaces = {})
|
||||
{
|
||||
std::cout << "Error\n" << message << '\n';
|
||||
|
||||
for (auto surface : surfaces)
|
||||
{
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
IMG_Quit();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
|
||||
/// @brief Program entry point
|
||||
int main( int argc, char *argv[] )
|
||||
{
|
||||
Magick::InitializeMagick(*argv);
|
||||
|
||||
// Init
|
||||
int inited = IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
|
||||
if (inited != (IMG_INIT_JPG | IMG_INIT_PNG))
|
||||
{
|
||||
std::cerr << "IMG_Init: Failed to init required jpg and png support!\n"
|
||||
<< "IMG_Init: " << IMG_GetError() << '\n';
|
||||
return -1;
|
||||
}
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
std::cerr << "There was an error initing SDL2: " << SDL_GetError() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Parsing command line
|
||||
TCLAP::CmdLine cmd("Picture - by Christophe Meneboeuf <christophe@xtof.info>", ' ', "0");
|
||||
TCLAP::CmdLine cmd("Picture - by Christophe Meneboeuf <christophe@xtof.info>", ' ', "0.1");
|
||||
TCLAP::ValueArg<string> imagePath("i", "image", "Source image path", true, "", "path_to_image");
|
||||
TCLAP::ValueArg<string> outputPath("o", "output", "Output path", true, "", "path_to_output");
|
||||
TCLAP::ValueArg<string> outputPath("o", "output", "Output path to write the HIRES file", true, "", "path_to_output");
|
||||
TCLAP::SwitchArg preview("p", "preview", "Open a window to display a live preview.");
|
||||
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.xorAdd(outputPath, preview); // live preview or output to disk
|
||||
cmd.add(assembly);
|
||||
cmd.add(preview);
|
||||
cmd.parse(argc, argv);
|
||||
|
||||
if (imagePath.getValue().size() == 0 || outputPath.getValue().size() == 0) {
|
||||
std::cout << "No input or output path provided." << std::endl;
|
||||
return -1;
|
||||
if (imagePath.getValue().size() == 0) {
|
||||
ExitOnError("No input path provided.\n");
|
||||
}
|
||||
|
||||
std::vector<SDL_Surface*> surfaces;
|
||||
try
|
||||
{
|
||||
{
|
||||
const auto filepath = imagePath.getValue();
|
||||
if (!exists(filepath)) {
|
||||
throw runtime_error("Cannot read " + filepath);
|
||||
}
|
||||
const auto imageRgb = Magick::Image{ filepath };
|
||||
auto imageQuantized = ImageQuantized{ imageRgb };
|
||||
const auto imageHiRes = Picture{ imageQuantized };
|
||||
if (assembly.getValue() == true) { //Ouput in ASM
|
||||
ofstream output(outputPath.getValue());
|
||||
output << imageHiRes.getAsm();
|
||||
}
|
||||
else { //Binary output
|
||||
ofstream output(outputPath.getValue(), ios::binary);
|
||||
const auto bytes = imageHiRes.getBlob();
|
||||
output.write(reinterpret_cast<const char*>(bytes.get()), bytes->size());
|
||||
ExitOnError("Cannot read " + filepath);
|
||||
}
|
||||
|
||||
if (preview.getValue()) {
|
||||
const auto bytes = imageHiRes.getBlob();
|
||||
SDL_Surface* surfaceRgb = IMG_Load(filepath.c_str());
|
||||
surfaces.push_back(surfaceRgb);
|
||||
if (surfaceRgb == nullptr)
|
||||
{
|
||||
ExitOnError("Cannot decode " + filepath, surfaces);
|
||||
}
|
||||
const ImageQuantized imageHiRes{ surfaceRgb };
|
||||
|
||||
// Preview
|
||||
if (preview.getValue())
|
||||
{
|
||||
if (assembly.getValue() == true)
|
||||
{
|
||||
std::cout << "\nIgnoring --asm option.\n";
|
||||
}
|
||||
const auto bytes = imageHiRes.getHiresBuffer();
|
||||
Display::Window::GetInstance()->display(filepath, bytes->data());
|
||||
}
|
||||
// Convertion to disk
|
||||
else
|
||||
{
|
||||
if (outputPath.getValue().size() == 0) {
|
||||
ExitOnError("No output path provided.\n");
|
||||
}
|
||||
|
||||
if (assembly.getValue() == true) { //Ouput in ASM
|
||||
ofstream output(outputPath.getValue());
|
||||
output << imageHiRes.getHiresAsm();
|
||||
}
|
||||
else { //Binary output
|
||||
ofstream output(outputPath.getValue(), ios::binary);
|
||||
const auto bytes = imageHiRes.getHiresBuffer();
|
||||
output.write(reinterpret_cast<const char*>(bytes.get()), bytes->size());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Fatal error
|
||||
catch (const exception& e) {
|
||||
cout << e.what();
|
||||
return -1;
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
ExitOnError(e.what(), surfaces);
|
||||
}
|
||||
|
||||
for (auto surface : surfaces)
|
||||
{
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
IMG_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,16 +23,18 @@
|
|||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <Magick++.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <tclap/CmdLine.h>
|
||||
|
||||
#include "ImageQuantized.h"
|
||||
#include "Picture.h"
|
||||
#include "Tile.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace RgbToHires;
|
||||
|
||||
|
||||
|
||||
|
||||
/// @brief Returns true if a file exists
|
||||
inline bool exists(const std::string& path)
|
||||
{
|
||||
|
@ -40,10 +42,25 @@ inline bool exists(const std::string& path)
|
|||
return (stat(path.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
|
||||
void ExitOnError(const std::string& message,
|
||||
std::vector<SDL_Surface*> surfaces = {})
|
||||
{
|
||||
std::cout << "Error\n" << message << '\n';
|
||||
|
||||
for (auto surface : surfaces)
|
||||
{
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
IMG_Quit();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/// @brief Program entry point
|
||||
int main( int argc, char *argv[] )
|
||||
{
|
||||
Magick::InitializeMagick(*argv);
|
||||
|
||||
//Parsing command line
|
||||
TCLAP::CmdLine cmd("Tile - by Christophe Meneboeuf <christophe@xtof.info>", ' ', "0");
|
||||
|
@ -69,29 +86,42 @@ int main( int argc, char *argv[] )
|
|||
std::cout << "Line number shall be < 11" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto filepath = imagePath.getValue();
|
||||
if (!exists(filepath)) {
|
||||
ExitOnError("Cannot read " + filepath);
|
||||
}
|
||||
|
||||
std::vector<SDL_Surface*> surfaces;
|
||||
try
|
||||
{
|
||||
const auto filepath = imagePath.getValue();
|
||||
if (!exists(filepath)) {
|
||||
throw runtime_error("Cannot read " + filepath);
|
||||
{
|
||||
SDL_Surface* surfaceRgb = IMG_Load(filepath.c_str());
|
||||
surfaces.push_back(surfaceRgb);
|
||||
if (surfaceRgb == nullptr)
|
||||
{
|
||||
ExitOnError("Cannot decode " + filepath, surfaces);
|
||||
}
|
||||
const auto imageRgb = Magick::Image{ filepath };
|
||||
auto imageQuantized = ImageQuantized{ imageRgb };
|
||||
const auto tileHiRes = Tile{ imageQuantized, column.getValue(), line.getValue()};
|
||||
|
||||
const ImageQuantized imageHiRes{ surfaceRgb };
|
||||
const auto tileHiRes = imageHiRes.getTile(column.getValue(), line.getValue());
|
||||
|
||||
// Always output in asm
|
||||
ofstream output(outputPath.getValue());
|
||||
output << tileHiRes.getAsm();
|
||||
|
||||
output << tileHiRes;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
ExitOnError(e.what(), surfaces);
|
||||
}
|
||||
|
||||
//Fatal error
|
||||
catch (const exception& e) {
|
||||
cout << e.what();
|
||||
return -1;
|
||||
for (auto surface : surfaces)
|
||||
{
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
||||
|
||||
SDL_Quit();
|
||||
IMG_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
|
||||
/* Rgb2Hires
|
||||
* Copyright (C) 2016 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __COMMON_H__
|
||||
#define __COMMON_H__
|
||||
|
||||
|
||||
#include <Magick++.h>
|
||||
|
||||
#define MAGICKCORE_ZERO_CONFIGURATION_SUPPORT 1
|
||||
|
||||
namespace RgbToHires {
|
||||
|
||||
constexpr std::size_t SHIFT = sizeof(Magick::Quantum)*8 - 8;
|
||||
|
||||
#define WHITE Magick::Color{0xFF << SHIFT, 0xFF << SHIFT, 0xFF << SHIFT}
|
||||
#define BLACK Magick::Color{0x00 << SHIFT, 0x00 << SHIFT, 0x00 << SHIFT}
|
||||
#define BLUE Magick::Color{0x07 << SHIFT, 0xA8 << SHIFT, 0xE0 << SHIFT}
|
||||
#define GREEN Magick::Color{0x43 << SHIFT, 0xC8 << SHIFT, 0x00 << SHIFT}
|
||||
#define ORANGE Magick::Color{0xF9 << SHIFT, 0x56 << SHIFT,0x1D << SHIFT}
|
||||
#define VIOLET Magick::Color{0xBB << SHIFT, 0x36 << SHIFT, 0xFF << SHIFT}
|
||||
|
||||
|
||||
constexpr unsigned WIDTH = 140u;
|
||||
constexpr unsigned HEIGHT = 192u;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,24 +1,39 @@
|
|||
/* Rgb2Hires
|
||||
* Copyright (C) 2016-2022 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <SDL_image.h>
|
||||
|
||||
#include "Picture.h"
|
||||
#include "Display.h"
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
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);
|
||||
void UpdateHiResRGBCell(const int x, const uint8_t* pLineAddr, ColorRgb* pOut);
|
||||
std::unique_ptr<Screen> ComputeRgbBuffer(const uint8_t* hires);
|
||||
|
||||
|
||||
|
@ -101,7 +116,7 @@ namespace RgbToHires
|
|||
} error;
|
||||
|
||||
auto pViewport = ComputeRgbBuffer(hiresblob);
|
||||
SDL_UpdateTexture(_pTexture, nullptr, pViewport->data(), sizeof(rgba8Bits_t) * 560);
|
||||
SDL_UpdateTexture(_pTexture, nullptr, pViewport->data(), sizeof(ColorRgb) * 560);
|
||||
SDL_RenderClear(_pRenderer);
|
||||
SDL_RenderCopy(_pRenderer, _pTexture, NULL, NULL);
|
||||
SDL_RenderPresent(_pRenderer);
|
||||
|
@ -118,24 +133,29 @@ namespace RgbToHires
|
|||
{
|
||||
bool isDone = false;
|
||||
int nbErrors = 0;
|
||||
std::vector<SDL_Surface*> surfaces;
|
||||
while (!isDone) // several attempts as the file may be marked modified before being written
|
||||
{
|
||||
try
|
||||
{
|
||||
this_thread::sleep_for(std::chrono::milliseconds(500)); // 500ms between each attempt
|
||||
// update hires image
|
||||
const auto imageRgb = Magick::Image{ path };
|
||||
auto imageQuantized = ImageQuantized{ imageRgb };
|
||||
const auto imageHiRes = Picture{ imageQuantized };
|
||||
SDL_Surface* surfaceRgb = IMG_Load(path.c_str());
|
||||
surfaces.push_back(surfaceRgb);
|
||||
if (surfaceRgb == nullptr)
|
||||
{
|
||||
throw("Cannot decode " + path);
|
||||
}
|
||||
const RgbToHires::ImageQuantized imageHiRes{ surfaceRgb };
|
||||
|
||||
// rgb conversion from hires data
|
||||
std::lock_guard<std::mutex> lock{ this->_mutex }; // protecting pViewport
|
||||
pViewport = ComputeRgbBuffer(imageHiRes.getBlob()->data());
|
||||
pViewport = ComputeRgbBuffer(imageHiRes.getHiresBuffer()->data());
|
||||
isDone = true;
|
||||
timeModified = std::filesystem::last_write_time(path);
|
||||
this->_isFileModified.store(isImgModified);
|
||||
}
|
||||
catch (Magick::Error& e)
|
||||
catch (std::exception& e)
|
||||
{
|
||||
++nbErrors;
|
||||
if (nbErrors >= 5) { // 5 atttemps
|
||||
|
@ -146,6 +166,7 @@ namespace RgbToHires
|
|||
}
|
||||
}
|
||||
}
|
||||
for(auto surface : surfaces) { SDL_FreeSurface(surface); }
|
||||
}
|
||||
}
|
||||
}); // thread
|
||||
|
@ -160,7 +181,7 @@ namespace RgbToHires
|
|||
// update the display with rgb data
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{ this->_mutex }; // protecting pViewport
|
||||
SDL_UpdateTexture(_pTexture, nullptr, pViewport->data(), sizeof(rgba8Bits_t) * 560);
|
||||
SDL_UpdateTexture(_pTexture, nullptr, pViewport->data(), sizeof(ColorRgb) * 560);
|
||||
}
|
||||
SDL_RenderClear(_pRenderer);
|
||||
SDL_RenderCopy(_pRenderer, _pTexture, NULL, NULL);
|
||||
|
@ -179,33 +200,15 @@ namespace RgbToHires
|
|||
|
||||
}
|
||||
|
||||
void SdlError(const std::string& error,
|
||||
SDL_Window* const pWindow = nullptr,
|
||||
SDL_Renderer* const pRenderer = nullptr
|
||||
)
|
||||
{
|
||||
std::cout << "Error: " << error << '\n';
|
||||
|
||||
if (pRenderer != nullptr) {
|
||||
SDL_DestroyRenderer(pRenderer);
|
||||
}
|
||||
if (pWindow != nullptr) {
|
||||
SDL_DestroyWindow(pWindow);
|
||||
}
|
||||
SDL_Quit();
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
constexpr std::array<rgba8Bits_t, 7> Palette = {
|
||||
rgba8Bits_t{0x00,0x00,0x00, 0xFF}, // black
|
||||
rgba8Bits_t{0xFF,0xFF,0xFF, 0xFF}, // white
|
||||
rgba8Bits_t{0x07,0xA8,0xE0, 0xFF}, // blue
|
||||
rgba8Bits_t{0xF9,0x56,0x1D, 0xFF}, // orange
|
||||
rgba8Bits_t{0x43,0xC8,0x00, 0xFF}, // green
|
||||
rgba8Bits_t{0xBB,0x36,0xFF, 0xFF}, // violet
|
||||
rgba8Bits_t{0x80,0x80,0x80, 0xFF} // dummy as AppleWin's code can overflow :( (no time to correct it)
|
||||
constexpr std::array<ColorRgb, 7> Palette = {
|
||||
BLACK,
|
||||
WHITE,
|
||||
BLUE,
|
||||
ORANGE,
|
||||
GREEN,
|
||||
VIOLET,
|
||||
BLACK // dummy
|
||||
};
|
||||
|
||||
|
||||
|
@ -219,7 +222,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, const uint8_t* pLineAddr, rgba8Bits_t* pOut)
|
||||
void UpdateHiResRGBCell(const int x, const uint8_t* pLineAddr, ColorRgb* pOut)
|
||||
{
|
||||
const int xpixel = x * 14;
|
||||
int xoffset = x & 1; // offset to start of the 2 bytes
|
||||
|
@ -235,7 +238,7 @@ namespace RgbToHires
|
|||
uint32_t dwordval = (byteval1 & 0x7F) | ((byteval2 & 0x7F) << 7) | ((byteval3 & 0x7F) << 14) | ((byteval4 & 0x7F) << 21);
|
||||
|
||||
// Extraction of 14 color pixels
|
||||
rgba8Bits_t colors[14];
|
||||
ColorRgb colors[14];
|
||||
int idxColor = 0;
|
||||
uint32_t dwordval_tmp = dwordval;
|
||||
dwordval_tmp = dwordval_tmp >> 7;
|
||||
|
@ -252,7 +255,7 @@ namespace RgbToHires
|
|||
if (i % 2) dwordval_tmp >>= 2;
|
||||
}
|
||||
// Black and White
|
||||
rgba8Bits_t bw[2];
|
||||
ColorRgb bw[2];
|
||||
bw[0] = Palette[0];
|
||||
bw[1] = Palette[1];
|
||||
|
||||
|
@ -299,9 +302,9 @@ namespace RgbToHires
|
|||
// Converting line per line, pixel-block per pixel-block
|
||||
auto pViewport = make_unique<Screen>();
|
||||
auto itLine = std::begin(*pViewport);
|
||||
for (const auto lineBlockOffset : LineAdresses)
|
||||
for (const auto lineBlockOffset : RgbToHires::LineAdresses)
|
||||
{
|
||||
for (const auto lineOffset : LineOffsets)
|
||||
for (const auto lineOffset : RgbToHires::LineOffsets)
|
||||
{
|
||||
const uint8_t* pHires = hires + lineBlockOffset + lineOffset; // interleaved HIRES source line
|
||||
for (std::size_t x = 0; x < itLine->size(); ++x)
|
||||
|
@ -317,6 +320,4 @@ namespace RgbToHires
|
|||
return pViewport;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,51 +29,43 @@ struct SDL_Window;
|
|||
struct SDL_Renderer;
|
||||
struct SDL_Texture;
|
||||
|
||||
namespace RgbToHires
|
||||
{
|
||||
namespace Display
|
||||
{
|
||||
using namespace RgbToHires;
|
||||
|
||||
struct rgba8Bits_t
|
||||
{
|
||||
uint8_t r = 0;
|
||||
uint8_t g = 0;
|
||||
uint8_t b = 0;
|
||||
uint8_t a = 0xff;
|
||||
};
|
||||
namespace Display
|
||||
{
|
||||
|
||||
using Block = std::array<rgba8Bits_t, 14>;
|
||||
using Line = std::array<Block, 40>;
|
||||
using Screen = std::array<Line, 192 * 2>;
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
Window() = default;
|
||||
~Window();
|
||||
using Block = std::array<ColorRgb, 14>;
|
||||
using Line = std::array<Block, 40>;
|
||||
using Screen = std::array<Line, 192 * 2>;
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
Window() = default;
|
||||
~Window();
|
||||
|
||||
static Window* GetInstance();
|
||||
static Window* GetInstance();
|
||||
|
||||
void display(const std::string& path, const uint8_t* hiresblob);
|
||||
void display(const std::string& path, const uint8_t* hiresblob);
|
||||
|
||||
private:
|
||||
bool init();
|
||||
void sdlError(const std::string& msg);
|
||||
private:
|
||||
bool init();
|
||||
void sdlError(const std::string& msg);
|
||||
|
||||
std::thread* _pThread = nullptr; //< to survey filechange
|
||||
std::mutex _mutex;
|
||||
std::atomic_bool _isFileModified = false;
|
||||
std::atomic_bool _stopFileSurvey = false;
|
||||
std::thread* _pThread = nullptr; //< to survey filechange
|
||||
std::mutex _mutex;
|
||||
std::atomic_bool _isFileModified = false;
|
||||
std::atomic_bool _stopFileSurvey = false;
|
||||
|
||||
static Window* S_pInstance;
|
||||
static const int SCALE = 2;
|
||||
static Window* S_pInstance;
|
||||
static const int SCALE = 2;
|
||||
|
||||
SDL_Window* _pWindow = nullptr;
|
||||
SDL_Renderer* _pRenderer = nullptr;
|
||||
SDL_Texture* _pTexture = nullptr;
|
||||
};
|
||||
SDL_Window* _pWindow = nullptr;
|
||||
SDL_Renderer* _pRenderer = nullptr;
|
||||
SDL_Texture* _pTexture = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/* Rgb2Hires
|
||||
* Copyright (C) 2016 Christophe Meneboeuf <christophe@xtof.info>
|
||||
* Copyright (C) 2016-2022 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include "ImageQuantized.h"
|
||||
//#include "ImageQuantized.h"
|
||||
#include "HiRes.h"
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace RgbToHires {
|
|||
_data[1] = 0;
|
||||
}
|
||||
|
||||
BlockHr::BlockHr(const BlockPixel& source)
|
||||
BlockHr::BlockHr(const BlockPixelRgb& source)
|
||||
{
|
||||
const auto groups = getGroup(source);
|
||||
//Init data, depending on the group
|
||||
|
@ -56,7 +56,7 @@ namespace RgbToHires {
|
|||
}
|
||||
|
||||
|
||||
pair<BlockHr::eColorGroup, BlockHr::eColorGroup> BlockHr::getGroup(const BlockPixel& block) const
|
||||
pair<BlockHr::eColorGroup, BlockHr::eColorGroup> BlockHr::getGroup(const BlockPixelRgb& block) const
|
||||
{
|
||||
pair<eColorGroup, eColorGroup> groups{ UNDEF, UNDEF };
|
||||
//1st block group, including the last semi-pixel
|
||||
|
@ -96,7 +96,7 @@ namespace RgbToHires {
|
|||
}
|
||||
|
||||
|
||||
uint8_t BlockHr::getDibit(const Magick::Color& color) const
|
||||
uint8_t BlockHr::getDibit(const ColorRgb& color) const
|
||||
{
|
||||
if (color == WHITE) {
|
||||
return 3;
|
||||
|
@ -112,7 +112,7 @@ namespace RgbToHires {
|
|||
}
|
||||
else {
|
||||
auto msg = string("Unsupported color used as input for hires pixel\nRGB:");
|
||||
msg = msg + to_string(color.redQuantum()) + to_string(color.greenQuantum()) + to_string(color.blueQuantum());
|
||||
msg = msg + to_string(color.r) + to_string(color.g) + to_string(color.b);
|
||||
throw(runtime_error(msg));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/* Rgb2Hires
|
||||
* Copyright (C) 2016 Christophe Meneboeuf <christophe@xtof.info>
|
||||
* Copyright (C) 2016-2022 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,23 +25,52 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <SDL2/SDL_image.h>
|
||||
|
||||
namespace RgbToHires
|
||||
{
|
||||
|
||||
struct ColorRgb : public SDL_Color
|
||||
{
|
||||
inline bool operator==(const ColorRgb& rhs) const
|
||||
{
|
||||
return r == rhs.r && g == rhs.g && b == rhs.b;
|
||||
}
|
||||
inline bool operator==(const SDL_Color& rhs) const
|
||||
{
|
||||
return r == rhs.r && g == rhs.g && b == rhs.b;
|
||||
}
|
||||
};
|
||||
|
||||
// HIRES Palette
|
||||
constexpr ColorRgb WHITE{ 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
constexpr ColorRgb BLACK{ 0x00, 0x00, 0x00, 0xFF };
|
||||
constexpr ColorRgb BLUE{ 0x07, 0xA8, 0xE0, 0xFF };
|
||||
constexpr ColorRgb GREEN{ 0x43, 0xC8, 0x00, 0xFF };
|
||||
constexpr ColorRgb ORANGE{ 0xF9, 0x56, 0x1D, 0xFF };
|
||||
constexpr ColorRgb VIOLET{ 0xBB, 0x36, 0xFF, 0xFF };
|
||||
|
||||
constexpr unsigned WIDTH = 140u;
|
||||
constexpr unsigned HEIGHT = 192u;
|
||||
|
||||
|
||||
static constexpr unsigned NB_PIXEL_PER_BLOCK = 7u;
|
||||
static constexpr unsigned NB_BLOCK_PER_LINE = 20u;
|
||||
static constexpr unsigned NB_LINES_PER_SCREEN = 192u;
|
||||
|
||||
|
||||
constexpr std::array<const uint16_t, 192 / 8> LineAdresses = {
|
||||
0x0000, 0x0080, 0x0100, 0x0180, 0x0200, 0x0280, 0x0300, 0x0380,
|
||||
0x0028, 0x00a8, 0x0128, 0x01a8, 0x0228, 0x02a8, 0x0328, 0x03a8,
|
||||
0x0050, 0x00d0, 0x0150, 0x01d0, 0x0250, 0x02d0, 0x0350, 0x03d0
|
||||
};
|
||||
}; ///< Base address of the interleaved lines
|
||||
|
||||
constexpr std::array<const uint16_t, 8> LineOffsets = {
|
||||
0x0, 0x400, 0x800, 0xc00, 0x1000, 0x1400, 0x1800, 0x1c00
|
||||
};
|
||||
}; ///< Offset to get the full address
|
||||
|
||||
using BlockPixel = std::array<Magick::PixelPacket, 7u>;
|
||||
|
||||
using BlockPixelRgb = std::array<ColorRgb, 7u>;
|
||||
|
||||
/// @brief A block of 7 pixels
|
||||
class BlockHr
|
||||
|
@ -50,7 +79,7 @@ namespace RgbToHires
|
|||
/// @brief Default constructor: all black
|
||||
BlockHr();
|
||||
/// @brief Construction from 7 pixels
|
||||
BlockHr(const BlockPixel& );
|
||||
BlockHr(const BlockPixelRgb& );
|
||||
/// @brief returns the position of the first element
|
||||
inline std::array<uint8_t, 2>::const_iterator begin() const {
|
||||
return _data.begin();
|
||||
|
@ -67,9 +96,9 @@ namespace RgbToHires
|
|||
UNDEF // black and white can be group1 or group2
|
||||
};
|
||||
/// @brief Returns the color group of these two 3.5 pixel blocks
|
||||
std::pair<eColorGroup, eColorGroup> getGroup(const BlockPixel&) const;
|
||||
std::pair<eColorGroup, eColorGroup> getGroup(const BlockPixelRgb&) const;
|
||||
/// @brief Returns the bit pait corresponding to the given color
|
||||
uint8_t getDibit(const Magick::Color&) const;
|
||||
uint8_t getDibit(const ColorRgb&) const;
|
||||
|
||||
std::array<uint8_t, 2> _data;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/* Rgb2Hires
|
||||
* Copyright (C) 2016 Christophe Meneboeuf <christophe@xtof.info>
|
||||
/*Rgb2Hires
|
||||
* Copyright (C) 2016-2022 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,6 +18,9 @@
|
|||
|
||||
#include <stdexcept>
|
||||
#include <iterator>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "ImageQuantized.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -25,60 +28,69 @@ using namespace std;
|
|||
namespace RgbToHires {
|
||||
|
||||
|
||||
ImageQuantized::ImageQuantized(const Magick::Image& src):
|
||||
Magick::Image(src)
|
||||
ImageQuantized::ImageQuantized(SDL_Surface* const src) :
|
||||
_format(SDL_PIXELFORMAT_BGRX8888)
|
||||
{
|
||||
const auto dim = size();
|
||||
if (dim.height() != HEIGHT || dim.width() != WIDTH) {
|
||||
if (src->h != HEIGHT || src->w != WIDTH) {
|
||||
throw std::runtime_error("Image dimension must be 140x192 pixels.");
|
||||
}
|
||||
auto pixelpacket = getPixels(0u, 0u, WIDTH, HEIGHT);
|
||||
for (auto i = 0u; i < HEIGHT; ++i)
|
||||
{
|
||||
for (auto j = 0u; j < WIDTH; ++j)
|
||||
{
|
||||
auto color =*pixelpacket;
|
||||
const auto distBlack = Distance(BLACK, color);
|
||||
const auto distWhite = Distance(WHITE, color);
|
||||
const auto distBlue = Distance(BLUE, color);
|
||||
const auto distGreen = Distance(GREEN, color);
|
||||
const auto distOrange = Distance(ORANGE, color);
|
||||
const auto distViolet = Distance(VIOLET, color);
|
||||
const auto distMin = std::min({ distBlack, distWhite, distBlue, \
|
||||
distGreen, distOrange, distViolet });
|
||||
|
||||
if (distMin == distBlack) {
|
||||
*pixelpacket++ = BLACK;
|
||||
SDL_Surface* rgb = SDL_ConvertSurfaceFormat(src, SDL_PIXELFORMAT_BGRX8888, 0);
|
||||
|
||||
SDL_LockSurface(rgb);
|
||||
auto srcPx = reinterpret_cast<const uint32_t*>(rgb->pixels);
|
||||
SDL_UnlockSurface(rgb);
|
||||
|
||||
// Construct HIRES image: lines are not properly ordered
|
||||
for (auto& line : _blobHr)
|
||||
{
|
||||
for (auto blockNr = 0u; blockNr < NB_BLOCK_PER_LINE; ++blockNr)
|
||||
{
|
||||
BlockRgb blockRgb;
|
||||
for (auto& pixel : blockRgb)
|
||||
{
|
||||
ColorRgb color;
|
||||
SDL_GetRGB(*srcPx, rgb->format, &color.r, &color.g, &color.b);
|
||||
pixel = Quantize(color);
|
||||
srcPx++;
|
||||
}
|
||||
line.emplace_back(BlockHr{ blockRgb });
|
||||
}
|
||||
}
|
||||
|
||||
//Constructing the map used to interleave the lines
|
||||
auto i = 0u;
|
||||
for (const auto& line : _blobHr)
|
||||
{
|
||||
uint16_t addr_interleaved = LineAdresses[i / 8] + LineOffsets[i % 8];
|
||||
_hrOrderedLines.insert(std::pair<uint16_t, const LineHr*>(addr_interleaved, &line));
|
||||
++i;
|
||||
}
|
||||
//Adding the 8 byte "memory holes"
|
||||
for (auto line : _hrOrderedLines)
|
||||
{
|
||||
if ((line.first & 0xFF) == 0x50 || (line.first & 0xFF) == 0xD0)
|
||||
{
|
||||
for (auto i = 0u; i < 4u; ++i)
|
||||
{
|
||||
const_cast<LineHr*>(line.second)->emplace_back(BlockHr{});
|
||||
}
|
||||
else if (distMin == distWhite) {
|
||||
*pixelpacket++ = WHITE;
|
||||
}
|
||||
else if (distMin == distBlue) {
|
||||
*pixelpacket++ = BLUE;
|
||||
}
|
||||
else if (distMin == distGreen) {
|
||||
*pixelpacket++ = GREEN;
|
||||
}
|
||||
else if (distMin == distOrange) {
|
||||
*pixelpacket++ = ORANGE;
|
||||
}
|
||||
else {
|
||||
*pixelpacket++ = VIOLET;
|
||||
}
|
||||
}
|
||||
}
|
||||
syncPixels();
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<ImageQuantized::Blob> ImageQuantized::getBlob() const
|
||||
unique_ptr<array<uint8_t, ImageQuantized::FRAME_SIZE>> ImageQuantized::getHiresBuffer() const
|
||||
{
|
||||
auto blob = std::unique_ptr<Blob>(new Blob);
|
||||
auto pixels = getConstPixels(0u, 0u, WIDTH, HEIGHT);
|
||||
for (auto& line : *blob) {
|
||||
for (auto& block : line) {
|
||||
for (auto pixel: block ) {
|
||||
pixel = *pixels++;
|
||||
auto blob = unique_ptr<array<uint8_t, FRAME_SIZE>>{ new array<uint8_t, FRAME_SIZE> };
|
||||
auto byte_blob = begin(*blob);
|
||||
for (const auto& line : _hrOrderedLines)
|
||||
{
|
||||
for (const auto& block : *(line.second))
|
||||
{
|
||||
for (const auto byte_block : block)
|
||||
{
|
||||
*byte_blob++ = byte_block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,20 +99,79 @@ namespace RgbToHires {
|
|||
|
||||
|
||||
|
||||
string ImageQuantized::getHiresAsm() const
|
||||
{
|
||||
string assembly{ "Picture:\n" };
|
||||
for (const auto& line : _hrOrderedLines)
|
||||
{
|
||||
assembly += "\t.byte\t";
|
||||
for (const auto& block : *(line.second))
|
||||
{
|
||||
for (const auto byte : block)
|
||||
{
|
||||
assembly += to_string(byte) + ", ";
|
||||
}
|
||||
}
|
||||
assembly.pop_back(); //removing the last coma
|
||||
assembly.pop_back();
|
||||
assembly += "\n";
|
||||
}
|
||||
return assembly;
|
||||
}
|
||||
|
||||
double ImageQuantized::Distance(const Magick::Color& color1, const Magick::Color& color2)
|
||||
|
||||
|
||||
std::string ImageQuantized::getTile(const unsigned col, const unsigned line) const
|
||||
{
|
||||
constexpr int TILE_W = 14;
|
||||
constexpr int TILE_H = 16;
|
||||
|
||||
std::string assembly;
|
||||
for (auto lineNr = line * TILE_H;
|
||||
lineNr < TILE_H * (line + 1); ++lineNr)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << ".byte ";
|
||||
|
||||
auto& line = _blobHr[lineNr];
|
||||
const unsigned nbBlockPerTile = TILE_W / NB_PIXEL_PER_BLOCK;
|
||||
for (unsigned blockHrNr = col * nbBlockPerTile;
|
||||
blockHrNr < nbBlockPerTile * (col + 1); ++blockHrNr)
|
||||
{
|
||||
const auto& block = line[blockHrNr];
|
||||
for (const auto byte : block)
|
||||
{
|
||||
stream << '$' << std::setw(2) << std::setfill('0') << std::uppercase
|
||||
<< std::hex << static_cast<int>(byte) << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
assembly += stream.str();
|
||||
assembly.pop_back(); //removing the last coma
|
||||
assembly.pop_back();
|
||||
assembly += "\n";
|
||||
|
||||
}
|
||||
|
||||
return assembly;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
double ImageQuantized::Distance(const ColorRgb& color1, const ColorRgb& color2)
|
||||
{
|
||||
|
||||
static constexpr double LUMA_RED = 0.299;
|
||||
static constexpr double LUMA_GREEN = 0.587;
|
||||
static constexpr double LUMA_BLUE = 0.114;
|
||||
|
||||
const auto y1 = LUMA_RED * color1.redQuantum() + LUMA_GREEN * color1.greenQuantum() + LUMA_BLUE * color1.blueQuantum();
|
||||
const auto u1 = 0.492 * (color1.blueQuantum() - y1);
|
||||
const auto v1 = 0.877 * (color1.redQuantum() - y1);
|
||||
const auto y2 = LUMA_RED * color2.redQuantum() + LUMA_GREEN * color2.greenQuantum() + LUMA_BLUE * color2.blueQuantum();
|
||||
const auto u2 = 0.492 * (color2.blueQuantum() - y2);
|
||||
const auto v2 = 0.877 * (color2.redQuantum() - y2);
|
||||
const auto y1 = LUMA_RED * color1.r + LUMA_GREEN * color1.g + LUMA_BLUE * color1.b;
|
||||
const auto u1 = 0.492 * (color1.b - y1);
|
||||
const auto v1 = 0.877 * (color1.r - y1);
|
||||
const auto y2 = LUMA_RED * color2.r + LUMA_GREEN * color2.g + LUMA_BLUE * color2.b;
|
||||
const auto u2 = 0.492 * (color2.b - y2);
|
||||
const auto v2 = 0.877 * (color2.r - y2);
|
||||
|
||||
const auto dy = (y1 - y2);
|
||||
const auto du = (u1 - u2);
|
||||
|
@ -110,4 +181,39 @@ namespace RgbToHires {
|
|||
}
|
||||
|
||||
|
||||
inline ColorRgb ImageQuantized::Quantize(const ColorRgb& color)
|
||||
{
|
||||
const auto distBlack = Distance(BLACK, color);
|
||||
auto distMin = distBlack;
|
||||
const auto distWhite = Distance(WHITE, color);
|
||||
if (distMin > distWhite) { distMin = distWhite; }
|
||||
const auto distBlue = Distance(BLUE, color);
|
||||
if (distMin > distBlue) { distMin = distBlue; }
|
||||
const auto distGreen = Distance(GREEN, color);
|
||||
if (distMin > distGreen) { distMin = distGreen; }
|
||||
const auto distOrange = Distance(ORANGE, color);
|
||||
if (distMin > distOrange) { distMin = distOrange; }
|
||||
const auto distViolet = Distance(VIOLET, color);
|
||||
if (distMin > distViolet) { distMin = distViolet; }
|
||||
|
||||
if (distMin == distBlack) {
|
||||
return BLACK;
|
||||
}
|
||||
else if (distMin == distWhite) {
|
||||
return WHITE;
|
||||
}
|
||||
else if (distMin == distBlue) {
|
||||
return BLUE;
|
||||
}
|
||||
else if (distMin == distGreen) {
|
||||
return GREEN;
|
||||
}
|
||||
else if (distMin == distOrange) {
|
||||
return ORANGE;
|
||||
}
|
||||
else {
|
||||
return VIOLET;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/* Rgb2Hires
|
||||
* Copyright (C) 2016 Christophe Meneboeuf <christophe@xtof.info>
|
||||
* Copyright (C) 2016-2022 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,32 +22,55 @@
|
|||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include "Common.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "HiRes.h"
|
||||
|
||||
namespace RgbToHires {
|
||||
|
||||
/// @brief Image quantized to the HIRES colors.
|
||||
/// @details Quantization works with a nearest distance algorithm.
|
||||
class ImageQuantized :
|
||||
public Magick::Image
|
||||
class ImageQuantized
|
||||
{
|
||||
public:
|
||||
|
||||
using Block = std::array<Magick::PixelPacket,7>;
|
||||
using Line = std::array<Block, 20>;
|
||||
using Blob = std::array<Line, 192>;
|
||||
// rgb
|
||||
using BlockRgb = std::array<ColorRgb, NB_PIXEL_PER_BLOCK>;
|
||||
using Line = std::array<BlockRgb, NB_BLOCK_PER_LINE>;
|
||||
using BlobRgb = std::array<Line, NB_LINES_PER_SCREEN>;
|
||||
// hires
|
||||
using LineHr = std::vector<BlockHr>;
|
||||
using BlobHr = std::array<LineHr, NB_LINES_PER_SCREEN>;
|
||||
|
||||
ImageQuantized(const Magick::Image& src);
|
||||
~ImageQuantized()=default;
|
||||
static constexpr unsigned FRAME_SIZE = 192 * 40 + 512; ///< Frame size in byte
|
||||
|
||||
ImageQuantized(SDL_Surface* const source);
|
||||
~ImageQuantized() = default;
|
||||
|
||||
// Buffer
|
||||
/// @brief Returns the binary hires picture
|
||||
std::unique_ptr <std::array<uint8_t, FRAME_SIZE>> getHiresBuffer() const;
|
||||
/// @brief Returns asm code corresponding to the image in memory (CA65 format)
|
||||
std::string getHiresAsm() const;
|
||||
|
||||
// Tile
|
||||
/// @brief Returns an HIRES tile. The line are not interleaved as in an HIRES framebuffer
|
||||
/// @param col Position: column number of the tile, from 0 to 9
|
||||
/// @param col Position: line number of the tile, from 0 to 11
|
||||
std::string getTile(const unsigned col, const unsigned line) const;
|
||||
|
||||
/// @brief Returns an array of bytes forming the RGB quantized image
|
||||
std::unique_ptr<Blob> getBlob() const;
|
||||
|
||||
private:
|
||||
/// @brief Computes the euclidian distance between two colors
|
||||
double Distance(const Magick::Color&, const Magick::Color&);
|
||||
ColorRgb Quantize(const ColorRgb& color);
|
||||
/// @brief Computes the euclidian distance between two colors
|
||||
double Distance(const ColorRgb&, const ColorRgb&);
|
||||
|
||||
|
||||
private:
|
||||
const SDL_PixelFormatEnum _format;
|
||||
BlobHr _blobHr;
|
||||
std::map<const uint16_t, const LineHr*> _hrOrderedLines; ///< map<adress,line's data>
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
#include "Picture.h"
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace RgbToHires
|
||||
{
|
||||
|
||||
Picture::Picture(const ImageQuantized& source)
|
||||
{
|
||||
auto pixel_src = source.getConstPixels(0u, 0u, WIDTH, HEIGHT);
|
||||
//Filling the storage with BlockHrs
|
||||
for (auto& line : _blob) {
|
||||
line.reserve(NB_BLOCKS_PER_LINE);
|
||||
//Useful data
|
||||
for (auto blockNr = 0u; blockNr < NB_BLOCKS_PER_LINE; ++blockNr) {
|
||||
BlockPixel blockPxRgb;
|
||||
for (auto& pxRgb : blockPxRgb) {
|
||||
pxRgb = *pixel_src++;
|
||||
}
|
||||
line.emplace_back(BlockHr{ blockPxRgb });
|
||||
}
|
||||
}
|
||||
//Constructing the map used to interleave the lines
|
||||
auto i = 0u;
|
||||
for (const auto& line : _blob) {
|
||||
auto addr_interleaved = LineAdresses[i / 8] + LineOffsets[i % 8];
|
||||
_hrOrderedLines.insert(pair<const uint16_t, const LineHr*>(addr_interleaved, &line));
|
||||
++i;
|
||||
}
|
||||
//Adding the 8 byte "memory holes"
|
||||
for (auto line : _hrOrderedLines) {
|
||||
if ((line.first & 0xFF) == 0x50 || (line.first & 0xFF) == 0xD0) {
|
||||
for (auto i = 0u; i < 4u; ++i) {
|
||||
const_cast<LineHr*>(line.second)->emplace_back(BlockHr{});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
unique_ptr<array<uint8_t, Picture::FRAME_SIZE>> Picture::getBlob() const
|
||||
{
|
||||
auto blob = unique_ptr<array<uint8_t, FRAME_SIZE>>{ new array<uint8_t, FRAME_SIZE> };
|
||||
auto byte_blob = begin(*blob);
|
||||
for (const auto& line : _hrOrderedLines) {
|
||||
for (const auto& block : *(line.second)) {
|
||||
for (const auto byte_block : block) {
|
||||
*byte_blob++ = byte_block;
|
||||
}
|
||||
}
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
|
||||
|
||||
|
||||
string Picture::getAsm() const
|
||||
{
|
||||
string assembly{ "Picture:\n" };
|
||||
for (const auto& line : _hrOrderedLines) {
|
||||
assembly += "\t.byte\t";
|
||||
for (const auto& block : *(line.second)) {
|
||||
for (const auto byte : block) {
|
||||
assembly += to_string(byte) + ", ";
|
||||
}
|
||||
}
|
||||
assembly.pop_back(); //removing the last coma
|
||||
assembly.pop_back();
|
||||
assembly += "\n";
|
||||
}
|
||||
return assembly;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/* Rgb2Hires
|
||||
* Copyright (C) 2016 Christophe Meneboeuf <christophe@xtof.info>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _PICTURE_H_
|
||||
#define _PICTURE_H_
|
||||
|
||||
#include "ImageQuantized.h"
|
||||
#include "HiRes.h"
|
||||
|
||||
namespace RgbToHires
|
||||
{
|
||||
|
||||
/// @brief A fullscreen HIRES picture
|
||||
class Picture
|
||||
{
|
||||
static constexpr unsigned NB_BLOCKS_PER_LINE = 20u;
|
||||
static constexpr unsigned NB_LINES_PER_SCREEN = 192u;
|
||||
|
||||
public:
|
||||
static constexpr unsigned FRAME_SIZE = 192 * 40 + 512; ///< Frame size in byte
|
||||
|
||||
Picture(const ImageQuantized&);
|
||||
~Picture() = default;
|
||||
|
||||
/// @brief Returns the binary hires picture
|
||||
std::unique_ptr <std::array<uint8_t, FRAME_SIZE>> getBlob() const;
|
||||
/// @brief Returns asm code corresponding to the image in memory (CA65 format)
|
||||
std::string getAsm() const;
|
||||
|
||||
private:
|
||||
|
||||
using LineHr = std::vector<BlockHr>;
|
||||
using Blob = std::array<LineHr, NB_LINES_PER_SCREEN>;
|
||||
|
||||
Blob _blob; ///< A frame ordered buffer of hires data
|
||||
std::map<const uint16_t, const LineHr*> _hrOrderedLines; ///< map<adress,line's data>
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -13,7 +13,7 @@ RgbToHires::Tile::Tile(const ImageQuantized& source, const unsigned col, const u
|
|||
line.reserve(NB_BLOCKS_PER_TILE);
|
||||
//Useful data
|
||||
for (auto blockNr = 0u; blockNr < NB_BLOCKS_PER_TILE; ++blockNr) {
|
||||
BlockPixel blockPxRgb;
|
||||
BlockPixelRgb blockPxRgb;
|
||||
for (auto& pxRgb : blockPxRgb) {
|
||||
pxRgb = *pixel_src++;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ RgbToHires::Tile::Tile(const ImageQuantized& source, const unsigned col, const u
|
|||
|
||||
}
|
||||
|
||||
std::string RgbToHires::Tile::getAsm() const
|
||||
std::string RgbToHires::Tile::getHiresAsm() const
|
||||
{
|
||||
std::string assembly;
|
||||
for (const auto& line : _blob)
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace RgbToHires
|
|||
~Tile() = default;
|
||||
|
||||
/// @brief Returns asm code corresponding to the tile: the lines are not interleaved!
|
||||
std::string getAsm() const;
|
||||
std::string getHiresAsm() const;
|
||||
|
||||
private:
|
||||
using LineHr = std::vector<BlockHr>;
|
||||
|
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
Loading…
Reference in New Issue