mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
279 lines
7 KiB
C++
279 lines
7 KiB
C++
#include "image.h"
|
|
#include "xlog.h"
|
|
#include <SDL3_image/SDL_image.h>
|
|
|
|
using namespace gfx;
|
|
|
|
|
|
// Simple helper function to load an image into a OpenGL texture with common settings
|
|
bool LoadTextureFromFile(const char* filename, Image &img)
|
|
{
|
|
|
|
SDL_Surface *surface, *temp;
|
|
|
|
surface = IMG_Load(filename);
|
|
if (!surface) {
|
|
SDL_Log("Couldn't load %s: %s\n", filename, SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
/* Use the tonemap operator to convert to SDR output */
|
|
const char *tonemap = NULL;
|
|
SDL_SetStringProperty(SDL_GetSurfaceProperties(surface), SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING, tonemap);
|
|
temp = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA32);
|
|
SDL_DestroySurface(surface);
|
|
if (!temp) {
|
|
SDL_Log("Couldn't convert surface: %s\n", SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
img.texture = SDL_CreateTextureFromSurface(renderer, temp);
|
|
SDL_DestroySurface(temp);
|
|
if (!img.texture) {
|
|
SDL_Log("Couldn't create texture: %s\n", SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
SDL_QueryTexture(static_cast<SDL_Texture*>(img.texture), NULL, NULL, &img.w, &img.h);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
Image::Image(Display &system, const std::string &path, bool isSvg)
|
|
: Entity(system)
|
|
, mFileName(path)
|
|
, mIsSvg(isSvg)
|
|
{
|
|
}
|
|
|
|
Image::~Image()
|
|
{
|
|
GfxEngine::DestroyTexture(TextureName());
|
|
}
|
|
|
|
std::string Image::TextureName() const
|
|
{
|
|
// On ajoute le scale car plusieurs texture avec le même fichier peuvent être créées
|
|
// mais avec des scales différents
|
|
// ça ne change rien pour les images RAW (png, jpg...)
|
|
return mFileName + std::to_string(mSvgScale);
|
|
}
|
|
|
|
void Image::SetActive(bool active)
|
|
{
|
|
mIsActive = active;
|
|
}
|
|
|
|
|
|
void Image::HandleOnClick(std::function<void(void)> callback)
|
|
{
|
|
mCallback = callback;
|
|
}
|
|
|
|
void Image::OnClick()
|
|
{
|
|
if (mCallback && mIsActive)
|
|
{
|
|
mCallback();
|
|
}
|
|
}
|
|
|
|
bool Image::IsCursorOver(const SDL_Point &pos, const Vector2 &origin) const
|
|
{
|
|
SDL_Rect rect = GetRect();
|
|
rect.x += origin.x;
|
|
rect.y += origin.y;
|
|
return SDL_PointInRect(&pos, &rect);
|
|
}
|
|
|
|
|
|
void Image::ProcessEvent(const SDL_Event &event, const Vector2 &origin)
|
|
{
|
|
SDL_Point mousePos;
|
|
mousePos.x = event.button.x;
|
|
mousePos.y = event.button.y;
|
|
|
|
bool isOver = IsCursorOver(mousePos, origin) && mIsActive;
|
|
|
|
if (event.type == SDL_MOUSEBUTTONUP)
|
|
{
|
|
if (isOver)
|
|
{
|
|
OnClick();
|
|
}
|
|
}
|
|
if (event.type == SDL_MOUSEMOTION)
|
|
{
|
|
if (isOver)
|
|
{
|
|
if (!mHighlight)
|
|
{
|
|
// Entered
|
|
mHighlight = true;
|
|
SetBlendMode(SDL_BLENDMODE_ADD);
|
|
SetColorMod({250, 250, 250, 255});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mHighlight)
|
|
{
|
|
// Exited
|
|
mHighlight = false;
|
|
SetBlendMode(SDL_BLENDMODE_BLEND);
|
|
SetColorMod({255, 255, 255, 255});
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void Image::OnCreate(SDL_Renderer *renderer)
|
|
{
|
|
Entity::OnCreate(renderer);
|
|
|
|
SDL_Texture *tex = nullptr;
|
|
|
|
if (GfxEngine::HasTexture(mFileName))
|
|
{
|
|
tex = GfxEngine::GetTexture(mFileName);
|
|
}
|
|
else
|
|
{
|
|
if (mIsSvg)
|
|
{
|
|
if (GfxEngine::LoadFile(mFileName.c_str(), mSvg))
|
|
{
|
|
std::string svg = UpdateSvg(mSvg);
|
|
|
|
float scale = mSvgScale * GetSystem().Ratio();
|
|
tex = Image::RenderSVG(renderer, svg.data(), scale);
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("[IMAGE] Cannot load SVG file");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tex = LoadImage(renderer, mFileName.c_str());
|
|
}
|
|
|
|
// Texture created, store it into our texture library
|
|
GfxEngine::StoreTexture(TextureName(), tex);
|
|
}
|
|
|
|
SetTexture(tex);
|
|
}
|
|
|
|
#define NANOSVG_IMPLEMENTATION
|
|
#include "nanosvg.h"
|
|
#define NANOSVGRAST_IMPLEMENTATION
|
|
#include "nanosvgrast.h"
|
|
|
|
/* Load a SVG type image from an SDL datasource */
|
|
SDL_Texture *Image::LoadSVG(SDL_Renderer *renderer, const char *filename)
|
|
{
|
|
std::string data;
|
|
SDL_Texture * tex = nullptr;
|
|
|
|
if (GfxEngine::LoadFile(filename, data))
|
|
{
|
|
tex = Image::RenderSVG(renderer, data.data());
|
|
}
|
|
|
|
if (tex == nullptr)
|
|
{
|
|
// errror, default texture so that the pointer is valid
|
|
tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 20, 20);
|
|
}
|
|
|
|
return tex;
|
|
}
|
|
|
|
SDL_Texture *Image::RenderSVG(SDL_Renderer *renderer, const std::string &svgData, float scale)
|
|
{
|
|
std::string data = svgData; // on est obligé de créer une copie car nsvgParse modifie le code SVG
|
|
struct NSVGimage *image;
|
|
struct NSVGrasterizer *rasterizer;
|
|
SDL_Surface *surface = NULL;
|
|
|
|
/* For now just use default units of pixels at 96 DPI */
|
|
image = nsvgParse(data.data(), "px", 96.0f);
|
|
|
|
if ( !image ) {
|
|
//IMG_SetError("Couldn't parse SVG image");
|
|
return NULL;
|
|
}
|
|
|
|
rasterizer = nsvgCreateRasterizer();
|
|
if ( !rasterizer ) {
|
|
// IMG_SetError("Couldn't create SVG rasterizer");
|
|
nsvgDelete( image );
|
|
return NULL;
|
|
}
|
|
|
|
surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
|
|
(int)(image->width * scale),
|
|
(int)(image->height * scale),
|
|
32,
|
|
0x000000FF,
|
|
0x0000FF00,
|
|
0x00FF0000,
|
|
0xFF000000);
|
|
if ( !surface ) {
|
|
nsvgDeleteRasterizer( rasterizer );
|
|
nsvgDelete( image );
|
|
return NULL;
|
|
}
|
|
|
|
nsvgRasterize(rasterizer, image, 0.0f, 0.0f, scale, (unsigned char *)surface->pixels, surface->w, surface->h, surface->pitch);
|
|
nsvgDeleteRasterizer( rasterizer );
|
|
nsvgDelete( image );
|
|
|
|
return SDL_CreateTextureFromSurface(renderer, surface);
|
|
}
|
|
|
|
SDL_Texture *Image::LoadImage(SDL_Renderer *renderer, const char* filename)
|
|
{
|
|
// Read data
|
|
int32_t width, height, bytesPerPixel;
|
|
void* data = stbi_load(filename, &width, &height, &bytesPerPixel, 0);
|
|
|
|
// Calculate pitch
|
|
int pitch;
|
|
pitch = width * bytesPerPixel;
|
|
pitch = (pitch + 3) & ~3;
|
|
|
|
// Setup relevance bitmask
|
|
int32_t Rmask, Gmask, Bmask, Amask;
|
|
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
|
|
Rmask = 0x000000FF;
|
|
Gmask = 0x0000FF00;
|
|
Bmask = 0x00FF0000;
|
|
Amask = (bytesPerPixel == 4) ? 0xFF000000 : 0;
|
|
#else
|
|
int s = (bytesPerPixel == 4) ? 0 : 8;
|
|
Rmask = 0xFF000000 >> s;
|
|
Gmask = 0x00FF0000 >> s;
|
|
Bmask = 0x0000FF00 >> s;
|
|
Amask = 0x000000FF >> s;
|
|
#endif
|
|
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(data, width, height, bytesPerPixel*8, pitch, Rmask, Gmask, Bmask, Amask);
|
|
SDL_Texture* t = nullptr;
|
|
if (surface)
|
|
{
|
|
t = SDL_CreateTextureFromSurface(renderer, surface);
|
|
}
|
|
else
|
|
{
|
|
t = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 20, 20);
|
|
}
|
|
|
|
STBI_FREE(data);
|
|
SDL_FreeSurface(surface);
|
|
return t;
|
|
}
|
|
|