File indexing completed on 2024-04-28 15:40:27
0001 // SPDX-FileCopyrightText: 2003 - 2022 Jesper K. Pedersen <blackie@kde.org> 0002 // SPDX-FileCopyrightText: 2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0003 // 0004 // SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 #include "ImageDisplay.h" 0007 0008 #include "Logging.h" 0009 #include "ViewHandler.h" 0010 0011 #include <DB/ImageDB.h> 0012 #include <ImageManager/AsyncLoader.h> 0013 #include <kpabase/SettingsData.h> 0014 0015 #include <KLocalizedString> 0016 #include <KMessageBox> 0017 #include <QApplication> 0018 #include <QCursor> 0019 #include <QMouseEvent> 0020 #include <QPaintEvent> 0021 #include <QPainter> 0022 #include <QResizeEvent> 0023 #include <QTimer> 0024 #include <cmath> 0025 0026 /** 0027 Area displaying the actual image in the viewer. 0028 0029 The purpose of this class is to display the actual image in the 0030 viewer. This involves controlling zooming and drawing on the images. 0031 0032 This class is quite complicated as it had to both be fast and memory 0033 efficient. The following are dead end tried: 0034 1) Initially QPainter::setWindow was used for zooming the images, but 0035 this had the effect that if you zoom to 100x100 from a 2300x1700 0036 image on a 800x600 display, then Qt would internally create a pixmap 0037 with the size (2300/100)*800, (1700/100)*600, which takes up 1.4Gb of 0038 memory! 0039 2) I tried doing all scaling and cropping using QPixmap's as that would 0040 allow me to keep all transformations on the X Server site (making 0041 resizing fast - or I beleived so). Unfortunately it showed up that 0042 this was much slower than doing it using QImage, and the result was 0043 thus that the looking at a series of images was slow. 0044 0045 The process is as follows: 0046 - The image loaded from disk is rotated and stored in _loadedImage. 0047 Initially this image is as large as the view, until the 0048 user starts zooming, at which time the image is reloaded to the size 0049 as it is on disk. 0050 - Then _loadedImage is cropped and scaled to _croppedAndScaledImg. This 0051 image is the size of the display. Resizing the window thus needs to 0052 redo step. 0053 - Finally in paintEvent _croppedAndScaledImg is drawn to the screen. 0054 0055 The above might very likely be simplified. Back in the old days it needed to be that 0056 complex to allow drawing on images. 0057 0058 To propagate the cache, we need to know which direction the 0059 images are viewed in, which is the job of the instance variable _forward. 0060 */ 0061 0062 Viewer::ImageDisplay::ImageDisplay(QWidget *parent) 0063 : AbstractDisplay(parent) 0064 , m_reloadImageInProgress(false) 0065 , m_forward(true) 0066 , m_curIndex(0) 0067 , m_busy(false) 0068 { 0069 m_viewHandler = new ViewHandler(this); 0070 0071 setMouseTracking(true); 0072 } 0073 0074 void Viewer::ImageDisplay::mousePressEvent(QMouseEvent *event) 0075 { 0076 QMouseEvent e(event->type(), mapPos(event->pos()), event->button(), event->buttons(), event->modifiers()); 0077 double ratio = sizeRatio(QSize(m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y()), size()); 0078 bool block = m_viewHandler->mousePressEvent(&e, event->pos(), ratio); 0079 if (!block) 0080 QWidget::mousePressEvent(event); 0081 update(); 0082 } 0083 0084 void Viewer::ImageDisplay::mouseMoveEvent(QMouseEvent *event) 0085 { 0086 QMouseEvent e(event->type(), mapPos(event->pos()), event->button(), event->buttons(), event->modifiers()); 0087 double ratio = sizeRatio(QSize(m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y()), size()); 0088 bool block = m_viewHandler->mouseMoveEvent(&e, event->pos(), ratio); 0089 if (!block) 0090 QWidget::mouseMoveEvent(event); 0091 update(); 0092 } 0093 0094 void Viewer::ImageDisplay::mouseReleaseEvent(QMouseEvent *event) 0095 { 0096 m_cache.remove(m_curIndex); 0097 QMouseEvent e(event->type(), mapPos(event->pos()), event->button(), event->buttons(), event->modifiers()); 0098 double ratio = sizeRatio(QSize(m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y()), size()); 0099 bool block = m_viewHandler->mouseReleaseEvent(&e, event->pos(), ratio); 0100 if (!block) { 0101 QWidget::mouseReleaseEvent(event); 0102 } 0103 Q_EMIT possibleChange(); 0104 update(); 0105 } 0106 0107 bool Viewer::ImageDisplay::setImageImpl(DB::ImageInfoPtr info, bool forward) 0108 { 0109 qCDebug(ViewerLog) << "setImage(" << info->fileName().relative() << "," << forward << ")"; 0110 m_loadedImage = QImage(); 0111 0112 // Find the index of the current image 0113 m_curIndex = 0; 0114 for (const DB::FileName &filename : qAsConst(m_imageList)) { 0115 if (filename == info->fileName()) 0116 break; 0117 ++m_curIndex; 0118 } 0119 0120 if (m_cache.contains(m_curIndex) && m_cache[m_curIndex].angle == info->angle()) { 0121 const ViewPreloadInfo &found = m_cache[m_curIndex]; 0122 m_loadedImage = found.img; 0123 updateZoomPoints(Settings::SettingsData::instance()->viewerStandardSize(), found.img.size()); 0124 cropAndScale(); 0125 info->setSize(found.size); 0126 Q_EMIT imageReady(); 0127 } else { 0128 requestImage(info, true); 0129 busy(); 0130 } 0131 m_forward = forward; 0132 updatePreload(); 0133 0134 return true; 0135 } 0136 0137 void Viewer::ImageDisplay::resizeEvent(QResizeEvent *event) 0138 { 0139 ImageManager::AsyncLoader::instance()->stop(this, ImageManager::StopOnlyNonPriorityLoads); 0140 m_cache.clear(); 0141 if (m_info) { 0142 cropAndScale(); 0143 if (event->size().width() > 1.5 * this->m_loadedImage.size().width() || event->size().height() > 1.5 * this->m_loadedImage.size().height()) 0144 potentiallyLoadFullSize(); // Only do if we scale much bigger. 0145 } 0146 updatePreload(); 0147 } 0148 0149 void Viewer::ImageDisplay::paintEvent(QPaintEvent *) 0150 { 0151 int x = (width() - m_croppedAndScaledImg.width()) / 2; 0152 int y = (height() - m_croppedAndScaledImg.height()) / 2; 0153 0154 QPainter painter(this); 0155 Q_ASSERT(painter.isActive()); 0156 painter.fillRect(0, 0, width(), height(), palette().base()); 0157 painter.drawImage(x, y, m_croppedAndScaledImg); 0158 } 0159 0160 QPoint Viewer::ImageDisplay::offset(int logicalWidth, int logicalHeight, int physicalWidth, int physicalHeight, double *ratio) 0161 { 0162 double rat = sizeRatio(QSize(logicalWidth, logicalHeight), QSize(physicalWidth, physicalHeight)); 0163 0164 int ox = (int)(physicalWidth - logicalWidth * rat) / 2; 0165 int oy = (int)(physicalHeight - logicalHeight * rat) / 2; 0166 if (ratio) 0167 *ratio = rat; 0168 return QPoint(ox, oy); 0169 } 0170 0171 void Viewer::ImageDisplay::zoom(QPoint p1, QPoint p2) 0172 { 0173 qCDebug(ViewerLog, "zoom(%d,%d, %d,%d)", p1.x(), p1.y(), p2.x(), p2.y()); 0174 if (!m_info) { 0175 // should not happen because the user can't click fast enough, but who knows... 0176 qCWarning(ViewerLog, "Trying to zoom without an image"); 0177 return; 0178 } 0179 m_cache.remove(m_curIndex); 0180 normalize(p1, p2); 0181 0182 double ratio; 0183 QPoint off = offset((p2 - p1).x(), (p2 - p1).y(), width(), height(), &ratio); 0184 off = off / ratio; 0185 0186 p1.setX(p1.x() - off.x()); 0187 p1.setY(p1.y() - off.y()); 0188 p2.setX(p2.x() + off.x()); 0189 p2.setY(p2.y() + off.y()); 0190 0191 m_zStart = p1; 0192 m_zEnd = p2; 0193 potentiallyLoadFullSize(); 0194 cropAndScale(); 0195 } 0196 0197 QPoint Viewer::ImageDisplay::mapPos(QPoint p) 0198 { 0199 QPoint off = offset(qAbs(m_zEnd.x() - m_zStart.x()), qAbs(m_zEnd.y() - m_zStart.y()), width(), height(), nullptr); 0200 p -= off; 0201 int x = (int)(m_zStart.x() + (m_zEnd.x() - m_zStart.x()) * ((double)p.x() / (width() - 2 * off.x()))); 0202 int y = (int)(m_zStart.y() + (m_zEnd.y() - m_zStart.y()) * ((double)p.y() / (height() - 2 * off.y()))); 0203 0204 return QPoint(x, y); 0205 } 0206 0207 void Viewer::ImageDisplay::zoomIn() 0208 { 0209 qCDebug(ViewerLog, "zoomIn()"); 0210 QPoint size = (m_zEnd - m_zStart); 0211 QPoint p1 = m_zStart + size * (0.2 / 2); 0212 QPoint p2 = m_zEnd - size * (0.2 / 2); 0213 zoom(p1, p2); 0214 } 0215 0216 void Viewer::ImageDisplay::zoomOut() 0217 { 0218 qCDebug(ViewerLog, "zoomOut()"); 0219 QPoint size = (m_zEnd - m_zStart); 0220 0221 // Bug 150971, Qt tries to render bigger and bigger images (10000x10000), hence running out of memory. 0222 if ((size.x() * size.y() > 25 * 1024 * 1024)) 0223 return; 0224 0225 QPoint p1 = m_zStart - size * (0.25 / 2); 0226 QPoint p2 = m_zEnd + size * (0.25 / 2); 0227 zoom(p1, p2); 0228 } 0229 0230 void Viewer::ImageDisplay::zoomFull() 0231 { 0232 qCDebug(ViewerLog, "zoomFull()"); 0233 m_zStart = QPoint(0, 0); 0234 m_zEnd = QPoint(m_loadedImage.width(), m_loadedImage.height()); 0235 zoom(QPoint(0, 0), QPoint(m_loadedImage.width(), m_loadedImage.height())); 0236 } 0237 0238 void Viewer::ImageDisplay::normalize(QPoint &p1, QPoint &p2) 0239 { 0240 int minx = qMin(p1.x(), p2.x()); 0241 int miny = qMin(p1.y(), p2.y()); 0242 int maxx = qMax(p1.x(), p2.x()); 0243 int maxy = qMax(p1.y(), p2.y()); 0244 p1 = QPoint(minx, miny); 0245 p2 = QPoint(maxx, maxy); 0246 } 0247 0248 void Viewer::ImageDisplay::pan(const QPoint &point) 0249 { 0250 m_zStart += point; 0251 m_zEnd += point; 0252 cropAndScale(); 0253 } 0254 0255 void Viewer::ImageDisplay::cropAndScale() 0256 { 0257 if (m_loadedImage.isNull()) { 0258 return; 0259 } 0260 0261 if (m_zStart != QPoint(0, 0) || m_zEnd != QPoint(m_loadedImage.width(), m_loadedImage.height())) { 0262 qCDebug(ViewerLog) << "cropAndScale(): using cropped image" << m_zStart << "-" << m_zEnd; 0263 // we request a copy that is bigger than the loaded image 0264 // copy fills the pixels outside the loaded image with transparent pixels 0265 m_croppedAndScaledImg = m_loadedImage.copy(m_zStart.x(), m_zStart.y(), m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y()); 0266 } else { 0267 qCDebug(ViewerLog) << "cropAndScale(): using full image."; 0268 m_croppedAndScaledImg = m_loadedImage; 0269 } 0270 0271 updateZoomCaption(); 0272 0273 if (!m_croppedAndScaledImg.isNull()) // I don't know how this can happen, but it seems not to be dangerous. 0274 { 0275 qCDebug(ViewerLog) << "cropAndScale(): scaling image to" << width() << "x" << height(); 0276 m_croppedAndScaledImg = m_croppedAndScaledImg.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); 0277 } else { 0278 qCDebug(ViewerLog) << "cropAndScale(): image is null."; 0279 } 0280 0281 update(); 0282 0283 Q_EMIT viewGeometryChanged(m_croppedAndScaledImg.size(), QRect(m_zStart, m_zEnd), sizeRatio(m_loadedImage.size(), m_info->size())); 0284 } 0285 0286 void Viewer::ImageDisplay::filterNone() 0287 { 0288 cropAndScale(); 0289 update(); 0290 } 0291 0292 bool Viewer::ImageDisplay::filterMono() 0293 { 0294 m_croppedAndScaledImg = m_croppedAndScaledImg.convertToFormat(m_croppedAndScaledImg.Format_Mono); 0295 update(); 0296 return true; 0297 } 0298 0299 // I can't believe there isn't a standard conversion for this??? -- WH 0300 bool Viewer::ImageDisplay::filterBW() 0301 { 0302 if (m_croppedAndScaledImg.depth() < 32) { 0303 KMessageBox::error(this, i18n("Insufficient color depth for this filter")); 0304 return false; 0305 } 0306 0307 for (int y = 0; y < m_croppedAndScaledImg.height(); ++y) { 0308 for (int x = 0; x < m_croppedAndScaledImg.width(); ++x) { 0309 int pixel = m_croppedAndScaledImg.pixel(x, y); 0310 int gray = qGray(pixel); 0311 int alpha = qAlpha(pixel); 0312 m_croppedAndScaledImg.setPixel(x, y, qRgba(gray, gray, gray, alpha)); 0313 } 0314 } 0315 update(); 0316 return true; 0317 } 0318 0319 bool Viewer::ImageDisplay::filterContrastStretch() 0320 { 0321 int redMin, redMax, greenMin, greenMax, blueMin, blueMax; 0322 0323 redMin = greenMin = blueMin = 255; 0324 redMax = greenMax = blueMax = 0; 0325 0326 if (m_croppedAndScaledImg.depth() < 32) { 0327 KMessageBox::error(this, i18n("Insufficient color depth for this filter")); 0328 return false; 0329 } 0330 0331 // Look for minimum and maximum intensities within each color channel 0332 for (int y = 0; y < m_croppedAndScaledImg.height(); ++y) { 0333 for (int x = 0; x < m_croppedAndScaledImg.width(); ++x) { 0334 int pixel = m_croppedAndScaledImg.pixel(x, y); 0335 int red = qRed(pixel); 0336 int green = qGreen(pixel); 0337 int blue = qBlue(pixel); 0338 redMin = redMin < red ? redMin : red; 0339 redMax = redMax > red ? redMax : red; 0340 greenMin = greenMin < green ? greenMin : green; 0341 greenMax = greenMax > green ? greenMax : green; 0342 blueMin = blueMin < blue ? blueMin : blue; 0343 blueMax = blueMax > blue ? blueMax : blue; 0344 } 0345 } 0346 0347 // Calculate factor for stretching each color intensity throughout the 0348 // whole range 0349 float redFactor, greenFactor, blueFactor; 0350 redFactor = ((float)(255) / (float)(redMax - redMin)); 0351 greenFactor = ((float)(255) / (float)(greenMax - greenMin)); 0352 blueFactor = ((float)(255) / (float)(blueMax - blueMin)); 0353 0354 // Perform the contrast stretching 0355 for (int y = 0; y < m_croppedAndScaledImg.height(); ++y) { 0356 for (int x = 0; x < m_croppedAndScaledImg.width(); ++x) { 0357 int pixel = m_croppedAndScaledImg.pixel(x, y); 0358 int red = qRed(pixel); 0359 int green = qGreen(pixel); 0360 int blue = qBlue(pixel); 0361 int alpha = qAlpha(pixel); 0362 0363 red = (red - redMin) * redFactor; 0364 red = red < 255 ? red : 255; 0365 red = red > 0 ? red : 0; 0366 green = (green - greenMin) * greenFactor; 0367 green = green < 255 ? green : 255; 0368 green = green > 0 ? green : 0; 0369 blue = (blue - blueMin) * blueFactor; 0370 blue = blue < 255 ? blue : 255; 0371 blue = blue > 0 ? blue : 0; 0372 m_croppedAndScaledImg.setPixel(x, y, qRgba(red, green, blue, alpha)); 0373 } 0374 } 0375 update(); 0376 return true; 0377 } 0378 0379 bool Viewer::ImageDisplay::filterHistogramEqualization() 0380 { 0381 int width, height; 0382 float R_histogram[256]; 0383 float G_histogram[256]; 0384 float B_histogram[256]; 0385 float d; 0386 0387 if (m_croppedAndScaledImg.depth() < 32) { 0388 KMessageBox::error(this, i18n("Insufficient color depth for this filter")); 0389 return false; 0390 } 0391 memset(R_histogram, 0, sizeof(R_histogram)); 0392 memset(G_histogram, 0, sizeof(G_histogram)); 0393 memset(B_histogram, 0, sizeof(B_histogram)); 0394 0395 width = m_croppedAndScaledImg.width(); 0396 height = m_croppedAndScaledImg.height(); 0397 d = 1.0 / width / height; 0398 0399 // Populate histogram for each color channel 0400 for (int y = 0; y < height; ++y) { 0401 for (int x = 1; x < width; ++x) { 0402 int pixel = m_croppedAndScaledImg.pixel(x, y); 0403 0404 R_histogram[qRed(pixel)] += d; 0405 G_histogram[qGreen(pixel)] += d; 0406 B_histogram[qBlue(pixel)] += d; 0407 } 0408 } 0409 0410 // Transfer histogram table to cumulative distribution table 0411 float R_sum = 0.0; 0412 float G_sum = 0.0; 0413 float B_sum = 0.0; 0414 for (int i = 0; i < 256; ++i) { 0415 R_sum += R_histogram[i]; 0416 G_sum += G_histogram[i]; 0417 B_sum += B_histogram[i]; 0418 0419 R_histogram[i] = R_sum * 255 + 0.5; 0420 G_histogram[i] = G_sum * 255 + 0.5; 0421 B_histogram[i] = B_sum * 255 + 0.5; 0422 } 0423 0424 // Equalize the image 0425 for (int y = 0; y < height; ++y) { 0426 for (int x = 0; x < width; ++x) { 0427 int pixel = m_croppedAndScaledImg.pixel(x, y); 0428 0429 m_croppedAndScaledImg.setPixel( 0430 x, y, qRgba(R_histogram[qRed(pixel)], G_histogram[qGreen(pixel)], B_histogram[qBlue(pixel)], qAlpha(pixel))); 0431 } 0432 } 0433 update(); 0434 return true; 0435 } 0436 0437 void Viewer::ImageDisplay::updateZoomCaption() 0438 { 0439 const QSize imgSize = m_loadedImage.size(); 0440 // similar to sizeRatio(), but we take the _highest_ factor. 0441 double ratio = ((double)imgSize.width()) / (m_zEnd.x() - m_zStart.x()); 0442 if (ratio * (m_zEnd.y() - m_zStart.y()) < imgSize.height()) { 0443 ratio = ((double)imgSize.height()) / (m_zEnd.y() - m_zStart.y()); 0444 } 0445 0446 Q_EMIT imageZoomCaptionChanged((ratio > 1.05) 0447 ? ki18n("[ zoom x%1 ]").subs(ratio, 0, 'f', 1).toString() 0448 : QString()); 0449 } 0450 0451 QImage Viewer::ImageDisplay::currentViewAsThumbnail() const 0452 { 0453 if (m_croppedAndScaledImg.isNull()) 0454 return QImage(); 0455 else 0456 return m_croppedAndScaledImg.scaled(512, 512, Qt::KeepAspectRatio, Qt::SmoothTransformation); 0457 } 0458 0459 bool Viewer::ImageDisplay::isImageZoomed(const Settings::StandardViewSize type, const QSize &imgSize) 0460 { 0461 if (type == Settings::FullSize) 0462 return true; 0463 0464 if (type == Settings::NaturalSizeIfFits) 0465 return !(imgSize.width() < width() && imgSize.height() < height()); 0466 0467 return false; 0468 } 0469 0470 void Viewer::ImageDisplay::pixmapLoaded(ImageManager::ImageRequest *request, const QImage &image) 0471 { 0472 const DB::FileName fileName = request->databaseFileName(); 0473 const QSize imgSize = request->size(); 0474 const QSize fullSize = request->fullSize(); 0475 const int angle = request->angle(); 0476 const bool loadedOK = request->loadedOK(); 0477 0478 // the image might have changed (even to a null value) while the pixmap was loaded 0479 if (loadedOK && m_info && fileName == m_info->fileName()) { 0480 if (fullSize.isValid() && !m_info->size().isValid()) 0481 m_info->setSize(fullSize); 0482 0483 if (!m_reloadImageInProgress) 0484 updateZoomPoints(Settings::SettingsData::instance()->viewerStandardSize(), image.size()); 0485 else { 0486 // See documentation for zoomPixelForPixel for details. 0487 // We just loaded a likely much larger image, so the zoom points 0488 // need to be scaled. Notice m_loadedImage is the size of the 0489 // old image. 0490 // when using raw images, the decoded image may be a preview 0491 // and have a size different from m_info->size(). Therefore, use fullSize here: 0492 double ratio = sizeRatio(m_loadedImage.size(), fullSize); 0493 0494 qCDebug(ViewerLog) << "Old size:" << m_loadedImage.size() << "; new size:" << m_info->size(); 0495 qCDebug(ViewerLog) << "Req size:" << imgSize << "fullsize:" << fullSize; 0496 qCDebug(ViewerLog) << "pixmapLoaded(): Zoom region was" << m_zStart << "-" << m_zEnd; 0497 m_zStart *= ratio; 0498 m_zEnd *= ratio; 0499 qCDebug(ViewerLog) << "pixmapLoaded(): Zoom region changed to" << m_zStart << "-" << m_zEnd; 0500 0501 m_reloadImageInProgress = false; 0502 } 0503 0504 // for zoom operations we need a format with alpha channel (see cropAndScale()) 0505 m_loadedImage = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); 0506 cropAndScale(); 0507 Q_EMIT imageReady(); 0508 } else { 0509 if (imgSize != size()) 0510 return; // Might be an old preload version, or a loaded version that never made it in time 0511 0512 ViewPreloadInfo info(image, fullSize, angle); 0513 m_cache.insert(indexOf(fileName), info); 0514 updatePreload(); 0515 } 0516 unbusy(); 0517 Q_EMIT possibleChange(); 0518 } 0519 0520 void Viewer::ImageDisplay::setImageList(const DB::FileNameList &list) 0521 { 0522 m_imageList = list; 0523 m_cache.clear(); 0524 } 0525 0526 void Viewer::ImageDisplay::updatePreload() 0527 { 0528 // cacheSize: number of images at current window dimensions (at 4 byte per pixel) 0529 const int cacheSize = (int)((long long)(Settings::SettingsData::instance()->viewerCacheSize() * 1024LL * 1024LL) / (width() * height() * 4)); 0530 bool cacheFull = (m_cache.count() > cacheSize); 0531 0532 int incr = (m_forward ? 1 : -1); 0533 int nextOnesInCache = 0; 0534 // Iterate from the current image in the direction of the viewing 0535 for (int i = m_curIndex + incr; cacheSize; i += incr) { 0536 if (m_forward ? (i >= (int)m_imageList.count()) : (i < 0)) 0537 break; 0538 0539 DB::ImageInfoPtr info = DB::ImageDB::instance()->info(m_imageList[i]); 0540 if (!info) { 0541 qCWarning(ViewerLog, "Info was null for index %d!", i); 0542 return; 0543 } 0544 0545 if (m_cache.contains(i)) { 0546 nextOnesInCache++; 0547 if (nextOnesInCache >= ceil(cacheSize / 2.0) && cacheFull) { 0548 // Ok enough images in cache 0549 return; 0550 } 0551 } else { 0552 requestImage(info); 0553 0554 if (cacheFull) { 0555 // The cache was full, we need to delete an item from the cache. 0556 0557 // First try to find an item from the direction we came from 0558 for (int j = (m_forward ? 0 : m_imageList.count() - 1); 0559 j != m_curIndex; 0560 j += (m_forward ? 1 : -1)) { 0561 if (m_cache.contains(j)) { 0562 m_cache.remove(j); 0563 return; 0564 } 0565 } 0566 0567 // OK We found no item in the direction we came from (think of home/end keys) 0568 for (int j = (m_forward ? m_imageList.count() - 1 : 0); 0569 j != m_curIndex; 0570 j += (m_forward ? -1 : 1)) { 0571 if (m_cache.contains(j)) { 0572 m_cache.remove(j); 0573 return; 0574 } 0575 } 0576 0577 Q_ASSERT(false); // We should never get here. 0578 } 0579 0580 return; 0581 } 0582 } 0583 } 0584 0585 int Viewer::ImageDisplay::indexOf(const DB::FileName &fileName) 0586 { 0587 int i = 0; 0588 for (const DB::FileName &name : qAsConst(m_imageList)) { 0589 if (name == fileName) 0590 break; 0591 ++i; 0592 } 0593 return i; 0594 } 0595 0596 void Viewer::ImageDisplay::busy() 0597 { 0598 if (!m_busy) 0599 qApp->setOverrideCursor(Qt::WaitCursor); 0600 m_busy = true; 0601 } 0602 0603 void Viewer::ImageDisplay::unbusy() 0604 { 0605 if (m_busy) 0606 qApp->restoreOverrideCursor(); 0607 m_busy = false; 0608 } 0609 0610 void Viewer::ImageDisplay::zoomPixelForPixel() 0611 { 0612 qCDebug(ViewerLog, "zoomPixelForPixel()"); 0613 if (!m_info) { 0614 // should not happen because the user can't click fast enough, but who knows... 0615 qCWarning(ViewerLog, "Trying to zoom without an image"); 0616 return; 0617 } 0618 // This is rather tricky. 0619 // We want to zoom to a pixel level for the real image, which we might 0620 // or might not have loaded yet. 0621 // 0622 // First we ask for zoom points as they would look like had we had the 0623 // real image loaded now. (We need to ask for them, for the real image, 0624 // otherwise we would just zoom to the pixel level of the view size 0625 // image) 0626 updateZoomPoints(Settings::NaturalSize, m_info->size()); 0627 0628 // The points now, however might not match the current visible image - 0629 // as this image might be be only view size large. We therefore need 0630 // to scale the coordinates. 0631 double ratio = sizeRatio(m_loadedImage.size(), m_info->size()); 0632 qCDebug(ViewerLog) << "zoomPixelForPixel(): Zoom region was" << m_zStart << "-" << m_zEnd; 0633 m_zStart /= ratio; 0634 m_zEnd /= ratio; 0635 qCDebug(ViewerLog) << "zoomPixelForPixel(): Zoom region changed to" << m_zStart << "-" << m_zEnd; 0636 cropAndScale(); 0637 potentiallyLoadFullSize(); 0638 } 0639 0640 void Viewer::ImageDisplay::rotate(const DB::ImageInfoPtr &info) 0641 { 0642 setImage(info, m_forward); 0643 } 0644 0645 void Viewer::ImageDisplay::updateZoomPoints(const Settings::StandardViewSize type, const QSize &imgSize) 0646 { 0647 const int iw = imgSize.width(); 0648 const int ih = imgSize.height(); 0649 0650 if (isImageZoomed(type, imgSize)) { 0651 m_zStart = QPoint(0, 0); 0652 m_zEnd = QPoint(iw, ih); 0653 qCDebug(ViewerLog) << "updateZoomPoints(): Zoom region reset to" << m_zStart << "-" << m_zEnd; 0654 } else { 0655 m_zStart = QPoint(-(width() - iw) / 2, -(height() - ih) / 2); 0656 m_zEnd = QPoint(iw + (width() - iw) / 2, ih + (height() - ih) / 2); 0657 qCDebug(ViewerLog) << "updateZoomPoints(): Zoom region set to" << m_zStart << "-" << m_zEnd; 0658 } 0659 } 0660 0661 void Viewer::ImageDisplay::potentiallyLoadFullSize() 0662 { 0663 if (m_info && m_info->size() != m_loadedImage.size()) { 0664 qCDebug(ViewerLog) << "Loading full size image for " << m_info->fileName().relative(); 0665 ImageManager::ImageRequest *request = new ImageManager::ImageRequest(m_info->fileName(), QSize(-1, -1), m_info->angle(), this); 0666 request->setPriority(ImageManager::Viewer); 0667 ImageManager::AsyncLoader::instance()->load(request); 0668 busy(); 0669 m_reloadImageInProgress = true; 0670 } 0671 } 0672 0673 /** 0674 * return the ratio of the two sizes. That is newSize/baseSize. 0675 */ 0676 double Viewer::ImageDisplay::sizeRatio(const QSize &baseSize, const QSize &newSize) const 0677 { 0678 double res = ((double)newSize.width()) / baseSize.width(); 0679 0680 if (res * baseSize.height() > newSize.height()) { 0681 res = ((double)newSize.height()) / baseSize.height(); 0682 } 0683 return res; 0684 } 0685 0686 void Viewer::ImageDisplay::requestImage(const DB::ImageInfoPtr &info, bool priority) 0687 { 0688 Settings::StandardViewSize viewSize = Settings::SettingsData::instance()->viewerStandardSize(); 0689 QSize s = size(); 0690 if (viewSize == Settings::NaturalSize) 0691 s = QSize(-1, -1); 0692 0693 ImageManager::ImageRequest *request = new ImageManager::ImageRequest(info->fileName(), s, info->angle(), this); 0694 request->setUpScale(viewSize == Settings::FullSize); 0695 request->setPriority(priority ? ImageManager::Viewer : ImageManager::ViewerPreload); 0696 ImageManager::AsyncLoader::instance()->load(request); 0697 } 0698 0699 void Viewer::ImageDisplay::hideEvent(QHideEvent *) 0700 { 0701 m_viewHandler->hideEvent(); 0702 } 0703 0704 // vi:expandtab:tabstop=4 shiftwidth=4: 0705 0706 #include "moc_ImageDisplay.cpp"