pagepainter.cpp 55.5 KB
Newer Older
1 2 3 4 5 6 7 8 9
/***************************************************************************
 *   Copyright (C) 2005 by Enrico Ros <eros.kde@email.it>                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

10 11
#include "pagepainter.h"

12 13 14
// qt / kde includes
#include <qrect.h>
#include <qpainter.h>
15
#include <qpalette.h>
16
#include <qpixmap.h>
Dirk Mueller's avatar
Dirk Mueller committed
17
#include <qvarlengtharray.h>
18
#include <kiconloader.h>
Yuri Chornoivan's avatar
Yuri Chornoivan committed
19
#include <QDebug>
Laurent Montel's avatar
Laurent Montel committed
20
#include <QApplication>
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
21
#include <QIcon>
22
#include <QTransform>
23

24 25 26
// system includes
#include <math.h>

27
// local includes
28
#include "core/area.h"
29
#include "core/page.h"
30
#include "core/page_p.h"
31
#include "core/annotations.h"
32
#include "core/utils.h"
33
#include "guiutils.h"
34
#include "settings.h"
35
#include "core/observer.h"
36
#include "core/tile.h"
37
#include "settings_core.h"
38
#include "ui/debug_ui.h"
39

Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
40
Q_GLOBAL_STATIC_WITH_ARGS( QPixmap, busyPixmap, ( KIconLoader::global()->loadIcon(QLatin1String("okular"), KIconLoader::NoGroup, IconSize(KIconLoader::Desktop), KIconLoader::DefaultState, QStringList(), 0, true) ) )
41

42 43
#define TEXTANNOTATION_ICONSIZE 24

44 45 46 47 48 49 50 51 52 53 54 55
inline QPen buildPen( const Okular::Annotation *ann, double width, const QColor &color )
{
    QPen p(
        QBrush( color ),
        width,
        ann->style().lineStyle() == Okular::Annotation::Dashed ? Qt::DashLine : Qt::SolidLine,
        Qt::SquareCap,
        Qt::MiterJoin
    );
    return p;
}

56
void PagePainter::paintPageOnPainter( QPainter * destPainter, const Okular::Page * page,
57
    Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits )
58
{
59
        paintCroppedPageOnPainter( destPainter, page, observer, flags, scaledWidth, scaledHeight, limits,
60
                                   Okular::NormalizedRect( 0, 0, 1, 1 ), nullptr );
61 62 63
}

void PagePainter::paintCroppedPageOnPainter( QPainter * destPainter, const Okular::Page * page,
64
    Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits,
65
    const Okular::NormalizedRect &crop, Okular::NormalizedPoint *viewPortPoint )
66
{
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
67 68
    qreal dpr = destPainter->device()->devicePixelRatioF();

Nicolas Frattaroli's avatar
Nicolas Frattaroli committed
69 70
    /* Calculate the cropped geometry of the page */
    QRect scaledCrop = crop.geometry( scaledWidth, scaledHeight );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
71 72 73 74 75

    /* variables prefixed with d are in the device pixels coordinate system, which translates to the rendered output - that means,
     * multiplied with the device pixel ratio of the target PaintDevice */
    const QRect dScaledCrop(QRectF(scaledCrop.x() * dpr, scaledCrop.y() * dpr, scaledCrop.width() * dpr, scaledCrop.height() * dpr).toAlignedRect());

Nicolas Frattaroli's avatar
Nicolas Frattaroli committed
76 77
    int croppedWidth = scaledCrop.width();
    int croppedHeight = scaledCrop.height();
78

Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
79 80 81 82
    int dScaledWidth = ceil(scaledWidth * dpr);
    int dScaledHeight = ceil(scaledHeight * dpr);
    const QRect dLimits(QRectF(limits.x() * dpr, limits.y() * dpr, limits.width() * dpr, limits.height() * dpr).toAlignedRect());

83 84
    QColor paperColor = Qt::white;
    QColor backgroundColor = paperColor;
85
    if ( Okular::SettingsCore::changeColors() )
86
    {
87
        switch ( Okular::SettingsCore::renderMode() )
88
        {
89
            case Okular::SettingsCore::EnumRenderMode::Inverted:
90
                backgroundColor = Qt::black;
91
                break;
92
            case Okular::SettingsCore::EnumRenderMode::Paper:
93
                paperColor = Okular::SettingsCore::paperColor();
94
                backgroundColor = paperColor;
95
                break;
96
            case Okular::SettingsCore::EnumRenderMode::Recolor:
97
                backgroundColor = Okular::Settings::recolorBackground();
98 99 100 101
                break;
            default: ;
        }
    }
102
    destPainter->fillRect( limits, backgroundColor );
103

104
    const bool hasTilesManager = page->hasTilesManager( observer );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
105
    QPixmap pixmap;
106 107

    if ( !hasTilesManager )
