SDL2 port: Picture executable

This commit is contained in:
Christophe Meneboeuf 2022-06-15 23:38:13 +02:00
parent 823c3dd370
commit 20f85a2dc5
11 changed files with 392 additions and 174 deletions

Binary file not shown.

View File

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 3.12) cmake_minimum_required (VERSION 3.19)
cmake_policy(SET CMP0074 NEW) cmake_policy(SET CMP0074 NEW)
project (Rgb2Hires) project (Rgb2Hires-SDL)
# flags not compatibles with magick++ # flags not compatibles with magick++
if(WIN32) if(WIN32)
@ -16,71 +16,65 @@ set(CMAKE_CXX_STANDARD 17) # required to use <filesystem>
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE ON)
# Conan init
# dependencies include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
if(WIN32) conan_basic_setup(TARGETS)
# 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)
# directories # directories
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/bin/debug) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/bin/debug)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/bin/release) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/bin/release)
# library # Library
add_library(${PROJECT_NAME} src/Common.h add_library(${PROJECT_NAME} src/Common.h
src/HiRes.cpp src/HiRes.cpp
src/HiRes.h src/HiRes.h
src/ImageQuantized.cpp src/ImageQuantized.cpp
src/ImageQuantized.h src/ImageQuantized.h
src/Picture.h # src/Picture.h
src/Picture.cpp # src/Picture.cpp
src/Tile.h # src/Tile.h
src/Tile.cpp # src/Tile.cpp
src/Display.h #src/Display.h
src/Display.cpp #src/Display.cpp
) )
target_compile_definitions(${PROJECT_NAME} PRIVATE MAGICKCORE_QUANTUM_DEPTH=16 MAGICKCORE_HDRI_ENABLE=0) ## dependencies
target_include_directories(${PROJECT_NAME} PRIVATE ${ImageMagick_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS}) conan_set_find_library_paths(${PROJECT_NAME})
conan_target_link_libraries(${PROJECT_NAME})
# Application Picture # Application Picture
add_executable(Picture src/App_Picture.cpp) add_executable( Picture src/App_Picture.cpp)
if(NOT WIN32) if(NOT WIN32)
set_target_properties(Picture PROPERTIES COMPILE_FLAGS -pthread LINK_FLAGS -pthread) set_target_properties(Picture PROPERTIES COMPILE_FLAGS -pthread LINK_FLAGS -pthread)
endif() endif()
target_compile_definitions(Picture PUBLIC MAGICKCORE_QUANTUM_DEPTH=16 MAGICKCORE_HDRI_ENABLE=0) ## dependencies
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
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "9") 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 target_link_libraries(Picture stdc++fs) # filesystem lib not included in stdc++ for gcc < 9
endif() 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 ## Application Tile
add_executable(Tile src/App_Tile.cpp) #add_executable(Tile src/App_Tile.cpp)
target_compile_definitions(Tile PUBLIC MAGICKCORE_QUANTUM_DEPTH=16 MAGICKCORE_HDRI_ENABLE=0) ### custom definitions
target_include_directories(Tile PRIVATE src ${ImageMagick_INCLUDE_DIRS}) #target_compile_definitions(Tile PRIVATE cimg_use_png)
target_link_libraries(Tile ${ImageMagick_LIBRARIES} ${PROJECT_NAME}) ### dependencies
set_property(TARGET Tile PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}) #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()
# Windows only: copy dlls #set_property(TARGET Tile PROPERTY
if(WIN32) # RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG})
set(DEBUG_EXE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copy_im_db_dlls.bat") #target_include_directories(Tile PRIVATE
set(RELEASE_EXE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copy_im_rl_dlls.bat") # ${CONAN_INCLUDE_DIRS_SDL}
string (REPLACE "/" "\\" WIN_BIN_DIR "$<$<CONFIG:debug>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}>$<$<CONFIG:release>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}>") # ${CONAN_INCLUDE_DIRS_TCLAP})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND "$<$<CONFIG:debug>:${DEBUG_EXE_PATH}>$<$<CONFIG:release>:${RELEASE_EXE_PATH}>" ${WIN_BIN_DIR}) #target_link_libraries(Tile PRIVATE
endif(WIN32) # CONAN_PKG::sdl
# ${PROJECT_NAME}
#)
#
#
#

View File

@ -0,0 +1,8 @@
[requires]
sdl/2.0.20
sdl_image/2.0.5
tclap/1.2.4
[generators]
cmake

View File

@ -23,13 +23,13 @@
#include <string> #include <string>
#include <sys/stat.h> #include <sys/stat.h>
#include <Magick++.h> #include <SDL2/SDL_image.h>
#include <tclap/CmdLine.h> #include <tclap/CmdLine.h>
#include "ImageQuantized.h" #include "ImageQuantized.h"
#include "Picture.h" //#include "Picture.h"
#include "Tile.h" //#include "Tile.h"
#include "Display.h" //#include "Display.h"
using namespace std; using namespace std;
using namespace RgbToHires; using namespace RgbToHires;
@ -41,10 +41,39 @@ inline bool exists(const std::string& path)
return (stat(path.c_str(), &buffer) == 0); 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 /// @brief Program entry point
int main( int argc, char *argv[] ) 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 //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");
@ -59,41 +88,48 @@ int main( int argc, char *argv[] )
cmd.parse(argc, argv); cmd.parse(argc, argv);
if (imagePath.getValue().size() == 0 || outputPath.getValue().size() == 0) { if (imagePath.getValue().size() == 0 || outputPath.getValue().size() == 0) {
std::cout << "No input or output path provided." << std::endl; std::cout << "No input or output path provided." << std::endl;
IMG_Quit();
return -1; return -1;
} }
try
{ const auto filepath = imagePath.getValue();
const auto filepath = imagePath.getValue(); if (!exists(filepath)) {
if (!exists(filepath)) { ExitOnError("Cannot read " + 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());
}
if (preview.getValue()) {
const auto bytes = imageHiRes.getBlob();
Display::Window::GetInstance()->display(filepath, bytes->data());
}
} }
//Fatal error std::vector<SDL_Surface*> surfaces;
catch (const exception& e) { SDL_Surface* surfaceRgb = IMG_Load(filepath.c_str());
cout << e.what(); surfaces.push_back(surfaceRgb);
return -1; if (surfaceRgb == nullptr)
{
ExitOnError("Cannot decode " + filepath, surfaces);
} }
const ImageQuantized imageHiRes{ surfaceRgb };
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());
}
//if (preview.getValue()) {
// const auto bytes = imageHiRes.getBlob();
// Display::Window::GetInstance()->display(filepath, bytes->data());
//}
for (auto surface : surfaces)
{
SDL_FreeSurface(surface);
}
SDL_Quit();
IMG_Quit();
return 0; return 0;
} }

