ctkVTKMagnifyView.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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.apache.org/licenses/LICENSE-2.0.txt
  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. #include <QTimerEvent>
  19. // CTK includes
  20. #include "ctkVTKMagnifyView.h"
  21. #include "ctkVTKMagnifyView_p.h"
  22. #include "ctkLogger.h"
  23. // VTK includes
  24. #include <QVTKWidget.h>
  25. #include <vtkMath.h>
  26. #include <vtkRenderWindow.h>
  27. #include <vtkUnsignedCharArray.h>
  28. // STD includes
  29. #include <cmath>
  30. // Convenient macro
  31. #define VTK_CREATE(type, name) \
  32. vtkSmartPointer<type> name = vtkSmartPointer<type>::New()
  33. //--------------------------------------------------------------------------
  34. static ctkLogger logger("org.commontk.visualization.vtk.widgets.ctkVTKMagnifyView");
  35. //--------------------------------------------------------------------------
  36. // --------------------------------------------------------------------------
  37. // ctkVTKMagnifyViewPrivate methods
  38. // --------------------------------------------------------------------------
  39. ctkVTKMagnifyViewPrivate::ctkVTKMagnifyViewPrivate(ctkVTKMagnifyView& object)
  40. : QObject(&object), q_ptr(&object)
  41. {
  42. this->ObservedQVTKWidgets = QList<QVTKWidget *>();
  43. this->Magnification = 1.0;
  44. this->ObserveRenderWindowEvents = true;
  45. this->EventHandler.EventType = NoEvent;
  46. this->EventHandler.Position = QPointF(0,0);
  47. this->EventHandler.UpdateInterval = 20;
  48. this->EventHandler.TimerId = 0;
  49. }
  50. // --------------------------------------------------------------------------
  51. ctkVTKMagnifyViewPrivate::~ctkVTKMagnifyViewPrivate()
  52. {
  53. if (this->EventHandler.TimerId != 0)
  54. {
  55. this->killTimer(this->EventHandler.TimerId);
  56. }
  57. }
  58. // --------------------------------------------------------------------------
  59. void ctkVTKMagnifyViewPrivate::init()
  60. {
  61. // Start by removing the pixmap
  62. this->EventHandler.EventType = RemovePixmapEvent;
  63. this->removePixmap();
  64. // Start the timer
  65. this->restartTimer();
  66. }
  67. // --------------------------------------------------------------------------
  68. void ctkVTKMagnifyViewPrivate::restartTimer()
  69. {
  70. // Kill any old timers
  71. if (this->EventHandler.TimerId != 0)
  72. {
  73. this->killTimer(this->EventHandler.TimerId);
  74. this->EventHandler.TimerId = 0;
  75. }
  76. // Start timer if appropriate
  77. if (this->EventHandler.UpdateInterval != 0)
  78. {
  79. this->EventHandler.TimerId = this->startTimer(this->EventHandler.UpdateInterval);
  80. Q_ASSERT(this->EventHandler.TimerId);
  81. }
  82. // Not using any timers, process events as they come
  83. else
  84. {
  85. this->EventHandler.TimerId = 0;
  86. }
  87. }
  88. // --------------------------------------------------------------------------
  89. void ctkVTKMagnifyViewPrivate::resetEventHandler()
  90. {
  91. this->EventHandler.EventType = NoEvent;
  92. }
  93. // --------------------------------------------------------------------------
  94. void ctkVTKMagnifyViewPrivate::timerEvent(QTimerEvent * event)
  95. {
  96. Q_UNUSED(event);
  97. Q_ASSERT(event->timerId() == this->EventHandler.TimerId);
  98. if (this->EventHandler.EventType == UpdatePixmapEvent)
  99. {
  100. this->updatePixmap();
  101. }
  102. else if (this->EventHandler.EventType == RemovePixmapEvent)
  103. {
  104. this->removePixmap();
  105. }
  106. }
  107. // --------------------------------------------------------------------------
  108. void ctkVTKMagnifyViewPrivate::pushUpdatePixmapEvent()
  109. {
  110. if (this->EventHandler.Widget.isNull())
  111. {
  112. return;
  113. }
  114. this->pushUpdatePixmapEvent(
  115. this->EventHandler.Widget.data()->mapFromGlobal(QCursor::pos()));
  116. }
  117. // --------------------------------------------------------------------------
  118. void ctkVTKMagnifyViewPrivate::pushUpdatePixmapEvent(QPointF pos)
  119. {
  120. if (this->EventHandler.Widget.isNull())
  121. {
  122. return;
  123. }
  124. // Add this event to the queue
  125. this->EventHandler.EventType = UpdatePixmapEvent;
  126. this->EventHandler.Position = pos;
  127. // Process the event if we handle all events
  128. if (this->EventHandler.UpdateInterval == 0)
  129. {
  130. this->updatePixmap();
  131. }
  132. }
  133. // --------------------------------------------------------------------------
  134. void ctkVTKMagnifyViewPrivate::pushRemovePixmapEvent()
  135. {
  136. // Add this event to the queue
  137. this->EventHandler.EventType = RemovePixmapEvent;
  138. // Process the event if we handle all events
  139. if (this->EventHandler.UpdateInterval == 0)
  140. {
  141. this->removePixmap();
  142. }
  143. }
  144. // --------------------------------------------------------------------------
  145. void ctkVTKMagnifyViewPrivate::connectRenderWindow(QVTKWidget * widget)
  146. {
  147. Q_ASSERT(widget);
  148. Q_ASSERT(this->ObserveRenderWindowEvents);
  149. vtkRenderWindow * renderWindow = widget->GetRenderWindow();
  150. if (renderWindow)
  151. {
  152. this->qvtkConnect(renderWindow, vtkCommand::EndEvent,
  153. this, SLOT(pushUpdatePixmapEvent()));
  154. }
  155. }
  156. // --------------------------------------------------------------------------
  157. void ctkVTKMagnifyViewPrivate::disconnectRenderWindow(QVTKWidget * widget)
  158. {
  159. Q_ASSERT(widget);
  160. vtkRenderWindow * renderWindow = widget->GetRenderWindow();
  161. if (renderWindow)
  162. {
  163. this->qvtkDisconnect(renderWindow, vtkCommand::EndEvent,
  164. this, SLOT(pushUpdatePixmapEvent()));
  165. }
  166. }
  167. // --------------------------------------------------------------------------
  168. void ctkVTKMagnifyViewPrivate::observe(QVTKWidget * widget)
  169. {
  170. Q_ASSERT(widget);
  171. // If we are not already observing the widget, add it to the list and install
  172. // the public implementation as the event filter to handle mousing
  173. if (!this->ObservedQVTKWidgets.contains(widget))
  174. {
  175. this->ObservedQVTKWidgets.append(widget);
  176. Q_Q(ctkVTKMagnifyView);
  177. widget->installEventFilter(q);
  178. if (this->ObserveRenderWindowEvents)
  179. {
  180. this->connectRenderWindow(widget);
  181. }
  182. }
  183. }
  184. // --------------------------------------------------------------------------
  185. void ctkVTKMagnifyViewPrivate::remove(QVTKWidget * widget)
  186. {
  187. Q_ASSERT(widget);
  188. // If we are observing the widget, remove it from the list and remove the
  189. // public implementations event filtering
  190. if (this->ObservedQVTKWidgets.contains(widget))
  191. {
  192. Q_ASSERT(this->ObservedQVTKWidgets.count(widget) == 1);
  193. this->ObservedQVTKWidgets.removeOne(widget);
  194. Q_Q(ctkVTKMagnifyView);
  195. widget->removeEventFilter(q);
  196. if (this->ObserveRenderWindowEvents)
  197. {
  198. this->disconnectRenderWindow(widget);
  199. }
  200. }
  201. }
  202. // --------------------------------------------------------------------------
  203. void ctkVTKMagnifyViewPrivate::removePixmap()
  204. {
  205. Q_ASSERT(this->EventHandler.EventType == RemovePixmapEvent);
  206. Q_Q(ctkVTKMagnifyView);
  207. QPixmap nullPixmap;
  208. q->setPixmap(nullPixmap);
  209. q->update();
  210. this->resetEventHandler();
  211. }
  212. // -------------------------------------------------------------------------
  213. void ctkVTKMagnifyViewPrivate::updatePixmap()
  214. {
  215. Q_ASSERT(this->EventHandler.EventType == UpdatePixmapEvent);
  216. Q_ASSERT(!this->EventHandler.Widget.isNull());
  217. Q_Q(ctkVTKMagnifyView);
  218. // Retrieve buffer of given QVTKWidget from its render window
  219. vtkRenderWindow * renderWindow = this->EventHandler.Widget.data()->GetRenderWindow();
  220. if (!renderWindow)
  221. {
  222. return;
  223. }
  224. // Get the window size and mouse position, and do error checking
  225. QPointF pos = this->EventHandler.Position;
  226. int * windowSize = renderWindow->GetSize();
  227. QPointF mouseWindowPos(pos.x(), static_cast<double>(windowSize[1]-1)-pos.y());
  228. if (mouseWindowPos.x() < 0 || mouseWindowPos.x() >= windowSize[0] ||
  229. mouseWindowPos.y() < 0 || mouseWindowPos.y() >= windowSize[1])
  230. {
  231. return;
  232. }
  233. // Compute indices into the render window's data array
  234. // Given a magnification and the label's widget size, compute the number of
  235. // pixels we can show. We should round to get a larger integer extent, since
  236. // we will later adjust the pixmap's location in paintEvent().
  237. // Left-right and up-down are in the render window coordinate frame.
  238. // (which is different in the y-direction compared to Qt coordinates).
  239. QSizeF sizeToMagnify = QSizeF(q->size()) / this->Magnification;
  240. double posLeft = (mouseWindowPos.x() - ((sizeToMagnify.width()-1.0) / 2.0));
  241. double posRight = (mouseWindowPos.x() + ((sizeToMagnify.width()-1.0) / 2.0));
  242. double posBottom = (mouseWindowPos.y() - ((sizeToMagnify.height()-1.0) / 2.0));
  243. double posTop = (mouseWindowPos.y() + ((sizeToMagnify.height()-1.0) / 2.0));
  244. // Round to ints, for indexing into the pixel array
  245. int indexLeft = std::floor(posLeft);
  246. int indexRight = std::ceil(posRight);
  247. int indexBottom = std::floor(posBottom);
  248. int indexTop = std::ceil(posTop);
  249. // Handle when mouse is near the border
  250. int minLeft = 0;
  251. int maxRight = windowSize[0]-1;
  252. int minBottom = 0;
  253. int maxTop = windowSize[1]-1;
  254. bool overLeft = indexLeft < minLeft;
  255. bool overRight = indexRight > maxRight;
  256. bool overBottom = indexBottom < minBottom;
  257. bool overTop = indexTop > maxTop;
  258. // Ensure we don't access nonexistant indices
  259. if (overLeft)
  260. {
  261. indexLeft = minLeft;
  262. posLeft = minLeft;
  263. }
  264. if (overRight)
  265. {
  266. indexRight = maxRight;
  267. posRight = maxRight;
  268. }
  269. if (overBottom)
  270. {
  271. indexBottom = minBottom;
  272. posBottom = minBottom;
  273. }
  274. if (overTop)
  275. {
  276. indexTop = maxTop;
  277. posTop = maxTop;
  278. }
  279. // Error case
  280. if (indexLeft > indexRight || indexBottom > indexTop)
  281. {
  282. return;
  283. }
  284. // Setup the pixelmap's position in the label
  285. Qt::Alignment alignment;
  286. if (overLeft && !overRight)
  287. {
  288. alignment = Qt::AlignRight;
  289. }
  290. else if (overRight && !overLeft)
  291. {
  292. alignment = Qt::AlignLeft;
  293. }
  294. else
  295. {
  296. alignment = Qt::AlignLeft;
  297. }
  298. if (overBottom && !overTop)
  299. {
  300. alignment = alignment | Qt::AlignTop;
  301. }
  302. else if (overTop && !overBottom)
  303. {
  304. alignment = alignment | Qt::AlignBottom;
  305. }
  306. else
  307. {
  308. alignment = alignment | Qt::AlignTop;
  309. }
  310. q->setAlignment(alignment);
  311. // Retrieve the pixel data into a QImage (flip vertically to move from render
  312. // window coordinates to Qt coordinates)
  313. QSize actualSize(indexRight-indexLeft+1, indexTop-indexBottom+1);
  314. QImage image(actualSize.width(), actualSize.height(), QImage::Format_RGB32);
  315. vtkUnsignedCharArray * pixelData = vtkUnsignedCharArray::New();
  316. pixelData->SetArray(image.bits(), actualSize.width() * actualSize.height() * 4, 1);
  317. int front = renderWindow->GetDoubleBuffer();
  318. int success = renderWindow->GetRGBACharPixelData(
  319. indexLeft, indexBottom, indexRight, indexTop, front, pixelData);
  320. if (!success)
  321. {
  322. return;
  323. }
  324. pixelData->Delete();
  325. image = image.rgbSwapped();
  326. image = image.mirrored();
  327. // Scale the image to zoom, using FastTransformation to prevent smoothing
  328. QSize imageSize = actualSize * this->Magnification;
  329. image = image.scaled(imageSize, Qt::KeepAspectRatioByExpanding,
  330. Qt::FastTransformation);
  331. // Crop the magnified image to solve the problem of magnified partial pixels
  332. double errorLeft
  333. = (posLeft - static_cast<double>(indexLeft)) * this->Magnification;
  334. double errorRight
  335. = (static_cast<double>(indexRight) - posRight) * this->Magnification;
  336. double errorBottom
  337. = (posBottom - static_cast<double>(indexBottom)) * this->Magnification;
  338. double errorTop
  339. = (static_cast<double>(indexTop) - posTop) * this->Magnification;
  340. // When cropping the Qt image, the 'adjust' variables are in Qt coordinates,
  341. // not render window coordinates (bottom and top switch).
  342. int cropIndexLeft = vtkMath::Round(errorLeft);
  343. int cropIndexRight = imageSize.width() - vtkMath::Round(errorRight) - 1;
  344. int cropIndexTop = vtkMath::Round(errorTop);
  345. int cropIndexBottom = imageSize.height() - vtkMath::Round(errorBottom) - 1;
  346. // Handle case when label size and magnification are not both even or odd
  347. // (errorLeft/errorRight/errorBottom/errorTop will have fractional component,
  348. // so cropped image wouldn't be the correct size unless we adjust further).
  349. int requiredWidth = vtkMath::Round((posRight - posLeft + 1) * this->Magnification);
  350. int requiredHeight = vtkMath::Round((posTop - posBottom + 1) * this->Magnification);
  351. int actualWidth = cropIndexRight - cropIndexLeft + 1;
  352. int actualHeight = cropIndexBottom - cropIndexTop + 1;
  353. int diffWidth = requiredWidth - actualWidth;
  354. int diffHeight = requiredHeight - actualHeight;
  355. // Too wide
  356. if (diffWidth < 0 && cropIndexRight != imageSize.width()-1)
  357. {
  358. Q_ASSERT(actualWidth - requiredWidth <= 1);
  359. cropIndexRight += diffWidth;
  360. }
  361. // Too narrow
  362. else if (diffWidth > 0 && cropIndexLeft != 0)
  363. {
  364. Q_ASSERT(requiredWidth - actualWidth <= 1);
  365. cropIndexLeft -= diffWidth;
  366. }
  367. // Too tall
  368. if (diffHeight < 0 && cropIndexBottom != imageSize.height()-1)
  369. {
  370. Q_ASSERT(actualHeight - requiredHeight <= 1);
  371. cropIndexBottom += diffHeight;
  372. }
  373. // Too short
  374. else if (diffHeight > 0 && cropIndexTop != 0)
  375. {
  376. Q_ASSERT(requiredHeight - actualHeight <= 1);
  377. cropIndexTop -= diffHeight;
  378. }
  379. // Finally crop the QImage for display
  380. QRect cropRect(QPoint(cropIndexLeft, cropIndexTop),
  381. QPoint(cropIndexRight, cropIndexBottom));
  382. image = image.copy(cropRect);
  383. // Finally, set the pixelmap to the new one we have created and update
  384. q->setPixmap(QPixmap::fromImage(image));
  385. q->update();
  386. this->resetEventHandler();
  387. }
  388. //---------------------------------------------------------------------------
  389. // ctkVTKMagnifyView methods
  390. // --------------------------------------------------------------------------
  391. ctkVTKMagnifyView::ctkVTKMagnifyView(QWidget* parentWidget)
  392. : Superclass(parentWidget)
  393. , d_ptr(new ctkVTKMagnifyViewPrivate(*this))
  394. {
  395. Q_D(ctkVTKMagnifyView);
  396. d->init();
  397. }
  398. // --------------------------------------------------------------------------
  399. ctkVTKMagnifyView::~ctkVTKMagnifyView()
  400. {
  401. }
  402. // --------------------------------------------------------------------------
  403. CTK_GET_CPP(ctkVTKMagnifyView, double, magnification, Magnification)
  404. // --------------------------------------------------------------------------
  405. void ctkVTKMagnifyView::setMagnification(double newMagnification)
  406. {
  407. Q_D(ctkVTKMagnifyView);
  408. if (newMagnification == d->Magnification || newMagnification <= 0)
  409. {
  410. return;
  411. }
  412. d->Magnification = newMagnification;
  413. this->update();
  414. }
  415. // --------------------------------------------------------------------------
  416. CTK_GET_CPP(ctkVTKMagnifyView, bool,
  417. observeRenderWindowEvents, ObserveRenderWindowEvents)
  418. // --------------------------------------------------------------------------
  419. void ctkVTKMagnifyView::setObserveRenderWindowEvents(bool newObserve)
  420. {
  421. Q_D(ctkVTKMagnifyView);
  422. if (newObserve == d->ObserveRenderWindowEvents)
  423. {
  424. return;
  425. }
  426. d->ObserveRenderWindowEvents = newObserve;
  427. // Connect/disconnect observations on vtkRenderWindow EndEvents, depending
  428. // on whether we are switching from not-observing to observing or from
  429. // observing to not-observing
  430. QList<QVTKWidget *>::iterator it = d->ObservedQVTKWidgets.begin();
  431. while (it != d->ObservedQVTKWidgets.end())
  432. {
  433. if (newObserve)
  434. {
  435. d->connectRenderWindow(*it);
  436. }
  437. else
  438. {
  439. d->disconnectRenderWindow(*it);
  440. }
  441. ++it;
  442. }
  443. }
  444. // --------------------------------------------------------------------------
  445. int ctkVTKMagnifyView::updateInterval() const
  446. {
  447. Q_D(const ctkVTKMagnifyView);
  448. return d->EventHandler.UpdateInterval;
  449. }
  450. // --------------------------------------------------------------------------
  451. void ctkVTKMagnifyView::setUpdateInterval(int newInterval)
  452. {
  453. Q_D(ctkVTKMagnifyView);
  454. if (newInterval == d->EventHandler.UpdateInterval || newInterval < 0)
  455. {
  456. return;
  457. }
  458. d->EventHandler.UpdateInterval = newInterval;
  459. d->restartTimer();
  460. }
  461. // --------------------------------------------------------------------------
  462. void ctkVTKMagnifyView::observe(QVTKWidget * widget)
  463. {
  464. Q_D(ctkVTKMagnifyView);
  465. if (widget)
  466. {
  467. d->observe(widget);
  468. }
  469. }
  470. // --------------------------------------------------------------------------
  471. void ctkVTKMagnifyView::observe(QList<QVTKWidget *> widgets)
  472. {
  473. foreach(QVTKWidget * widget, widgets)
  474. {
  475. this->observe(widget);
  476. }
  477. }
  478. // --------------------------------------------------------------------------
  479. bool ctkVTKMagnifyView::isObserved(QVTKWidget * widget) const
  480. {
  481. if (!widget)
  482. {
  483. return false;
  484. }
  485. Q_D(const ctkVTKMagnifyView);
  486. return d->ObservedQVTKWidgets.contains(widget);
  487. }
  488. // --------------------------------------------------------------------------
  489. int ctkVTKMagnifyView::numberObserved() const
  490. {
  491. Q_D(const ctkVTKMagnifyView);
  492. return d->ObservedQVTKWidgets.length();
  493. }
  494. // --------------------------------------------------------------------------
  495. bool ctkVTKMagnifyView::hasCursorInObservedWidget()const
  496. {
  497. Q_D(const ctkVTKMagnifyView);
  498. // checking underMouse is faster than
  499. // QApplication::widgetAt(QCursor::pos())
  500. foreach(const QVTKWidget* widget, d->ObservedQVTKWidgets)
  501. {
  502. if (widget->underMouse())
  503. {
  504. return true;
  505. }
  506. }
  507. return false;
  508. }
  509. // --------------------------------------------------------------------------
  510. void ctkVTKMagnifyView::remove(QVTKWidget * widget)
  511. {
  512. Q_D(ctkVTKMagnifyView);
  513. if (widget)
  514. {
  515. d->remove(widget);
  516. }
  517. }
  518. // --------------------------------------------------------------------------
  519. void ctkVTKMagnifyView::remove(QList<QVTKWidget *> widgets)
  520. {
  521. foreach(QVTKWidget * widget, widgets)
  522. {
  523. this->remove(widget);
  524. }
  525. }
  526. // --------------------------------------------------------------------------
  527. bool ctkVTKMagnifyView::eventFilter(QObject * obj, QEvent * event)
  528. {
  529. // The given object should be a QVTKWidget in our list
  530. QVTKWidget * widget = static_cast<QVTKWidget *>(obj);
  531. Q_ASSERT(widget);
  532. Q_D(ctkVTKMagnifyView);
  533. Q_ASSERT(d->ObservedQVTKWidgets.contains(widget));
  534. d->EventHandler.Widget = QWeakPointer<QVTKWidget>(widget);
  535. QEvent::Type eventType = event->type();
  536. // On mouse move, update the pixmap with the zoomed image
  537. if (eventType == QEvent::MouseMove)
  538. {
  539. QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
  540. Q_ASSERT(mouseEvent);
  541. d->pushUpdatePixmapEvent(mouseEvent->posF());
  542. }
  543. // On enter, update the pixmap with the zoomed image (required for zooming when
  544. // widget is created with mouse already within it), and emit signal of enter event
  545. else if (eventType == QEvent::Enter)
  546. {
  547. d->pushUpdatePixmapEvent();
  548. emit enteredObservedWidget(widget);
  549. }
  550. // On leave, fill the pixmap with a solid color and emit signal of leave event
  551. else if (eventType == QEvent::Leave)
  552. {
  553. d->pushRemovePixmapEvent();
  554. emit leftObservedWidget(widget);
  555. }
  556. return this->Superclass::eventFilter(obj, event);
  557. }