108
    {
109
        /** 1 - RETRIEVE THE 'PAGE+ID' PIXMAP OR A SIMILAR 'PAGE' ONE **/
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
110 111 112 113 114 115
        const QPixmap *p = page->_o_nearestPixmap( observer, dScaledWidth, dScaledHeight );

        if (p != NULL) {
            pixmap = *p;
            pixmap.setDevicePixelRatio( qApp->devicePixelRatio() );
        }
116 117

        /** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
118 119 120 121
        double pixmapRescaleRatio = !pixmap.isNull() ? dScaledWidth / (double)pixmap.width() : -1;
        long pixmapPixels = !pixmap.isNull() ? (long)pixmap.width() * (long)pixmap.height() : 0;
        if ( pixmap.isNull() || pixmapRescaleRatio > 20.0 || pixmapRescaleRatio < 0.25 ||
             (dScaledWidth > pixmap.width() && pixmapPixels > 60000000L) )
122
        {
123
            // draw something on the blank page: the okular icon or a cross (as a fallback)
124
            if ( !busyPixmap()->isNull() )
125
            {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
126
                busyPixmap->setDevicePixelRatio(dpr);
127
                destPainter->drawPixmap( QPoint( 10, 10 ), *busyPixmap() );
128 129 130 131 132 133 134 135
            }
            else
            {
                destPainter->setPen( Qt::gray );
                destPainter->drawLine( 0, 0, croppedWidth-1, croppedHeight-1 );
                destPainter->drawLine( 0, croppedHeight-1, croppedWidth-1, 0 );
            }
            return;
136
        }
137 138
    }

139
    /** 2 - FIND OUT WHAT TO PAINT (Flags + Configuration + Presence) **/
140
    bool canDrawHighlights = (flags & Highlights) && !page->m_highlights.isEmpty();
141
    bool canDrawTextSelection = (flags & TextSelection) && page->textSelection();
142
    bool canDrawAnnotations = (flags & Annotations) && !page->m_annotations.isEmpty();
143 144
    bool enhanceLinks = (flags & EnhanceLinks) && Okular::Settings::highlightLinks();
    bool enhanceImages = (flags & EnhanceImages) && Okular::Settings::highlightImages();
145

146
    // vectors containing objects to draw
Pino Toscano's avatar
Pino Toscano committed
147
    // make this a qcolor, rect map, since we don't need
148
    // to know s_id here! we are only drawing this right?
149 150 151 152
    QList< QPair<QColor, Okular::NormalizedRect> > * bufferedHighlights = nullptr;
    QList< Okular::Annotation * > * bufferedAnnotations = nullptr;
    QList< Okular::Annotation * > * unbufferedAnnotations = nullptr;
    Okular::Annotation *boundingRectOnlyAnn = nullptr; // Paint the bounding rect of this annotation
153 154
    // fill up lists with visible annotation/highlight objects/text selections
    if ( canDrawHighlights || canDrawTextSelection || canDrawAnnotations )
155
    {
156
        // precalc normalized 'limits rect' for intersection
157 158 159 160
        double nXMin = ( (double)limits.left() / scaledWidth ) + crop.left,
               nXMax = ( (double)limits.right() / scaledWidth )  + crop.left,
               nYMin = ( (double)limits.top() / scaledHeight ) + crop.top,
               nYMax = ( (double)limits.bottom() / scaledHeight ) + crop.top;
161 162
        // append all highlights inside limits to their list
        if ( canDrawHighlights )
163
        {
164
            if ( !bufferedHighlights )
165
                 bufferedHighlights = new QList< QPair<QColor, Okular::NormalizedRect> >();
166 167 168
/*            else
            {*/
                
169
                Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax );
Laurent Montel's avatar
Laurent Montel committed
170
                QLinkedList< Okular::HighlightAreaRect * >::const_iterator h2It = page->m_highlights.constBegin(), hEnd = page->m_highlights.constEnd();
171
                Okular::HighlightAreaRect::const_iterator hIt;
172
                for ( ; h2It != hEnd; ++h2It )
Laurent Montel's avatar
Laurent Montel committed
173
                    for (hIt=(*h2It)->constBegin(); hIt!=(*h2It)->constEnd(); ++hIt)
174
                    {
175
                        if ((*hIt).intersects(limitRect))
176 177
                            bufferedHighlights->append( qMakePair((*h2It)->color,*hIt) );
                    }
178 179 180 181 182 183
                delete limitRect;
            //}
        }
        if ( canDrawTextSelection )
        {
            if ( !bufferedHighlights )
184
                 bufferedHighlights = new QList< QPair<QColor, Okular::NormalizedRect>  >();
185 186 187
/*            else
            {*/
                Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax );
188
                const Okular::RegularAreaRect *textSelection = page->textSelection();
Laurent Montel's avatar
Laurent Montel committed
189
                Okular::HighlightAreaRect::const_iterator hIt = textSelection->constBegin(), hEnd = textSelection->constEnd();
190 191
                for ( ; hIt != hEnd; ++hIt )
                {
192
                    if ( (*hIt).intersects( limitRect ) )
193
                        bufferedHighlights->append( qMakePair( page->textSelectionColor(), *hIt ) );
194
                }
195 196
                delete limitRect;
            //}
197
        }
198 199
        // append annotations inside limits to the un/buffered list
        if ( canDrawAnnotations )