View File

@ -20,25 +20,32 @@
#ifndef __COMMON_H__ #ifndef __COMMON_H__
#define __COMMON_H__ #define __COMMON_H__
#include <cstdint>
#include <Magick++.h> #include <SDL2/SDL.h>
#define MAGICKCORE_ZERO_CONFIGURATION_SUPPORT 1
namespace RgbToHires { namespace RgbToHires {
constexpr std::size_t SHIFT = sizeof(Magick::Quantum)*8 - 8; struct Color : public SDL_Color
{
inline bool operator==(const Color& 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;
}
};
#define WHITE Magick::Color{0xFF << SHIFT, 0xFF << SHIFT, 0xFF << SHIFT} constexpr Color WHITE {0xFF, 0xFF, 0xFF, 0xFF};
#define BLACK Magick::Color{0x00 << SHIFT, 0x00 << SHIFT, 0x00 << SHIFT} constexpr Color BLACK {0x00, 0x00, 0x00, 0xFF};
#define BLUE Magick::Color{0x07 << SHIFT, 0xA8 << SHIFT, 0xE0 << SHIFT} constexpr Color BLUE {0x07, 0xA8, 0xE0, 0xFF};
#define GREEN Magick::Color{0x43 << SHIFT, 0xC8 << SHIFT, 0x00 << SHIFT} constexpr Color GREEN {0x43, 0xC8, 0x00, 0xFF};
#define ORANGE Magick::Color{0xF9 << SHIFT, 0x56 << SHIFT,0x1D << SHIFT} constexpr Color ORANGE{0xF9, 0x56, 0x1D, 0xFF};
#define VIOLET Magick::Color{0xBB << SHIFT, 0x36 << SHIFT, 0xFF << SHIFT} constexpr Color VIOLET{0xBB, 0x36, 0xFF, 0xFF};
constexpr unsigned WIDTH = 140u;
constexpr unsigned WIDTH = 140u; constexpr unsigned HEIGHT = 192u;
constexpr unsigned HEIGHT = 192u;
} }

