changeset 585:b9ce24c606ae

TextSceneLayer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 19 Apr 2019 17:57:58 +0200
parents 434ceeb0bcab
children 5b4890d289cf
files Framework/OpenGL/ColorTextureOpenGLProgram.cpp Framework/OpenGL/ColorTextureOpenGLProgram.h Framework/OpenGL/TextOpenGLProgram.cpp Framework/OpenGL/TextOpenGLProgram.h Framework/Scene2D/CompositorHelper.cpp Framework/Scene2D/CompositorHelper.h Framework/Scene2D/TextSceneLayer.cpp Framework/Scene2D/TextSceneLayer.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 9 files changed, 873 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/OpenGL/ColorTextureOpenGLProgram.cpp	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,126 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ColorTextureOpenGLProgram.h"
+
+namespace OrthancStone
+{
+  namespace OpenGL
+  {
+    ColorTextureOpenGLProgram::ColorTextureOpenGLProgram(IOpenGLContext&  context) :
+      context_(context)
+    {
+      static const char* VERTEX_SHADER = 
+        "attribute vec2 a_texcoord;    \n"
+        "attribute vec4 a_position;    \n"
+        "uniform mat4 u_matrix;        \n"
+        "varying vec2 v_texcoord;      \n"
+        "void main()                   \n"
+        "{                             \n"
+        "  gl_Position = u_matrix * a_position; \n"
+        "  v_texcoord = a_texcoord;             \n"
+        "}";
+
+      static const char* FRAGMENT_SHADER = 
+        "uniform sampler2D u_texture;  \n"
+        "varying vec2 v_texcoord;      \n"
+        "void main()                   \n"
+        "{                             \n"
+        "  gl_FragColor = texture2D(u_texture, v_texcoord); \n"
+        "}";
+
+      static const float POSITIONS[COMPONENTS * COUNT] = {
+        0, 0,
+        0, 1,
+        1, 0,
+        1, 0,
+        0, 1,
+        1, 1
+      };
+        
+      context_.MakeCurrent();
+
+      program_.reset(new OpenGLProgram);
+      program_->CompileShaders(VERTEX_SHADER, FRAGMENT_SHADER);
+
+      positionLocation_ = program_->GetAttributeLocation("a_position");
+      textureLocation_ = program_->GetAttributeLocation("a_texcoord");
+
+      glGenBuffers(2, buffers_);
+
+      glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]);
+      glBufferData(GL_ARRAY_BUFFER, sizeof(float) * COMPONENTS * COUNT, POSITIONS, GL_STATIC_DRAW);
+
+      glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]);
+      glBufferData(GL_ARRAY_BUFFER, sizeof(float) * COMPONENTS * COUNT, POSITIONS, GL_STATIC_DRAW);
+    }
+
+      
+    ColorTextureOpenGLProgram::~ColorTextureOpenGLProgram()
+    {
+      context_.MakeCurrent();
+      glDeleteBuffers(2, buffers_);
+    }
+
+    
+    void ColorTextureOpenGLProgram::Apply(OpenGLTexture& texture,
+                                          const AffineTransform2D& transform,
+                                          bool useAlpha)
+    {
+      context_.MakeCurrent();
+      program_->Use();
+
+      AffineTransform2D scale = AffineTransform2D::CreateScaling
+        (texture.GetWidth(), texture.GetHeight());
+
+      AffineTransform2D t = AffineTransform2D::Combine(transform, scale);
+
+      float m[16];
+      t.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight());
+
+      texture.Bind(program_->GetUniformLocation("u_texture"));
+      glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m);
+
+      glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]);
+      glEnableVertexAttribArray(positionLocation_);
+      glVertexAttribPointer(positionLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0);
+
+      glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]);
+      glEnableVertexAttribArray(textureLocation_);
+      glVertexAttribPointer(textureLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0);
+
+      if (useAlpha)
+      {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        glDrawArrays(GL_TRIANGLES, 0, COUNT);
+        glDisable(GL_BLEND);
+      }
+      else
+      {
+        glDrawArrays(GL_TRIANGLES, 0, COUNT);
+      }
+
+      glDisableVertexAttribArray(positionLocation_);
+      glDisableVertexAttribArray(textureLocation_);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/OpenGL/ColorTextureOpenGLProgram.h	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,55 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IOpenGLContext.h"
+#include "OpenGLProgram.h"
+#include "OpenGLTexture.h"
+#include "../Toolbox/AffineTransform2D.h"
+
+namespace OrthancStone
+{
+  namespace OpenGL
+  {
+    class ColorTextureOpenGLProgram : public boost::noncopyable
+    {
+    private:
+      static const unsigned int COMPONENTS = 2;
+      static const unsigned int COUNT = 6;  // 2 triangles in 2D
+
+      IOpenGLContext&               context_;
+      std::auto_ptr<OpenGLProgram>  program_;
+      GLint                         positionLocation_;
+      GLint                         textureLocation_;
+      GLuint                        buffers_[2];
+
+    public:
+      ColorTextureOpenGLProgram(IOpenGLContext&  context);
+
+      ~ColorTextureOpenGLProgram();
+
+      void Apply(OpenGLTexture& texture,
+                 const AffineTransform2D& transform,
+                 bool useAlpha);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/OpenGL/TextOpenGLProgram.cpp	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,185 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "TextOpenGLProgram.h"
+
+#include "../Fonts/OpenGLTextCoordinates.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  namespace OpenGL
+  {
+    TextOpenGLProgram::TextOpenGLProgram(IOpenGLContext&  context) :
+      context_(context)
+    {
+      static const char* VERTEX_SHADER = 
+        "attribute vec2 a_texcoord;    \n"
+        "attribute vec4 a_position;    \n"
+        "uniform mat4 u_matrix;        \n"
+        "varying vec2 v_texcoord;      \n"
+        "void main()                   \n"
+        "{                             \n"
+        "  gl_Position = u_matrix * a_position; \n"
+        "  v_texcoord = a_texcoord;             \n"
+        "}";
+
+      static const char* FRAGMENT_SHADER = 
+        "uniform sampler2D u_texture;  \n"
+        "uniform vec3 u_color;         \n"
+        "varying vec2 v_texcoord;      \n"
+        "void main()                   \n"
+        "{                             \n"
+        "  vec4 v = texture2D(u_texture, v_texcoord);  \n"
+        "  gl_FragColor = vec4(u_color * v.w, v.w);    \n"   // Premultiplied alpha
+        "}";
+
+      context_.MakeCurrent();
+
+      program_.reset(new OpenGLProgram);
+      program_->CompileShaders(VERTEX_SHADER, FRAGMENT_SHADER);
+
+      positionLocation_ = program_->GetAttributeLocation("a_position");
+      textureLocation_ = program_->GetAttributeLocation("a_texcoord");
+    }
+
+
+    TextOpenGLProgram::Data::Data(IOpenGLContext& context,
+                                  const GlyphTextureAlphabet& alphabet,
+                                  const TextSceneLayer& layer) :
+      context_(context),
+      red_(layer.GetRedAsFloat()),
+      green_(layer.GetGreenAsFloat()),
+      blue_(layer.GetBlueAsFloat()),
+      x_(layer.GetX()),
+      y_(layer.GetY()),
+      border_(layer.GetBorder()),
+      anchor_(layer.GetAnchor())
+    {
+      OpenGLTextCoordinates coordinates(alphabet, layer.GetText());
+      textWidth_ = coordinates.GetTextWidth();
+      textHeight_ = coordinates.GetTextHeight();
+
+      if (coordinates.IsEmpty())
+      {
+        coordinatesCount_ = 0;
+      }
+      else
+      {
+        coordinatesCount_ = coordinates.GetRenderingCoords().size();
+
+        context_.MakeCurrent();
+        glGenBuffers(2, buffers_);
+
+        glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coordinatesCount_,
+                     &coordinates.GetRenderingCoords() [0], GL_STATIC_DRAW);
+
+        glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coordinatesCount_,
+                     &coordinates.GetTextureCoords() [0], GL_STATIC_DRAW);
+      }
+    }
+        
+
+    TextOpenGLProgram::Data::~Data()
+    {
+      if (!IsEmpty())
+      {
+        context_.MakeCurrent();
+        glDeleteBuffers(2, buffers_);
+      }
+    }
+
+
+    GLuint TextOpenGLProgram::Data::GetSceneLocationsBuffer() const
+    {
+      if (IsEmpty())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return buffers_[0];
+      }
+    }
+
+
+    GLuint TextOpenGLProgram::Data::GetTextureLocationsBuffer() const
+    {
+      if (IsEmpty())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return buffers_[1];
+      }
+    }
+
+
+    void TextOpenGLProgram::Apply(OpenGLTexture& fontTexture,
+                                  const Data& data,
+                                  const AffineTransform2D& transform)
+    {
+      if (!data.IsEmpty())
+      {
+        context_.MakeCurrent();
+        program_->Use();
+
+        double dx, dy;  // In pixels
+        ComputeAnchorTranslation(dx, dy, data.GetAnchor(), data.GetTextWidth(), data.GetTextHeight(), data.GetBorder());
+      
+        double x = data.GetX();
+        double y = data.GetY();
+        transform.Apply(x, y);
+
+        const AffineTransform2D t = AffineTransform2D::CreateOffset(x + dx, y + dy);
+
+        float m[16];
+        t.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight());
+
+        fontTexture.Bind(program_->GetUniformLocation("u_texture"));
+        glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m);
+        glUniform3f(program_->GetUniformLocation("u_color"), 
+                    data.GetRed(), data.GetGreen(), data.GetBlue());
+
+        glBindBuffer(GL_ARRAY_BUFFER, data.GetSceneLocationsBuffer());
+        glEnableVertexAttribArray(positionLocation_);
+        glVertexAttribPointer(positionLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0);
+
+        glBindBuffer(GL_ARRAY_BUFFER, data.GetTextureLocationsBuffer());
+        glEnableVertexAttribArray(textureLocation_);
+        glVertexAttribPointer(textureLocation_, COMPONENTS, GL_FLOAT, GL_FALSE, 0, 0);
+
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        glDrawArrays(GL_TRIANGLES, 0, data.GetCoordinatesCount() / COMPONENTS);
+        glDisable(GL_BLEND);
+
+        glDisableVertexAttribArray(positionLocation_);
+        glDisableVertexAttribArray(textureLocation_);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/OpenGL/TextOpenGLProgram.h	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,138 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IOpenGLContext.h"
+#include "OpenGLProgram.h"
+#include "OpenGLTexture.h"
+
+#include "../Fonts/GlyphTextureAlphabet.h"
+#include "../Scene2D/TextSceneLayer.h"
+#include "../Toolbox/AffineTransform2D.h"
+
+namespace OrthancStone
+{
+  namespace OpenGL
+  {
+    class TextOpenGLProgram : public boost::noncopyable
+    {
+    public:
+      class Data : public boost::noncopyable
+      {
+      private:
+        IOpenGLContext&  context_;
+        size_t           coordinatesCount_;
+        GLuint           buffers_[2];
+        float            red_;
+        float            green_;
+        float            blue_;
+        double           x_;
+        double           y_;
+        double           border_;
+        unsigned int     textWidth_;
+        unsigned int     textHeight_;
+        BitmapAnchor     anchor_;
+
+      public:
+        Data(IOpenGLContext& context,
+             const GlyphTextureAlphabet& alphabet,
+             const TextSceneLayer& layer);
+
+        ~Data();
+        
+        bool IsEmpty() const
+        {
+          return coordinatesCount_ == 0;
+        }
+
+        size_t GetCoordinatesCount() const
+        {
+          return coordinatesCount_;
+        }
+
+        GLuint GetSceneLocationsBuffer() const;
+
+        GLuint GetTextureLocationsBuffer() const;
+
+        float GetRed() const
+        {
+          return red_;
+        }
+
+        float GetGreen() const
+        {
+          return green_;
+        }
+
+        float GetBlue() const
+        {
+          return blue_;
+        }
+
+        double GetX() const
+        {
+          return x_;
+        }
+
+        double GetY() const
+        {
+          return y_;
+        }
+
+        double GetBorder() const
+        {
+          return border_;
+        }
+
+        unsigned int GetTextWidth() const
+        {
+          return textWidth_;
+        }
+
+        unsigned int GetTextHeight() const
+        {
+          return textHeight_;
+        }
+
+        BitmapAnchor GetAnchor() const
+        {
+          return anchor_;
+        }
+      };
+      
+    private:
+      static const unsigned int COMPONENTS = 2;
+
+      IOpenGLContext&               context_;
+      std::auto_ptr<OpenGLProgram>  program_;
+      GLint                         positionLocation_;
+      GLint                         textureLocation_;
+
+    public:
+      TextOpenGLProgram(IOpenGLContext&  context);
+
+      void Apply(OpenGLTexture& fontTexture,
+                 const Data& data,
+                 const AffineTransform2D& transform);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/CompositorHelper.cpp	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,139 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CompositorHelper.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class CompositorHelper::Item : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<ILayerRenderer>  renderer_;
+    const ISceneLayer&             layer_;
+    uint64_t                       lastRevision_;
+
+  public:
+    Item(ILayerRenderer* renderer,     // Takes ownership
+         const ISceneLayer& layer) :
+      renderer_(renderer),
+      layer_(layer),
+      lastRevision_(layer.GetRevision())
+    {
+      if (renderer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    ILayerRenderer& GetRenderer() const
+    {
+      assert(renderer_.get() != NULL);
+      return *renderer_;
+    }
+
+    const ISceneLayer& GetLayer() const
+    {
+      return layer_;
+    }
+
+    uint64_t GetLastRevision() const
+    {
+      return lastRevision_;
+    }
+
+    void UpdateRenderer()
+    {
+      assert(renderer_.get() != NULL);
+      renderer_->Update(layer_);
+      lastRevision_ = layer_.GetRevision();
+    }
+  };
+
+
+  void CompositorHelper::Visit(const ISceneLayer& layer,
+                               int depth)
+  {
+    Content::iterator found = content_.find(depth);
+
+    assert(found == content_.end() ||
+           found->second != NULL);
+
+    if (found == content_.end() ||
+        &found->second->GetLayer() != &layer)
+    {
+      // This is the first time this layer is rendered, or the layer
+      // is not the same as before
+      if (found != content_.end())
+      {
+        delete found->second;
+        content_.erase(found);
+      }
+
+      std::auto_ptr<ILayerRenderer> renderer(factory_.Create(layer));
+
+      if (renderer.get() != NULL)
+      {
+        renderer->Render(sceneTransform_);
+        content_[depth] = new Item(renderer.release(), layer);
+      }
+    }
+    else
+    {
+      // This layer has already been rendered
+      if (found->second->GetLastRevision() < layer.GetRevision())
+      {
+        found->second->UpdateRenderer();
+      }
+
+      found->second->GetRenderer().Render(sceneTransform_);
+    }
+
+    // Check invariants
+    assert(content_.find(depth) == content_.end() ||
+           (&content_[depth]->GetLayer() == &layer &&
+            content_[depth]->GetLastRevision() == layer.GetRevision()));
+  }
+
+
+  CompositorHelper::~CompositorHelper()
+  {
+    for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+  
+  void CompositorHelper::Refresh(unsigned int canvasWidth,
+                                 unsigned int canvasHeight)
+  {
+    // Bring coordinate (0,0) to the center of the canvas
+    AffineTransform2D offset = AffineTransform2D::CreateOffset(
+      static_cast<double>(canvasWidth) / 2.0,
+      static_cast<double>(canvasHeight) / 2.0);
+
+    sceneTransform_ = AffineTransform2D::Combine(offset, scene_.GetSceneToCanvasTransform());
+    scene_.Apply(*this);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/CompositorHelper.h	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,81 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Scene2D.h"
+
+namespace OrthancStone
+{
+  class CompositorHelper : protected Scene2D::IVisitor
+  {
+  public:
+    class ILayerRenderer : public boost::noncopyable
+    {
+    public:
+      virtual ~ILayerRenderer()
+      {
+      }
+
+      virtual void Render(const AffineTransform2D& transform) = 0;
+
+      // "Update()" is only called if the type of the layer has not changed
+      virtual void Update(const ISceneLayer& layer) = 0;
+    };
+
+    class IRendererFactory : public boost::noncopyable
+    {
+    public:
+      virtual ~IRendererFactory()
+      {
+      }
+
+      virtual ILayerRenderer* Create(const ISceneLayer& layer) = 0;
+    };
+
+  private:
+    class Item;
+
+    typedef std::map<int, Item*>  Content;
+
+    Scene2D&           scene_;
+    IRendererFactory&  factory_;
+    Content            content_;
+    AffineTransform2D  sceneTransform_;
+
+  protected:
+    virtual void Visit(const ISceneLayer& layer,
+                       int depth);
+
+  public:
+    CompositorHelper(Scene2D& scene,
+                     IRendererFactory& factory) :
+      scene_(scene),
+      factory_(factory)
+    {
+    }
+
+    ~CompositorHelper();
+
+    void Refresh(unsigned int canvasWidth,
+                 unsigned int canvasHeight);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/TextSceneLayer.cpp	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,48 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "TextSceneLayer.h"
+
+namespace OrthancStone
+{
+  TextSceneLayer::TextSceneLayer(double x,
+                                 double y,
+                                 const std::string& utf8,
+                                 size_t fontIndex,
+                                 BitmapAnchor anchor,
+                                 unsigned int border) :
+    x_(x),
+    y_(y),
+    utf8_(utf8),
+    fontIndex_(fontIndex),
+    anchor_(anchor),
+    border_(border)
+  {
+  }
+
+
+  ISceneLayer* TextSceneLayer::Clone() const
+  {
+    std::auto_ptr<TextSceneLayer> cloned(new TextSceneLayer(x_, y_, utf8_, fontIndex_, anchor_, border_));
+    cloned->SetColor(GetRed(), GetGreen(), GetBlue());
+    return cloned.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/TextSceneLayer.h	Fri Apr 19 17:57:58 2019 +0200
@@ -0,0 +1,97 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ColorSceneLayer.h"
+#include "../StoneEnumerations.h"
+
+#include <memory>
+#include <string>
+
+namespace OrthancStone
+{
+  class TextSceneLayer : public ColorSceneLayer
+  {
+  private:
+    double         x_;
+    double         y_;
+    std::string    utf8_;
+    size_t         fontIndex_;
+    BitmapAnchor   anchor_;
+    unsigned int   border_;
+  
+  public:
+    TextSceneLayer(double x,
+                   double y,
+                   const std::string& utf8,
+                   size_t fontIndex,
+                   BitmapAnchor anchor,
+                   unsigned int border);
+
+    virtual ISceneLayer* Clone() const;
+
+    double GetX() const
+    {
+      return x_;
+    }
+    
+    double GetY() const
+    {
+      return y_;
+    }
+
+    unsigned int GetBorder() const
+    {
+      return border_;
+    }
+  
+    const std::string& GetText() const
+    {
+      return utf8_;
+    }
+
+    size_t GetFontIndex() const
+    {
+      return fontIndex_;
+    }
+
+    BitmapAnchor GetAnchor() const
+    {
+      return anchor_;
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_Text;
+    }
+
+    virtual bool GetBoundingBox(Extent2D& target) const
+    {
+      return false;
+    }
+
+    virtual uint64_t GetRevision() const
+    {
+      return 0;
+    }
+  };
+}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Apr 19 17:36:00 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Apr 19 17:57:58 2019 +0200
@@ -289,9 +289,11 @@
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyTextLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWindowingTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CompositorHelper.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp
   ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp
@@ -381,9 +383,11 @@
 if (ENABLE_OPENGL)
   list(APPEND ORTHANC_STONE_SOURCES
     ${ORTHANC_STONE_ROOT}/Framework/Fonts/OpenGLTextCoordinates.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/OpenGL/ColorTextureOpenGLProgram.cpp
     ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLProgram.cpp
     ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLShader.cpp
     ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLTexture.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/OpenGL/TextOpenGLProgram.cpp
     )
 endif()