200
        {
Laurent Montel's avatar
Laurent Montel committed
201
            QLinkedList< Okular::Annotation * >::const_iterator aIt = page->m_annotations.constBegin(), aEnd = page->m_annotations.constEnd();
202 203
            for ( ; aIt != aEnd; ++aIt )
            {
204
                Okular::Annotation * ann = *aIt;
205
                int flags = ann->flags();
206

207 208
                if ( flags & Okular::Annotation::Hidden )
                    continue;
209 210 211 212

                if ( flags & Okular::Annotation::ExternallyDrawn )
                {
                    // ExternallyDrawn annots are never rendered by PagePainter.
213 214 215
                    // Just paint the boundingRect if the annot is moved or resized.
                    if ( flags & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) )
                    {
216
                        boundingRectOnlyAnn = ann;
217
                    }
Pino Toscano's avatar
Pino Toscano committed
218
                    continue;
219
                }
Pino Toscano's avatar
Pino Toscano committed
220

221
                bool intersects = ann->transformedBoundingRectangle().intersects( nXMin, nYMin, nXMax, nYMax );
222
                if ( ann->subType() == Okular::Annotation::AText )
223
                {
224
                    Okular::TextAnnotation * ta = static_cast< Okular::TextAnnotation * >( ann );
225
                    if ( ta->textType() == Okular::TextAnnotation::Linked )
226
                    {
227 228 229 230
                        Okular::NormalizedRect iconrect( ann->transformedBoundingRectangle().left,
                                                         ann->transformedBoundingRectangle().top,
                                                         ann->transformedBoundingRectangle().left + TEXTANNOTATION_ICONSIZE / page->width(),
                                                         ann->transformedBoundingRectangle().top + TEXTANNOTATION_ICONSIZE / page->height() );
231 232 233 234
                        intersects = iconrect.intersects( nXMin, nYMin, nXMax, nYMax );
                    }
                }
                if ( intersects )
235
                {
236 237
                    Okular::Annotation::SubType type = ann->subType();
                    if ( type == Okular::Annotation::ALine || type == Okular::Annotation::AHighlight ||
238
                         type == Okular::Annotation::AInk  /*|| (type == Annotation::AGeom && ann->style().opacity() < 0.99)*/ )
239 240
                    {
                        if ( !bufferedAnnotations )
241
                            bufferedAnnotations = new QList< Okular::Annotation * >();
242 243 244 245 246
                        bufferedAnnotations->append( ann );
                    }
                    else
                    {
                        if ( !unbufferedAnnotations )
247
                            unbufferedAnnotations = new QList< Okular::Annotation * >();
248 249
                        unbufferedAnnotations->append( ann );
                    }
250
                }
251 252
            }
        }
253
        // end of intersections checking
254
    }
255

256
    /** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/
257
    bool bufferAccessibility = (flags & Accessibility) && Okular::SettingsCore::changeColors() && (Okular::SettingsCore::renderMode() != Okular::SettingsCore::EnumRenderMode::Paper);
258
    bool useBackBuffer = bufferAccessibility || bufferedHighlights || bufferedAnnotations || viewPortPoint;
259 260
    QPixmap * backPixmap = nullptr;
    QPainter * mixedPainter = nullptr;
261
    QRect limitsInPixmap = limits.translated( scaledCrop.topLeft() );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
262 263
    QRect dLimitsInPixmap = dLimits.translated( dScaledCrop.topLeft() );

264
        // limits within full (scaled but uncropped) pixmap
265 266 267

    /** 4A -- REGULAR FLOW. PAINT PIXMAP NORMAL OR RESCALED USING GIVEN QPAINTER **/
    if ( !useBackBuffer )
268
    {
269
        if ( hasTilesManager )
270
        {
271
            const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight );
272
            const QList<Okular::Tile> tiles = page->tilesAt( observer, normalizedLimits );
273
            QList<Okular::Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd();
274 275
            while ( tIt != tEnd )
            {
276
                const Okular::Tile &tile = *tIt;
277
                QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
278
                QRect dTileRect = QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect();
Mailson Menezes's avatar
Mailson Menezes committed
279
                QRect limitsInTile = limits & tileRect;
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
280 281
                QRectF dLimitsInTile = dLimits & dTileRect;

282
                if ( !limitsInTile.isEmpty() )
Mailson Menezes's avatar
Mailson Menezes committed
283
                {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
284 285 286 287 288 289 290 291 292
                    QPixmap* tilePixmap = tile.pixmap();
                    tilePixmap->setDevicePixelRatio( qApp->devicePixelRatio() );

                    if ( tilePixmap->width() == dTileRect.width() && tilePixmap->height() == dTileRect.height() ) {
                        destPainter->drawPixmap( limitsInTile.topLeft(), *tilePixmap,
                                dLimitsInTile.translated( -dTileRect.topLeft() ) );
                    } else {
                        destPainter->drawPixmap( tileRect, *tilePixmap );
                    }
Mailson Menezes's avatar
Mailson Menezes committed
293
                }
294 295 296 297 298
                tIt++;
            }
        }
        else
        {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
299 300 301
            QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap);
            scaledCroppedPixmap.setDevicePixelRatio(dpr);
            destPainter->drawPixmap( limits.topLeft(), scaledCroppedPixmap, QRectF(0, 0, dLimits.width(),dLimits.height()));
302
        }
303

304
        // 4A.2. active painter is the one passed to this method
305
        mixedPainter = destPainter;
306 307
    }
    /** 4B -- BUFFERED FLOW. IMAGE PAINTING + OPERATIONS. QPAINTER OVER PIXMAP  **/
