ctkVTKMagnifyWidget.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.commontk.org/LICENSE
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. // Qt includes
  15. #include <QEvent>
  16. #include <QMouseEvent>
  17. #include <QPointF>
  18. // CTK includes
  19. #include "ctkVTKMagnifyWidget.h"
  20. #include "ctkLogger.h"
  21. // VTK includes
  22. #include <QVTKWidget.h>
  23. #include <vtkRenderWindow.h>
  24. #include <vtkUnsignedCharArray.h>
  25. // STD includes
  26. #include <math.h>
  27. // Convenient macro
  28. #define VTK_CREATE(type, name) \
  29. vtkSmartPointer<type> name = vtkSmartPointer<type>::New()
  30. //--------------------------------------------------------------------------
  31. static ctkLogger logger("org.commontk.visualization.vtk.widgets.ctkVTKMagnifyWidget");
  32. //--------------------------------------------------------------------------
  33. //-----------------------------------------------------------------------------
  34. class ctkVTKMagnifyWidgetPrivate
  35. {
  36. Q_DECLARE_PUBLIC(ctkVTKMagnifyWidget);
  37. protected:
  38. ctkVTKMagnifyWidget* const q_ptr;
  39. public:
  40. ctkVTKMagnifyWidgetPrivate(ctkVTKMagnifyWidget& object);
  41. void init();
  42. void removePixmap();
  43. void observe(QVTKWidget * widget);
  44. void remove(QVTKWidget * widget);
  45. void updatePixmap(QVTKWidget * widget, QPointF pos);
  46. QList<QVTKWidget *> ObservedQVTKWidgets;
  47. double Magnification;
  48. };
  49. // --------------------------------------------------------------------------
  50. // ctkVTKMagnifyWidgetPrivate methods
  51. // --------------------------------------------------------------------------
  52. ctkVTKMagnifyWidgetPrivate::ctkVTKMagnifyWidgetPrivate(ctkVTKMagnifyWidget& object)
  53. :q_ptr(&object)
  54. {
  55. this->ObservedQVTKWidgets = QList<QVTKWidget *>();
  56. this->Magnification = 1.0;
  57. }
  58. // --------------------------------------------------------------------------
  59. void ctkVTKMagnifyWidgetPrivate::init()
  60. {
  61. this->removePixmap();
  62. }
  63. // --------------------------------------------------------------------------
  64. void ctkVTKMagnifyWidgetPrivate::removePixmap()
  65. {
  66. Q_Q(ctkVTKMagnifyWidget);
  67. QPixmap nullPixmap;
  68. q->setPixmap(nullPixmap);
  69. }
  70. // --------------------------------------------------------------------------
  71. void ctkVTKMagnifyWidgetPrivate::observe(QVTKWidget * widget)
  72. {
  73. Q_ASSERT(widget);
  74. // If we are not already observing the widget, add it to the list and install
  75. // the public implementation as the event filter to handle mousing
  76. if (!this->ObservedQVTKWidgets.contains(widget))
  77. {
  78. this->ObservedQVTKWidgets.append(widget);
  79. Q_Q(ctkVTKMagnifyWidget);
  80. widget->installEventFilter(q);
  81. }
  82. }
  83. // --------------------------------------------------------------------------
  84. void ctkVTKMagnifyWidgetPrivate::remove(QVTKWidget * widget)
  85. {
  86. Q_ASSERT(widget);
  87. // If we are observing the widget, remove it from the list and remove the
  88. // public implementations event filtering
  89. if (this->ObservedQVTKWidgets.contains(widget))
  90. {
  91. Q_ASSERT(this->ObservedQVTKWidgets.count(widget) == 1);
  92. this->ObservedQVTKWidgets.removeOne(widget);
  93. Q_Q(ctkVTKMagnifyWidget);
  94. widget->removeEventFilter(q);
  95. }
  96. }
  97. // --------------------------------------------------------------------------
  98. void ctkVTKMagnifyWidgetPrivate::updatePixmap(QVTKWidget * widget, QPointF pos)
  99. {
  100. Q_ASSERT(widget);
  101. Q_Q(ctkVTKMagnifyWidget);
  102. // Retrieve buffer of given QVTKWidget from its render window
  103. vtkRenderWindow * renderWindow = widget->GetRenderWindow();
  104. if (!renderWindow)
  105. {
  106. this->removePixmap();
  107. return;
  108. }
  109. // Get the window size and mouse position, and do error checking
  110. int * windowSize = renderWindow->GetSize();
  111. QPointF mouseWindowPos(pos.x(), static_cast<double>(windowSize[1]-1)-pos.y());
  112. if (mouseWindowPos.x() < 0 || mouseWindowPos.x() >= windowSize[0] ||
  113. mouseWindowPos.y() < 0 || mouseWindowPos.y() >= windowSize[1])
  114. {
  115. this->removePixmap();
  116. return;
  117. }
  118. // Compute indices into the render window's data array
  119. // Given a magnification and the label's widget size, compute the number of
  120. // pixels we can show. We should round to get a larger integer extent, since
  121. // we will later adjust the pixmap's location in paintEvent().
  122. // Left-right and up-down are in the render window coordinate frame.
  123. // (which is different in the y-direction compared to Qt coordinates).
  124. QSizeF sizeToMagnify = QSizeF(q->size()) / this->Magnification;
  125. double posLeft = (mouseWindowPos.x() - ((sizeToMagnify.width()-1.0) / 2.0));
  126. double posRight = (mouseWindowPos.x() + ((sizeToMagnify.width()-1.0) / 2.0));
  127. double posBottom = (mouseWindowPos.y() - ((sizeToMagnify.height()-1.0) / 2.0));
  128. double posTop = (mouseWindowPos.y() + ((sizeToMagnify.height()-1.0) / 2.0));
  129. // Round to ints, for indexing into the pixel array
  130. int indexLeft = floor(posLeft);
  131. int indexRight = ceil(posRight);
  132. int indexBottom = floor(posBottom);
  133. int indexTop = ceil(posTop);
  134. // Handle when mouse is near the border
  135. int minLeft = 0;
  136. int maxRight = windowSize[0]-1;
  137. int minBottom = 0;
  138. int maxTop = windowSize[1]-1;
  139. bool overLeft = indexLeft < minLeft;
  140. bool overRight = indexRight > maxRight;
  141. bool overBottom = indexBottom < minBottom;
  142. bool overTop = indexTop > maxTop;
  143. // Ensure we don't access nonexistant indices
  144. if (overLeft)
  145. {
  146. indexLeft = minLeft;
  147. posLeft = minLeft;
  148. }
  149. if (overRight)
  150. {
  151. indexRight = maxRight;
  152. posRight = maxRight;
  153. }
  154. if (overBottom)
  155. {
  156. indexBottom = minBottom;
  157. posBottom = minBottom;
  158. }
  159. if (overTop)
  160. {
  161. indexTop = maxTop;
  162. posTop = maxTop;
  163. }
  164. // Error case
  165. if (indexLeft > indexRight || indexBottom > indexTop)
  166. {
  167. this->removePixmap();
  168. return;
  169. }
  170. // Setup the pixelmap's position in the label
  171. Qt::Alignment alignment;
  172. if (overLeft && !overRight)
  173. {
  174. alignment = Qt::AlignRight;
  175. }
  176. else if (overRight && !overLeft)
  177. {
  178. alignment = Qt::AlignLeft;
  179. }
  180. else
  181. {
  182. alignment = Qt::AlignLeft;
  183. }
  184. if (overBottom && !overTop)
  185. {
  186. alignment = alignment | Qt::AlignTop;
  187. }
  188. else if (overTop && !overBottom)
  189. {
  190. alignment = alignment | Qt::AlignBottom;
  191. }
  192. else
  193. {
  194. alignment = alignment | Qt::AlignTop;
  195. }
  196. q->setAlignment(alignment);
  197. // Retrieve the pixel data into a QImage (flip vertically to move from render
  198. // window coordinates to Qt coordinates)
  199. QSize actualSize(indexRight-indexLeft+1, indexTop-indexBottom+1);
  200. QImage image(actualSize.width(), actualSize.height(), QImage::Format_RGB32);
  201. vtkUnsignedCharArray * pixelData = vtkUnsignedCharArray::New();
  202. pixelData->SetArray(image.bits(), actualSize.width() * actualSize.height() * 4, 1);
  203. int front = renderWindow->GetDoubleBuffer();
  204. int success = renderWindow->GetRGBACharPixelData(
  205. indexLeft, indexBottom, indexRight, indexTop, front, pixelData);
  206. if (!success)
  207. {
  208. this->removePixmap();
  209. return;
  210. }
  211. pixelData->Delete();
  212. image = image.rgbSwapped();
  213. image = image.mirrored();
  214. // Scale the image to zoom, using FastTransformation to prevent smoothing
  215. QSize imageSize = actualSize * this->Magnification;
  216. image = image.scaled(imageSize, Qt::KeepAspectRatioByExpanding,
  217. Qt::FastTransformation);
  218. // Crop the magnified image to solve the problem of magnified partial pixels
  219. double errorLeft
  220. = (posLeft - static_cast<double>(indexLeft)) * this->Magnification;
  221. double errorRight
  222. = (static_cast<double>(indexRight) - posRight) * this->Magnification;
  223. double errorBottom
  224. = (posBottom - static_cast<double>(indexBottom)) * this->Magnification;
  225. double errorTop
  226. = (static_cast<double>(indexTop) - posTop) * this->Magnification;
  227. // When cropping the Qt image, the 'adjust' variables are in Qt coordinates,
  228. // not render window coordinates (bottom and top switch).
  229. int cropIndexLeft = round(errorLeft);
  230. int cropIndexRight = imageSize.width() - round(errorRight) - 1;
  231. int cropIndexTop = round(errorTop);
  232. int cropIndexBottom = imageSize.height() - round(errorBottom) - 1;
  233. // Handle case when label size and magnification are not both even or odd
  234. // (errorLeft/errorRight/errorBottom/errorTop will have fractional component,
  235. // so cropped image wouldn't be the correct size unless we adjust further).
  236. int requiredWidth = round((posRight - posLeft + 1) * this->Magnification);
  237. int requiredHeight = round((posTop - posBottom + 1) * this->Magnification);
  238. int actualWidth = cropIndexRight - cropIndexLeft + 1;
  239. int actualHeight = cropIndexBottom - cropIndexTop + 1;
  240. int diffWidth = requiredWidth - actualWidth;
  241. int diffHeight = requiredHeight - actualHeight;
  242. // Too wide
  243. if (diffWidth < 0 && cropIndexRight != imageSize.width()-1)
  244. {
  245. Q_ASSERT(actualWidth - requiredWidth <= 1);
  246. cropIndexRight += diffWidth;
  247. }
  248. // Too narrow
  249. else if (diffWidth > 0 && cropIndexLeft != 0)
  250. {
  251. Q_ASSERT(requiredWidth - actualWidth <= 1);
  252. cropIndexLeft -= diffWidth;
  253. }
  254. // Too tall
  255. if (diffHeight < 0 && cropIndexBottom != imageSize.height()-1)
  256. {
  257. Q_ASSERT(actualHeight - requiredHeight <= 1);
  258. cropIndexBottom += diffHeight;
  259. }
  260. // Too short
  261. else if (diffHeight > 0 && cropIndexTop != 0)
  262. {
  263. Q_ASSERT(requiredHeight - actualHeight <= 1);
  264. cropIndexTop -= diffHeight;
  265. }
  266. // Finally crop the QImage for display
  267. QRect cropRect(QPoint(cropIndexLeft, cropIndexTop),
  268. QPoint(cropIndexRight, cropIndexBottom));
  269. image = image.copy(cropRect);
  270. // Finally, set the pixelmap to the new one we have created
  271. q->setPixmap(QPixmap::fromImage(image));
  272. }
  273. //---------------------------------------------------------------------------
  274. // ctkVTKMagnifyWidget methods
  275. // --------------------------------------------------------------------------
  276. ctkVTKMagnifyWidget::ctkVTKMagnifyWidget(QWidget* parentWidget)
  277. : Superclass(parentWidget)
  278. , d_ptr(new ctkVTKMagnifyWidgetPrivate(*this))
  279. {
  280. Q_D(ctkVTKMagnifyWidget);
  281. d->init();
  282. }
  283. // --------------------------------------------------------------------------
  284. ctkVTKMagnifyWidget::~ctkVTKMagnifyWidget()
  285. {
  286. }
  287. // --------------------------------------------------------------------------
  288. CTK_GET_CPP(ctkVTKMagnifyWidget, double, magnification, Magnification);
  289. // --------------------------------------------------------------------------
  290. void ctkVTKMagnifyWidget::setMagnification(double newMagnification)
  291. {
  292. Q_D(ctkVTKMagnifyWidget);
  293. if (newMagnification == d->Magnification || newMagnification <= 0)
  294. {
  295. return;
  296. }
  297. d->Magnification = newMagnification;
  298. this->update();
  299. }
  300. // --------------------------------------------------------------------------
  301. void ctkVTKMagnifyWidget::observe(QVTKWidget * widget)
  302. {
  303. Q_D(ctkVTKMagnifyWidget);
  304. if (widget)
  305. {
  306. d->observe(widget);
  307. }
  308. }
  309. // --------------------------------------------------------------------------
  310. void ctkVTKMagnifyWidget::observe(QList<QVTKWidget *> widgets)
  311. {
  312. foreach(QVTKWidget * widget, widgets)
  313. {
  314. this->observe(widget);
  315. }
  316. }
  317. // --------------------------------------------------------------------------
  318. bool ctkVTKMagnifyWidget::isObserved(QVTKWidget * widget) const
  319. {
  320. if (!widget)
  321. {
  322. return false;
  323. }
  324. Q_D(const ctkVTKMagnifyWidget);
  325. return d->ObservedQVTKWidgets.contains(widget);
  326. }
  327. // --------------------------------------------------------------------------
  328. int ctkVTKMagnifyWidget::numberObserved() const
  329. {
  330. Q_D(const ctkVTKMagnifyWidget);
  331. return d->ObservedQVTKWidgets.length();
  332. }
  333. // --------------------------------------------------------------------------
  334. void ctkVTKMagnifyWidget::remove(QVTKWidget * widget)
  335. {
  336. Q_D(ctkVTKMagnifyWidget);
  337. if (widget)
  338. {
  339. d->remove(widget);
  340. }
  341. }
  342. // --------------------------------------------------------------------------
  343. void ctkVTKMagnifyWidget::remove(QList<QVTKWidget *> widgets)
  344. {
  345. foreach(QVTKWidget * widget, widgets)
  346. {
  347. this->remove(widget);
  348. }
  349. }
  350. // --------------------------------------------------------------------------
  351. bool ctkVTKMagnifyWidget::eventFilter(QObject * obj, QEvent * event)
  352. {
  353. // The given object should be a QVTKWidget in our list
  354. QVTKWidget * widget = static_cast<QVTKWidget *>(obj);
  355. Q_ASSERT(widget);
  356. Q_D(ctkVTKMagnifyWidget);
  357. Q_ASSERT(d->ObservedQVTKWidgets.contains(widget));
  358. QEvent::Type eventType = event->type();
  359. // On mouse move, update the pixmap with the zoomed image
  360. if (eventType == QEvent::MouseMove)
  361. {
  362. QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
  363. Q_ASSERT(mouseEvent);
  364. d->updatePixmap(widget, mouseEvent->posF());
  365. this->update();
  366. return false;
  367. }
  368. // On enter, update the pixmap with the zoomed image (required for zooming when widget
  369. // is created with mouse already within it), and emit signal of enter event
  370. else if (eventType == QEvent::Enter)
  371. {
  372. QPointF posF = widget->mapFromGlobal(QCursor::pos());
  373. d->updatePixmap(widget, posF);
  374. this->update();
  375. emit enteredObservedWidget(widget);
  376. return false;
  377. }
  378. // On leave, fill the pixmap with a solid color and emit signal of leave event
  379. else if (eventType == QEvent::Leave)
  380. {
  381. d->removePixmap();
  382. this->update();
  383. emit leftObservedWidget(widget);
  384. return false;
  385. }
  386. // For other event types, use standard event processing
  387. else
  388. {
  389. return QObject::eventFilter(obj, event);
  390. }
  391. }