/*========================================================================= Program: Visualization Toolkit Module: vtkCornerAnnotation.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 "vtkCornerAnnotation.h" #include "vtkAlgorithmOutput.h" #include "vtkImageActor.h" #include "vtkImageData.h" #include "vtkImageMapToWindowLevelColors.h" #include "vtkImageMapper3D.h" #include "vtkObjectFactory.h" #include "vtkPropCollection.h" #include "vtkTextMapper.h" #include "vtkTextProperty.h" #include "vtkViewport.h" #include "vtkWindow.h" #include //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkCornerAnnotation); vtkCxxSetObjectMacro(vtkCornerAnnotation, ImageActor, vtkImageActor); vtkCxxSetObjectMacro(vtkCornerAnnotation, WindowLevel, vtkImageMapToWindowLevelColors); vtkCxxSetObjectMacro(vtkCornerAnnotation, TextProperty, vtkTextProperty); //------------------------------------------------------------------------------ vtkCornerAnnotation::vtkCornerAnnotation() { this->PositionCoordinate->SetCoordinateSystemToNormalizedViewport(); this->PositionCoordinate->SetValue(0.2, 0.85); this->LastSize[0] = 0; this->LastSize[1] = 0; this->MaximumLineHeight = 1.0; this->MinimumFontSize = 6; this->MaximumFontSize = 200; this->LinearFontScaleFactor = 5.0; this->NonlinearFontScaleFactor = 0.35; this->FontSize = 15; this->TextProperty = vtkTextProperty::New(); this->TextProperty->ShadowOff(); for (int i = 0; i < NumTextPositions; i++) { this->CornerText[i] = nullptr; this->TextMapper[i] = vtkTextMapper::New(); this->TextActor[i] = vtkActor2D::New(); this->TextActor[i]->SetMapper(this->TextMapper[i]); } this->ImageActor = nullptr; this->LastImageActor = nullptr; this->WindowLevel = nullptr; this->LevelShift = 0; this->LevelScale = 1; this->ShowSliceAndImage = 1; } //------------------------------------------------------------------------------ vtkCornerAnnotation::~vtkCornerAnnotation() { this->SetTextProperty(nullptr); for (int i = 0; i < NumTextPositions; i++) { delete[] this->CornerText[i]; this->TextMapper[i]->Delete(); this->TextActor[i]->Delete(); } this->SetWindowLevel(nullptr); this->SetImageActor(nullptr); } //------------------------------------------------------------------------------ // Release any graphics resources that are being consumed by this actor. // The parameter window could be used to determine which graphic // resources to release. void vtkCornerAnnotation::ReleaseGraphicsResources(vtkWindow* win) { this->Superclass::ReleaseGraphicsResources(win); for (int i = 0; i < NumTextPositions; i++) { this->TextActor[i]->ReleaseGraphicsResources(win); } } //------------------------------------------------------------------------------ void vtkCornerAnnotation::TextReplace(vtkImageActor* ia, vtkImageMapToWindowLevelColors* wl) { int i; char *text, *text2; int slice = 0, slice_max = 0; char *rpos, *tmp; double window = 0, level = 0; long int windowi = 0, leveli = 0; vtkImageData *wl_input = nullptr, *ia_input = nullptr; int input_type_is_float = 0; if (wl) { window = wl->GetWindow(); window *= this->LevelScale; level = wl->GetLevel(); level = level * this->LevelScale + this->LevelShift; windowi = (long int)window; leveli = (long int)level; wl_input = vtkImageData::SafeDownCast(wl->GetInput()); if (wl_input) { input_type_is_float = (wl_input->GetScalarType() == VTK_FLOAT || wl_input->GetScalarType() == VTK_DOUBLE); } } if (ia) { slice = ia->GetSliceNumber() - ia->GetSliceNumberMin() + 1; slice_max = ia->GetSliceNumberMax() - ia->GetSliceNumberMin() + 1; ia_input = ia->GetInput(); if (!wl_input && ia_input) { input_type_is_float = (ia_input->GetScalarType() == VTK_FLOAT || ia_input->GetScalarType() == VTK_DOUBLE); } } // search for tokens, replace and then assign to TextMappers for (i = 0; i < NumTextPositions; i++) { if (this->CornerText[i] && strlen(this->CornerText[i])) { size_t textSize = strlen(this->CornerText[i]) + 1000; text = new char[textSize]; text2 = new char[textSize]; strcpy(text, this->CornerText[i]); // now do the replacements rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (ia && this->ShowSliceAndImage) { snprintf(text2, textSize, "%sImage: %i%s", text, slice, rpos + 7); } else { snprintf(text2, textSize, "%s%s", text, rpos + 7); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (ia && this->ShowSliceAndImage) { snprintf(text2, textSize, "%sImage: %i / %i%s", text, slice, slice_max, rpos + 15); } else { snprintf(text2, textSize, "%s%s", text, rpos + 15); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (ia && this->ShowSliceAndImage) { snprintf(text2, textSize, "%sSlice: %i%s", text, slice, rpos + 7); } else { snprintf(text2, textSize, "%s%s", text, rpos + 7); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (ia && this->ShowSliceAndImage) { snprintf(text2, textSize, "%sSlice: %i / %i%s", text, slice, slice_max, rpos + 15); } else { snprintf(text2, textSize, "%s%s", text, rpos + 15); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (ia && this->ShowSliceAndImage) { double* dbounds = ia->GetDisplayBounds(); int* dext = ia->GetDisplayExtent(); double pos; if (dext[0] == dext[1]) { pos = dbounds[0]; } else if (dext[2] == dext[3]) { pos = dbounds[2]; } else { pos = dbounds[4]; } snprintf(text2, textSize, "%s%g%s", text, pos, rpos + 11); } else { snprintf(text2, textSize, "%s%s", text, rpos + 11); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (wl) { if (input_type_is_float) { snprintf(text2, textSize, "%sWindow: %g%s", text, window, rpos + 8); } else { snprintf(text2, textSize, "%sWindow: %li%s", text, windowi, rpos + 8); } } else { snprintf(text2, textSize, "%s%s", text, rpos + 8); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (wl) { if (input_type_is_float) { snprintf(text2, textSize, "%sLevel: %g%s", text, level, rpos + 7); } else { snprintf(text2, textSize, "%sLevel: %li%s", text, leveli, rpos + 7); } } else { snprintf(text2, textSize, "%s%s", text, rpos + 7); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } rpos = strstr(text, ""); while (rpos) { *rpos = '\0'; if (wl) { if (input_type_is_float) { snprintf(text2, textSize, "%sWW/WL: %g / %g%s", text, window, level, rpos + 14); } else { snprintf(text2, textSize, "%sWW/WL: %li / %li%s", text, windowi, leveli, rpos + 14); } } else { snprintf(text2, textSize, "%s%s", text, rpos + 14); } tmp = text; text = text2; text2 = tmp; rpos = strstr(text, ""); } this->TextMapper[i]->SetInput(text); delete[] text; delete[] text2; } else { this->TextMapper[i]->SetInput(""); } } } //------------------------------------------------------------------------------ int vtkCornerAnnotation::RenderOverlay(vtkViewport* viewport) { // Everything is built, just have to render // only render if font is at least minimum font if (this->FontSize >= this->MinimumFontSize) { for (int i = 0; i < NumTextPositions; i++) { this->TextActor[i]->RenderOverlay(viewport); } } return 1; } namespace { // Ported from old vtkTextMapper implementation int GetNumberOfLines(const char* str) { if (str == nullptr || *str == '\0') { return 0; } int result = 1; while (str != nullptr) { if ((str = strstr(str, "\n")) != nullptr) { result++; str++; // Skip '\n' } } return result; } } //------------------------------------------------------------------------------ int vtkCornerAnnotation::RenderOpaqueGeometry(vtkViewport* viewport) { int fontSize; // Check to see whether we have to rebuild everything // If the viewport has changed we may - or may not need // to rebuild, it depends on if the projected coords change int viewport_size_has_changed = 0; if (viewport->GetMTime() > this->BuildTime || (viewport->GetVTKWindow() && viewport->GetVTKWindow()->GetMTime() > this->BuildTime)) { const int* vSize = viewport->GetSize(); if (this->LastSize[0] != vSize[0] || this->LastSize[1] != vSize[1]) { viewport_size_has_changed = 1; } } // Is there an image actor ? vtkImageMapToWindowLevelColors* wl = this->WindowLevel; vtkImageActor* ia = nullptr; if (this->ImageActor) { ia = this->ImageActor; } else { vtkPropCollection* pc = viewport->GetViewProps(); int numProps = pc->GetNumberOfItems(); for (int i = 0; i < numProps; i++) { ia = vtkImageActor::SafeDownCast(pc->GetItemAsObject(i)); if (ia) { if (ia->GetInput() && !wl) { wl = vtkImageMapToWindowLevelColors::SafeDownCast(ia->GetMapper()->GetInputAlgorithm()); } break; } } } int tprop_has_changed = (this->TextProperty && this->TextProperty->GetMTime() > this->BuildTime); // Check to see whether we have to rebuild everything if (viewport_size_has_changed || tprop_has_changed || (this->GetMTime() > this->BuildTime) || (ia && (ia != this->LastImageActor || ia->GetMTime() > this->BuildTime)) || (wl && wl->GetMTime() > this->BuildTime)) { const int* vSize = viewport->GetSize(); vtkDebugMacro(<< "Rebuilding text"); // Replace text this->TextReplace(ia, wl); // Get the viewport size in display coordinates this->LastSize[0] = vSize[0]; this->LastSize[1] = vSize[1]; // Only adjust size then the text changes due to non w/l slice reasons if (viewport_size_has_changed || tprop_has_changed || this->GetMTime() > this->BuildTime) { // Rebuid text props. // Perform shallow copy here since each individual corner has a // different alignment/size but they share the other this->TextProperty // attributes. fontSize = this->TextMapper[0]->GetTextProperty()->GetFontSize(); if (tprop_has_changed) { for (int i = 0; i < NumTextPositions; i++) { vtkTextProperty* tprop = this->TextMapper[i]->GetTextProperty(); tprop->ShallowCopy(this->TextProperty); tprop->SetFontSize(fontSize); } this->SetTextActorsJustification(); } // Update all the composing objects to find the best size for the font // use the last size as a first guess /* +---------+ |2 7 3| | | |6 5| | | |0 4 1| +---------+ */ int tempi[2 * NumTextPositions]; int allZeros = 1; for (int i = 0; i < NumTextPositions; i++) { this->TextMapper[i]->GetSize(viewport, tempi + i * 2); if (tempi[2 * i] > 0 || tempi[2 * i + 1] > 0) { allZeros = 0; } } if (allZeros) { return 0; } int height_02 = tempi[1] + tempi[5]; // total height of text in left top/bottom corners int height_13 = tempi[3] + tempi[7]; // total height of text in right top/bottom corners int height_47 = tempi[9] + tempi[15]; // total height of text at center of top/bottom edges int width_01 = tempi[0] + tempi[2]; // total width of text on bottom left/right corners int width_23 = tempi[4] + tempi[6]; // total width of text on top left/right corners int width_56 = tempi[10] + tempi[12]; // total width of text at center of left/right edges int max_width_corners = (width_01 > width_23) ? width_01 : width_23; int max_width = (width_56 > max_width_corners) ? width_56 : max_width_corners; int num_lines_02 = GetNumberOfLines(this->TextMapper[0]->GetInput()) + GetNumberOfLines(this->TextMapper[2]->GetInput()); int num_lines_13 = GetNumberOfLines(this->TextMapper[1]->GetInput()) + GetNumberOfLines(this->TextMapper[3]->GetInput()); int num_lines_47 = GetNumberOfLines(this->TextMapper[4]->GetInput()) + GetNumberOfLines(this->TextMapper[7]->GetInput()); int line_max_02 = (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_02 ? num_lines_02 : 1); int line_max_13 = (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_13 ? num_lines_13 : 1); int line_max_47 = (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_47 ? num_lines_47 : 1); // Target size is to use 90% of x and y int tSize[2]; tSize[0] = (int)(0.9 * vSize[0]); tSize[1] = (int)(0.9 * vSize[1]); // While the size is too small increase it while (height_02 < tSize[1] && height_13 < tSize[1] && height_47 < tSize[1] && max_width < tSize[0] && height_02 < line_max_02 && height_13 < line_max_13 && height_47 < line_max_47 && fontSize < 100) { fontSize++; for (int i = 0; i < NumTextPositions; i++) { this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize); this->TextMapper[i]->GetSize(viewport, tempi + i * 2); } height_02 = tempi[1] + tempi[5]; height_13 = tempi[3] + tempi[7]; height_47 = tempi[9] + tempi[15]; width_01 = tempi[0] + tempi[2]; width_23 = tempi[4] + tempi[6]; width_56 = tempi[10] + tempi[12]; max_width_corners = (width_01 > width_23) ? width_01 : width_23; max_width = (width_56 > max_width_corners) ? width_56 : max_width_corners; } // While the size is too large decrease it while ((height_02 > tSize[1] || height_13 > tSize[1] || height_47 > tSize[1] || max_width > tSize[0] || height_02 > line_max_02 || height_13 > line_max_13 || height_47 > line_max_47) && fontSize > 0) { fontSize--; for (int i = 0; i < NumTextPositions; i++) { this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize); this->TextMapper[i]->GetSize(viewport, tempi + i * 2); } height_02 = tempi[1] + tempi[5]; height_13 = tempi[3] + tempi[7]; height_47 = tempi[9] + tempi[15]; width_01 = tempi[0] + tempi[2]; width_23 = tempi[4] + tempi[6]; width_56 = tempi[10] + tempi[12]; max_width_corners = (width_01 > width_23) ? width_01 : width_23; max_width = (width_56 > max_width_corners) ? width_56 : max_width_corners; } fontSize = static_cast(pow((double)fontSize, NonlinearFontScaleFactor) * LinearFontScaleFactor); if (fontSize > this->MaximumFontSize) { fontSize = this->MaximumFontSize; } this->FontSize = fontSize; for (int i = 0; i < NumTextPositions; i++) { this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize); } // Now set the position of the TextActors this->SetTextActorsPosition(vSize); for (int i = 0; i < NumTextPositions; i++) { this->TextActor[i]->SetProperty(this->GetProperty()); } } this->BuildTime.Modified(); this->LastImageActor = ia; } // Everything is built, just have to render if (this->FontSize >= this->MinimumFontSize) { for (int i = 0; i < NumTextPositions; i++) { this->TextActor[i]->RenderOpaqueGeometry(viewport); } } return 1; } //------------------------------------------------------------------------------ // Description: // Does this prop have some translucent polygonal geometry? vtkTypeBool vtkCornerAnnotation::HasTranslucentPolygonalGeometry() { return 0; } //------------------------------------------------------------------------------ void vtkCornerAnnotation::SetTextActorsPosition(const int vsize[2]) { this->TextActor[LowerLeft]->SetPosition(5, 5); this->TextActor[LowerRight]->SetPosition(vsize[0] - 5, 5); this->TextActor[UpperLeft]->SetPosition(5, vsize[1] - 5); this->TextActor[UpperRight]->SetPosition(vsize[0] - 5, vsize[1] - 5); this->TextActor[LowerEdge]->SetPosition(vsize[0] / 2, 5); this->TextActor[UpperEdge]->SetPosition(vsize[0] / 2, vsize[1] - 5); this->TextActor[LeftEdge]->SetPosition(5, vsize[1] / 2); this->TextActor[RightEdge]->SetPosition(vsize[0] - 5, vsize[1] / 2); } //------------------------------------------------------------------------------ void vtkCornerAnnotation::SetTextActorsJustification() { vtkTextProperty* tprop = this->TextMapper[LowerLeft]->GetTextProperty(); tprop->SetJustificationToLeft(); tprop->SetVerticalJustificationToBottom(); tprop = this->TextMapper[LowerRight]->GetTextProperty(); tprop->SetJustificationToRight(); tprop->SetVerticalJustificationToBottom(); tprop = this->TextMapper[UpperLeft]->GetTextProperty(); tprop->SetJustificationToLeft(); tprop->SetVerticalJustificationToTop(); tprop = this->TextMapper[UpperRight]->GetTextProperty(); tprop->SetJustificationToRight(); tprop->SetVerticalJustificationToTop(); tprop = this->TextMapper[LowerEdge]->GetTextProperty(); tprop->SetJustificationToCentered(); tprop->SetVerticalJustificationToBottom(); tprop = this->TextMapper[UpperEdge]->GetTextProperty(); tprop->SetJustificationToCentered(); tprop->SetVerticalJustificationToTop(); tprop = this->TextMapper[LeftEdge]->GetTextProperty(); tprop->SetJustificationToLeft(); tprop->SetVerticalJustificationToCentered(); tprop = this->TextMapper[RightEdge]->GetTextProperty(); tprop->SetJustificationToRight(); tprop->SetVerticalJustificationToCentered(); } //------------------------------------------------------------------------------ void vtkCornerAnnotation::SetText(int i, const char* text) { if (i < 0 || i >= NumTextPositions) { return; } if (!text || (this->CornerText[i] && (!strcmp(this->CornerText[i], text)))) { return; } delete[] this->CornerText[i]; this->CornerText[i] = new char[strlen(text) + 1]; strcpy(this->CornerText[i], text); this->Modified(); } //------------------------------------------------------------------------------ const char* vtkCornerAnnotation::GetText(int i) { if (i < 0 || i >= NumTextPositions) { return nullptr; } return this->CornerText[i]; } //------------------------------------------------------------------------------ void vtkCornerAnnotation::ClearAllTexts() { for (int i = 0; i < NumTextPositions; i++) { this->SetText(i, ""); } } //------------------------------------------------------------------------------ void vtkCornerAnnotation::CopyAllTextsFrom(vtkCornerAnnotation* ca) { for (int i = 0; i < NumTextPositions; i++) { this->SetText(i, ca->GetText(i)); } } //------------------------------------------------------------------------------ void vtkCornerAnnotation::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "ImageActor: " << this->GetImageActor() << endl; os << indent << "MinimumFontSize: " << this->GetMinimumFontSize() << endl; os << indent << "MaximumFontSize: " << this->GetMaximumFontSize() << endl; os << indent << "LinearFontScaleFactor: " << this->GetLinearFontScaleFactor() << endl; os << indent << "NonlinearFontScaleFactor: " << this->GetNonlinearFontScaleFactor() << endl; os << indent << "WindowLevel: " << this->GetWindowLevel() << endl; os << indent << "Mapper: " << this->GetMapper() << endl; os << indent << "MaximumLineHeight: " << this->MaximumLineHeight << endl; os << indent << "LevelShift: " << this->LevelShift << endl; os << indent << "LevelScale: " << this->LevelScale << endl; os << indent << "TextProperty: " << this->TextProperty << endl; os << indent << "ShowSliceAndImage: " << this->ShowSliceAndImage << endl; }