308 309
    else
    {
310
        // the image over which we are going to draw
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
311 312 313 314
        QImage backImage = QImage( dLimits.width(), dLimits.height(), QImage::Format_ARGB32_Premultiplied );
        backImage.setDevicePixelRatio(dpr);
        backImage.fill( paperColor );
        QPainter p( &backImage );
315

316
        if ( hasTilesManager )
317
        {
318
            const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight );
319
            const QList<Okular::Tile> tiles = page->tilesAt( observer, normalizedLimits );
320 321 322
            QList<Okular::Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd();
            while ( tIt != tEnd )
            {
323
                const Okular::Tile &tile = *tIt;
324
                QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
325
                QRect dTileRect(QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect());
326
                QRect limitsInTile = limits & tileRect;
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
327 328
                QRect dLimitsInTile = dLimits & dTileRect;

329
                if ( !limitsInTile.isEmpty() )
330
                {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
331 332 333 334
                    QPixmap* tilePixmap = tile.pixmap();
                    tilePixmap->setDevicePixelRatio( qApp->devicePixelRatio() );

                    if ( tilePixmap->width() == dTileRect.width() && tilePixmap->height() == dTileRect.height() )
335
                    {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
336 337
                        p.drawPixmap( limitsInTile.translated( -limits.topLeft() ).topLeft(), *tilePixmap,
                                dLimitsInTile.translated( -dTileRect.topLeft() ) );
338 339 340
                    }
                    else
                    {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
341 342
                        double xScale = tilePixmap->width() / (double)dTileRect.width();
                        double yScale = tilePixmap->height() / (double)dTileRect.height();
343
                        QTransform transform( xScale, 0, 0, yScale, 0, 0 );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
344 345
                        p.drawPixmap( limitsInTile.translated( -limits.topLeft() ), *tilePixmap,
                                transform.mapRect( dLimitsInTile ).translated( -transform.mapRect( dTileRect ).topLeft() ) );
346 347 348 349 350 351 352 353
                    }
                }
                ++tIt;
            }
        }
        else
        {
            // 4B.1. draw the page pixmap: normal or scaled
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
354 355 356
            QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap);
            scaledCroppedPixmap.setDevicePixelRatio(dpr);
            p.drawPixmap( 0, 0, scaledCroppedPixmap );
357
        }
358

Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
359 360
        p.end();

361
        // 4B.2. modify pixmap following accessibility settings
362
        if ( bufferAccessibility )
363
        {
364
            switch ( Okular::SettingsCore::renderMode() )
365
            {
366
                case Okular::SettingsCore::EnumRenderMode::Inverted:
367
                    // Invert image pixels using QImage internal function
Pino Toscano's avatar
Pino Toscano committed
368
                    backImage.invertPixels(QImage::InvertRgb);
369
                    break;
370
                case Okular::SettingsCore::EnumRenderMode::Recolor:
371
                    recolor(&backImage, Okular::Settings::recolorForeground(), Okular::Settings::recolorBackground());
372
                    break;
373
                case Okular::SettingsCore::EnumRenderMode::BlackWhite:
374 375 376
                    // Manual Gray and Contrast
                    unsigned int * data = (unsigned int *)backImage.bits();
                    int val, pixels = backImage.width() * backImage.height(),
377
                        con = Okular::Settings::bWContrast(), thr = 255 - Okular::Settings::bWThreshold();
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
                    for( int i = 0; i < pixels; ++i )
                    {
                        val = qGray( data[i] );
                        if ( val > thr )
                            val = 128 + (127 * (val - thr)) / (255 - thr);
                        else if ( val < thr )
                            val = (128 * val) / thr;
                        if ( con > 2 )
                        {
                            val = con * ( val - thr ) / 2 + thr;
                            if ( val > 255 )
                                val = 255;
                            else if ( val < 0 )
                                val = 0;
                        }
                        data[i] = qRgba( val, val, val, 255 );
                    }
                    break;
            }
        }
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
398

399
        // 4B.3. highlight rects in page
400
        if ( bufferedHighlights )
401 402
        {
            // draw highlights that are inside the 'limits' paint region
403
            for (const auto& highlight : *bufferedHighlights)
404
            {
405
                const Okular::NormalizedRect & r = highlight.second;
406
                // find out the rect to highlight on pixmap
407
                QRect highlightRect = r.geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ).intersected( limits );
Pino Toscano's avatar
Pino Toscano committed
408
                highlightRect.translate( -limits.left(), -limits.top() );
409

410 411 412 413
                const QColor highlightColor = highlight.first;
                QPainter painter(&backImage);
                painter.setCompositionMode(QPainter::CompositionMode_Multiply);
                painter.fillRect(highlightRect, highlightColor);
414 415 416 417 418

                auto frameColor = highlightColor.darker(150);
                const QRect frameRect = r.geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ).translated( -limits.left(), -limits.top() );
                painter.setPen(frameColor);
                painter.drawRect(frameRect);
419
            }
420
        }
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
421

422
        // 4B.4. paint annotations [COMPOSITED ONES]
423
        if ( bufferedAnnotations )
