428 lines
11 KiB
C++
428 lines
11 KiB
C++
/**
|
|
* Orthanc - A Lightweight, RESTful DICOM Store
|
|
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
* Department, University Hospital of Liege, Belgium
|
|
* Copyright (C) 2017-2023 Osimis S.A., Belgium
|
|
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
|
|
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
|
|
#include "../PrecompiledHeaders.h"
|
|
#include "Font.h"
|
|
|
|
#if !defined(ORTHANC_ENABLE_LOCALE)
|
|
# error ORTHANC_ENABLE_LOCALE must be defined to use this file
|
|
#endif
|
|
|
|
#if ORTHANC_SANDBOXED == 0
|
|
# include "../SystemToolbox.h"
|
|
#endif
|
|
|
|
#include "../OrthancException.h"
|
|
#include "../Toolbox.h"
|
|
#include "Image.h"
|
|
#include "ImageProcessing.h"
|
|
|
|
#include <cassert>
|
|
#include <stdio.h>
|
|
#include <memory>
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
namespace Orthanc
|
|
{
|
|
Font::Font() :
|
|
size_(0),
|
|
maxHeight_(0)
|
|
{
|
|
}
|
|
|
|
|
|
Font::~Font()
|
|
{
|
|
for (Characters::iterator it = characters_.begin();
|
|
it != characters_.end(); ++it)
|
|
{
|
|
delete it->second;
|
|
}
|
|
}
|
|
|
|
|
|
void Font::LoadFromMemory(const std::string& font)
|
|
{
|
|
Json::Value v;
|
|
if (!Toolbox::ReadJson(v, font) ||
|
|
v.type() != Json::objectValue ||
|
|
!v.isMember("Name") ||
|
|
!v.isMember("Size") ||
|
|
!v.isMember("Characters") ||
|
|
v["Name"].type() != Json::stringValue ||
|
|
v["Size"].type() != Json::intValue ||
|
|
v["Characters"].type() != Json::objectValue)
|
|
{
|
|
throw OrthancException(ErrorCode_BadFont);
|
|
}
|
|
|
|
name_ = v["Name"].asString();
|
|
size_ = v["Size"].asUInt();
|
|
maxHeight_ = 0;
|
|
|
|
Json::Value::Members characters = v["Characters"].getMemberNames();
|
|
|
|
for (size_t i = 0; i < characters.size(); i++)
|
|
{
|
|
const Json::Value& info = v["Characters"][characters[i]];
|
|
if (info.type() != Json::objectValue ||
|
|
!info.isMember("Advance") ||
|
|
!info.isMember("Bitmap") ||
|
|
!info.isMember("Height") ||
|
|
!info.isMember("Top") ||
|
|
!info.isMember("Width") ||
|
|
info["Advance"].type() != Json::intValue ||
|
|
info["Bitmap"].type() != Json::arrayValue ||
|
|
info["Height"].type() != Json::intValue ||
|
|
info["Top"].type() != Json::intValue ||
|
|
info["Width"].type() != Json::intValue)
|
|
{
|
|
throw OrthancException(ErrorCode_BadFont);
|
|
}
|
|
|
|
std::unique_ptr<Character> c(new Character);
|
|
|
|
c->advance_ = info["Advance"].asUInt();
|
|
c->height_ = info["Height"].asUInt();
|
|
c->top_ = info["Top"].asUInt();
|
|
c->width_ = info["Width"].asUInt();
|
|
c->bitmap_.resize(info["Bitmap"].size());
|
|
|
|
if (c->height_ > maxHeight_)
|
|
{
|
|
maxHeight_ = c->height_;
|
|
}
|
|
|
|
for (Json::Value::ArrayIndex j = 0; j < info["Bitmap"].size(); j++)
|
|
{
|
|
if (info["Bitmap"][j].type() != Json::intValue)
|
|
{
|
|
throw OrthancException(ErrorCode_BadFont);
|
|
}
|
|
|
|
int value = info["Bitmap"][j].asInt();
|
|
if (value < 0 || value > 255)
|
|
{
|
|
throw OrthancException(ErrorCode_BadFont);
|
|
}
|
|
|
|
c->bitmap_[j] = static_cast<uint8_t>(value);
|
|
}
|
|
|
|
int index = boost::lexical_cast<int>(characters[i]);
|
|
if (index < 0 || index > 255)
|
|
{
|
|
throw OrthancException(ErrorCode_BadFont);
|
|
}
|
|
|
|
characters_[static_cast<char>(index)] = c.release();
|
|
}
|
|
}
|
|
|
|
|
|
#if ORTHANC_SANDBOXED == 0
|
|
void Font::LoadFromFile(const std::string& path)
|
|
{
|
|
std::string font;
|
|
SystemToolbox::ReadFile(font, path);
|
|
LoadFromMemory(font);
|
|
}
|
|
#endif
|
|
|
|
const std::string &Font::GetName() const
|
|
{
|
|
return name_;
|
|
}
|
|
|
|
unsigned int Font::GetSize() const
|
|
{
|
|
return size_;
|
|
}
|
|
|
|
|
|
static unsigned int MyMin(unsigned int a,
|
|
unsigned int b)
|
|
{
|
|
return a < b ? a : b;
|
|
}
|
|
|
|
|
|
void Font::DrawCharacter(ImageAccessor& target,
|
|
const Character& character,
|
|
int x,
|
|
int y,
|
|
const uint8_t color[4]) const
|
|
{
|
|
// Compute the bounds of the character
|
|
if (x >= static_cast<int>(target.GetWidth()) ||
|
|
y >= static_cast<int>(target.GetHeight()))
|
|
{
|
|
// The character is out of the image
|
|
return;
|
|
}
|
|
|
|
unsigned int left = x < 0 ? -x : 0;
|
|
unsigned int top = y < 0 ? -y : 0;
|
|
unsigned int width = MyMin(character.width_, target.GetWidth() - x);
|
|
unsigned int height = MyMin(character.height_, target.GetHeight() - y);
|
|
|
|
unsigned int bpp = target.GetBytesPerPixel();
|
|
|
|
// Blit the font bitmap OVER the target image
|
|
// https://en.wikipedia.org/wiki/Alpha_compositing
|
|
|
|
for (unsigned int cy = top; cy < height; cy++)
|
|
{
|
|
uint8_t* p = reinterpret_cast<uint8_t*>(target.GetRow(y + cy)) + (x + left) * bpp;
|
|
unsigned int pos = cy * character.width_ + left;
|
|
|
|
switch (target.GetFormat())
|
|
{
|
|
case PixelFormat_Grayscale8:
|
|
{
|
|
assert(bpp == 1);
|
|
for (unsigned int cx = left; cx < width; cx++, pos++, p++)
|
|
{
|
|
uint16_t alpha = character.bitmap_[pos];
|
|
uint16_t value = alpha * static_cast<uint16_t>(color[0]) + (255 - alpha) * static_cast<uint16_t>(*p);
|
|
*p = static_cast<uint8_t>(value >> 8);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PixelFormat_RGB24:
|
|
{
|
|
assert(bpp == 3);
|
|
for (unsigned int cx = left; cx < width; cx++, pos++, p += 3)
|
|
{
|
|
uint16_t alpha = character.bitmap_[pos];
|
|
for (uint8_t i = 0; i < 3; i++)
|
|
{
|
|
uint16_t value = alpha * static_cast<uint16_t>(color[i]) + (255 - alpha) * static_cast<uint16_t>(p[i]);
|
|
p[i] = static_cast<uint8_t>(value >> 8);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PixelFormat_RGBA32:
|
|
case PixelFormat_BGRA32:
|
|
{
|
|
assert(bpp == 4);
|
|
|
|
for (unsigned int cx = left; cx < width; cx++, pos++, p += 4)
|
|
{
|
|
float alpha = static_cast<float>(character.bitmap_[pos]) / 255.0f;
|
|
float beta = (1.0f - alpha) * static_cast<float>(p[3]) / 255.0f;
|
|
float denom = 1.0f / (alpha + beta);
|
|
|
|
for (uint8_t i = 0; i < 3; i++)
|
|
{
|
|
p[i] = static_cast<uint8_t>((alpha * static_cast<float>(color[i]) +
|
|
beta * static_cast<float>(p[i])) * denom);
|
|
}
|
|
|
|
p[3] = static_cast<uint8_t>(255.0f * (alpha + beta));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_NotImplemented);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void Font::DrawInternal(ImageAccessor& target,
|
|
const std::string& utf8,
|
|
int x,
|
|
int y,
|
|
const uint8_t color[4]) const
|
|
{
|
|
if (target.GetFormat() != PixelFormat_Grayscale8 &&
|
|
target.GetFormat() != PixelFormat_RGB24 &&
|
|
target.GetFormat() != PixelFormat_RGBA32 &&
|
|
target.GetFormat() != PixelFormat_BGRA32)
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented);
|
|
}
|
|
|
|
int a = x;
|
|
|
|
#if ORTHANC_ENABLE_LOCALE == 1
|
|
std::string s = Toolbox::ConvertFromUtf8(utf8, Encoding_Latin1);
|
|
#else
|
|
// If the locale support is disabled, simply drop non-ASCII
|
|
// characters from the source UTF-8 string
|
|
std::string s = Toolbox::ConvertToAscii(utf8);
|
|
#endif
|
|
|
|
for (size_t i = 0; i < s.size(); i++)
|
|
{
|
|
if (s[i] == '\n')
|
|
{
|
|
// Go to the next line
|
|
a = x;
|
|
y += maxHeight_ + 1;
|
|
}
|
|
else
|
|
{
|
|
Characters::const_iterator c = characters_.find(s[i]);
|
|
if (c != characters_.end())
|
|
{
|
|
DrawCharacter(target, *c->second, a, y + static_cast<int>(c->second->top_), color);
|
|
a += c->second->advance_;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Font::Draw(ImageAccessor& target,
|
|
const std::string& utf8,
|
|
int x,
|
|
int y,
|
|
uint8_t grayscale) const
|
|
{
|
|
uint8_t color[4] = { grayscale, grayscale, grayscale, 255 };
|
|
DrawInternal(target, utf8, x, y, color);
|
|
}
|
|
|
|
|
|
void Font::Draw(ImageAccessor& target,
|
|
const std::string& utf8,
|
|
int x,
|
|
int y,
|
|
uint8_t r,
|
|
uint8_t g,
|
|
uint8_t b) const
|
|
{
|
|
uint8_t color[4];
|
|
|
|
switch (target.GetFormat())
|
|
{
|
|
case PixelFormat_BGRA32:
|
|
color[0] = b;
|
|
color[1] = g;
|
|
color[2] = r;
|
|
color[3] = 255;
|
|
break;
|
|
|
|
default:
|
|
color[0] = r;
|
|
color[1] = g;
|
|
color[2] = b;
|
|
color[3] = 255;
|
|
break;
|
|
}
|
|
|
|
DrawInternal(target, utf8, x, y, color);
|
|
}
|
|
|
|
|
|
void Font::ComputeTextExtent(unsigned int& width,
|
|
unsigned int& height,
|
|
const std::string& utf8) const
|
|
{
|
|
width = 0;
|
|
height = 0;
|
|
|
|
#if ORTHANC_ENABLE_LOCALE == 1
|
|
std::string s = Toolbox::ConvertFromUtf8(utf8, Encoding_Latin1);
|
|
#else
|
|
// If the locale support is disabled, simply drop non-ASCII
|
|
// characters from the source UTF-8 string
|
|
std::string s = Toolbox::ConvertToAscii(utf8);
|
|
#endif
|
|
|
|
// Compute the text extent
|
|
unsigned int x = 0;
|
|
unsigned int y = 0;
|
|
|
|
for (size_t i = 0; i < s.size(); i++)
|
|
{
|
|
if (s[i] == '\n')
|
|
{
|
|
// Go to the next line
|
|
x = 0;
|
|
y += (maxHeight_ + 1);
|
|
}
|
|
else
|
|
{
|
|
Characters::const_iterator c = characters_.find(s[i]);
|
|
if (c != characters_.end())
|
|
{
|
|
x += c->second->advance_;
|
|
|
|
unsigned int bottom = y + c->second->top_ + c->second->height_;
|
|
if (bottom > height)
|
|
{
|
|
height = bottom;
|
|
}
|
|
|
|
if (x > width)
|
|
{
|
|
width = x;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ImageAccessor* Font::Render(const std::string& utf8,
|
|
PixelFormat format,
|
|
uint8_t r,
|
|
uint8_t g,
|
|
uint8_t b) const
|
|
{
|
|
unsigned int width, height;
|
|
ComputeTextExtent(width, height, utf8);
|
|
|
|
std::unique_ptr<ImageAccessor> target(new Image(format, width, height, false));
|
|
ImageProcessing::Set(*target, 0, 0, 0, 255);
|
|
Draw(*target, utf8, 0, 0, r, g, b);
|
|
|
|
return target.release();
|
|
}
|
|
|
|
|
|
ImageAccessor* Font::RenderAlpha(const std::string& utf8) const
|
|
{
|
|
unsigned int width, height;
|
|
ComputeTextExtent(width, height, utf8);
|
|
|
|
std::unique_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, width, height, false));
|
|
ImageProcessing::Set(*target, 0);
|
|
Draw(*target, utf8, 0, 0, 255);
|
|
|
|
return target.release();
|
|
}
|
|
}
|