// SPDX-FileCopyrightText: 2003-2022 Jesper K. Pedersen <blackie@kde.org>
// SPDX-FileCopyrightText: 2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
// SPDX-FileCopyrightText: 2024 Tobias Leupold <tl@stonemx.de>
//
// SPDX-License-Identifier: GPL-2.0-or-later

#include "ImageDisplay.h"

#include "Logging.h"
#include "ViewHandler.h"

#include <DB/ImageDB.h>
#include <ImageManager/AsyncLoader.h>
#include <kpabase/SettingsData.h>

#include <KLocalizedString>
#include <KMessageBox>
#include <QApplication>
#include <QCursor>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QResizeEvent>
#include <QTimer>

#include <cmath>
#include <utility>

/**
   Area displaying the actual image in the viewer.

   The purpose of this class is to display the actual image in the
   viewer. This involves controlling zooming and drawing on the images.

   This class is quite complicated as it had to both be fast and memory
   efficient. The following are dead end tried:
   1) Initially QPainter::setWindow was used for zooming the images, but
      this had the effect that if you zoom to 100x100 from a 2300x1700
      image on a 800x600 display, then Qt would internally create a pixmap
      with the size (2300/100)*800, (1700/100)*600, which takes up 1.4Gb of
      memory!
   2) I tried doing all scaling and cropping using QPixmap's as that would
      allow me to keep all transformations on the X Server site (making
      resizing fast - or I beleived so). Unfortunately it showed up that
      this was much slower than doing it using QImage, and the result was
      thus that the looking at a series of images was slow.

   The process is as follows:
   - The image loaded from disk is rotated and stored in _loadedImage.
     Initially this image is as large as the view, until the
     user starts zooming, at which time the image is reloaded to the size
     as it is on disk.
   - Then _loadedImage is cropped and scaled to _croppedAndScaledImg. This
     image is the size of the display. Resizing the window thus needs to
     redo step.
   - Finally in paintEvent _croppedAndScaledImg is drawn to the screen.

   The above might very likely be simplified. Back in the old days it needed to be that
   complex to allow drawing on images.

   To propagate the cache, we need to know which direction the
   images are viewed in, which is the job of the instance variable _forward.
*/

Viewer::ImageDisplay::ImageDisplay(QWidget *parent)
    : AbstractDisplay(parent)
    , m_reloadImageInProgress(false)
    , m_forward(true)
    , m_curIndex(0)
    , m_busy(false)
{
    m_viewHandler = new ViewHandler(this);

    setMouseTracking(true);
}

void Viewer::ImageDisplay::mousePressEvent(QMouseEvent *event)
{
    QMouseEvent e(event->type(), QPointF(mapPos(event->position().toPoint())), event->position(), event->button(), event->buttons(), event->modifiers());
    double ratio = sizeRatio(QSize(m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y()), size());
    bool block = m_viewHandler->mousePressEvent(&e, event->pos(), ratio);
    if (!block)
        QWidget::mousePressEvent(event);
    update();
}

void Viewer::ImageDisplay::mouseMoveEvent(QMouseEvent *event)
{
    QMouseEvent e(event->type(), QPointF(mapPos(event->position().toPoint())), event->position(), event->button(), event->buttons(), event->modifiers());
    double ratio = sizeRatio(QSize(m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y()), size());
    bool block = m_viewHandler->mouseMoveEvent(&e, event->pos(), ratio);
    if (!block)
        QWidget::mouseMoveEvent(event);
    update();
}

void Viewer::ImageDisplay::mouseReleaseEvent(QMouseEvent *event)
{
    m_cache.remove(m_curIndex);
    QMouseEvent e(event->type(), QPointF(mapPos(event->position().toPoint())), event->position(), event->button(), event->buttons(), event->modifiers());
    double ratio = sizeRatio(QSize(m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y()), size());
    bool block = m_viewHandler->mouseReleaseEvent(&e, event->pos(), ratio);
    if (!block) {
        QWidget::mouseReleaseEvent(event);
    }
    Q_EMIT possibleChange();
    update();
}

