zimageview.cpp

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

Generated on Mon Oct 23 20:08:16 2006 for Vidalia by  doxygen 1.5.0