424
        {
425 426 427 428 429 430
            // Albert: This is quite "heavy" but all the backImage that reach here are QImage::Format_ARGB32_Premultiplied
            // and have to be so that the QPainter::CompositionMode_Multiply works
            // we could also put a
            // backImage = backImage.convertToFormat(QImage::Format_ARGB32_Premultiplied)
            // that would be almost a noop, but we'll leave the assert for now
            Q_ASSERT(backImage.format() == QImage::Format_ARGB32_Premultiplied);
431 432
            // precalc constants for normalizing [0,1] page coordinates into normalized [0,1] limit rect coordinates
            double pageScale = (double)croppedWidth / page->width();
433
            double xOffset = (double)limits.left() / (double)scaledWidth + crop.left,
434
                   xScale = (double)scaledWidth / (double)limits.width(),
435
                   yOffset = (double)limits.top() / (double)scaledHeight + crop.top,
436
                   yScale = (double)scaledHeight / (double)limits.height();
Enrico Ros's avatar
Enrico Ros committed
437 438

            // paint all buffered annotations in the page
Laurent Montel's avatar
Laurent Montel committed
439
            QList< Okular::Annotation * >::const_iterator aIt = bufferedAnnotations->constBegin(), aEnd = bufferedAnnotations->constEnd();
440 441
            for ( ; aIt != aEnd; ++aIt )
            {
442 443
                Okular::Annotation * a = *aIt;
                Okular::Annotation::SubType type = a->subType();
444 445 446 447
                QColor acolor = a->style().color();
                if ( !acolor.isValid() )
                    acolor = Qt::yellow;
                acolor.setAlphaF( a->style().opacity() );
448

449
                // draw LineAnnotation MISSING: caption, dash pattern, endings for multipoint lines
450
                if ( type == Okular::Annotation::ALine )
451
                {
452 453 454 455
                    LineAnnotPainter linepainter { (Okular::LineAnnotation *) a,
                        { page->width(), page->height() }, pageScale,
                        { xScale, 0., 0., yScale, -xOffset * xScale, -yOffset * yScale } };
                    linepainter.draw( backImage );
Enrico Ros's avatar
Enrico Ros committed
456 457
                }
                // draw HighlightAnnotation MISSING: under/strike width, feather, capping
458
                else if ( type == Okular::Annotation::AHighlight )
459 460
                {
                    // get the annotation
461
                    Okular::HighlightAnnotation * ha = (Okular::HighlightAnnotation *) a;
462
                    Okular::HighlightAnnotation::HighlightType type = ha->highlightType();
463 464

                    // draw each quad of the annotation
465
                    int quads = ha->highlightQuads().size();
466 467 468
                    for ( int q = 0; q < quads; q++ )
                    {
                        NormalizedPath path;
469
                        const Okular::HighlightAnnotation::Quad & quad = ha->highlightQuads()[ q ];
Enrico Ros's avatar
Enrico Ros committed
470
                        // normalize page point to image
471 472
                        for ( int i = 0; i < 4; i++ )
                        {
473
                            Okular::NormalizedPoint point;
474 475
                            point.x = (quad.transformedPoint( i ).x - xOffset) * xScale;
                            point.y = (quad.transformedPoint( i ).y - yOffset) * yScale;
476 477
                            path.append( point );
                        }
Enrico Ros's avatar
Enrico Ros committed
478 479 480 481
                        // draw the normalized path into image
                        switch ( type )
                        {
                            // highlight the whole rect
482
                            case Okular::HighlightAnnotation::Highlight:
483
                                drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply );
Enrico Ros's avatar
Enrico Ros committed
484 485
                                break;
                            // highlight the bottom part of the rect
486
                            case Okular::HighlightAnnotation::Squiggly:
487 488 489 490
                                path[ 3 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0;
                                path[ 3 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0;
                                path[ 2 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0;
                                path[ 2 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0;
491
                                drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply );
Enrico Ros's avatar
Enrico Ros committed
492 493
                                break;
                            // make a line at 3/4 of the height
494
                            case Okular::HighlightAnnotation::Underline:
495 496 497 498
                                path[ 0 ].x = ( 3 * path[ 0 ].x + path[ 3 ].x ) / 4.0;
                                path[ 0 ].y = ( 3 * path[ 0 ].y + path[ 3 ].y ) / 4.0;
                                path[ 1 ].x = ( 3 * path[ 1 ].x + path[ 2 ].x ) / 4.0;
                                path[ 1 ].y = ( 3 * path[ 1 ].y + path[ 2 ].y ) / 4.0;
Enrico Ros's avatar
Enrico Ros committed
499 500
                                path.pop_back();
                                path.pop_back();
501
                                drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale );
Enrico Ros's avatar
Enrico Ros committed
502 503
                                break;
                            // make a line at 1/2 of the height
504
                            case Okular::HighlightAnnotation::StrikeOut:
Enrico Ros's avatar
Enrico Ros committed
505 506 507 508 509 510
                                path[ 0 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0;
                                path[ 0 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0;
                                path[ 1 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0;
                                path[ 1 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0;
                                path.pop_back();
                                path.pop_back();
511
                                drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale );
Enrico Ros's avatar
Enrico Ros committed
512 513
                                break;
                        }
514 515
                    }
                }
Enrico Ros's avatar
Enrico Ros committed
516
                // draw InkAnnotation MISSING:invar width, PENTRACER
517
                else if ( type == Okular::Annotation::AInk )
Enrico Ros's avatar
Enrico Ros committed
518 519
                {
                    // get the annotation
520
                    Okular::InkAnnotation * ia = (Okular::InkAnnotation *) a;
Enrico Ros's avatar
Enrico Ros committed
521 522

                    // draw each ink path
523 524
                    const QList< QLinkedList<Okular::NormalizedPoint> > transformedInkPaths = ia->transformedInkPaths();

525 526
                    const QPen inkPen = buildPen( a, a->style().width(), acolor );

527
                    int paths = transformedInkPaths.size();
Enrico Ros's avatar
Enrico Ros committed
528 529 530
                    for ( int p = 0; p < paths; p++ )
                    {
                        NormalizedPath path;
531
                        const QLinkedList<Okular::NormalizedPoint> & inkPath = transformedInkPaths[ p ];
Enrico Ros's avatar
Enrico Ros committed
532 533

                        // normalize page point to image
Laurent Montel's avatar
Laurent Montel committed
534
                        QLinkedList<Okular::NormalizedPoint>::const_iterator pIt = inkPath.constBegin(), pEnd = inkPath.constEnd();
Enrico Ros's avatar
Enrico Ros committed
535 536
                        for ( ; pIt != pEnd; ++pIt )
                        {
537 538
                            const Okular::NormalizedPoint & inkPoint = *pIt;
                            Okular::NormalizedPoint point;
Enrico Ros's avatar
Enrico Ros committed
539 540 541 542 543
                            point.x = (inkPoint.x - xOffset) * xScale;
                            point.y = (inkPoint.y - yOffset) * yScale;
                            path.append( point );
                        }
                        // draw the normalized path into image
544
                        drawShapeOnImage( backImage, path, false, inkPen, QBrush(), pageScale );
Enrico Ros's avatar
Enrico Ros committed
545 546 547 548
                    }
                }
            } // end current annotation drawing
        }
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
        if(viewPortPoint)
        {
            QPainter painter(&backImage);
            painter.translate( -limits.left(), -limits.top() );
            painter.setPen( QApplication::palette().color( QPalette::Active, QPalette::Highlight ) );
            painter.drawLine( 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1, viewPortPoint->y * scaledHeight + 1 );
// ROTATION CURRENTLY NOT IMPLEMENTED
/*
            if( page->rotation() == Okular::Rotation0)
            {

            }
            else if(page->rotation() == Okular::Rotation270)
            {
                painter.drawLine( viewPortPoint->y * scaledHeight + 1, 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1);
            }
            else if(page->rotation() == Okular::Rotation180)
            {
                painter.drawLine( 0, (1.0 - viewPortPoint->y) * scaledHeight - 1, scaledWidth - 1, (1.0 - viewPortPoint->y) * scaledHeight - 1 );
            }
            else if(page->rotation() == Okular::Rotation90) // not right, rotation clock-wise
            {
                painter.drawLine( scaledWidth - (viewPortPoint->y * scaledHeight + 1), 0, scaledWidth - (viewPortPoint->y * scaledHeight + 1), scaledWidth - 1);
            }
*/
        }

576
        // 4B.5. create the back pixmap converting from the local image
Tobias Koenig's avatar
Tobias Koenig committed
577
        backPixmap = new QPixmap( QPixmap::fromImage( backImage ) );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
578
        backPixmap->setDevicePixelRatio(dpr);
579 580 581

        // 4B.6. create a painter over the pixmap and set it as the active one
        mixedPainter = new QPainter( backPixmap );
582
        mixedPainter->translate( -limits.left(), -limits.top() );
583 584
    }

585
    /** 5 -- MIXED FLOW. Draw ANNOTATIONS [OPAQUE ONES] on ACTIVE PAINTER  **/
586
    if ( unbufferedAnnotations )
587
    {
588
        // iterate over annotations and paint AText, AGeom, AStamp
Laurent Montel's avatar
Laurent Montel committed
589
        QList< Okular::Annotation * >::const_iterator aIt = unbufferedAnnotations->constBegin(), aEnd = unbufferedAnnotations->constEnd();
590 591
        for ( ; aIt != aEnd; ++aIt )
        {
592
            Okular::Annotation * a = *aIt;
593

Pino Toscano's avatar
Pino Toscano committed
594
            // honor opacity settings on supported types
595
            unsigned int opacity = (unsigned int)( a->style().color().alpha() * a->style().opacity() );
596 597 598
            // skip the annotation drawing if all the annotation is fully
            // transparent, but not with text annotations
            if ( opacity <= 0 && a->subType() != Okular::Annotation::AText )
599 600
                continue;

601 602 603 604 605
            QColor acolor = a->style().color();
            if ( !acolor.isValid() )
                acolor = Qt::yellow;
            acolor.setAlpha( opacity );

606
            // get annotation boundary and drawn rect
607
            QRect annotBoundary = a->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() );
608
            QRect annotRect = annotBoundary.intersected( limits );
609 610
            QRect innerRect( annotRect.left() - annotBoundary.left(), annotRect.top() -
                    annotBoundary.top(), annotRect.width(), annotRect.height() );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
611
            QRectF dInnerRect(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr);
612

613
            Okular::Annotation::SubType type = a->subType();
614

Pino Toscano's avatar
Pino Toscano committed
615
            // draw TextAnnotation
616
            if ( type == Okular::Annotation::AText )
617
            {
618
                Okular::TextAnnotation * text = (Okular::TextAnnotation *)a;
619
                if ( text->textType() == Okular::TextAnnotation::InPlace )
Pino Toscano's avatar
Pino Toscano committed
620
                {
621
                    QImage image( annotBoundary.size(), QImage::Format_ARGB32 );
622
                    image.fill( acolor.rgba() );
Pino Toscano's avatar
Pino Toscano committed
623
                    QPainter painter( &image );
624
                    painter.setFont( text->textFont() );
625
                    painter.setPen( text->textColor() );
626
                    Qt::AlignmentFlag halign = ( text->inplaceAlignment() == 1 ? Qt::AlignHCenter : ( text->inplaceAlignment() == 2 ? Qt::AlignRight : Qt::AlignLeft ) );
627 628
                    const double invXScale = (double)page->width() / scaledWidth;
                    const double invYScale = (double)page->height() / scaledHeight;
629
                    const double borderWidth = text->style().width();
630
                    painter.scale( 1 / invXScale, 1 / invYScale );
631 632 633
                    painter.drawText( borderWidth * invXScale, borderWidth * invYScale,
                                      (image.width() - 2 * borderWidth) * invXScale,
                                      (image.height() - 2 * borderWidth) * invYScale,
634
                                      Qt::AlignTop | halign | Qt::TextWordWrap,
635
                                      text->contents() );
636
                    painter.resetTransform();
637 638 639 640 641 642 643 644
                    //Required as asking for a zero width pen results
                    //in a default width pen (1.0) being created
                    if ( borderWidth != 0 )
                    {
                        QPen pen( Qt::black, borderWidth );
                        painter.setPen( pen );
                        painter.drawRect( 0, 0, image.width() - 1, image.height() - 1 );
                    }
Pino Toscano's avatar
Pino Toscano committed
645 646 647 648
                    painter.end();

                    mixedPainter->drawImage( annotBoundary.topLeft(), image );
                }
649
                else if ( text->textType() == Okular::TextAnnotation::Linked )
650
                {
651
                // get pixmap, colorize and alpha-blend it
Pino Toscano's avatar
Pino Toscano committed
652
                    QString path;
653
                    QPixmap pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::User, 32, KIconLoader::DefaultState, QStringList(), &path, true );
Pino Toscano's avatar
Pino Toscano committed
654
                    if ( path.isEmpty() )
655
                        pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::NoGroup, 32 );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
656
                    QRect annotBoundary2 = QRect( annotBoundary.topLeft(), QSize( TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr ) );
657
                    QRect annotRect2 = annotBoundary2.intersected( limits );
658 659
                    QRect innerRect2( annotRect2.left() - annotBoundary2.left(), annotRect2.top() -
                    annotBoundary2.top(), annotRect2.width(), annotRect2.height() );
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
660 661 662 663 664

                    QPixmap scaledCroppedPixmap = pixmap.scaled(TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr).copy(dInnerRect.toAlignedRect());
                    scaledCroppedPixmap.setDevicePixelRatio(dpr);
                    QImage scaledCroppedImage = scaledCroppedPixmap.toImage();

665 666 667
                    // if the annotation color is valid (ie it was set), then
                    // use it to colorize the icon, otherwise the icon will be
                    // "gray"
668
                    if ( a->style().color().isValid() )
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
669 670
                        GuiUtils::colorizeImage( scaledCroppedImage, a->style().color(), opacity );
                    pixmap = QPixmap::fromImage( scaledCroppedImage );
671

Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
672 673
                    // draw the mangled image to painter
                    mixedPainter->drawPixmap( annotRect.topLeft(), pixmap);
674 675
                }

676 677
            }
            // draw StampAnnotation
678
            else if ( type == Okular::Annotation::AStamp )
679
            {
680
                Okular::StampAnnotation * stamp = (Okular::StampAnnotation *)a;
681 682

                // get pixmap and alpha blend it if needed
683
                QPixmap pixmap = GuiUtils::loadStamp( stamp->stampIconName(), annotBoundary.width() );
684 685
                if ( !pixmap.isNull() ) // should never happen but can happen on huge sizes
                {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
686 687 688 689 690 691 692
                    const QRect dInnerRect(QRectF(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr).toAlignedRect());

                    QPixmap scaledCroppedPixmap = pixmap.scaled(annotBoundary.width() * dpr, annotBoundary.height() * dpr).copy(dInnerRect);
                    scaledCroppedPixmap.setDevicePixelRatio(dpr);

                    QImage scaledCroppedImage = scaledCroppedPixmap.toImage();

693
                    if ( opacity < 255 )
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
694 695
                        changeImageAlpha( scaledCroppedImage, opacity );
                    pixmap = QPixmap::fromImage( scaledCroppedImage );
696 697 698 699

                    // draw the scaled and al
                    mixedPainter->drawPixmap( annotRect.topLeft(), pixmap );
                }
700 701
            }
            // draw GeomAnnotation
702 703 704
            else if ( type == Okular::Annotation::AGeom )
            {
                Okular::GeomAnnotation * geom = (Okular::GeomAnnotation *)a;
705 706
                // check whether there's anything to draw
                if ( geom->style().width() || geom->geometricalInnerColor().isValid() )
707
                {
708
                    mixedPainter->save();
709
                    const double width = geom->style().width() * Okular::Utils::realDpi(nullptr).width() / ( 72.0 * 2.0 ) * scaledWidth / page->width();
710
                    QRectF r( .0, .0, annotBoundary.width(), annotBoundary.height() );
711
                    r.adjust( width, width, -width, -width );
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
                    r.translate( annotBoundary.topLeft() );
                    if ( geom->geometricalInnerColor().isValid() )
                    {
                        r.adjust( width, width, -width, -width );
                        const QColor color = geom->geometricalInnerColor();
                        mixedPainter->setPen( Qt::NoPen );
                        mixedPainter->setBrush( QColor( color.red(), color.green(), color.blue(), opacity ) );
                        if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare )
                            mixedPainter->drawRect( r );
                        else
                            mixedPainter->drawEllipse( r );
                        r.adjust( -width, -width, width, width );
                    }
                    if ( geom->style().width() ) // need to check the original size here..
                    {
727
                        mixedPainter->setPen( buildPen( a, width * 2, acolor ) );
728 729 730 731 732 733 734
                        mixedPainter->setBrush( Qt::NoBrush );
                        if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare )
                            mixedPainter->drawRect( r );
                        else
                            mixedPainter->drawEllipse( r );
                    }
                    mixedPainter->restore();
735 736
                }
            }