bool Viewer::ImageDisplay::setImageImpl(DB::ImageInfoPtr info, bool forward)
{
    qCDebug(ViewerLog) << "setImage(" << info->fileName().relative() << "," << forward << ")";
    m_loadedImage = QImage();

    // Find the index of the current image
    m_curIndex = 0;
    for (const DB::FileName &filename : std::as_const(m_imageList)) {
        if (filename == info->fileName())
            break;
        ++m_curIndex;
    }

    if (m_cache.contains(m_curIndex) && m_cache[m_curIndex].angle == info->angle()) {
        const ViewPreloadInfo &found = m_cache[m_curIndex];
        m_loadedImage = found.img;
        updateZoomPoints(Settings::SettingsData::instance()->viewerStandardSize(), found.img.size());
        cropAndScale();
        info->setSize(found.size);
        Q_EMIT imageReady();
    } else {
        requestImage(info, true);
        busy();
    }
    m_forward = forward;
    updatePreload();

    return true;
}

void Viewer::ImageDisplay::resizeEvent(QResizeEvent *event)
{
    ImageManager::AsyncLoader::instance()->stop(this, ImageManager::StopOnlyNonPriorityLoads);
    m_cache.clear();
    if (m_info) {
        cropAndScale();
        if (event->size().width() > 1.5 * this->m_loadedImage.size().width() || event->size().height() > 1.5 * this->m_loadedImage.size().height())
            potentiallyLoadFullSize(); // Only do if we scale much bigger.
    }
    updatePreload();
}

void Viewer::ImageDisplay::paintEvent(QPaintEvent *)
{
    int x = (width() - m_croppedAndScaledImg.width()) / 2;
    int y = (height() - m_croppedAndScaledImg.height()) / 2;

    QPainter painter(this);
    Q_ASSERT(painter.isActive());
    painter.fillRect(0, 0, width(), height(), palette().base());
    painter.drawImage(x, y, m_croppedAndScaledImg);
}

QPoint Viewer::ImageDisplay::offset(int logicalWidth, int logicalHeight, int physicalWidth, int physicalHeight, double *ratio)
{
    double rat = sizeRatio(QSize(logicalWidth, logicalHeight), QSize(physicalWidth, physicalHeight));

    int ox = (int)(physicalWidth - logicalWidth * rat) / 2;
    int oy = (int)(physicalHeight - logicalHeight * rat) / 2;
    if (ratio)
        *ratio = rat;
    return QPoint(ox, oy);
}

void Viewer::ImageDisplay::zoom(QPoint p1, QPoint p2)
{
    qCDebug(ViewerLog, "zoom(%d,%d, %d,%d)", p1.x(), p1.y(), p2.x(), p2.y());
    if (!m_info) {
        // should not happen because the user can't click fast enough, but who knows...
        qCWarning(ViewerLog, "Trying to zoom without an image");
        return;
    }
    m_cache.remove(m_curIndex);
    normalize(p1, p2);

    double ratio;
    QPoint off = offset((p2 - p1).x(), (p2 - p1).y(), width(), height(), &ratio);
    off = off / ratio;

    p1.setX(p1.x() - off.x());
    p1.setY(p1.y() - off.y());
    p2.setX(p2.x() + off.x());
    p2.setY(p2.y() + off.y());

    m_zStart = p1;
    m_zEnd = p2;
    potentiallyLoadFullSize();
    cropAndScale();
}

