zimageview.cpp

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

Generated on Wed Sep 5 15:49:28 2007 for Vidalia by  doxygen 1.5.3