View File

@ -17,7 +17,7 @@
*/ */
#include <stdexcept> #include <stdexcept>
#include "ImageQuantized.h" //#include "ImageQuantized.h"
#include "HiRes.h" #include "HiRes.h"
@ -96,7 +96,7 @@ namespace RgbToHires {
} }
uint8_t BlockHr::getDibit(const Magick::Color& color) const uint8_t BlockHr::getDibit(const Color& color) const
{ {
if (color == WHITE) { if (color == WHITE) {
return 3; return 3;
@ -112,7 +112,7 @@ namespace RgbToHires {
} }
else { else {
auto msg = string("Unsupported color used as input for hires pixel\nRGB:"); 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)); throw(runtime_error(msg));
} }
} }

View File

@ -31,6 +31,11 @@
namespace RgbToHires namespace RgbToHires
{ {
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 = { constexpr std::array<const uint16_t, 192 / 8> LineAdresses = {
0x0000, 0x0080, 0x0100, 0x0180, 0x0200, 0x0280, 0x0300, 0x0380, 0x0000, 0x0080, 0x0100, 0x0180, 0x0200, 0x0280, 0x0300, 0x0380,
0x0028, 0x00a8, 0x0128, 0x01a8, 0x0228, 0x02a8, 0x0328, 0x03a8, 0x0028, 0x00a8, 0x0128, 0x01a8, 0x0228, 0x02a8, 0x0328, 0x03a8,
@ -41,7 +46,7 @@ namespace RgbToHires
0x0, 0x400, 0x800, 0xc00, 0x1000, 0x1400, 0x1800, 0x1c00 0x0, 0x400, 0x800, 0xc00, 0x1000, 0x1400, 0x1800, 0x1c00
}; };
using BlockPixel = std::array<Magick::PixelPacket, 7u>; using BlockPixel = std::array<Color, 7u>;
/// @brief A block of 7 pixels /// @brief A block of 7 pixels
class BlockHr class BlockHr
@ -69,7 +74,7 @@ namespace RgbToHires
/// @brief Returns the color group of these two 3.5 pixel blocks /// @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 BlockPixel&) const;
/// @brief Returns the bit pait corresponding to the given color /// @brief Returns the bit pait corresponding to the given color
uint8_t getDibit(const Magick::Color&) const; uint8_t getDibit(const Color&) const;
std::array<uint8_t, 2> _data; std::array<uint8_t, 2> _data;
}; };

View File