QPoint Viewer::ImageDisplay::mapPos(QPoint p)
{
    QPoint off = offset(qAbs(m_zEnd.x() - m_zStart.x()), qAbs(m_zEnd.y() - m_zStart.y()), width(), height(), nullptr);
    p -= off;
    int x = (int)(m_zStart.x() + (m_zEnd.x() - m_zStart.x()) * ((double)p.x() / (width() - 2 * off.x())));
    int y = (int)(m_zStart.y() + (m_zEnd.y() - m_zStart.y()) * ((double)p.y() / (height() - 2 * off.y())));

    return QPoint(x, y);
}

void Viewer::ImageDisplay::zoomIn()
{
    qCDebug(ViewerLog, "zoomIn()");
    QPoint size = (m_zEnd - m_zStart);
    QPoint p1 = m_zStart + size * (0.2 / 2);
    QPoint p2 = m_zEnd - size * (0.2 / 2);
    zoom(p1, p2);
}

void Viewer::ImageDisplay::zoomOut()
{
    qCDebug(ViewerLog, "zoomOut()");
    QPoint size = (m_zEnd - m_zStart);

    // Bug 150971, Qt tries to render bigger and bigger images (10000x10000), hence running out of memory.
    if ((size.x() * size.y() > 25 * 1024 * 1024))
        return;

    QPoint p1 = m_zStart - size * (0.25 / 2);
    QPoint p2 = m_zEnd + size * (0.25 / 2);
    zoom(p1, p2);
}

void Viewer::ImageDisplay::zoomFull()
{
    qCDebug(ViewerLog, "zoomFull()");
    m_zStart = QPoint(0, 0);
    m_zEnd = QPoint(m_loadedImage.width(), m_loadedImage.height());
    zoom(QPoint(0, 0), QPoint(m_loadedImage.width(), m_loadedImage.height()));
}

void Viewer::ImageDisplay::normalize(QPoint &p1, QPoint &p2)
{
    int minx = qMin(p1.x(), p2.x());
    int miny = qMin(p1.y(), p2.y());
    int maxx = qMax(p1.x(), p2.x());
    int maxy = qMax(p1.y(), p2.y());
    p1 = QPoint(minx, miny);
    p2 = QPoint(maxx, maxy);
}

void Viewer::ImageDisplay::pan(const QPoint &point)
{
    m_zStart += point;
    m_zEnd += point;
    cropAndScale();
}

void Viewer::ImageDisplay::cropAndScale()
{
    if (m_loadedImage.isNull()) {
        return;
    }

    if (m_zStart != QPoint(0, 0) || m_zEnd != QPoint(m_loadedImage.width(), m_loadedImage.height())) {
        qCDebug(ViewerLog) << "cropAndScale(): using cropped image" << m_zStart << "-" << m_zEnd;
        // we request a copy that is bigger than the loaded image
        // copy fills the pixels outside the loaded image with transparent pixels
        m_croppedAndScaledImg = m_loadedImage.copy(m_zStart.x(), m_zStart.y(), m_zEnd.x() - m_zStart.x(), m_zEnd.y() - m_zStart.y());
    } else {
        qCDebug(ViewerLog) << "cropAndScale(): using full image.";
        m_croppedAndScaledImg = m_loadedImage;
    }

    updateZoomCaption();

    if (!m_croppedAndScaledImg.isNull()) // I don't know how this can happen, but it seems not to be dangerous.
    {
        qCDebug(ViewerLog) << "cropAndScale(): scaling image to" << width() << "x" << height();
        m_croppedAndScaledImg = m_croppedAndScaledImg.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
    } else {
        qCDebug(ViewerLog) << "cropAndScale(): image is null.";
    }

    update();

    Q_EMIT viewGeometryChanged(m_croppedAndScaledImg.size(), QRect(m_zStart, m_zEnd), sizeRatio(m_loadedImage.size(), m_info->size()));
}

void Viewer::ImageDisplay::filterNone()
{
    cropAndScale();
    update();
}

bool Viewer::ImageDisplay::filterMono()
{
    m_croppedAndScaledImg = m_croppedAndScaledImg.convertToFormat(m_croppedAndScaledImg.Format_Mono);
    update();
    return true;
}

