Mercurial > hg > orthanc
view OrthancFramework/Sources/Images/Font.cpp @ 5626:72dcf3a8d7d5
todo: Combine StudyDate + StudyTime in C-Find matching
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Thu, 16 May 2024 15:31:31 +0200 |
parents | 48b8dae6dc77 |
children | f7adfb22e20e |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2024 Osimis S.A., Belgium * Copyright (C) 2021-2024 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(); } }