ZImageView.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file ZImageView.cpp
00013 ** \version $Id: ZImageView.cpp 3735 2009-04-28 20:28:01Z edmanm $
00014 ** \brief Displays an image and allows zooming and panning
00015 */
00016 
00017 #include "ZImageView.h"
00018 
00019 #include <QPainter>
00020 #include <QMouseEvent>
00021 
00022 #include <cmath>
00023 
00024 #if QT_VERSION >= 0x040200
00025 #define CURSOR_NORMAL           QCursor(Qt::OpenHandCursor)
00026 #define CURSOR_MOUSE_PRESS      QCursor(Qt::ClosedHandCursor)
00027 #else
00028 #define CURSOR_NORMAL           QCursor(Qt::CrossCursor)
00029 #define CURSOR_MOUSE_PRESS      QCursor(Qt::SizeAllCursor)
00030 #endif
00031 
00032 
00033 /** Constructor. */
00034 ZImageView::ZImageView(QWidget *parent)
00035   : QWidget(parent)
00036 {
00037   /* Initialize members */
00038   _zoom = 0.0;
00039   _desiredX = 0.0;
00040   _desiredY = 0.0;
00041   _maxZoomFactor = 2.0;
00042   _padding = 60;
00043 
00044   setCursor(CURSOR_NORMAL);
00045   updateViewport();
00046   resetZoomPoint();
00047   repaint();
00048 }
00049 
00050 /** Sets the displayed image. */
00051 void
00052 ZImageView::setImage(QImage& img)
00053 {
00054   _image = img.copy();
00055   updateViewport();
00056   resetZoomPoint();
00057 
00058   if (isVisible()) {
00059     repaint();
00060   }
00061 }
00062 
00063 /** Draws the scaled image on the widget. */
00064 void
00065 ZImageView::drawScaledImage()
00066 {
00067   if (!isVisible()) {
00068     return;
00069   }
00070 
00071   QBrush background(QColor("#fdfdfd"));
00072   if (_image.isNull()) {
00073     QPainter p(this);
00074     p.fillRect(rect(), background);
00075     return;
00076   }
00077 
00078   QRect sRect = rect();
00079   QRect iRect = _image.rect();
00080   QRect r = _view;
00081 
00082   // Think of the _view as being overlaid on the image.  The _view has the same
00083   // aspect ratio as the screen, so we cut the _view region out of the _image
00084   // and scale it to the screen dimensions and paint it.
00085 
00086   // There is a slight catch in that the _view may be larger than the image in 
00087   // one or both directions.  In that case, we need to reduce the _view region
00088   // to lie within the image, then paint the background around it.  Copying
00089   // a region from an image where the region is bigger than the image results
00090   // in the parts outside the image being black, which is not what we want.
00091 
00092   // The view has the same aspect ratio as the screen, so the vertical and 
00093   // horizontal scale factors will be equal.
00094 
00095   double scaleFactor = double(sRect.width()) / double(_view.width());
00096 
00097   // Constrain r to lie entirely within the image.
00098   if (r.top() < 0) {
00099     r.setTop(0);
00100   }
00101   if (iRect.bottom() < r.bottom()) {
00102     r.setBottom(iRect.bottom());
00103   }
00104   if (r.left() < 0) {
00105     r.setLeft(0);
00106   }
00107   if (iRect.right() < r.right()) {
00108     r.setRight(iRect.right());
00109   }
00110 
00111   // Figure out the size that the 'r' region will be when drawn to the screen.
00112   QSize scaleTo(int(double(r.width()) * scaleFactor), 
00113                 int(double(r.height()) * scaleFactor));
00114 
00115   /** Make a copy of the image so we don't ruin the original */
00116   QImage i = _image.copy();
00117   
00118   /** Create a QPainter that draws directly on the copied image and call the
00119    * virtual function to draw whatever the subclasses need to on the image. */
00120   QPainter painter;
00121   painter.begin(&i);
00122   paintImage(&painter);
00123   painter.end();
00124 
00125   /** Rescale the image copy */
00126   i = i.copy(r).scaled(scaleTo,
00127                      Qt::KeepAspectRatioByExpanding,
00128                      Qt::SmoothTransformation);
00129 
00130   int extraWidth = int(double(sRect.width() - i.width()) / 2.0);
00131   int extraHeight = int(double(sRect.height() - i.height()) / 2.0);
00132 
00133   // We don't want to paint the background
00134   // because this isn't double buffered and that would flicker.
00135   // We could double buffer it, but that would cost ~3 MB of memory.
00136   
00137   QPainter p(this);
00138   if (extraWidth > 0) {
00139     p.fillRect(0, 0, extraWidth, sRect.height(), background);
00140     p.fillRect(sRect.width() - extraWidth, 0,
00141                sRect.width(), sRect.height(), background);
00142   }
00143 
00144   if (extraHeight > 0) {
00145     p.fillRect(0, 0, sRect.width(), extraHeight, background);
00146     p.fillRect(0, sRect.height() - extraHeight,
00147                sRect.width(), sRect.height(), background);
00148   }
00149 
00150   // Finally, paint the image copy.
00151   p.drawImage(extraWidth, extraHeight, i);
00152 }
00153         
00154 /** Updates the displayed viewport. */
00155 void
00156 ZImageView::updateViewport(int screendx, int screendy)
00157 {
00158   /* The gist of this is to find the biggest and smallest possible viewports,
00159    * then use the _zoom factor to interpolate between them.  Also pan the 
00160    * viewport, but constrain each dimension to lie within the image or to be 
00161    * centered if the image is too small in that direction. */
00162 
00163   QRect sRect = rect();
00164   QRect iRect = _image.rect();
00165 
00166   float sw = float(sRect.width());
00167   float sh = float(sRect.height());
00168   float iw = float(iRect.width());
00169   float ih = float(iRect.height());
00170         
00171   // Get the initial max and min sizes for the viewport.  These won't have the 
00172   // correct aspect ratio.  They will actually be the least upper bound and 
00173   // greatest lower bound of the set containing the screen and image rects.
00174   float maxw = float(std::max<int>(sRect.width(), iRect.width())) + _padding;
00175   float maxh = float(std::max<int>(sRect.height(), iRect.height())) + _padding;
00176   float minw = std::ceil(float(sRect.width()) / _maxZoomFactor);
00177   float minh = std::ceil(float(sRect.height()) / _maxZoomFactor);
00178 
00179   // Now that we have the glb and the lub, we expand/shrink them until
00180   // the aspect ratio is that of the screen.
00181   float aspect = sw / sh;
00182 
00183   // Fix the max rect.
00184   float newmaxh = maxh;
00185   float newmaxw = aspect * newmaxh;
00186   if (newmaxw < maxw) {
00187     newmaxw = maxw;
00188     newmaxh = maxw / aspect;
00189   }
00190 
00191   // Fix the min rect.
00192   float newminh = minh;
00193   float newminw = aspect * newminh;
00194   if (minw < newminw) {
00195     newminw = minw;
00196     newminh = newminw / aspect;
00197   }
00198         
00199   // Now interpolate between max and min.
00200   float vw = (1.0f - _zoom) * (newmaxw - newminw) + newminw;
00201   float vh = (1.0f - _zoom) * (newmaxh - newminh) + newminh;
00202 
00203   _view.setWidth(int(vw));
00204   _view.setHeight(int(vh));
00205 
00206   // Now pan the view
00207 
00208   // Convert the pan delta from screen coordinates to view coordinates.
00209   float vdx = vw * (float(screendx) / sw);
00210   float vdy = vh * (float(screendy) / sh);
00211         
00212   // Constrain the center of the viewport to the image rect.
00213   _desiredX = qBound(0.0f, _desiredX + vdx, iw);
00214   _desiredY = qBound(0.0f, _desiredY + vdy, ih);
00215   _view.moveCenter(QPoint(int(_desiredX), int(_desiredY)));
00216 
00217   QPoint viewCenter = _view.center();
00218   float vx = viewCenter.x();
00219   float vy = viewCenter.y();
00220 
00221   // The viewport may be wider than the height and/or width.  In that case,
00222   // center the view over the image in the appropriate directions.
00223   //
00224   // If the viewport is smaller than the image in either direction, then make
00225   // sure the edge of the viewport isn't past the edge of the image.
00226         
00227   vdx = 0;
00228   vdy = 0;
00229    
00230   if (iw <= vw) {
00231     vdx = (iw / 2.0f) - vx;  // Center horizontally.
00232   } else {
00233     // Check that the edge of the view isn't past the edge of the image.
00234     float vl = float(_view.left());
00235     float vr = float(_view.right());
00236     if (vl < 0) {
00237       vdx = -vl;
00238     } else if (vr > iw) {
00239       vdx = iw - vr;
00240     }
00241   }
00242     
00243   if (ih <= vh) {
00244     vdy = (ih / 2.0f) - vy; // Center vertically.
00245   } else {
00246     // Check that the edge of the view isn't past the edge of the image.
00247     float vt = float(_view.top());
00248     float vb = float(_view.bottom());
00249     if (vt < 0) {
00250       vdy = -vt;
00251     } else if (vb > ih) {
00252       vdy = ih - vb;
00253     }
00254   }
00255 
00256   _view.translate(int(vdx), int(vdy));
00257 }
00258 
00259 /** Resets the zoom point back to the center of the viewport. */
00260 void
00261 ZImageView::resetZoomPoint()
00262 {
00263   QPoint viewCenter = _view.center();
00264   _desiredX = viewCenter.x();
00265   _desiredY = viewCenter.y();
00266 }
00267 
00268 /** Handles repainting this widget by updating the viewport and drawing the
00269  * scaled image. */
00270 void
00271 ZImageView::paintEvent(QPaintEvent*)
00272 {
00273   updateViewport();
00274   drawScaledImage();
00275 }
00276 
00277 /** Sets the current zoom percentage to the given value and scrolls the
00278  * viewport to center the given point. */
00279 void
00280 ZImageView::zoom(QPoint zoomAt, float pct)
00281 {
00282   _desiredX = zoomAt.x();
00283   _desiredY = zoomAt.y();
00284   zoom(pct);
00285 }
00286 
00287 /** Sets the current zoom percentage to the given value. */
00288 void
00289 ZImageView::zoom(float pct)
00290 {
00291   _zoom = qBound(0.0f, pct, 1.0f);
00292   repaint();
00293 }
00294 
00295 /** Zooms into the image by 10% */
00296 void
00297 ZImageView::zoomIn()
00298 {
00299   zoom(_zoom + .1);
00300 }
00301 
00302 /** Zooms away from the image by 10% */
00303 void
00304 ZImageView::zoomOut()
00305 {
00306   zoom(_zoom - .1);
00307 }
00308 
00309 /** Responds to the user pressing a mouse button. */
00310 void
00311 ZImageView::mousePressEvent(QMouseEvent *e)
00312 {
00313   e->accept();
00314   setCursor(CURSOR_MOUSE_PRESS);
00315   _mouseX = e->x();
00316   _mouseY = e->y();
00317 }
00318 
00319 /** Responds to the user releasing a mouse button. */
00320 void 
00321 ZImageView::mouseReleaseEvent(QMouseEvent *e)
00322 {
00323   e->accept();
00324   setCursor(CURSOR_NORMAL);
00325   updateViewport();
00326   resetZoomPoint();
00327 }
00328 
00329 /** Responds to the user double-clicking a mouse button on the image. A left
00330  * double-click zooms in on the image and a right double-click zooms out.
00331  * Zooming is centered on the location of the double-click. */
00332 void
00333 ZImageView::mouseDoubleClickEvent(QMouseEvent *e)
00334 {
00335   e->accept();
00336   
00337   QPoint center = rect().center(); 
00338   int dx = e->x() - center.x();
00339   int dy = e->y() - center.y();
00340   updateViewport(dx, dy);
00341   resetZoomPoint();
00342 
00343   Qt::MouseButton btn = e->button();
00344   if (btn == Qt::LeftButton)
00345     zoomIn();
00346   else if (btn == Qt::RightButton)
00347     zoomOut();
00348 }
00349 
00350 /** Responds to the user moving the mouse. */
00351 void
00352 ZImageView::mouseMoveEvent(QMouseEvent *e)
00353 {
00354   e->accept();
00355   int dx = _mouseX - e->x();
00356   int dy = _mouseY - e->y();
00357   _mouseX = e->x();
00358   _mouseY = e->y();
00359 
00360   updateViewport(dx, dy);
00361   if (0.001 <= _zoom) {
00362     repaint();
00363   }
00364 }
00365 
00366 void
00367 ZImageView::wheelEvent(QWheelEvent *e)
00368 {
00369   if (e->delta() > 0) {
00370     zoomIn();
00371   } else {
00372     zoomOut();
00373   }
00374 }

Generated on 31 Mar 2010 for Vidalia by  doxygen 1.6.1