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

Generated on Wed Dec 23 21:11:10 2009 for Vidalia by  doxygen 1.6.1