ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ScaleBarWidget.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ScaleBarWidget.h"
9 
10 #include <vtkActor2D.h>
11 #include <vtkAlgorithmOutput.h>
12 #include <vtkCamera.h>
13 #include <vtkCoordinate.h>
14 #include <vtkLineSource.h>
15 #include <vtkPolyData.h>
16 #include <vtkPolyDataMapper2D.h>
17 #include <vtkProperty2D.h>
18 #include <vtkRenderWindow.h>
19 #include <vtkRenderWindowInteractor.h>
20 #include <vtkRenderer.h>
21 #include <vtkSmartPointer.h>
22 #include <vtkTextActor.h>
23 #include <vtkTextProperty.h>
24 
25 #include <QApplication>
26 #include <QCoreApplication>
27 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
28 #include <QDesktopWidget>
29 #endif
30 #include <QProcessEnvironment>
31 #include <QScreen>
32 #include <QString>
33 #include <algorithm>
34 #include <cmath>
35 #include <cstring>
36 #include <sstream>
37 
38 ScaleBarWidget::ScaleBarWidget(vtkRenderer* renderer) {
39  // Get cross-platform optimized DPI scaling
40  dpiScale = getPlatformAwareDPIScale();
41 
42  // Create line segment
43  auto lineSource = vtkSmartPointer<vtkLineSource>::New();
44  lineSource->SetPoint1(0.0, 0.0, 0.0);
45  lineSource->SetPoint2(100.0, 0.0, 0.0); // Initial length
46 
48  mapper->SetInputConnection(lineSource->GetOutputPort());
49 
51  lineActor->SetMapper(mapper);
52  lineActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
53  lineActor->GetProperty()->SetLineWidth(3.0 * dpiScale);
54 
55  // Create text - using cross-platform optimized font size
57  textActor->SetInput("1 m");
58  int optimizedFontSize = getOptimizedFontSize(18);
59  textActor->GetTextProperty()->SetFontSize(optimizedFontSize);
60  textActor->GetTextProperty()->SetColor(1.0, 1.0, 1.0);
61  textActor->GetTextProperty()->SetJustificationToCentered();
62  textActor->GetTextProperty()->SetVerticalJustificationToTop();
63  // Initial position set to center, will be adjusted in update()
64  textActor->SetPosition(100.0 * dpiScale, 25.0 * dpiScale);
65 
66  // Create tick marks
67  leftTickActor = createTickActor(0.0, 0.0, 10.0 * dpiScale);
68  rightTickActor = createTickActor(100.0, 0.0, 10.0 * dpiScale);
69 
70  if (renderer) {
71  renderer->AddActor2D(lineActor);
72  renderer->AddActor2D(textActor);
73  renderer->AddActor2D(leftTickActor);
74  renderer->AddActor2D(rightTickActor);
75  }
76 }
77 
79 
81  visible = v;
82  lineActor->SetVisibility(v);
83  textActor->SetVisibility(v);
84  leftTickActor->SetVisibility(v);
85  rightTickActor->SetVisibility(v);
86 }
87 
88 double ScaleBarWidget::getDPIScale() {
89  // DPI retrieval method compatible with different Qt versions
90  if (!QApplication::instance()) {
91  return 1.0;
92  }
93 
94 // Method 1: Qt 5.6+ uses QScreen::devicePixelRatio() (recommended)
95 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
96  QScreen* screen = QApplication::primaryScreen();
97  if (screen) {
98  return screen->devicePixelRatio();
99  }
100 #endif
101 
102 // Method 2: Qt 5.0+ uses QApplication::devicePixelRatio()
103 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
104  QCoreApplication* coreApp = QCoreApplication::instance();
105  QApplication* app = qobject_cast<QApplication*>(coreApp);
106  if (app) {
107  return app->devicePixelRatio();
108  }
109 #endif
110 
111 // Method 3: Qt 4.x calculates using QDesktopWidget
112 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
113  QDesktopWidget* desktop = QApplication::desktop();
114  if (desktop) {
115  // Calculate scaling from physical and logical DPI
116  int physicalDPI = desktop->physicalDpiX();
117  int logicalDPI = desktop->logicalDpiX();
118  if (logicalDPI > 0) {
119  return static_cast<double>(physicalDPI) / logicalDPI;
120  }
121  }
122 #endif
123 
124  // Method 4: Through environment variable or system detection
125  const char* qt_scale_factor = qgetenv("QT_SCALE_FACTOR");
126  if (qt_scale_factor) {
127  bool ok;
128  double scale = QString(qt_scale_factor).toDouble(&ok);
129  if (ok && scale > 0) {
130  return scale;
131  }
132  }
133 
134  return 1.0; // Default scaling
135 }
136 
137 int ScaleBarWidget::getOptimizedFontSize(int baseFontSize) {
138  // Get screen information
139  QScreen* screen = QApplication::primaryScreen();
140  if (!screen) {
141  return baseFontSize;
142  }
143 
144  // Get screen resolution information
145  QSize screenSize = screen->size();
146  int screenWidth = screenSize.width();
147  int screenHeight = screenSize.height();
148  int screenDPI = screen->physicalDotsPerInch();
149  int dpiScale = static_cast<int>(getDPIScale());
150 
151  // Platform-specific base font size adjustment
152  int platformBaseSize = baseFontSize;
153 #ifdef Q_OS_MAC
154  // macOS: Default font is slightly larger, but need to account for Retina
155  // display over-scaling
156  platformBaseSize = baseFontSize;
157  if (dpiScale > 1) {
158  // Retina display: Use smaller font to avoid over-scaling
159  platformBaseSize = std::max(12, baseFontSize - (dpiScale - 1) * 3);
160  }
161 #elif defined(Q_OS_WIN)
162  // Windows: Adjust font size based on DPI
163  if (screenDPI > 120) {
164  // High DPI display
165  platformBaseSize = std::max(12, baseFontSize - 2);
166  } else if (screenDPI < 96) {
167  // Low DPI display
168  platformBaseSize = baseFontSize + 2;
169  }
170 #elif defined(Q_OS_LINUX)
171  // Linux: Adjust based on screen resolution
172  if (screenWidth >= 1920 && screenHeight >= 1080) {
173  // High resolution display
174  platformBaseSize = std::max(12, baseFontSize - 2);
175  } else if (screenWidth < 1366) {
176  // Low resolution display
177  platformBaseSize = baseFontSize + 2;
178  }
179 #endif
180 
181  // Resolution-specific adjustment
182  int resolutionFactor = 0;
183  if (screenWidth >= 2560 && screenHeight >= 1440) {
184  // 2K and above resolution
185  resolutionFactor = -1;
186  } else if (screenWidth < 1366) {
187  // Low resolution
188  resolutionFactor = 1;
189  }
190 
191  // Final font size calculation
192  int finalSize = platformBaseSize + resolutionFactor;
193 
194  // Ensure font size is within reasonable range
195  finalSize = std::max(10, std::min(32, finalSize));
196 
197  return finalSize;
198 }
199 
200 double ScaleBarWidget::getPlatformAwareDPIScale() {
201  double dpiScale = getDPIScale();
202  QScreen* screen = QApplication::primaryScreen();
203  if (!screen) {
204  return dpiScale;
205  }
206 
207  // Get screen information
208  QSize screenSize = screen->size();
209  int screenWidth = screenSize.width();
210  int screenHeight = screenSize.height();
211  int screenDPI = screen->physicalDotsPerInch();
212 
213  // Platform-specific DPI scaling adjustment
214  double adjustedScale = dpiScale;
215 
216 #ifdef Q_OS_MAC
217  // macOS: Retina display needs special handling
218  if (dpiScale > 1) {
219  // For UI elements, use smaller scaling to avoid over-scaling
220  adjustedScale = 1.0 + (dpiScale - 1.0) * 0.6;
221  }
222 #elif defined(Q_OS_WIN)
223  // Windows: Adjust based on DPI settings
224  if (screenDPI > 120) {
225  // High DPI display, reduce scaling appropriately
226  adjustedScale = std::min(adjustedScale, 1.4);
227  } else if (screenDPI < 96) {
228  // Low DPI display, increase scaling appropriately
229  adjustedScale = std::max(adjustedScale, 1.0);
230  }
231 #elif defined(Q_OS_LINUX)
232  // Linux: Adjust based on resolution
233  if (screenWidth >= 2560 && screenHeight >= 1440) {
234  // Ultra high resolution, reduce scaling
235  adjustedScale = std::min(adjustedScale, 1.2);
236  } else if (screenWidth < 1366) {
237  // Low resolution, increase scaling
238  adjustedScale = std::max(adjustedScale, 1.0);
239  }
240 #endif
241 
242  // Ensure scaling is within reasonable range
243  adjustedScale = std::max(0.8, std::min(1.8, adjustedScale));
244 
245  return adjustedScale;
246 }
247 
248 vtkSmartPointer<vtkActor2D> ScaleBarWidget::createTickActor(double x,
249  double y,
250  double length) {
251  auto lineSource = vtkSmartPointer<vtkLineSource>::New();
252  lineSource->SetPoint1(x, y, 0.0);
253  lineSource->SetPoint2(x, y + length, 0.0); // Vertical tick mark
254 
256  mapper->SetInputConnection(lineSource->GetOutputPort());
257 
258  auto actor = vtkSmartPointer<vtkActor2D>::New();
259  actor->SetMapper(mapper);
260  actor->GetProperty()->SetColor(1.0, 1.0, 1.0);
261  actor->GetProperty()->SetLineWidth(2.0 * dpiScale);
262 
263  return actor;
264 }
265 
266 void ScaleBarWidget::update(vtkRenderer* renderer,
267  vtkRenderWindowInteractor* interactor) {
268  if (!visible || !renderer || !renderer->GetRenderWindow()) return;
269 
270  // Dynamically update DPI scaling (in case window moves to different DPI
271  // monitor)
272  double currentDPIScale = getPlatformAwareDPIScale();
273  if (std::abs(currentDPIScale - dpiScale) > 0.1) {
274  dpiScale = currentDPIScale;
275  // Update font size and line width - using cross-platform optimized font
276  // size
277  if (textActor) {
278  int optimizedFontSize = getOptimizedFontSize(18);
279  textActor->GetTextProperty()->SetFontSize(optimizedFontSize);
280  textActor->GetTextProperty()->SetJustificationToCentered();
281  textActor->GetTextProperty()->SetVerticalJustificationToTop();
282  }
283  if (lineActor) {
284  lineActor->GetProperty()->SetLineWidth(3.0 * dpiScale);
285  }
286  if (leftTickActor) {
287  leftTickActor->GetProperty()->SetLineWidth(2.0 * dpiScale);
288  }
289  if (rightTickActor) {
290  rightTickActor->GetProperty()->SetLineWidth(2.0 * dpiScale);
291  }
292  }
293 
294  // Get window size
295  int* size = renderer->GetRenderWindow()->GetSize();
296  int winW = size[0];
297  int winH = size[1];
298  // Scale bar length (pixels), considering DPI scaling
299  int barPixelLen = static_cast<int>(
300  (winW / 6.0) * dpiScale); // Approximately 1/6 of window width
301 
302  // Calculate ScaleBar position centered at window bottom
303  double bottomMargin = 25.0 * dpiScale; // Bottom margin
304  double centerX =
305  static_cast<double>(winW) / 2.0; // Window center X coordinate
306  double bottomY = bottomMargin; // Bottom Y coordinate
307 
308  // Calculate ScaleBar start and end positions (centered)
309  double p1[3] = {centerX - static_cast<double>(barPixelLen) / 2.0, bottomY,
310  0.0}; // Line left endpoint
311  double p2[3] = {centerX + static_cast<double>(barPixelLen) / 2.0, bottomY,
312  0.0}; // Line right endpoint
313  // Convert screen coordinates to world coordinates
314  double world1[4], world2[4];
315  renderer->SetDisplayPoint(static_cast<int>(p1[0]), static_cast<int>(p1[1]),
316  0);
317  renderer->DisplayToWorld();
318  memcpy(world1, renderer->GetWorldPoint(), sizeof(double) * 4);
319  renderer->SetDisplayPoint(static_cast<int>(p2[0]), static_cast<int>(p2[1]),
320  0);
321  renderer->DisplayToWorld();
322  memcpy(world2, renderer->GetWorldPoint(), sizeof(double) * 4);
323  // Calculate world distance
324  double dx = (world2[0] / world2[3]) - (world1[0] / world1[3]);
325  double dy = (world2[1] / world2[3]) - (world1[1] / world1[3]);
326  double dz = (world2[2] / world2[3]) - (world1[2] / world1[3]);
327  double dist = sqrt(dx * dx + dy * dy + dz * dz);
328  // Select appropriate display unit
329  double showLen = dist;
330  std::string unit = "m";
331  if (showLen < 0.01) {
332  showLen *= 1000;
333  unit = "mm";
334  } else if (showLen < 1) {
335  showLen *= 100;
336  unit = "cm";
337  } else if (showLen > 1000) {
338  showLen /= 1000;
339  unit = "km";
340  }
341  // Round to nice number
342  double niceLen = showLen;
343  if (showLen > 10)
344  niceLen = round(showLen / 10) * 10;
345  else if (showLen > 1)
346  niceLen = round(showLen);
347  else
348  niceLen = round(showLen * 10) / 10.0;
349  // Update line segment
350  auto mapper = dynamic_cast<vtkPolyDataMapper2D*>(lineActor->GetMapper());
351  if (mapper) {
352  auto lineSource = dynamic_cast<vtkLineSource*>(
353  mapper->GetInputConnection(0, 0)->GetProducer());
354  if (lineSource) {
355  lineSource->SetPoint1(p1[0], p1[1], 0.0);
356  lineSource->SetPoint2(p2[0], p2[1], 0.0);
357  lineSource->Update();
358  }
359  }
360  // Update tick mark positions
361  if (leftTickActor && rightTickActor) {
362  auto leftMapper =
363  dynamic_cast<vtkPolyDataMapper2D*>(leftTickActor->GetMapper());
364  auto rightMapper =
365  dynamic_cast<vtkPolyDataMapper2D*>(rightTickActor->GetMapper());
366 
367  if (leftMapper && rightMapper) {
368  auto leftSource = dynamic_cast<vtkLineSource*>(
369  leftMapper->GetInputConnection(0, 0)->GetProducer());
370  auto rightSource = dynamic_cast<vtkLineSource*>(
371  rightMapper->GetInputConnection(0, 0)->GetProducer());
372 
373  if (leftSource && rightSource) {
374  double tickLength = 8.0 * dpiScale;
375  leftSource->SetPoint1(p1[0], p1[1], 0.0);
376  leftSource->SetPoint2(p1[0], p1[1] + tickLength, 0.0);
377  leftSource->Update();
378 
379  rightSource->SetPoint1(p2[0], p2[1], 0.0);
380  rightSource->SetPoint2(p2[0], p2[1] + tickLength, 0.0);
381  rightSource->Update();
382  }
383  }
384  }
385 
386  // Update text
387  std::ostringstream oss;
388  oss.precision(2);
389  oss << std::fixed << niceLen << " " << unit;
390  textActor->SetInput(oss.str().c_str());
391 
392  // Set text centered alignment
393  textActor->GetTextProperty()->SetJustificationToCentered();
394  textActor->GetTextProperty()->SetVerticalJustificationToTop();
395 
396  // Calculate text position: centered below the line
397  double textX = centerX; // Use window center X coordinate to ensure text is
398  // centered
399  double textY = bottomY - 10.0 * dpiScale; // Below the line
400  textActor->SetPosition(textX, textY);
401 }
int size
ScaleBarWidget(vtkRenderer *renderer)
void update(vtkRenderer *renderer, vtkRenderWindowInteractor *interactor)
void setVisible(bool visible)
normal_z y
normal_z x