Compare commits

...

8 Commits

12 changed files with 378 additions and 15 deletions

9
.gitignore vendored
View File

@@ -1,4 +1,13 @@
# Exclude the executable
trmnl_sdl
# Exclude configuration files
*.json
# Exclude images
*.bmp
*.jpg
*.png
# Exclude fonts
*.ttf

View File

@@ -2,8 +2,9 @@ src_files += main.cpp
src_files += sdl_helpers.cpp
src_files += Widgets/Widget.cpp
src_files += Widgets/WidgetText.cpp
src_files += Widgets/WidgetImage.cpp
src_files += Widgets/WidgetRect.cpp
src_files += Widgets/WidgetText.cpp
all:
g++ $(src_files) -Wall -lSDL2 -lSDL2_ttf -lSDL2_image -o trmnl_sdl

View File

@@ -9,13 +9,26 @@ You will need the following packages: `build-essential` `libsdl2-dev` `libsdl2-i
Just run `make` and an executable called `trmnl_sdl` should be produced.
# How to use
1. You will need to write a JSON config file.
It can either be named `config.json` or you will need to pass it as the first command line parameter to the executable.
### General use
1. You will need to write a JSON config file named `config.json`.
An example structure of the config file can be found in `config_example.json`.
You can find more info on the available Widgets and their parameters at the end of this file.
2. Run the executable
3. Convert the image to a suitable format via an ImageMagick command from below.
### Different ways to supply JSON
1. Default
* Command: `./trmnl_sdl`
* JSON Used: `config.json`
2. Specified file
* Command: `./trmnl_sdl path/to/json/file.json`
* JSON Used: `path/to/json/file.json`
3. Standard input
* Command: `./trmnl_sdl -`
* JSON Used: Supplied on the standard input for the process.
# ImageMagick commands to prepare image for TRMNL device
### Convert image without dithering
`convert trmnl.png -monochrome -colors 2 -depth 1 -strip png:output.png`
@@ -63,13 +76,35 @@ Controls vertical alignment of elements. **String** type. Has several options:
* center - Align objects to be centered
* bottom - Align objects to the bottom
### ImageResize
Controls image resizing. **String** type. Has several options:
* none - Image is not resized and is only clipped by the bounding box
* fit - Image is uniformly scaled to fit inside the box
* stretch - Image is scaled (with possible stretching) to fully fill the box
### TextFit
Controls automatic change the text size depending on contents. **String** type. Has several options:
Controls the automatic change of the text size depending on contents. **String** type. Has several options:
* none - Text is not changed in any way
* shrink - Renders text with desired size and shrinks it to fit if contents are too large
* auto - Renders text with desired size and enlarges/shrinks it to fit if contents are too small/large
# List of Widgets and their parameters
### image
Renders an image with optional scaling
| Name | Type | Required | Default | Description |
| ---: | :--: | :------: | ------: | :---------- |
| x | int | | 0 | Horizontal position in pixels (from left to right) |
| y | int | | 0 | Vertical position in pixels (from top to bottom) |
| width | int | REQ | | Width in pixels |
| height | int | REQ | | Height in pixels |
| filename | string | | | Filename of the image to render |
| resize_type | ImageResize | | fit | Whether to resize the image and how to do so |
| bg_color | color | | 255, 255, 255, 0 (Transparent WHITE) | The background color to fill around the image if it is not fully in the box |
| halign | HAlign | | center | Horizontal alignment of the image |
| valign | VAlign | | center | Vertical alignment of the image |
Supported image formats: **JPG**, **PNG**, **BMP**
### rect
Renders a rectangle with optional rounded corners using either fill or internal stroke.
| Name | Type | Required | Default | Description |
@@ -84,7 +119,6 @@ Renders a rectangle with optional rounded corners using either fill or internal
### text
Renders text within a specified box
| Name | Type | Required | Default | Description |
| ---: | :--: | :------: | ------: | :---------- |
| x | int | | 0 | Horizontal position in pixels (from left to right) |

165
Widgets/WidgetImage.cpp Normal file
View File

@@ -0,0 +1,165 @@
#include "WidgetImage.h"
#include <SDL2/SDL_image.h>
using std::string;
WidgetImage::WidgetImage(int x, int y, int width, int height, string filename,
ImageResize resize_type, HorizontalAlign halign, VerticalAlign valign,
SDL_Color bg_color)
: Widget(x, y, width, height),
m_filename(filename),
m_image_surface(nullptr),
m_resize_type(resize_type),
m_halign(halign),
m_valign(valign),
m_bg_color(bg_color)
{
m_image_surface = IMG_Load(m_filename.c_str());
if (nullptr == m_image_surface)
{
// TODO: Print errors
}
}
void WidgetImage::set_filename(const string& filename)
{
if (m_image_surface != nullptr)
{
SDL_FreeSurface(m_image_surface);
m_image_surface = nullptr;
}
m_filename = filename;
m_image_surface = IMG_Load(m_filename.c_str());
if (nullptr == m_image_surface)
{
// TODO: Print errors
}
}
void WidgetImage::set_resize(ImageResize type)
{
m_resize_type = type;
}
void WidgetImage::set_halign(HorizontalAlign halign)
{
m_halign = halign;
}
void WidgetImage::set_valign(VerticalAlign valign)
{
m_valign = valign;
}
void WidgetImage::set_bg_color(SDL_Color bg_color)
{
m_bg_color = bg_color;
}
void WidgetImage::draw()
{
if (nullptr == m_surface)
{
return;
}
// Clear surface with BG color
SDL_FillRect(m_surface, nullptr,
SDL_MapRGBA(m_surface->format, m_bg_color.r, m_bg_color.g, m_bg_color.b, m_bg_color.a));
if (nullptr == m_image_surface)
{
return;
}
SDL_Rect align;
SDL_Surface* scaled_image = nullptr;
switch (m_resize_type)
{
case RESIZE_NONE:
align = surface_align(m_surface, m_image_surface, m_halign, m_valign);
SDL_BlitSurface(m_image_surface, nullptr, m_surface, &align);
break;
case RESIZE_FIT:
scaled_image = image_scale_fit();
if (nullptr != scaled_image)
{
align = surface_align(m_surface, scaled_image, m_halign, m_valign);
SDL_BlitScaled(scaled_image, nullptr, m_surface, &align);
SDL_FreeSurface(scaled_image);
}
break;
case RESIZE_STRETCH:
SDL_BlitScaled(m_image_surface, nullptr, m_surface, nullptr);
break;
}
}
std::unique_ptr<Widget> WidgetImage::builder(const nlohmann::json& j)
{
int x = 0;
int y = 0;
int width = 0;
int height = 0;
string filename;
ImageResize resize_type = RESIZE_FIT;
HorizontalAlign halign = HALIGN_CENTER;
VerticalAlign valign = VALIGN_CENTER;
SDL_Color bg_color = {.r = 255, .g = 255, .b = 255, .a = SDL_ALPHA_TRANSPARENT};
json_extract(j, "x", x);
json_extract(j, "y", y);
json_extract(j, "width", width);
json_extract(j, "height", height);
json_extract(j, "filename", filename);
json_extract(j, "resize_type", resize_type);
json_extract(j, "halign", halign);
json_extract(j, "valign", valign);
json_extract(j, "bg_color", bg_color);
return std::make_unique<WidgetImage>(x, y, width, height, filename,
resize_type, halign, valign, bg_color);
}
SDL_Surface* WidgetImage::image_scale_fit()
{
SDL_Rect align = {.x = 0, .y = 0, .w = 0, .h = 0};
if ((nullptr == m_surface) || (nullptr == m_image_surface))
{
return nullptr;
}
double x_scale = m_surface->w;
x_scale /= m_image_surface->w;
double y_scale = m_surface->h;
y_scale /= m_image_surface->h;
// Find smallest scale factor
double min_scale = x_scale;
if (y_scale < min_scale)
{
min_scale = y_scale;
}
// Scale with double in the front and then clamp to int via SDL_Rect
align.w = min_scale * m_image_surface->w;
align.h = min_scale * m_image_surface->h;
SDL_Surface* scaled_image_surface =
SDL_CreateRGBSurfaceWithFormat(0, align.w, align.h, 32, SDL_PIXELFORMAT_RGBA8888);
if (nullptr == scaled_image_surface)
{
// TODO: Print Error
return nullptr;
}
SDL_BlitScaled(m_image_surface, nullptr, scaled_image_surface, nullptr);
return scaled_image_surface;
}

59
Widgets/WidgetImage.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef WIDGET_IMAGE_H_
#define WIDGET_IMAGE_H_
#include "Widget.h"
#include <string>
#include "../sdl_helpers.h"
// Renders an image with optional scaling
class WidgetImage : public Widget
{
protected:
// Image filepath
std::string m_filename;
// Image surface
SDL_Surface* m_image_surface;
// Whether to resize the image and how
// RESIZE_NONE - Do not resize
// RESIZE_FIT - Scale the image to fit the rectangle
// RESIZE_STRETCH - Scale and stretch the image to fit the box perfectly
ImageResize m_resize_type;
// Default - center
HorizontalAlign m_halign;
// Default - center
VerticalAlign m_valign;
// Background color to be used
// Default - transparent white
SDL_Color m_bg_color;
public:
WidgetImage(int x, int y, int width, int height, std::string filename,
ImageResize resize_type = RESIZE_FIT,
HorizontalAlign halign = HALIGN_CENTER,
VerticalAlign valign = VALIGN_CENTER,
SDL_Color bg_color = SDL_Color{.r = 255, .g = 255, .b = 255, .a = SDL_ALPHA_TRANSPARENT});
void set_filename(const std::string& filename);
void set_resize(ImageResize type);
void set_halign(HorizontalAlign halign);
void set_valign(VerticalAlign valign);
void set_bg_color(SDL_Color bg_color);
virtual void draw() override;
static std::unique_ptr<Widget> builder(const nlohmann::json& j);
protected:
// Create a new surface of the image_surface that is scaled via RESIZE_FIT method
// NOTE: The user MUST free the surface when done with it
SDL_Surface* image_scale_fit();
};
#endif // WIDGET_IMAGE_H_

View File

@@ -24,8 +24,28 @@ m_radius(radius)
{
}
void WidgetRect::set_color(SDL_Color color)
{
m_color = color;
}
void WidgetRect::set_stroke_size(int stroke_size)
{
m_stroke_size = stroke_size;
}
void WidgetRect::set_radius(int radius)
{
m_radius = radius;
}
void WidgetRect::draw()
{
if (nullptr == m_surface)
{
return;
}
// Clear surface
SDL_FillRect(m_surface, nullptr, SDL_MapRGBA(m_surface->format, 255, 255, 255, SDL_ALPHA_TRANSPARENT));

View File

@@ -27,11 +27,15 @@ public:
WidgetRect(int x, int y, int width, int height,
int radius = 0, int stroke_size = -1);
void set_color(SDL_Color color);
void set_stroke_size(int stroke_size);
void set_radius(int radius);
virtual void draw() override;
static std::unique_ptr<Widget> builder(const nlohmann::json& j);
private:
protected:
// x, y - center of circle
// quadrant - 1--4
void draw_circle_corner(int x, int y, int quadrant);

View File

@@ -168,8 +168,22 @@ void WidgetText::set_color(SDL_Color text_color)
m_text_color = text_color;
}
void WidgetText::set_halign_via_visible(bool value)
{
m_halign_via_visible = value;
}
void WidgetText::set_valign_via_visible(bool value)
{
m_valign_via_visible = value;
}
void WidgetText::draw()
{
if (nullptr == m_surface)
{
return;
}
// Clear surface
SDL_FillRect(m_surface, nullptr, SDL_MapRGBA(m_surface->format, 255, 255, 255, SDL_ALPHA_TRANSPARENT));

View File

@@ -2,7 +2,6 @@
#define WIDGET_TEXT_H_
#include "Widget.h"
#include <SDL2/SDL.h>
#include <string>
@@ -71,6 +70,8 @@ public:
void set_halign(HorizontalAlign halign);
void set_valign(VerticalAlign valign);
void set_color(SDL_Color text_color);
void set_halign_via_visible(bool value);
void set_valign_via_visible(bool value);
virtual void draw() override;

View File

@@ -12,8 +12,9 @@
#include "sdl_helpers.h"
#include "Widgets/Widget.h"
#include "Widgets/WidgetText.h"
#include "Widgets/WidgetImage.h"
#include "Widgets/WidgetRect.h"
#include "Widgets/WidgetText.h"
using std::cout;
using std::endl;
@@ -26,6 +27,7 @@ using nlohmann::json;
void init_builders(map<string, unique_ptr<Widget>(*)(const json&)>& widget_builders)
{
widget_builders["image"] = &WidgetImage::builder;
widget_builders["rect"] = &WidgetRect::builder;
widget_builders["text"] = &WidgetText::builder;
}
@@ -58,8 +60,18 @@ int main(int argc, char **argv)
cfg_filename = argv[1];
}
// Read JSON CFG
ok = read_config_json(cfg, cfg_filename);
// Read config
if ("-" == cfg_filename)
{
// Read JSON from std input
ok = read_config_json(cfg, std::cin);
}
else
{
// Read JSON from config file
ok = read_config_json(cfg, cfg_filename);
}
if (!ok)
{
result = -1;

View File

@@ -87,6 +87,10 @@ SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied,
return align;
}
// These are set in order to handle scaled blits
align.w = applied->w;
align.h = applied->h;
if (nullptr == hint_external)
{
hint = SDL_Rect{.x = 0, .y = 0, .w = applied->w, .h = applied->h};
@@ -137,15 +141,20 @@ bool read_config_json(json& cfg, const string& filename, ostream* log)
{
if (nullptr != log)
{
*log << "Could not open config.json" << endl;
*log << "Could not open config file" << endl;
}
return false;
}
return read_config_json(cfg, cfg_file, log);
}
bool read_config_json(json& cfg, istream& in, ostream* log)
{
// Parse with comments
try
{
cfg = json::parse(cfg_file, nullptr, true, true);
cfg = json::parse(in, nullptr, true, true);
}
catch (const std::exception &e)
{
@@ -167,7 +176,7 @@ void json_extract(const json& j, const string& key, string& out)
}
}
void json_extract(const nlohmann::json& j, const string& key, int& out)
void json_extract(const json& j, const string& key, int& out)
{
if (j.contains(key) && j[key].is_number_integer())
{
@@ -175,7 +184,7 @@ void json_extract(const nlohmann::json& j, const string& key, int& out)
}
}
void json_extract(const nlohmann::json& j, const string& key, bool& out)
void json_extract(const json& j, const string& key, bool& out)
{
if (j.contains(key) && j[key].is_boolean())
{
@@ -238,10 +247,24 @@ void json_extract(const json& j, const string& key, SDL_Color& out)
}
}
void json_extract(const nlohmann::json& j, const string& key, Uint8& out)
void json_extract(const json& j, const string& key, Uint8& out)
{
if (j.contains(key) && j[key].is_number_unsigned())
{
out = j[key];
}
}
void json_extract(const json& j, const string& key, ImageResize& out)
{
if (j.contains(key))
{
try
{
out = j[key].get<ImageResize>();
}
catch(const std::exception& e)
{
}
}
}

View File

@@ -52,6 +52,20 @@ NLOHMANN_JSON_SERIALIZE_ENUM(TextFit,
{FIT_AUTO, "auto"},
})
enum ImageResize
{
RESIZE_NONE,
RESIZE_FIT,
RESIZE_STRETCH
};
NLOHMANN_JSON_SERIALIZE_ENUM(ImageResize,
{
{RESIZE_NONE, "none"},
{RESIZE_FIT, "fit"},
{RESIZE_STRETCH, "stretch"},
})
// Call this before everything
// Prints its messages
bool init_sdl();
@@ -76,6 +90,12 @@ SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied,
// log - output errors to this ostream, silent if NULL
bool read_config_json(nlohmann::json& cfg, const std::string& filename, std::ostream* log = &std::cout);
// Reads the stream and tries to parse JSON from it with comments
// cfg - output json struct
// in - input stream to read
// log - output errors to this ostream, silent if NULL
bool read_config_json(nlohmann::json& cfg, std::istream& in, std::ostream* log = &std::cout);
// JSON Extractors - They do not override already set values if key is not present
void json_extract(const nlohmann::json& j, const std::string& key, std::string& out);
@@ -86,5 +106,6 @@ void json_extract(const nlohmann::json& j, const std::string& key, VerticalAlign
void json_extract(const nlohmann::json& j, const std::string& key, TextFit& out);
void json_extract(const nlohmann::json& j, const std::string& key, SDL_Color& out);
void json_extract(const nlohmann::json& j, const std::string& key, Uint8& out);
void json_extract(const nlohmann::json& j, const std::string& key, ImageResize& out);
#endif // SDL_HELPERS_H_