Mercurial > hg > orthanc-stone
changeset 2227:71e573e4dcc1
added GlyphAlphabet::IndentUtf8() and GlyphBitmapAlphabet::RenderColorText()
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 25 Apr 2025 14:20:44 +0200 |
parents | 639b4960bb51 |
children | 0191a7693b50 |
files | OrthancStone/Sources/Fonts/GlyphAlphabet.cpp OrthancStone/Sources/Fonts/GlyphAlphabet.h OrthancStone/Sources/Fonts/GlyphBitmapAlphabet.cpp OrthancStone/Sources/Fonts/GlyphBitmapAlphabet.h OrthancStone/Sources/Fonts/GlyphTextureAlphabet.cpp OrthancStone/Sources/Toolbox/DynamicBitmap.cpp OrthancStone/Sources/Toolbox/DynamicBitmap.h OrthancStone/UnitTestsSources/CMakeLists.txt OrthancStone/UnitTestsSources/ImageToolboxTests.cpp |
diffstat | 9 files changed, 317 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancStone/Sources/Fonts/GlyphAlphabet.cpp Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/Sources/Fonts/GlyphAlphabet.cpp Fri Apr 25 14:20:44 2025 +0200 @@ -23,6 +23,8 @@ #include "GlyphAlphabet.h" +#include "../Toolbox/DynamicBitmap.h" + #include <OrthancException.h> #include <Toolbox.h> @@ -142,6 +144,8 @@ void GlyphAlphabet::Apply(ITextVisitor& visitor, const std::string& utf8) const { + DynamicBitmap empty(Orthanc::PixelFormat_Grayscale8, 0, 0, true); + size_t pos = 0; int x = 0; int y = 0; @@ -167,21 +171,33 @@ size_t length; Orthanc::Toolbox::Utf8ToUnicodeCharacter(unicode, length, utf8, pos); - Content::const_iterator glyph = content_.find(unicode); - - if (glyph != content_.end()) + if (unicode == '\r' || + IsDeviceControlCharacter(unicode)) { - assert(glyph->second != NULL); - const Orthanc::IDynamicObject* payload = - (glyph->second->HasPayload() ? &glyph->second->GetPayload() : NULL); + /** + * This is a device control character, which is used to change the color of the text. + * Make sure that such a character is invisible (i.e., zero width and height). + **/ + visitor.Visit(unicode, x, y, 0, 0, &empty); + } + else + { + Content::const_iterator glyph = content_.find(unicode); + + if (glyph != content_.end()) + { + assert(glyph->second != NULL); + const Orthanc::IDynamicObject* payload = + (glyph->second->HasPayload() ? &glyph->second->GetPayload() : NULL); - visitor.Visit(unicode, - x + glyph->second->GetOffsetLeft(), - y + glyph->second->GetOffsetTop(), - glyph->second->GetWidth(), - glyph->second->GetHeight(), - payload); - x += glyph->second->GetAdvanceX(); + visitor.Visit(unicode, + x + glyph->second->GetOffsetLeft(), + y + glyph->second->GetOffsetTop(), + glyph->second->GetWidth(), + glyph->second->GetHeight(), + payload); + x += glyph->second->GetAdvanceX(); + } } assert(length != 0); @@ -189,4 +205,135 @@ } } } + + + bool GlyphAlphabet::IsDeviceControlCharacter(uint32_t unicode) + { + return (unicode == 0x11 || + unicode == 0x12 || + unicode == 0x13 || + unicode == 0x14); + } + + + static void Copy(std::string& target, + const std::string& source, + size_t start, + size_t end, + bool ignoreDeviceControl) + { + size_t i = start; + while (i < end) + { + uint32_t unicode; + size_t length; + Orthanc::Toolbox::Utf8ToUnicodeCharacter(unicode, length, source, i); + assert(length != 0); + + if (unicode != '\r' && + (!ignoreDeviceControl || !GlyphAlphabet::IsDeviceControlCharacter(unicode))) + { + for (size_t j = 0; j < length; j++) + { + target.push_back(source[i + j]); + } + } + + i += length; + } + + if (i != end) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + void GlyphAlphabet::IndentUtf8(std::string& target, + const std::string& source, + unsigned int maxLineWidth, + bool ignoreDeviceControl /* whether DC1, DC2, DC3, and DC4 codes are used to change color */) + { + if (maxLineWidth == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + target.clear(); + target.reserve(source.size()); + + unsigned int currentLineWidth = 0; + + size_t pos = 0; + while (pos < source.size()) + { + if (source[pos] == ' ' || + (ignoreDeviceControl && IsDeviceControlCharacter(source[pos]))) + { + pos++; + } + else if (source[pos] == '\n') + { + target.push_back('\n'); + currentLineWidth = 0; + pos++; + } + else + { + // We are at the beginning of a word + size_t wordEnd = pos; + unsigned int wordLength = 0; // Will be smaller than "wordEnd - pos" because of UTF8 + while (wordEnd < source.size()) + { + uint32_t unicode; + size_t length; + Orthanc::Toolbox::Utf8ToUnicodeCharacter(unicode, length, source, wordEnd); + assert(length != 0); + + if (unicode == '\r' || + (ignoreDeviceControl && IsDeviceControlCharacter(unicode))) + { + // Ignore carriage returns (and possibly device control characters) + wordEnd += length; + } + else if (unicode == '\n' || + unicode == ' ') + { + break; // We found the end of the word + } + else + { + wordEnd += length; + wordLength ++; + } + } + + if (wordLength != 0) + { + if (currentLineWidth == 0) + { + Copy(target, source, pos, wordEnd, ignoreDeviceControl); + currentLineWidth = wordLength; + } + else + { + if (currentLineWidth + wordLength + 1 <= maxLineWidth) + { + target.push_back(' '); + Copy(target, source, pos, wordEnd, ignoreDeviceControl); + currentLineWidth += wordLength + 1; + } + else + { + target.push_back('\n'); + Copy(target, source, pos, wordEnd, ignoreDeviceControl); + currentLineWidth = wordLength; + } + } + } + + pos = wordEnd; + } + } + } }
--- a/OrthancStone/Sources/Fonts/GlyphAlphabet.h Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/Sources/Fonts/GlyphAlphabet.h Fri Apr 25 14:20:44 2025 +0200 @@ -106,5 +106,12 @@ void Apply(ITextVisitor& visitor, const std::string& utf8) const; + + static bool IsDeviceControlCharacter(uint32_t unicode); + + static void IndentUtf8(std::string& target, + const std::string& source, + unsigned int maxLineWidth, + bool ignoreDeviceControl /* whether DC1, DC2, DC3, and DC4 codes are used to change color */); }; }
--- a/OrthancStone/Sources/Fonts/GlyphBitmapAlphabet.cpp Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/Sources/Fonts/GlyphBitmapAlphabet.cpp Fri Apr 25 14:20:44 2025 +0200 @@ -25,6 +25,7 @@ #include "TextBoundingBox.h" #include "../Toolbox/DynamicBitmap.h" +#include "../Toolbox/ImageToolbox.h" #include <Images/Image.h> #include <Images/ImageProcessing.h> @@ -38,6 +39,12 @@ const GlyphBitmapAlphabet& that_; int offsetX_; int offsetY_; + bool useColors_; + Color activeColor_; + Color color1_; + Color color2_; + Color color3_; + Color color4_; public: RenderTextVisitor(Orthanc::ImageAccessor& target, @@ -47,10 +54,24 @@ target_(target), that_(that), offsetX_(offsetX), - offsetY_(offsetY) + offsetY_(offsetY), + useColors_(false) { } + void SetColors(const Color& color1, + const Color& color2, + const Color& color3, + const Color& color4) + { + useColors_ = true; + activeColor_ = color1; + color1_ = color1; + color2_ = color2; + color3_ = color3; + color4_ = color4; + } + virtual void Visit(uint32_t unicode, int x, int y, @@ -70,7 +91,35 @@ static_cast<unsigned int>(top) + height <= target_.GetHeight() && width == glyph.GetBitmap().GetWidth() && height == glyph.GetBitmap().GetHeight()); - + + if (useColors_) + { + if (unicode == 0x11) + { + activeColor_ = color1_; + } + else if (unicode == 0x12) + { + activeColor_ = color2_; + } + else if (unicode == 0x13) + { + activeColor_ = color3_; + } + else if (unicode == 0x14) + { + activeColor_ = color4_; + } + else + { + Orthanc::ImageAccessor region; + target_.GetRegion(region, left, top, width, height); + + std::unique_ptr<Orthanc::ImageAccessor> colorized(ImageToolbox::Colorize(glyph.GetBitmap(), activeColor_)); + Orthanc::ImageProcessing::Copy(region, *colorized); + } + } + else { Orthanc::ImageAccessor region; target_.GetRegion(region, left, top, width, height); @@ -103,7 +152,7 @@ std::unique_ptr<Orthanc::ImageAccessor> bitmap( new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, box.GetWidth(), box.GetHeight(), - true /* force minimal pitch */)); + true /* force minimal pitch, to be used in OpenGL textures */)); Orthanc::ImageProcessing::Set(*bitmap, 0); @@ -120,4 +169,31 @@ alphabet_.Register(font, utf8); return RenderText(utf8); } + + + Orthanc::ImageAccessor* GlyphBitmapAlphabet::RenderColorText(FontRenderer& font, + const std::string& utf8, + const Color color1, + const Color color2, + const Color color3, + const Color color4) + { + alphabet_.Register(font, utf8); + + TextBoundingBox box(alphabet_, utf8); + + std::unique_ptr<Orthanc::ImageAccessor> bitmap( + new Orthanc::Image(Orthanc::PixelFormat_RGB24, + box.GetWidth(), box.GetHeight(), + true /* force minimal pitch, to be used in OpenGL textures */)); + + Orthanc::ImageProcessing::Set(*bitmap, 0); + + RenderTextVisitor visitor(*bitmap, *this, -box.GetLeft(), -box.GetTop()); + visitor.SetColors(color1, color2, color3, color4); + + alphabet_.Apply(visitor, utf8); + + return bitmap.release(); + } }
--- a/OrthancStone/Sources/Fonts/GlyphBitmapAlphabet.h Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/Sources/Fonts/GlyphBitmapAlphabet.h Fri Apr 25 14:20:44 2025 +0200 @@ -25,6 +25,8 @@ #include "GlyphAlphabet.h" +#include "../Scene2D/Color.h" + #include <Images/ImageAccessor.h> namespace OrthancStone @@ -58,5 +60,12 @@ Orthanc::ImageAccessor* RenderText(FontRenderer& font, const std::string& utf8); + + Orthanc::ImageAccessor* RenderColorText(FontRenderer& font, + const std::string& utf8, + const Color color1 = Color(255, 255, 255), + const Color color2 = Color(0, 0, 0), + const Color color3 = Color(0, 0, 0), + const Color color4 = Color(0, 0, 0)); }; }
--- a/OrthancStone/Sources/Fonts/GlyphTextureAlphabet.cpp Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/Sources/Fonts/GlyphTextureAlphabet.cpp Fri Apr 25 14:20:44 2025 +0200 @@ -298,7 +298,7 @@ std::unique_ptr<Orthanc::ImageAccessor> bitmap( new Orthanc::Image(Orthanc::PixelFormat_RGBA32, box.GetWidth(), box.GetHeight(), - true /* force minimal pitch */)); + true /* force minimal pitch, to be used in OpenGL textures */)); Orthanc::ImageProcessing::Set(*bitmap, 0, 0, 0, 0);
--- a/OrthancStone/Sources/Toolbox/DynamicBitmap.cpp Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/Sources/Toolbox/DynamicBitmap.cpp Fri Apr 25 14:20:44 2025 +0200 @@ -26,8 +26,19 @@ #include <Images/Image.h> #include <OrthancException.h> + namespace OrthancStone { + DynamicBitmap::DynamicBitmap(Orthanc::ImageAccessor* bitmap) : + bitmap_(bitmap) + { + if (bitmap == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + DynamicBitmap::DynamicBitmap(const Orthanc::ImageAccessor& bitmap) : bitmap_(Orthanc::Image::Clone(bitmap)) { @@ -36,4 +47,13 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } } + + + DynamicBitmap::DynamicBitmap(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height, + bool forceMinimalPitch) + { + bitmap_.reset(new Orthanc::Image(format, width, height, forceMinimalPitch)); + } }
--- a/OrthancStone/Sources/Toolbox/DynamicBitmap.h Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/Sources/Toolbox/DynamicBitmap.h Fri Apr 25 14:20:44 2025 +0200 @@ -37,7 +37,14 @@ std::unique_ptr<Orthanc::ImageAccessor> bitmap_; public: - explicit DynamicBitmap(const Orthanc::ImageAccessor& bitmap); + explicit DynamicBitmap(Orthanc::ImageAccessor* bitmap /* Takes ownership */); + + explicit DynamicBitmap(const Orthanc::ImageAccessor& bitmap /* Clone the image */); + + DynamicBitmap(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height, + bool forceMinimalPitch); const Orthanc::ImageAccessor& GetBitmap() const {
--- a/OrthancStone/UnitTestsSources/CMakeLists.txt Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/UnitTestsSources/CMakeLists.txt Fri Apr 25 14:20:44 2025 +0200 @@ -19,7 +19,7 @@ # <http://www.gnu.org/licenses/>. -cmake_minimum_required(VERSION 2.8.10) +cmake_minimum_required(VERSION 2.8.10...4.0) cmake_policy(SET CMP0058 NEW) project(UnitTests)
--- a/OrthancStone/UnitTestsSources/ImageToolboxTests.cpp Fri Apr 25 12:52:46 2025 +0200 +++ b/OrthancStone/UnitTestsSources/ImageToolboxTests.cpp Fri Apr 25 14:20:44 2025 +0200 @@ -21,6 +21,7 @@ **/ +#include "../Sources/Fonts/GlyphAlphabet.h" #include "../Sources/Toolbox/ImageToolbox.h" // #include <boost/chrono.hpp> @@ -261,3 +262,34 @@ SimpleHisto_T_BinSize10_2<Orthanc::PixelFormat_Grayscale32>(); } + +TEST(GlyphAlphabet, Indent) +{ + std::string s; + for (unsigned int i = 1; i < 11; i++) + { + OrthancStone::GlyphAlphabet::IndentUtf8(s, "Hello World", i, true); ASSERT_EQ("Hello\nWorld", s); + } + + OrthancStone::GlyphAlphabet::IndentUtf8(s, "Hello World", 11, true); ASSERT_EQ("Hello World", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "Hello World", 12, true); ASSERT_EQ("Hello World", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, " \r ", 2, true); ASSERT_EQ("", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, " \n ", 2, true); ASSERT_EQ("\n", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "A\rB\r\r\rC\rD", 2, true); ASSERT_EQ("ABCD", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, " AB\rC\rD ", 2, true); ASSERT_EQ("ABCD", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, " \r Hello \r \r World \r ", 10, true); ASSERT_EQ("Hello\nWorld", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, " \r Hello \r \r World \r ", 11, true); ASSERT_EQ("Hello World", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "HelloWorld", 1, true); ASSERT_EQ("HelloWorld", s); + + // Tests with device control characters (\021 in octal equals 0x11) + OrthancStone::GlyphAlphabet::IndentUtf8(s, " A\020\021B\022C\r\023D\024E\025 ", 2, true); + ASSERT_EQ("A\020BCDE\025", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, " A\020\021B\022C\r\023D\024E\025 ", 2, false); + ASSERT_EQ("A\020\021B\022C\023D\024E\025", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "X A\021B\022C\r\023D\024E Y", 9, true); ASSERT_EQ("X ABCDE Y", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "X A\021B\022C\r\023D\024E Y", 9, false); ASSERT_EQ("X\nA\021B\022C\023D\024E\nY", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "X A\021B\022C\r\023D\024E Y", 10, false); ASSERT_EQ("X\nA\021B\022C\023D\024E\nY", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "X A\021B\022C\r\023D\024E Y", 11, false); ASSERT_EQ("X A\021B\022C\023D\024E\nY", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "X A\021B\022C\r\023D\024E Y", 12, false); ASSERT_EQ("X A\021B\022C\023D\024E\nY", s); + OrthancStone::GlyphAlphabet::IndentUtf8(s, "X A\021B\022C\r\023D\024E Y", 13, false); ASSERT_EQ("X A\021B\022C\023D\024E Y", s); +}