@ -1,6 +1,6 @@
/* Rgb2Hires /*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 * 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 * it under the terms of the GNU General Public License as published by
@ -18,6 +18,7 @@
#include <stdexcept> #include <stdexcept>
#include <iterator> #include <iterator>
#include "ImageQuantized.h" #include "ImageQuantized.h"
using namespace std; using namespace std;
@ -25,60 +26,69 @@ using namespace std;
namespace RgbToHires { namespace RgbToHires {
ImageQuantized::ImageQuantized(const Magick::Image& src): ImageQuantized::ImageQuantized(SDL_Surface* const src) :
Magick::Image(src) _format(SDL_PIXELFORMAT_BGRX8888)
{ {
const auto dim = size(); if (src->h != HEIGHT || src->w != WIDTH) {
if (dim.height() != HEIGHT || dim.width() != WIDTH) {
throw std::runtime_error("Image dimension must be 140x192 pixels."); 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) { SDL_Surface* rgb = SDL_ConvertSurfaceFormat(src, SDL_PIXELFORMAT_BGRX8888, 0);
*pixelpacket++ = BLACK;
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)
{
Color 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::getBlob() const
{ {
auto blob = std::unique_ptr<Blob>(new Blob); auto blob = unique_ptr<array<uint8_t, FRAME_SIZE>>{ new array<uint8_t, FRAME_SIZE> };
auto pixels = getConstPixels(0u, 0u, WIDTH, HEIGHT); auto byte_blob = begin(*blob);
for (auto& line : *blob) { for (const auto& line : _hrOrderedLines)
for (auto& block : line) { {
for (auto pixel: block ) { for (const auto& block : *(line.second))
pixel = *pixels++; {
for (const auto byte_block : block)
{
*byte_blob++ = byte_block;
} }
} }
} }
@ -87,20 +97,42 @@ namespace RgbToHires {
string ImageQuantized::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;
}
double ImageQuantized::Distance(const Magick::Color& color1, const Magick::Color& color2)
double ImageQuantized::Distance(const Color& color1, const Color& color2)
{ {
static constexpr double LUMA_RED = 0.299; static constexpr double LUMA_RED = 0.299;
static constexpr double LUMA_GREEN = 0.587; static constexpr double LUMA_GREEN = 0.587;
static constexpr double LUMA_BLUE = 0.114; static constexpr double LUMA_BLUE = 0.114;
const auto y1 = LUMA_RED * color1.redQuantum() + LUMA_GREEN * color1.greenQuantum() + LUMA_BLUE * color1.blueQuantum(); const auto y1 = LUMA_RED * color1.r + LUMA_GREEN * color1.g + LUMA_BLUE * color1.b;
const auto u1 = 0.492 * (color1.blueQuantum() - y1); const auto u1 = 0.492 * (color1.b - y1);
const auto v1 = 0.877 * (color1.redQuantum() - y1); const auto v1 = 0.877 * (color1.r - y1);
const auto y2 = LUMA_RED * color2.redQuantum() + LUMA_GREEN * color2.greenQuantum() + LUMA_BLUE * color2.blueQuantum(); const auto y2 = LUMA_RED * color2.r + LUMA_GREEN * color2.g + LUMA_BLUE * color2.b;
const auto u2 = 0.492 * (color2.blueQuantum() - y2); const auto u2 = 0.492 * (color2.b - y2);
const auto v2 = 0.877 * (color2.redQuantum() - y2); const auto v2 = 0.877 * (color2.r - y2);
const auto dy = (y1 - y2); const auto dy = (y1 - y2);
const auto du = (u1 - u2); const auto du = (u1 - u2);
@ -110,4 +142,38 @@ namespace RgbToHires {
} }
inline Color ImageQuantized::Quantize(const Color& 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 == 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;
}
}
} }

View File

@ -22,32 +22,49 @@
#include <memory> #include <memory>
#include <array> #include <array>
#include "Common.h" #include <map>
#include <string>
#include "Common.h"
#include "HiRes.h"
namespace RgbToHires { namespace RgbToHires {
/// @brief Image quantized to the HIRES colors. /// @brief Image quantized to the HIRES colors.
/// @details Quantization works with a nearest distance algorithm. /// @details Quantization works with a nearest distance algorithm.
class ImageQuantized : class ImageQuantized
public Magick::Image
{ {
public: public:
using Block = std::array<Magick::PixelPacket,7>; // rgb
using Line = std::array<Block, 20>; using BlockRgb = std::array<Color, NB_PIXEL_PER_BLOCK>;
using Blob = std::array<Line, 192>; 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); static constexpr unsigned FRAME_SIZE = 192 * 40 + 512; ///< Frame size in byte
~ImageQuantized()=default;
/// @brief Returns an array of bytes forming the RGB quantized image ImageQuantized(SDL_Surface* const source);
std::unique_ptr<Blob> getBlob() const; ~ImageQuantized() = 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: private:
/// @brief Computes the euclidian distance between two colors Color Quantize(const Color& color);
double Distance(const Magick::Color&, const Magick::Color&); /// @brief Computes the euclidian distance between two colors
double Distance(const Color&, const Color&);
private:
const SDL_PixelFormatEnum _format;
BlobHr _blobHr;
std::map<const uint16_t, const LineHr*> _hrOrderedLines; ///< map<adress,line's data>
}; };
} }

View File

@ -1,3 +1,5 @@
#include <memory>
#include "Picture.h" #include "Picture.h"
@ -6,24 +8,35 @@ using namespace std;
namespace RgbToHires namespace RgbToHires
{ {
Picture::Picture(const ImageQuantized& source) Picture::Picture(const CImg<uint8_t>& source)
{ {
auto pixel_src = source.getConstPixels(0u, 0u, WIDTH, HEIGHT);
//Filling the storage with BlockHrs //Filling the storage with BlockHrs
for (auto& line : _blob) { int lineNr = 0;
for (auto& line : _blobRgb)
{
int colNr = 0;
line.reserve(NB_BLOCKS_PER_LINE); line.reserve(NB_BLOCKS_PER_LINE);
//Useful data //Useful data
for (auto blockNr = 0u; blockNr < NB_BLOCKS_PER_LINE; ++blockNr) { for (auto blockNr = 0u; blockNr < NB_BLOCKS_PER_LINE; ++blockNr)
{
BlockPixel blockPxRgb; BlockPixel blockPxRgb;
for (auto& pxRgb : blockPxRgb) { for (auto& pxRgb : blockPxRgb)
pxRgb = *pixel_src++; {
pxRgb = {
source(colNr, lineNr, 0, 0),
source(colNr, lineNr, 0, 1),
source(colNr, lineNr, 0, 2)
};
++colNr;
} }
line.emplace_back(BlockHr{ blockPxRgb }); line.emplace_back(BlockHr{ blockPxRgb });
} }
++lineNr;
} }
//Constructing the map used to interleave the lines //Constructing the map used to interleave the lines
auto i = 0u; auto i = 0u;
for (const auto& line : _blob) { for (const auto& line : _blobRgb) {
auto addr_interleaved = LineAdresses[i / 8] + LineOffsets[i % 8]; auto addr_interleaved = LineAdresses[i / 8] + LineOffsets[i % 8];
_hrOrderedLines.insert(pair<const uint16_t, const LineHr*>(addr_interleaved, &line)); _hrOrderedLines.insert(pair<const uint16_t, const LineHr*>(addr_interleaved, &line));
++i; ++i;
@ -73,4 +86,74 @@ namespace RgbToHires
return assembly; return assembly;
} }
void Picture::Quantize()
{
/*const auto dim = size();
if (dim.height() != HEIGHT || dim.width() != 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;
}
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;
}
}
}*/
}
double Picture::Distance(const RGB_t& color1, const RGB_t& 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.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);
const auto dv = (v1 - v2);
return (dy * dy + du * du + dv * dv);
}
} }

View File

@ -18,6 +18,8 @@
#ifndef _PICTURE_H_ #ifndef _PICTURE_H_
#define _PICTURE_H_ #define _PICTURE_H_
#include <map>
#include "ImageQuantized.h" #include "ImageQuantized.h"
#include "HiRes.h" #include "HiRes.h"
@ -46,7 +48,7 @@ namespace RgbToHires
using LineHr = std::vector<BlockHr>; using LineHr = std::vector<BlockHr>;
using Blob = std::array<LineHr, NB_LINES_PER_SCREEN>; using Blob = std::array<LineHr, NB_LINES_PER_SCREEN>;
Blob _blob; ///< A frame ordered buffer of hires data Blob _blobRgb; ///< A frame ordered buffer of hires data
std::map<const uint16_t, const LineHr*> _hrOrderedLines; ///< map<adress,line's data> std::map<const uint16_t, const LineHr*> _hrOrderedLines; ///< map<adress,line's data>
}; };