// I can't believe there isn't a standard conversion for this??? -- WH
bool Viewer::ImageDisplay::filterBW()
{
    if (m_croppedAndScaledImg.depth() < 32) {
        KMessageBox::error(this, i18n("Insufficient color depth for this filter"));
        return false;
    }

    for (int y = 0; y < m_croppedAndScaledImg.height(); ++y) {
        for (int x = 0; x < m_croppedAndScaledImg.width(); ++x) {
            int pixel = m_croppedAndScaledImg.pixel(x, y);
            int gray = qGray(pixel);
            int alpha = qAlpha(pixel);
            m_croppedAndScaledImg.setPixel(x, y, qRgba(gray, gray, gray, alpha));
        }
    }
    update();
    return true;
}

bool Viewer::ImageDisplay::filterContrastStretch()
{
    int redMin, redMax, greenMin, greenMax, blueMin, blueMax;

    redMin = greenMin = blueMin = 255;
    redMax = greenMax = blueMax = 0;

    if (m_croppedAndScaledImg.depth() < 32) {
        KMessageBox::error(this, i18n("Insufficient color depth for this filter"));
        return false;
    }

    // Look for minimum and maximum intensities within each color channel
    for (int y = 0; y < m_croppedAndScaledImg.height(); ++y) {
        for (int x = 0; x < m_croppedAndScaledImg.width(); ++x) {
            int pixel = m_croppedAndScaledImg.pixel(x, y);
            int red = qRed(pixel);
            int green = qGreen(pixel);
            int blue = qBlue(pixel);
            redMin = redMin < red ? redMin : red;
            redMax = redMax > red ? redMax : red;
            greenMin = greenMin < green ? greenMin : green;
            greenMax = greenMax > green ? greenMax : green;
            blueMin = blueMin < blue ? blueMin : blue;
            blueMax = blueMax > blue ? blueMax : blue;
        }
    }

    // Calculate factor for stretching each color intensity throughout the
    // whole range
    float redFactor, greenFactor, blueFactor;
    redFactor = ((float)(255) / (float)(redMax - redMin));
    greenFactor = ((float)(255) / (float)(greenMax - greenMin));
    blueFactor = ((float)(255) / (float)(blueMax - blueMin));

    // Perform the contrast stretching
    for (int y = 0; y < m_croppedAndScaledImg.height(); ++y) {
        for (int x = 0; x < m_croppedAndScaledImg.width(); ++x) {
            int pixel = m_croppedAndScaledImg.pixel(x, y);
            int red = qRed(pixel);
            int green = qGreen(pixel);
            int blue = qBlue(pixel);
            int alpha = qAlpha(pixel);

            red = (red - redMin) * redFactor;
            red = red < 255 ? red : 255;
            red = red > 0 ? red : 0;
            green = (green - greenMin) * greenFactor;
            green = green < 255 ? green : 255;
            green = green > 0 ? green : 0;
            blue = (blue - blueMin) * blueFactor;
            blue = blue < 255 ? blue : 255;
            blue = blue > 0 ? blue : 0;
            m_croppedAndScaledImg.setPixel(x, y, qRgba(red, green, blue, alpha));
        }
    }
    update();
    return true;
}

