/*========================================================================= Program: Visualization Toolkit Module: vtkQtTreeRingLabelMapper.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. =========================================================================*/ /*------------------------------------------------------------------------- Copyright 2008 Sandia Corporation. Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains certain rights in this software. -------------------------------------------------------------------------*/ #include "vtkQtTreeRingLabelMapper.h" #include "vtkCamera.h" #include "vtkCoordinate.h" #include "vtkDataSetAttributes.h" #include "vtkDoubleArray.h" #include "vtkExecutive.h" #include "vtkImageData.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPlaneSource.h" #include "vtkPolyDataMapper2D.h" #include "vtkProperty2D.h" #include "vtkQImageToImageSource.h" #include "vtkRenderWindow.h" #include "vtkRenderer.h" #include "vtkStringArray.h" #include "vtkTextProperty.h" #include "vtkTexture.h" #include "vtkTextureMapToPlane.h" #include "vtkTexturedActor2D.h" #include "vtkTree.h" #include #include #include #include #include #include #include // #include // #include #include "vtkSmartPointer.h" #define VTK_CREATE(type, name) vtkSmartPointer name = vtkSmartPointer::New() vtkStandardNewMacro(vtkQtTreeRingLabelMapper); vtkCxxSetObjectMacro(vtkQtTreeRingLabelMapper, LabelTextProperty, vtkTextProperty); vtkQtTreeRingLabelMapper::vtkQtTreeRingLabelMapper() { this->Input = nullptr; this->Renderer = nullptr; this->VCoord = vtkCoordinate::New(); this->LabeledComponent = (-1); this->FieldDataArray = 0; this->FieldDataName = nullptr; this->TextRotationArrayName = nullptr; this->SetTextRotationArrayName("TextRotation"); this->SetSectorsArrayName("area"); this->LabelTextProperty = vtkTextProperty::New(); this->LabelTextProperty->SetFontSize(10); this->LabelTextProperty->SetBold(1); this->LabelTextProperty->SetItalic(1); this->LabelTextProperty->SetShadow(1); this->LabelTextProperty->SetFontFamilyToArial(); this->SetLabelFormat("%s"); this->WindowSize[0] = this->WindowSize[1] = 0; this->PlaneSource = vtkPlaneSource::New(); this->TextureMapToPlane = vtkTextureMapToPlane::New(); this->polyDataMapper = vtkPolyDataMapper2D::New(); this->QtImageSource = vtkQImageToImageSource::New(); this->LabelTexture = vtkTexture::New(); this->QtImage = new QImage(1, 1, QImage::Format_ARGB32_Premultiplied); // FIXME: QImage is initialized to grey. This will fix that, although it is a bit of a hack... QPainter painter(this->QtImage); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); painter.setCompositionMode(QPainter::CompositionMode_Clear); painter.drawImage(0, 0, *this->QtImage); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); this->QtImageSource->SetQImage(this->QtImage); this->LabelTexture->SetInputConnection(this->QtImageSource->GetOutputPort()); this->LabelTexture->PremultipliedAlphaOn(); this->TextureMapToPlane->SetSRange(0., 1.); this->TextureMapToPlane->SetTRange(0., 1.); this->TextureMapToPlane->SetInputConnection(this->PlaneSource->GetOutputPort()); this->TextureMapToPlane->AutomaticPlaneGenerationOn(); this->polyDataMapper->SetInputConnection(this->TextureMapToPlane->GetOutputPort()); } vtkQtTreeRingLabelMapper::~vtkQtTreeRingLabelMapper() { this->SetRenderer(nullptr); this->SetLabelTextProperty(nullptr); this->SetFieldDataName(nullptr); this->SetTextRotationArrayName(nullptr); this->VCoord->Delete(); this->PlaneSource->Delete(); this->TextureMapToPlane->Delete(); this->polyDataMapper->Delete(); this->QtImageSource->Delete(); this->LabelTexture->Delete(); delete this->QtImage; } //------------------------------------------------------------------------------ void vtkQtTreeRingLabelMapper::RenderOverlay(vtkViewport* viewport, vtkActor2D* actor) { vtkRenderer* ren = vtkRenderer::SafeDownCast(viewport); if (ren) { this->LabelTexture->Render(ren); } this->polyDataMapper->RenderOverlay(viewport, actor); } //------------------------------------------------------------------------------ void vtkQtTreeRingLabelMapper::RenderOpaqueGeometry(vtkViewport* viewport, vtkActor2D* actor) { if (!QApplication::instance()) { vtkErrorMacro("This class requires a QApplication instance."); return; } vtkTextProperty* tprop = this->LabelTextProperty; if (!tprop) { vtkErrorMacro(<< "Need text property to render labels"); return; } int numComp = 0, activeComp = 0; vtkAbstractArray* abstractData; vtkDataArray *numericData, *sectorInfo; vtkStringArray* stringData; vtkTree* input = this->GetInputTree(); if (!input) { vtkErrorMacro(<< "Need input tree to render labels (2)"); return; } if (input->GetNumberOfVertices() == 0) { return; } vtkDataSetAttributes* pd = input->GetVertexData(); sectorInfo = this->GetInputArrayToProcess(0, input); if (!sectorInfo) { vtkErrorMacro(<< "Input Tree does not have sector information."); return; } vtkRenderer* renderer = vtkRenderer::SafeDownCast(viewport); if (this->CurrentViewPort != viewport || this->GetMTime() > this->BuildTime || input->GetMTime() > this->BuildTime || tprop->GetMTime() > this->BuildTime || renderer->GetActiveCamera()->GetMTime() > this->BuildTime) { vtkDebugMacro(<< "Rebuilding labels"); const int* size = renderer->GetSize(); this->WindowSize[0] = size[0]; this->WindowSize[1] = size[1]; // See if we have to recalculate fonts sizes if (this->CurrentViewPort != viewport) { this->CurrentViewPort = viewport; } // figure out what to label, and if we can label it abstractData = nullptr; numericData = nullptr; stringData = nullptr; switch (this->LabelMode) { case VTK_LABEL_SCALARS: if (pd->GetScalars()) { numericData = pd->GetScalars(); } break; case VTK_LABEL_VECTORS: if (pd->GetVectors()) { numericData = pd->GetVectors(); } break; case VTK_LABEL_NORMALS: if (pd->GetNormals()) { numericData = pd->GetNormals(); } break; case VTK_LABEL_TCOORDS: if (pd->GetTCoords()) { numericData = pd->GetTCoords(); } break; case VTK_LABEL_TENSORS: if (pd->GetTensors()) { numericData = pd->GetTensors(); } break; case VTK_LABEL_FIELD_DATA: { int arrayNum; if (this->FieldDataName != nullptr) { abstractData = pd->GetAbstractArray(this->FieldDataName, arrayNum); } else { arrayNum = (this->FieldDataArray < pd->GetNumberOfArrays() ? this->FieldDataArray : pd->GetNumberOfArrays() - 1); abstractData = pd->GetAbstractArray(arrayNum); } numericData = vtkArrayDownCast(abstractData); stringData = vtkArrayDownCast(abstractData); }; break; } // determine number of components and check input if (numericData) { numComp = numericData->GetNumberOfComponents(); activeComp = 0; if (this->LabeledComponent >= 0) { activeComp = (this->LabeledComponent < numComp ? this->LabeledComponent : numComp - 1); numComp = 1; } } else if (!stringData) { vtkErrorMacro(<< "Need input data to render labels (3)"); return; } this->LabelTree(input, sectorInfo, numericData, stringData, activeComp, numComp, viewport); } VTK_CREATE(vtkQImageToImageSource, qis); qis->SetQImage(this->QtImage); this->LabelTexture->SetInputConnection(qis->GetOutputPort()); this->LabelTexture->PremultipliedAlphaOn(); this->PlaneSource->SetOrigin(0, 0, 0); this->PlaneSource->SetPoint1(this->WindowSize[0], 0, 0); this->PlaneSource->SetPoint2(0, this->WindowSize[1], 0); this->polyDataMapper->RenderOpaqueGeometry(viewport, actor); } void vtkQtTreeRingLabelMapper::LabelTree(vtkTree* tree, vtkDataArray* sectorInfo, vtkDataArray* numericData, vtkStringArray* stringData, int activeComp, int numComps, vtkViewport* viewport) { delete this->QtImage; this->QtImage = new QImage(this->WindowSize[0], this->WindowSize[1], QImage::Format_ARGB32); char string[1024]; vtkIdType i, root = tree->GetRoot(); if (root < 0) { vtkErrorMacro(<< "Input Tree does not have a root."); return; } vtkDataArray* TextRotationArray = tree->GetVertexData()->GetArray(this->TextRotationArrayName); // FIXME - The image surface appears to be initialized to grey. This clears it, but seems like // there should be a more appropriate way to do this... QPainter painter(this->QtImage); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); painter.setCompositionMode(QPainter::CompositionMode_Clear); painter.drawImage(0, 0, *this->QtImage); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); // end FIXME // set text properties from LabelTextProperty QFont fontSpec(this->LabelTextProperty->GetFontFamilyAsString()); fontSpec.setStyleStrategy(QFont::PreferAntialias); fontSpec.setBold(this->LabelTextProperty->GetBold()); fontSpec.setItalic(this->LabelTextProperty->GetItalic()); fontSpec.setPointSize(this->LabelTextProperty->GetFontSize()); double* fc = this->LabelTextProperty->GetColor(); double slimits[4], sdimDC[2], textPosDC[2]; for (i = 0; i < tree->GetNumberOfVertices(); i++) { sectorInfo->GetTuple(i, slimits); // check to see if the point is in the window if (!this->PointInWindow(slimits, sdimDC, textPosDC, viewport)) { continue; } // check to see if the text will fit in the sector this->GetVertexLabel(i, numericData, stringData, activeComp, numComps, string, sizeof(string)); QString ResultString(string); double x[3]; x[0] = textPosDC[0]; x[1] = textPosDC[1]; // Now, create the layout for the actual label... QString textString, testString; // Qt's coordinate system starts at the top left corner of the layout... // vtk has been using the text baseline as the starting point, so // we need to add a correction factor to account for the difference QFontMetricsF fontMetric(fontSpec); double baseline = fontMetric.ascent(); // set ellipsis bounds for this piece of text // Note, don't use ellipsis unless at least 5 characters (w's) can be displayed... QString minString("wwwww"); #if (QT_VERSION <= QT_VERSION_CHECK(5, 11, 0)) int minStringWidth = fontMetric.width(minString); #else int minStringWidth = fontMetric.horizontalAdvance(minString); #endif double allowedTextWidth = 0; if (sdimDC[0] > sdimDC[1]) { if (sdimDC[0] < minStringWidth) { continue; } allowedTextWidth = static_cast(sdimDC[0]); } else { if (sdimDC[1] < minStringWidth) { continue; } allowedTextWidth = static_cast(sdimDC[1]); } // FIXME - This next step assumes no markup to the original text, which is probably a bad // choice, but is necessary due to Qt's current methods for handling and computing // rich text widths and ellipsis... // QTextStream(&testString) << fontMetric.elidedText( QString::fromUtf8( ResultString.c_str() // ), Qt::ElideRight, allowedTextWidth ); QTextStream(&testString) << fontMetric.elidedText( ResultString, Qt::ElideRight, allowedTextWidth); QTextStream(&textString) << "" << testString << ""; // end FIXME // check to see if the text will fit in the sector; // if not, don't draw the label... if (sdimDC[0] > sdimDC[1]) { if (sdimDC[1] < fontMetric.height()) continue; } else { if (sdimDC[0] < fontMetric.height()) continue; } double delta_x = 0., delta_y = 0.; switch (this->LabelTextProperty->GetJustification()) { case VTK_TEXT_LEFT: break; case VTK_TEXT_CENTERED: // FIXME - The width is not correct for html encodings... #if (QT_VERSION <= QT_VERSION_CHECK(5, 11, 0)) delta_x = -(fontMetric.width(testString)) / 2.; #else delta_x = -(fontMetric.horizontalAdvance(testString)) / 2.; #endif break; case VTK_TEXT_RIGHT: // FIXME - The width is not correct for html encodings... #if (QT_VERSION <= QT_VERSION_CHECK(5, 11, 0)) delta_x = -fontMetric.width(testString); #else delta_x = -fontMetric.horizontalAdvance(testString); #endif break; } switch (this->LabelTextProperty->GetVerticalJustification()) { case VTK_TEXT_TOP: break; case VTK_TEXT_CENTERED: delta_y = -(fontMetric.height()) / 2.; // delta_y = -fontMetric.ascent()/2.; break; case VTK_TEXT_BOTTOM: delta_y = -baseline; break; } double h = this->WindowSize[1]; // specify the clockwise text rotation angle double rotation = 0.; if (TextRotationArray) { TextRotationArray->GetTuple(i, &rotation); rotation *= -1.; } if (this->LabelTextProperty->GetShadow()) { painter.save(); int shOff[2]; this->LabelTextProperty->GetShadowOffset(shOff); painter.translate(x[0], h - x[1]); // Make sure the shadow offset is an even number of pixels in x and y. // This allows the shadow text to render the same way as the main text. QTransform t; t.rotate(rotation); t.translate(shOff[0], -shOff[1]); QPointF pt = t.map(QPoint(0, 0)); painter.translate(static_cast(pt.x() + 0.5), static_cast(pt.y() + 0.5)); painter.rotate(rotation); painter.translate(delta_x, delta_y); double shadowColor[3]; this->LabelTextProperty->GetShadowColor(shadowColor); QTextDocument textDocument; textDocument.setDocumentMargin(0); textDocument.setDefaultFont(fontSpec); QString shadowStyleSheet; QTextStream(&shadowStyleSheet) << "* { color: rgb( " << shadowColor[0] * 255 << ", " << shadowColor[1] * 255 << ", " << shadowColor[2] * 255 << " ) }"; textDocument.setDefaultStyleSheet(shadowStyleSheet); textDocument.setHtml(textString); textDocument.drawContents(&painter); painter.restore(); } painter.save(); painter.translate(x[0], h - x[1]); painter.rotate(rotation); painter.translate(delta_x, delta_y); QTextDocument textDocument; textDocument.setDocumentMargin(0); textDocument.setDefaultFont(fontSpec); QString styleSheet; QTextStream(&styleSheet) << "* { color: rgb( " << fc[0] * 255 << ", " << fc[1] * 255 << ", " << fc[2] * 255 << " ) }"; textDocument.setDefaultStyleSheet(styleSheet); textDocument.setHtml(textString); textDocument.drawContents(&painter); painter.restore(); } // QFile file( "C:/src/qtfonts.png" ); // file.open( QIODevice::WriteOnly); // this->QtImage->save(&file, "PNG"); // file.close(); this->BuildTime.Modified(); } bool vtkQtTreeRingLabelMapper::PointInWindow( double* sinfo, double* newDim, double* textPosDC, vtkViewport* viewport) { double r = (0.5 * (sinfo[3] - sinfo[2])) + sinfo[2]; double theta = sinfo[0] + (0.5 * (sinfo[1] - sinfo[0])); double x = r * cos(vtkMath::RadiansFromDegrees(theta)); double y = r * sin(vtkMath::RadiansFromDegrees(theta)); this->VCoord->SetViewport(viewport); this->VCoord->SetValue(x, y, 0.); int* dc = VCoord->GetComputedDisplayValue(nullptr); textPosDC[0] = dc[0]; textPosDC[1] = dc[1]; // Get the window extents vtkWindow* win = viewport->GetVTKWindow(); const int* winSize = win->GetSize(); bool return_value = true; if (dc[0] < 0 || dc[0] > winSize[0]) return_value = false; if (dc[1] < 0 || dc[1] > winSize[1]) return_value = false; double xlc = sinfo[2] * cos(vtkMath::RadiansFromDegrees(sinfo[0])); double xuc = sinfo[3] * cos(vtkMath::RadiansFromDegrees(sinfo[0])); double ylc = sinfo[2] * sin(vtkMath::RadiansFromDegrees(sinfo[0])); double yuc = sinfo[3] * sin(vtkMath::RadiansFromDegrees(sinfo[0])); this->VCoord->SetValue(xlc, ylc, 0.); int* dc1 = VCoord->GetComputedDisplayValue(nullptr); double dc1x = dc1[0]; double dc1y = dc1[1]; this->VCoord->SetValue(xuc, yuc, 0.); int* dc2 = VCoord->GetComputedDisplayValue(nullptr); double dc2x = dc2[0]; double dc2y = dc2[1]; double height = sqrt(((dc2x - dc1x) * (dc2x - dc1x)) + ((dc2y - dc1y) * (dc2y - dc1y))); double widthWC = r * vtkMath::RadiansFromDegrees(sinfo[1] - sinfo[0]); double width = widthWC * height / sqrt((xuc - xlc) * (xuc - xlc) + (yuc - ylc) * (yuc - ylc)); newDim[0] = width; newDim[1] = height; // We are done with the coordinate, so release the viewport this->VCoord->SetViewport(nullptr); return return_value; } //------------------------------------------------------------------------------ void vtkQtTreeRingLabelMapper::SetSectorsArrayName(const char* name) { this->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_VERTICES, name); } vtkTree* vtkQtTreeRingLabelMapper::GetInputTree() { return vtkTree::SafeDownCast(this->GetExecutive()->GetInputData(0, 0)); } void vtkQtTreeRingLabelMapper::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "WindowSize: " << this->WindowSize[0] << "w x" << this->WindowSize[1] << "h\n"; if (this->LabelTextProperty) { os << indent << "Label Text Property:\n"; this->LabelTextProperty->PrintSelf(os, indent.GetNextIndent()); } else { os << indent << "Label Text Property: (none)\n"; } os << indent << "TextRotationArrayName: " << (this->TextRotationArrayName ? this->TextRotationArrayName : "(none)") << endl; } void vtkQtTreeRingLabelMapper::GetVertexLabel(vtkIdType vertex, vtkDataArray* numericData, vtkStringArray* stringData, int activeComp, int numComps, char* string, size_t stringSize) { char format[1024]; double val; int j; if (numericData) { if (numComps == 1) { if (numericData->GetDataType() == VTK_CHAR) { if (strcmp(this->LabelFormat, "%c") != 0) { vtkErrorMacro(<< "Label format must be %c to use with char"); string[0] = '\0'; return; } snprintf(string, stringSize, this->LabelFormat, static_cast(numericData->GetComponent(vertex, activeComp))); } else { snprintf( string, stringSize, this->LabelFormat, numericData->GetComponent(vertex, activeComp)); } } else { strcpy(format, "("); strcat(format, this->LabelFormat); for (j = 0; j < (numComps - 1); j++) { snprintf(string, stringSize, format, numericData->GetComponent(vertex, j)); strcpy(format, string); strcat(format, ", "); strcat(format, this->LabelFormat); } snprintf(string, stringSize, format, numericData->GetComponent(vertex, numComps - 1)); strcat(string, ")"); } } else if (stringData) // rendering string data { if (strcmp(this->LabelFormat, "%s") != 0) { vtkErrorMacro(<< "Label format must be %s to use with strings"); string[0] = '\0'; return; } snprintf(string, stringSize, this->LabelFormat, stringData->GetValue(vertex).c_str()); } else // Use the vertex id { val = static_cast(vertex); snprintf(string, stringSize, this->LabelFormat, val); } } vtkMTimeType vtkQtTreeRingLabelMapper::GetMTime() { vtkMTimeType filterMTime = this->MTime.GetMTime(); if (this->Renderer) { vtkRenderWindow* rw = this->Renderer->GetRenderWindow(); if (rw) { vtkMTimeType renWindMTime = rw->GetMTime(); if (renWindMTime > filterMTime) { const int* rwSize = rw->GetSize(); if (rwSize[0] != this->WindowSize[0] || rwSize[1] != this->WindowSize[1]) { return renWindMTime; } } } } return filterMTime; }