/*========================================================================= 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 "vtkImageMapper3D.h" #include "vtkImageMapToWindowLevelColors.h" #include "vtkObjectFactory.h" #include "vtkPropCollection.h" #include "vtkTextMapper.h" #include "vtkTextProperty.h" #include "vtkViewport.h" #include "vtkWindow.h" //---------------------------------------------------------------------------- vtkStandardNewMacro(vtkCornerAnnotation); vtkSetObjectImplementationMacro(vtkCornerAnnotation,ImageActor,vtkImageActor); vtkSetObjectImplementationMacro(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 chage int viewport_size_has_changed = 0; if (viewport->GetMTime() > this->BuildTime || (viewport->GetVTKWindow() && viewport->GetVTKWindow()->GetMTime() > this->BuildTime)) { 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)) { 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 aligment/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 botttom 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? int vtkCornerAnnotation::HasTranslucentPolygonalGeometry() { return 0; } //---------------------------------------------------------------------------- void vtkCornerAnnotation::SetTextActorsPosition(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; }