737 738

            // draw extents rectangle
739
            if ( Okular::Settings::debugDrawAnnotationRect() )
740
            {
741
                mixedPainter->setPen( a->style().color() );
742
                mixedPainter->drawRect( annotBoundary );
743
            }
744 745 746
        }
    }

747 748
    if ( boundingRectOnlyAnn )
    {
749
        QRect annotBoundary = boundingRectOnlyAnn->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() );
750 751 752 753
        mixedPainter->setPen( Qt::DashLine );
        mixedPainter->drawRect( annotBoundary );
    }

754
    /** 6 -- MIXED FLOW. Draw LINKS+IMAGES BORDER on ACTIVE PAINTER  **/
755
    if ( enhanceLinks || enhanceImages )
756
    {
757 758
        mixedPainter->save();
        mixedPainter->scale( scaledWidth, scaledHeight );
759
        mixedPainter->translate( -crop.left, -crop.top );
760

Pino Toscano's avatar
Pino Toscano committed
761
        QColor normalColor = QApplication::palette().color( QPalette::Active, QPalette::Highlight );
762 763
        // enlarging limits for intersection is like growing the 'rectGeometry' below
        QRect limitsEnlarged = limits;
Pino Toscano's avatar
Pino Toscano committed
764
        limitsEnlarged.adjust( -2, -2, 2, 2 );
765
        // draw rects that are inside the 'limits' paint region as opaque rects
Laurent Montel's avatar
Laurent Montel committed
766
        QLinkedList< Okular::ObjectRect * >::const_iterator lIt = page->m_rects.constBegin(), lEnd = page->m_rects.constEnd();
767 768
        for ( ; lIt != lEnd; ++lIt )
        {
769
            Okular::ObjectRect * rect = *lIt;
Pino Toscano's avatar
Pino Toscano committed
770
            if ( (enhanceLinks && rect->objectType() == Okular::ObjectRect::Action) ||
771
                 (enhanceImages && rect->objectType() == Okular::ObjectRect::Image) )
772
            {
773
                if ( limitsEnlarged.intersects( rect->boundingRect( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ) ) )
774
                {
775
                    mixedPainter->strokePath( rect->region(), QPen( normalColor, 0 ) );
776 777 778
                }
            }
        }
779
        mixedPainter->restore();
780 781
    }

