Commit 564112e2 authored by David C. Lonie's avatar David C. Lonie
Browse files

Add a GL2PS special case for vtkTextActor3D.

This implements a vtkFreeTypeTools::StringToPath method that
generates path data describing the outline of a rendered text
string. This path gets transformed to match the vtkTextActor3D's
appearance, then projected into 2D and drawn to the GL2PS buffer.

Change-Id: I2513a9d03799f290688624dd09f419def03e2996
parent c2f54b97
......@@ -4,6 +4,7 @@ set(GL2PSTests
TestGL2PSExporterRasterExclusion.cxx
TestGL2PSExporterVector.cxx
TestGL2PSExporterVolumeRaster.cxx
TestGL2PSTextActor3D.cxx
TestStackedPlotGL2PS.cxx
)
......
/*=========================================================================
Program: Visualization Toolkit
Module: TestStringToPath.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkTextActor3D.h"
#include "vtkCamera.h"
#include "vtkGL2PSExporter.h"
#include "vtkNew.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTestingInteractor.h"
#include "vtkTextProperty.h"
//----------------------------------------------------------------------------
int TestGL2PSTextActor3D(int, char *[])
{
vtkNew<vtkTextActor3D> actor1;
actor1->SetInput("Some text!");
actor1->GetTextProperty()->SetFontSize(36);
actor1->GetTextProperty()->SetOrientation(45);
// These should be ignored by both the actor and exporter:
actor1->GetTextProperty()->SetVerticalJustificationToCentered();
actor1->GetTextProperty()->SetJustificationToCentered();
actor1->SetPosition(-100, 25, -100);
actor1->RotateWXYZ(50, 1, .5, -.2);
actor1->GetTextProperty()->SetColor(0.6, 0.5, 0.8);
vtkNew<vtkTextActor3D> actor2;
actor2->SetInput("Some more text!");
actor2->GetTextProperty()->SetFontSize(40);
actor2->SetPosition(-50, 0, -200);
actor2->RotateWXYZ(-70, 0, 1, 0);
actor2->GetTextProperty()->SetColor(0.7, 0.3, 0.2);
vtkNew<vtkTextActor3D> actor3;
actor3->SetInput("More text!");
actor3->GetTextProperty()->SetFontSize(36);
actor3->GetTextProperty()->SetColor(0.8, 0.8, 0.6);
actor3->SetPosition(-100, -25, 0);
actor3->RotateWXYZ(70, 0, 1, 0);
vtkNew<vtkTextActor3D> actor4;
actor4->SetInput("Testing...");
actor4->GetTextProperty()->SetFontSize(22);
actor4->SetPosition(-75, -75, 25);
actor4->RotateWXYZ(40, -.2, 1, .3);
actor4->GetTextProperty()->SetColor(0.2, 0.6, 0.4);
vtkNew<vtkTextActor3D> actor5;
actor5->SetInput("A somewhat longer string of text!");
actor5->GetTextProperty()->SetFontSize(26);
actor5->GetTextProperty()->SetColor(1, 1, 1);
actor5->SetPosition(-240, -110, -500);
actor5->RotateWXYZ(-25, 1, 0, 1);
vtkNew<vtkRenderer> ren;
vtkNew<vtkRenderWindow> win;
win->AddRenderer(ren.GetPointer());
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(win.GetPointer());
ren->AddActor(actor1.GetPointer());
ren->AddActor(actor2.GetPointer());
ren->AddActor(actor3.GetPointer());
ren->AddActor(actor4.GetPointer());
ren->AddActor(actor5.GetPointer());
ren->SetBackground(0.0, 0.0, 0.0);
ren->GetActiveCamera()->SetPosition(0, 0, 400);
ren->GetActiveCamera()->SetFocalPoint(0, 0, 0);
ren->GetActiveCamera()->SetViewUp(0, 1, 0);
win->SetSize(600, 600);
win->Render();
vtkNew<vtkGL2PSExporter> exp;
exp->SetRenderWindow(win.GetPointer());
exp->SetFileFormatToPS();
exp->CompressOff();
exp->SetSortToSimple();
exp->DrawBackgroundOn();
std::string fileprefix = vtkTestingInteractor::TempDirectory +
std::string("/TestGL2PSTextActor3D");
exp->SetFilePrefix(fileprefix.c_str());
exp->WriteTimeStampOff(); // Otherwise hashes won't match
exp->Write();
// Finally render the scene and compare the image to a reference image
win->SetMultiSamples(0);
win->GetInteractor()->Initialize();
win->GetInteractor()->Start();
}
......@@ -24,6 +24,7 @@
#include "vtkContextActor.h"
#include "vtkContextScene.h"
#include "vtkCoordinate.h"
#include "vtkFreeTypeTools.h"
#include "vtkGL2PSContextDevice2D.h"
#include "vtkGL2PSUtilities.h"
#include "vtkIntArray.h"
......@@ -40,7 +41,9 @@
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkRendererCollection.h"
#include "vtkStdString.h"
#include "vtkTextActor.h"
#include "vtkTextActor3D.h"
#include "vtkTextMapper.h"
#include "vtkTextProperty.h"
#include "vtkTransform.h"
......@@ -310,6 +313,11 @@ void vtkGL2PSExporter::WriteData()
{
this->DrawMathTextActor3D(textAct, renCol);
}
else if (vtkTextActor3D *textAct =
vtkTextActor3D::SafeDownCast(prop))
{
this->DrawTextActor3D(textAct, renCol);
}
else // Some other prop
{
continue;
......@@ -626,6 +634,105 @@ void vtkGL2PSExporter::DrawTextActor(vtkTextActor *textAct,
glPopMatrix();
}
void vtkGL2PSExporter::DrawTextActor3D(vtkTextActor3D *textAct,
vtkRendererCollection *renCol)
{
const char *string = textAct->GetInput();
vtkNew<vtkTextProperty> tprop;
int *winsize = this->RenderWindow->GetSize();
tprop->ShallowCopy(textAct->GetTextProperty());
tprop->SetJustificationToLeft(); // Ignored by textactor3d
tprop->SetVerticalJustificationToBottom(); // Ignored by textactor3d
// Get path
vtkNew<vtkPath> path;
vtkFreeTypeTools::GetInstance()->StringToPath(tprop.GetPointer(),
vtkStdString(string),
path.GetPointer());
// Extract gl transform info
vtkMatrix4x4 *actorMatrix = textAct->GetMatrix();
vtkNew<vtkMatrix4x4> projectionMatrix;
vtkNew<vtkMatrix4x4> modelviewMatrix;
vtkNew<vtkMatrix4x4> transformMatrix;
double glMatrix[16];
double viewport[4];
double depthRange[2];
double *textBounds = textAct->GetBounds();
double rasterPos[3] = {(textBounds[1] + textBounds[0]) * 0.5,
(textBounds[3] + textBounds[2]) * 0.5,
(textBounds[5] + textBounds[4]) * 0.5};
double scale[2] = {1.0, 1.0};
double *dcolor = tprop->GetColor();
unsigned char color[3] = {static_cast<unsigned char>(dcolor[0]*255),
static_cast<unsigned char>(dcolor[1]*255),
static_cast<unsigned char>(dcolor[2]*255)};
double winsized[2] = {static_cast<double>(winsize[0]),
static_cast<double>(winsize[1])};
vtkSmartPointer<vtkPoints> origPoints = path->GetPoints();
for (renCol->InitTraversal();vtkRenderer *ren = renCol->GetNextItem();)
{
// Setup gl matrices
ren->GetActiveCamera()->Render(ren);
// Build transformation matrix
glGetDoublev(GL_PROJECTION_MATRIX, glMatrix);
projectionMatrix->DeepCopy(glMatrix);
projectionMatrix->Transpose();
glGetDoublev(GL_MODELVIEW_MATRIX, glMatrix);
modelviewMatrix->DeepCopy(glMatrix);
modelviewMatrix->Transpose();
vtkMatrix4x4::Multiply4x4(projectionMatrix.GetPointer(),
modelviewMatrix.GetPointer(),
transformMatrix.GetPointer());
vtkMatrix4x4::Multiply4x4(transformMatrix.GetPointer(),
actorMatrix,
transformMatrix.GetPointer());
glGetDoublev(GL_VIEWPORT, viewport);
glGetDoublev(GL_DEPTH_RANGE, depthRange);
const double halfWidth = viewport[2] * 0.5;
const double halfHeight = viewport[3] * 0.5;
const double zFactor1 = (depthRange[1] - depthRange[0]) * 0.5;
const double zFactor2 = (depthRange[1] + depthRange[0]) * 0.5;
vtkNew<vtkPoints> newPoints;
newPoints->DeepCopy(origPoints);
double point[4];
double translation[2] = {0.0, 0.0};
for (vtkIdType i = 0; i < path->GetNumberOfPoints(); ++i)
{
newPoints->GetPoint(i, point);
point[3] = 1.0;
// Convert path to clip coordinates:
// <out point> = [projection] [modelview] [actor matrix] <in point>
transformMatrix->MultiplyPoint(point, point);
// Clip to NDC
const double invW = 1.0 / point[3];
point[0] *= invW;
point[1] *= invW;
point[2] *= invW;
// NDC to device:
point[0] = point[0] * halfWidth + viewport[0] + halfWidth;
point[1] = point[1] * halfHeight + viewport[1] + halfHeight;
point[2] = point[2] * zFactor1 + zFactor2;
newPoints->SetPoint(i, point);
}
path->SetPoints(newPoints.GetPointer());
vtkGL2PSUtilities::DrawPath(path.GetPointer(), rasterPos, winsized,
translation, scale, 0.0, color);
glMatrixMode(GL_PROJECTION_MATRIX);
glPopMatrix();
glMatrixMode(GL_MODELVIEW_MATRIX);
glPopMatrix();
}
}
void vtkGL2PSExporter::DrawTextMapper(vtkTextMapper *textMap,
vtkActor2D *textAct,
......
......@@ -95,6 +95,7 @@ class vtkPropCollection;
class vtkProp3DCollection;
class vtkRendererCollection;
class vtkTextActor;
class vtkTextActor3D;
class vtkTextMapper;
class VTKIOEXPORT_EXPORT vtkGL2PSExporter : public vtkExporter
......@@ -282,6 +283,7 @@ protected:
void SetPropVisibilities(vtkPropCollection *col, int vis);
void DrawTextActor(vtkTextActor *textAct, vtkRendererCollection *renCol);
void DrawTextActor3D(vtkTextActor3D *textAct, vtkRendererCollection *renCol);
void DrawTextMapper(vtkTextMapper *textMap, vtkActor2D *textAct,
vtkRendererCollection *renCol);
void DrawMathTextActor(vtkMathTextActor *textAct,
......
# add tests that do not require data or produce vector output
set(MyTests
)
if(VTK_DATA_ROOT)
# add tests that require data
set(MyTests ${MyTests}
TestFTStringToPath.cxx
)
endif()
# Use the testing object factory, to reduce boilerplate code in tests.
include("${vtkTestingRendering_SOURCE_DIR}/vtkTestingObjectFactory.cmake")
vtk_module_test_executable(${vtk-module}CxxTests ${Tests})
set(TestsToRun ${Tests})
remove(TestsToRun CxxTests.cxx)
# Add all the executables
foreach(test ${TestsToRun})
get_filename_component(TName ${test} NAME_WE)
if(VTK_DATA_ROOT)
if(${${TName}Error})
set(_error_threshold ${${TName}Error})
else()
set(_error_threshold 10)
endif()
add_test(NAME ${vtk-module}Cxx-${TName}
COMMAND ${vtk-module}CxxTests ${TName}
-D ${VTK_DATA_ROOT}
-T ${VTK_TEST_OUTPUT_DIR}
-V Baseline/Rendering/${TName}.png
-E ${_error_threshold})
else()
add_test(NAME FreeType-${TName}
COMMAND ${vtk-module}CxxTests ${TName})
endif()
endforeach()
/*=========================================================================
Program: Visualization Toolkit
Module: TestStringToPath.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkFreeTypeTools.h"
#include "vtkContext2D.h"
#include "vtkContextItem.h"
#include "vtkContextScene.h"
#include "vtkContextView.h"
#include "vtkIntArray.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPath.h"
#include "vtkPen.h"
#include "vtkPoints.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkTextProperty.h"
//----------------------------------------------------------------------------
class StringToPathContextTest : public vtkContextItem
{
public:
static StringToPathContextTest *New();
vtkTypeMacro(StringToPathContextTest, vtkContextItem);
// Paint event for the chart, called whenever the chart needs to be drawn
virtual bool Paint(vtkContext2D *painter);
void SetPath(vtkPath *path) { this->Path = path; }
protected:
vtkPath *Path;
};
//----------------------------------------------------------------------------
int TestFTStringToPath(int argc, char *argv[])
{
// Set up a 2D context view, context test object and add it to the scene
vtkNew<vtkContextView> view;
view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
view->GetRenderWindow()->SetSize(460, 100);
vtkNew<StringToPathContextTest> test;
view->GetScene()->AddItem(test.GetPointer());
vtkNew<vtkPath> path;
vtkNew<vtkTextProperty> tprop;
vtkFreeTypeTools::GetInstance()->StringToPath(tprop.GetPointer(),
vtkStdString("FreeType Path"),
path.GetPointer());
test->SetPath(path.GetPointer());
view->GetRenderWindow()->SetMultiSamples(0);
view->GetInteractor()->Initialize();
view->GetInteractor()->Start();
return EXIT_SUCCESS;
}
// Make our new derived class to draw a diagram
vtkStandardNewMacro(StringToPathContextTest)
// This function aims to test the primitives provided by the 2D API.
bool StringToPathContextTest::Paint(vtkContext2D *painter)
{
// RGB color lookup table by path point code:
double color [4][3];
color[vtkPath::MOVE_TO][0] = 1.0;
color[vtkPath::MOVE_TO][1] = 0.0;
color[vtkPath::MOVE_TO][2] = 0.0;
color[vtkPath::LINE_TO][0] = 0.0;
color[vtkPath::LINE_TO][1] = 1.0;
color[vtkPath::LINE_TO][2] = 0.0;
color[vtkPath::CONIC_CURVE][0] = 0.0;
color[vtkPath::CONIC_CURVE][1] = 0.0;
color[vtkPath::CONIC_CURVE][2] = 1.0;
color[vtkPath::CUBIC_CURVE][0] = 1.0;
color[vtkPath::CUBIC_CURVE][1] = 0.0;
color[vtkPath::CUBIC_CURVE][2] = 1.0;
vtkPoints *points = this->Path->GetPoints();
vtkIntArray *codes = this->Path->GetCodes();
if (points->GetNumberOfPoints() != codes->GetNumberOfTuples())
{
return false;
}
// scaling factor and offset to ensure that the points will fit the view:
double scale = 5.16591;
double offset = 20.0;
// Draw the control points, colored by codes:
double point[3];
painter->GetPen()->SetWidth(2);
for (vtkIdType i = 0; i < points->GetNumberOfPoints(); ++i)
{
points->GetPoint(i, point);
int code = codes->GetValue(i);
painter->GetPen()->SetColorF(color[code]);
painter->DrawPoint(point[0]*scale + offset, point[1]*scale + offset);
}
return true;
}
......@@ -5,4 +5,7 @@ vtk_module(vtkRenderingFreeType
vtkRenderingCore
vtkfreetype
vtkftgl
TEST_DEPENDS
vtkTestingRendering
vtkViewsContext2D
)
......@@ -18,6 +18,7 @@
#include "vtkTextProperty.h"
#include "vtkObjectFactory.h"
#include "vtkMath.h"
#include "vtkPath.h"
#include "vtkImageData.h"
#include "vtkSmartPointer.h"
......@@ -466,6 +467,20 @@ bool vtkFreeTypeTools::RenderString(vtkTextProperty *tprop,
return this->PopulateImageData(tprop, str, x, y, data);
}
//----------------------------------------------------------------------------
bool vtkFreeTypeTools::StringToPath(vtkTextProperty *tprop,
const vtkStdString &str, vtkPath *path)
{
return this->PopulatePath(tprop, str, 0, 0, path);
}
//----------------------------------------------------------------------------
bool vtkFreeTypeTools::StringToPath(vtkTextProperty *tprop,
const vtkUnicodeString &str, vtkPath *path)
{
return this->PopulatePath(tprop, str, 0, 0, path);
}
//----------------------------------------------------------------------------
vtkTypeUInt16 vtkFreeTypeTools::HashString(const char *str)
{
......@@ -1303,6 +1318,289 @@ bool vtkFreeTypeTools::PopulateImageData(vtkTextProperty *tprop,
return true;
}
//----------------------------------------------------------------------------
template <typename T>
bool vtkFreeTypeTools::PopulatePath(vtkTextProperty *tprop,
const T& str,
int x, int y,
vtkPath *path)
{
if (!path)
{
return false;
}
path->Reset();
// Map the text property to a unique id that will be used as face id, get the
// font face and establish whether kerning information is available.
unsigned long tprop_cache_id;
FT_Face face;
bool face_has_kerning = false;
if (!this->GetFace(tprop, tprop_cache_id, face, face_has_kerning))
{
return false;
}
// Text property size and opacity
int tprop_font_size = tprop->GetFontSize();
FT_OutlineGlyph outline_glyph;
FT_Outline *outline;
FT_UInt gindex, previous_gindex = 0;
FT_Vector kerning_delta;
// The FT_CURVE defines don't really work in a switch...only the first two
// bits are meaningful, and the rest appear to be garbage. We'll convert them
// into values in the enum below:
enum controlType
{
FIRST_POINT,
ON_POINT,
CUBIC_POINT,
CONIC_POINT
};
// Bounding box for all control points, used for justification
double cbox[4] = {VTK_FLOAT_MAX, VTK_FLOAT_MAX, VTK_FLOAT_MIN, VTK_FLOAT_MIN};
// Render char by char
for (typename T::const_iterator it = str.begin(); it != str.end(); ++it)
{
outline = this->GetOutline(*it, tprop_cache_id, tprop_font_size, gindex,
outline_glyph);
if (!outline)
{
// Glyph not found in the face.
continue;
}
if (outline->n_points > 0)
{
int pen_x = x;
int pen_y = y;
// Add the kerning
if (face_has_kerning && previous_gindex && gindex)
{
FT_Get_Kerning(
face, previous_gindex, gindex, ft_kerning_default, &kerning_delta);
pen_x += kerning_delta.x >> 6;
pen_y += kerning_delta.y >> 6;
}
previous_gindex = gindex;
short point = 0;
for (short contour = 0; contour < outline->n_contours; ++contour)
{
short contourEnd = outline->contours[contour];
controlType lastTag = FIRST_POINT;
controlType contourStartTag;
double contourStartVec[2];
double lastVec[2];
for (; point <= contourEnd; ++point)
{
FT_Vector ftvec = outline->points[point];
char fttag = outline->tags[point];
controlType tag;
if (fttag & FT_CURVE_TAG_ON)
{
tag = ON_POINT;
}
else if (fttag & FT_CURVE_TAG_CUBIC)
{
tag = CUBIC_POINT;
}
else if (fttag & FT_CURVE_TAG_CONIC)
{
tag = CONIC_POINT;
}
double vec[2];
vec[0] = ftvec.x / 64.0 + x;
vec[1] = ftvec.y / 64.0 + y;
// Update (control) bounding box
if (vec[0] < cbox[0])
{
cbox[0] = vec[0];
}
if (vec[1] < cbox[1])
{
cbox[1] = vec[1];
}
if (vec[0] > cbox[2])
{
cbox[2] = vec[0];
}
if (vec[1] > cbox[3])
{
cbox[3] = vec[1];
}
// Handle the first point here, unless it is a CONIC point, in which
// case the switches below handle it.
if (lastTag == FIRST_POINT && tag != CONIC_POINT)
{
path->InsertNextPoint(vec[0], vec[1], 0.0, vtkPath::MOVE_TO);
lastTag = tag;
contourStartTag = tag;
lastVec[0] = vec[0];
lastVec[1] = vec[1];
contourStartVec[0] = vec[0];