bool Viewer::ImageDisplay::filterHistogramEqualization()
{
    int width, height;
    float R_histogram[256];
    float G_histogram[256];
    float B_histogram[256];
    float d;

    if (m_croppedAndScaledImg.depth() < 32) {
        KMessageBox::error(this, i18n("Insufficient color depth for this filter"));
        return false;
    }
    memset(R_histogram, 0, sizeof(R_histogram));
    memset(G_histogram, 0, sizeof(G_histogram));
    memset(B_histogram, 0, sizeof(B_histogram));

    width = m_croppedAndScaledImg.width();
    height = m_croppedAndScaledImg.height();
    d = 1.0 / width / height;

    // Populate histogram for each color channel
    for (int y = 0; y < height; ++y) {
        for (int x = 1; x < width; ++x) {
            int pixel = m_croppedAndScaledImg.pixel(x, y);

            R_histogram[qRed(pixel)] += d;
            G_histogram[qGreen(pixel)] += d;
            B_histogram[qBlue(pixel)] += d;
        }
    }

    // Transfer histogram table to cumulative distribution table
    float R_sum = 0.0;
    float G_sum = 0.0;
    float B_sum = 0.0;
    for (int i = 0; i < 256; ++i) {
        R_sum += R_histogram[i];
        G_sum += G_histogram[i];
        B_sum += B_histogram[i];

        R_histogram[i] = R_sum * 255 + 0.5;
        G_histogram[i] = G_sum * 255 + 0.5;
        B_histogram[i] = B_sum * 255 + 0.5;
    }

    // Equalize the image
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            int pixel = m_croppedAndScaledImg.pixel(x, y);

            m_croppedAndScaledImg.setPixel(
                x, y, qRgba(R_histogram[qRed(pixel)], G_histogram[qGreen(pixel)], B_histogram[qBlue(pixel)], qAlpha(pixel)));
        }
    }
    update();
    return true;
}

bool Viewer::ImageDisplay::canRotate()
{
    return true;
}

bool Viewer::ImageDisplay::canZoom()
{
    return true;
}

void Viewer::ImageDisplay::updateZoomCaption()
{
    const QSize imgSize = m_loadedImage.size();
    // similar to sizeRatio(), but we take the _highest_ factor.
    double ratio = ((double)imgSize.width()) / (m_zEnd.x() - m_zStart.x());
    if (ratio * (m_zEnd.y() - m_zStart.y()) < imgSize.height()) {
        ratio = ((double)imgSize.height()) / (m_zEnd.y() - m_zStart.y());
    }

    Q_EMIT imageZoomCaptionChanged((ratio > 1.05)
                                       ? ki18n("[ zoom x%1 ]").subs(ratio, 0, 'f', 1).toString()
                                       : QString());
}