782 783
    /** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/
    if ( useBackBuffer )
784
    {
785
        delete mixedPainter;
786 787 788
        destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap );
        delete backPixmap;
    }
789 790 791 792 793

    // delete object containers
    delete bufferedHighlights;
    delete bufferedAnnotations;
    delete unbufferedAnnotations;
794
}
795 796


797 798
/** Private Helpers :: Pixmap conversion **/
void PagePainter::cropPixmapOnImage( QImage & dest, const QPixmap * src, const QRect & r )
799
{
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
800 801
    qreal dpr = src->devicePixelRatioF();

802
    // handle quickly the case in which the whole pixmap has to be converted
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
803
    if ( r == QRect( 0, 0, src->width() / dpr, src->height() / dpr ) )
804
    {
805
        dest = src->toImage();
806
        dest = dest.convertToFormat(QImage::Format_ARGB32_Premultiplied);
807 808 809 810
    }
    // else copy a portion of the src to an internal pixmap (smaller) and convert it
    else
    {
Lukas Hetzenecker's avatar
Lukas Hetzenecker committed
811 812
        QImage croppedImage( r.width() * dpr, r.height() * dpr, QImage::Format_ARGB32_Premultiplied );
        croppedImage.setDevicePixelRatio(dpr);
813
        QPainter p( &croppedImage );
814
        p.drawPixmap( 0, 0, *src, r.left(), r.top(), r.width(), r.height() );
815
        p.end();
816
        dest = croppedImage;
817 818 819
    }
}

820 821 822
void PagePainter::recolor(QImage *image, const QColor &foreground, const QColor &background)
{
    if (image->format() != QImage::Format_ARGB32_Premultiplied) {
823
        qCWarning(OkularUiDebug) << "Wrong image format! Converting...";
824 825 826 827 828 829 830 831 832 833 834 835 836 837
        *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied);
    }

    Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied);

    const float scaleRed = background.redF() - foreground.redF();
    const float scaleGreen = background.greenF() - foreground.greenF();
    const float scaleBlue = background.blueF() - foreground.blueF();

    for (int y=0; y<image->height(); y++) {
        QRgb *pixels = reinterpret_cast<QRgb*>(image->scanLine(y));

        for (int x=0; x<image->width(); x++) {
            const int lightness = qGray(pixels[x]);
838 839 840
            pixels[x] = qRgba(scaleRed * lightness + foreground.red(),
                           scaleGreen * lightness + foreground.green(),
                           scaleBlue * lightness + foreground.blue(),
841 842 843 844 845
                           qAlpha(pixels[x]));
        }
    }
}

846
/** Private Helpers :: Image Drawing **/
847
// from Arthur - qt4
848
static inline int qt_div_255(int x) { return (x + (x>>8) + 0x80) >> 8; }