QImage Viewer::ImageDisplay::currentViewAsThumbnail() const
{
    if (m_croppedAndScaledImg.isNull())
        return QImage();
    else
        return m_croppedAndScaledImg.scaled(512, 512, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}

bool Viewer::ImageDisplay::isImageZoomed(const Settings::StandardViewSize type, const QSize &imgSize)
{
    if (type == Settings::FullSize)
        return true;

    if (type == Settings::NaturalSizeIfFits)
        return !(imgSize.width() < width() && imgSize.height() < height());

    return false;
}

void Viewer::ImageDisplay::pixmapLoaded(ImageManager::ImageRequest *request, const QImage &image)
{
    const DB::FileName fileName = request->databaseFileName();
    const QSize imgSize = request->size();
    const QSize fullSize = request->fullSize();
    const int angle = request->angle();
    const bool loadedOK = request->loadedOK();

    // the image might have changed (even to a null value) while the pixmap was loaded
    if (loadedOK && m_info && fileName == m_info->fileName()) {
        if (fullSize.isValid() && !m_info->size().isValid())
            m_info->setSize(fullSize);

        if (!m_reloadImageInProgress)
            updateZoomPoints(Settings::SettingsData::instance()->viewerStandardSize(), image.size());
        else {
            // See documentation for zoomPixelForPixel for details.
            // We just loaded a likely much larger image, so the zoom points
            // need to be scaled. Notice m_loadedImage is the size of the
            // old image.
            // when using raw images, the decoded image may be a preview
            // and have a size different from m_info->size(). Therefore, use fullSize here:
            double ratio = sizeRatio(m_loadedImage.size(), fullSize);

            qCDebug(ViewerLog) << "Old size:" << m_loadedImage.size() << "; new size:" << m_info->size();
            qCDebug(ViewerLog) << "Req size:" << imgSize << "fullsize:" << fullSize;
            qCDebug(ViewerLog) << "pixmapLoaded(): Zoom region was" << m_zStart << "-" << m_zEnd;
            m_zStart *= ratio;
            m_zEnd *= ratio;
            qCDebug(ViewerLog) << "pixmapLoaded(): Zoom region changed to" << m_zStart << "-" << m_zEnd;

            m_reloadImageInProgress = false;
        }

        // for zoom operations we need a format with alpha channel (see cropAndScale())
        m_loadedImage = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
        cropAndScale();
        Q_EMIT imageReady();
    } else {
        if (imgSize != size())
            return; // Might be an old preload version, or a loaded version that never made it in time

        ViewPreloadInfo info(image, fullSize, angle);
        m_cache.insert(indexOf(fileName), info);
        updatePreload();
    }
    unbusy();
    Q_EMIT possibleChange();
}

void Viewer::ImageDisplay::setImageList(const DB::FileNameList &list)
{
    m_imageList = list;
    m_cache.clear();
}

void Viewer::ImageDisplay::updatePreload()
{
    // cacheSize: number of images at current window dimensions (at 4 byte per pixel)
    const int cacheSize = (int)((long long)(Settings::SettingsData::instance()->viewerCacheSize() * 1024LL * 1024LL) / (width() * height() * 4));
    bool cacheFull = (m_cache.count() > cacheSize);

    int incr = (m_forward ? 1 : -1);
    int nextOnesInCache = 0;
    // Iterate from the current image in the direction of the viewing
    for (int i = m_curIndex + incr; cacheSize; i += incr) {
        if (m_forward ? (i >= (int)m_imageList.count()) : (i < 0))
            break;

        DB::ImageInfoPtr info = DB::ImageDB::instance()->info(m_imageList[i]);
        if (!info) {
            qCWarning(ViewerLog, "Info was null for index %d!", i);
            return;
        }

        if (m_cache.contains(i)) {
            nextOnesInCache++;
            if (nextOnesInCache >= ceil(cacheSize / 2.0) && cacheFull) {
                // Ok enough images in cache
                return;
            }
        } else {
            requestImage(info);

            if (cacheFull) {
                // The cache was full, we need to delete an item from the cache.

                // First try to find an item from the direction we came from
                for (int j = (m_forward ? 0 : m_imageList.count() - 1);
                     j != m_curIndex;
                     j += (m_forward ? 1 : -1)) {
                    if (m_cache.contains(j)) {
                        m_cache.remove(j);
                        return;
                    }
                }

                // OK We found no item in the direction we came from (think of home/end keys)
                for (int j = (m_forward ? m_imageList.count() - 1 : 0);
                     j != m_curIndex;
                     j += (m_forward ? -1 : 1)) {
                    if (m_cache.contains(j)) {
                        m_cache.remove(j);
                        return;
                    }
                }

                Q_ASSERT(false); // We should never get here.
            }

            return;
        }
    }
}

int Viewer::ImageDisplay::indexOf(const DB::FileName &fileName)
{
    int i = 0;
    for (const DB::FileName &name : std::as_const(m_imageList)) {
        if (name == fileName)
            break;
        ++i;
    }
    return i;
}

void Viewer::ImageDisplay::busy()
{
    if (!m_busy)
        qApp->setOverrideCursor(Qt::WaitCursor);
    m_busy = true;
}

void Viewer::ImageDisplay::unbusy()
{
    if (m_busy)
        qApp->restoreOverrideCursor();
    m_busy = false;
}

void Viewer::ImageDisplay::zoomPixelForPixel()
{
    qCDebug(ViewerLog, "zoomPixelForPixel()");
    if (!m_info) {
        // should not happen because the user can't click fast enough, but who knows...
        qCWarning(ViewerLog, "Trying to zoom without an image");
        return;
    }
    // This is rather tricky.
    // We want to zoom to a pixel level for the real image, which we might
    // or might not have loaded yet.
    //
    // First we ask for zoom points as they would look like had we had the
    // real image loaded now. (We need to ask for them, for the real image,
    // otherwise we would just zoom to the pixel level of the view size
    // image)
    updateZoomPoints(Settings::NaturalSize, m_info->size());

    // The points now, however might not match the current visible image -
    // as this image might be be only view size large. We therefore need
    // to scale the coordinates.
    double ratio = sizeRatio(m_loadedImage.size(), m_info->size());
    qCDebug(ViewerLog) << "zoomPixelForPixel(): Zoom region was" << m_zStart << "-" << m_zEnd;
    m_zStart /= ratio;
    m_zEnd /= ratio;
    qCDebug(ViewerLog) << "zoomPixelForPixel(): Zoom region changed to" << m_zStart << "-" << m_zEnd;
    cropAndScale();
    potentiallyLoadFullSize();
}

void Viewer::ImageDisplay::rotate(const DB::ImageInfoPtr &info)
{
    setImage(info, m_forward);
}

void Viewer::ImageDisplay::updateZoomPoints(const Settings::StandardViewSize type, const QSize &imgSize)
{
    const int iw = imgSize.width();
    const int ih = imgSize.height();

    if (isImageZoomed(type, imgSize)) {
        m_zStart = QPoint(0, 0);
        m_zEnd = QPoint(iw, ih);
        qCDebug(ViewerLog) << "updateZoomPoints(): Zoom region reset to" << m_zStart << "-" << m_zEnd;
    } else {
        m_zStart = QPoint(-(width() - iw) / 2, -(height() - ih) / 2);
        m_zEnd = QPoint(iw + (width() - iw) / 2, ih + (height() - ih) / 2);
        qCDebug(ViewerLog) << "updateZoomPoints(): Zoom region set to" << m_zStart << "-" << m_zEnd;
    }
}

void Viewer::ImageDisplay::potentiallyLoadFullSize()
{
    if (m_info && m_info->size() != m_loadedImage.size()) {
        qCDebug(ViewerLog) << "Loading full size image for " << m_info->fileName().relative();
        ImageManager::ImageRequest *request = new ImageManager::ImageRequest(m_info->fileName(), QSize(-1, -1), m_info->angle(), this);
        request->setPriority(ImageManager::Viewer);
        ImageManager::AsyncLoader::instance()->load(request);
        busy();
        m_reloadImageInProgress = true;
    }
}

/**
 * return the ratio of the two sizes. That is  newSize/baseSize.
 */
double Viewer::ImageDisplay::sizeRatio(const QSize &baseSize, const QSize &newSize) const
{
    double res = ((double)newSize.width()) / baseSize.width();

    if (res * baseSize.height() > newSize.height()) {
        res = ((double)newSize.height()) / baseSize.height();
    }
    return res;
}

void Viewer::ImageDisplay::requestImage(const DB::ImageInfoPtr &info, bool priority)
{
    Settings::StandardViewSize viewSize = Settings::SettingsData::instance()->viewerStandardSize();
    QSize s = size();
    if (viewSize == Settings::NaturalSize)
        s = QSize(-1, -1);

    ImageManager::ImageRequest *request = new ImageManager::ImageRequest(info->fileName(), s, info->angle(), this);
    request->setUpScale(viewSize == Settings::FullSize);
    request->setPriority(priority ? ImageManager::Viewer : ImageManager::ViewerPreload);
    ImageManager::AsyncLoader::instance()->load(request);
}

void Viewer::ImageDisplay::hideEvent(QHideEvent *)
{
    m_viewHandler->hideEvent();
}

// vi:expandtab:tabstop=4 shiftwidth=4:

#include "moc_ImageDisplay.cpp"
