1 /***************************************************************************
2 * Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
10 #include "pagepainter.h"
17 #include <qvarlengtharray.h>
18 #include <kiconloader.h>
20 #include <QApplication>
21 #include <qimageblitz.h>
27 #include "core/area.h"
28 #include "core/page.h"
29 #include "core/annotations.h"
30 #include "core/utils.h"
34 K_GLOBAL_STATIC_WITH_ARGS( QPixmap
, busyPixmap
, ( KIconLoader::global()->loadIcon("okular", KIconLoader::NoGroup
, 32, KIconLoader::DefaultState
, QStringList(), 0, true) ) )
36 #define TEXTANNOTATION_ICONSIZE 24
38 inline QPen
buildPen( const Okular::Annotation
*ann
, double width
, const QColor
&color
)
43 ann
->style().lineStyle() == Okular::Annotation::Dashed
? Qt::DashLine
: Qt::SolidLine
,
50 void PagePainter::paintPageOnPainter( QPainter
* destPainter
, const Okular::Page
* page
,
51 int pixID
, int flags
, int scaledWidth
, int scaledHeight
, const QRect
&limits
)
53 paintCroppedPageOnPainter( destPainter
, page
, pixID
, flags
, scaledWidth
, scaledHeight
, limits
,
54 Okular::NormalizedRect( 0, 0, 1, 1 ) );
57 void PagePainter::paintCroppedPageOnPainter( QPainter
* destPainter
, const Okular::Page
* page
,
58 int pixID
, int flags
, int scaledWidth
, int scaledHeight
, const QRect
&limits
,
59 const Okular::NormalizedRect
&crop
)
61 /* Calculate the cropped geometry of the page */
62 QRect scaledCrop
= crop
.geometry( scaledWidth
, scaledHeight
);
63 int croppedWidth
= scaledCrop
.width();
64 int croppedHeight
= scaledCrop
.height();
66 /** 1 - RETRIEVE THE 'PAGE+ID' PIXMAP OR A SIMILAR 'PAGE' ONE **/
67 const QPixmap
* pixmap
= page
->_o_nearestPixmap( pixID
, scaledWidth
, scaledHeight
);
69 QColor color
= Qt::white
;
70 if ( Okular::Settings::changeColors() )
72 switch ( Okular::Settings::renderMode() )
74 case Okular::Settings::EnumRenderMode::Inverted
:
77 case Okular::Settings::EnumRenderMode::Paper
:
78 color
= Okular::Settings::paperColor();
80 case Okular::Settings::EnumRenderMode::Recolor
:
81 color
= Okular::Settings::recolorBackground();
86 destPainter
->fillRect( limits
, color
);
88 /** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/
89 double pixmapRescaleRatio
= pixmap
? scaledWidth
/ (double)pixmap
->width() : -1;
90 long pixmapPixels
= pixmap
? (long)pixmap
->width() * (long)pixmap
->height() : 0;
91 if ( !pixmap
|| pixmapRescaleRatio
> 20.0 || pixmapRescaleRatio
< 0.25 ||
92 (scaledWidth
!= pixmap
->width() && pixmapPixels
> 6000000L) )
94 // draw something on the blank page: the okular icon or a cross (as a fallback)
95 if ( !busyPixmap
->isNull() )
97 destPainter
->drawPixmap( QPoint( 10, 10 ), *busyPixmap
);
101 destPainter
->setPen( Qt::gray
);
102 destPainter
->drawLine( 0, 0, croppedWidth
-1, croppedHeight
-1 );
103 destPainter
->drawLine( 0, croppedHeight
-1, croppedWidth
-1, 0 );
108 /** 2 - FIND OUT WHAT TO PAINT (Flags + Configuration + Presence) **/
109 bool canDrawHighlights
= (flags
& Highlights
) && !page
->m_highlights
.isEmpty();
110 bool canDrawTextSelection
= (flags
& TextSelection
) && page
->textSelection();
111 bool canDrawAnnotations
= (flags
& Annotations
) && !page
->m_annotations
.isEmpty();
112 bool enhanceLinks
= (flags
& EnhanceLinks
) && Okular::Settings::highlightLinks();
113 bool enhanceImages
= (flags
& EnhanceImages
) && Okular::Settings::highlightImages();
114 // vectors containing objects to draw
115 // make this a qcolor, rect map, since we don't need
116 // to know s_id here! we are only drawing this right?
117 QList
< QPair
<QColor
, Okular::NormalizedRect
> > * bufferedHighlights
= 0;
118 QList
< Okular::Annotation
* > * bufferedAnnotations
= 0;
119 QList
< Okular::Annotation
* > * unbufferedAnnotations
= 0;
120 // fill up lists with visible annotation/highlight objects/text selections
121 if ( canDrawHighlights
|| canDrawTextSelection
|| canDrawAnnotations
)
123 // precalc normalized 'limits rect' for intersection
124 double nXMin
= ( (double)limits
.left() / (double)scaledWidth
) + crop
.left
,
125 nXMax
= ( (double)limits
.right() / (double)scaledWidth
) + crop
.left
,
126 nYMin
= ( (double)limits
.top() / (double)scaledHeight
) + crop
.top
,
127 nYMax
= ( (double)limits
.bottom() / (double)scaledHeight
) + crop
.top
;
128 // append all highlights inside limits to their list
129 if ( canDrawHighlights
)
131 if ( !bufferedHighlights
)
132 bufferedHighlights
= new QList
< QPair
<QColor
, Okular::NormalizedRect
> >();
136 Okular::NormalizedRect
* limitRect
= new Okular::NormalizedRect(nXMin
, nYMin
, nXMax
, nYMax
);
137 QLinkedList
< Okular::HighlightAreaRect
* >::const_iterator h2It
= page
->m_highlights
.begin(), hEnd
= page
->m_highlights
.end();
138 Okular::HighlightAreaRect::const_iterator hIt
;
139 for ( ; h2It
!= hEnd
; ++h2It
)
140 for (hIt
=(*h2It
)->begin(); hIt
!=(*h2It
)->end(); ++hIt
)
142 if ((*hIt
).intersects(limitRect
))
143 bufferedHighlights
->append( qMakePair((*h2It
)->color
,*hIt
) );
148 if ( canDrawTextSelection
)
150 if ( !bufferedHighlights
)
151 bufferedHighlights
= new QList
< QPair
<QColor
, Okular::NormalizedRect
> >();
154 Okular::NormalizedRect
* limitRect
= new Okular::NormalizedRect(nXMin
, nYMin
, nXMax
, nYMax
);
155 const Okular::RegularAreaRect
*textSelection
= page
->textSelection();
156 Okular::HighlightAreaRect::const_iterator hIt
= textSelection
->begin(), hEnd
= textSelection
->end();
157 for ( ; hIt
!= hEnd
; ++hIt
)
159 if ( (*hIt
).intersects( limitRect
) )
160 bufferedHighlights
->append( qMakePair( page
->textSelectionColor(), *hIt
) );
165 // append annotations inside limits to the un/buffered list
166 if ( canDrawAnnotations
)
168 QLinkedList
< Okular::Annotation
* >::const_iterator aIt
= page
->m_annotations
.begin(), aEnd
= page
->m_annotations
.end();
169 for ( ; aIt
!= aEnd
; ++aIt
)
171 Okular::Annotation
* ann
= *aIt
;
172 if ( ann
->flags() & ( Okular::Annotation::Hidden
/*| Okular::Annotation::External*/ ) )
175 bool intersects
= ann
->transformedBoundingRectangle().intersects( nXMin
, nYMin
, nXMax
, nYMax
);
176 if ( ann
->subType() == Okular::Annotation::AText
)
178 Okular::TextAnnotation
* ta
= static_cast< Okular::TextAnnotation
* >( ann
);
179 if ( ta
->textType() == Okular::TextAnnotation::Linked
)
181 Okular::NormalizedRect
iconrect( ann
->transformedBoundingRectangle().left
,
182 ann
->transformedBoundingRectangle().top
,
183 ann
->transformedBoundingRectangle().left
+ TEXTANNOTATION_ICONSIZE
/ page
->width(),
184 ann
->transformedBoundingRectangle().top
+ TEXTANNOTATION_ICONSIZE
/ page
->height() );
185 intersects
= iconrect
.intersects( nXMin
, nYMin
, nXMax
, nYMax
);
190 Okular::Annotation::SubType type
= ann
->subType();
191 if ( type
== Okular::Annotation::ALine
|| type
== Okular::Annotation::AHighlight
||
192 type
== Okular::Annotation::AInk
/*|| (type == Annotation::AGeom && ann->style().opacity() < 0.99)*/ )
194 if ( !bufferedAnnotations
)
195 bufferedAnnotations
= new QList
< Okular::Annotation
* >();
196 bufferedAnnotations
->append( ann
);
200 if ( !unbufferedAnnotations
)
201 unbufferedAnnotations
= new QList
< Okular::Annotation
* >();
202 unbufferedAnnotations
->append( ann
);
207 // end of intersections checking
210 /** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/
211 bool bufferAccessibility
= (flags
& Accessibility
) && Okular::Settings::changeColors() && (Okular::Settings::renderMode() != Okular::Settings::EnumRenderMode::Paper
);
212 bool useBackBuffer
= bufferAccessibility
|| bufferedHighlights
|| bufferedAnnotations
;
213 QPixmap
* backPixmap
= 0;
214 QPainter
* mixedPainter
= 0;
215 QRect limitsInPixmap
= limits
.translated( crop
.geometry( scaledWidth
, scaledHeight
).topLeft() );
216 // limits within full (scaled but uncropped) pixmap
218 /** 4A -- REGULAR FLOW. PAINT PIXMAP NORMAL OR RESCALED USING GIVEN QPAINTER **/
219 if ( !useBackBuffer
)
221 // 4A.1. if size is ok, draw the page pixmap using painter
222 if ( pixmap
->width() == scaledWidth
&& pixmap
->height() == scaledHeight
)
223 destPainter
->drawPixmap( limits
.topLeft(), *pixmap
, limitsInPixmap
);
225 // else draw a scaled portion of the magnified pixmap
229 scalePixmapOnImage( destImage
, pixmap
, scaledWidth
, scaledHeight
, limitsInPixmap
);
230 destPainter
->drawImage( limits
.left(), limits
.top(), destImage
, 0, 0,
231 limits
.width(),limits
.height() );
234 // 4A.2. active painter is the one passed to this method
235 mixedPainter
= destPainter
;
237 /** 4B -- BUFFERED FLOW. IMAGE PAINTING + OPERATIONS. QPAINTER OVER PIXMAP **/
240 // the image over which we are going to draw
243 // 4B.1. draw the page pixmap: normal or scaled
244 if ( pixmap
->width() == scaledWidth
&& pixmap
->height() == scaledHeight
)
245 cropPixmapOnImage( backImage
, pixmap
, limitsInPixmap
);
247 scalePixmapOnImage( backImage
, pixmap
, scaledWidth
, scaledHeight
, limitsInPixmap
);
249 // 4B.2. modify pixmap following accessibility settings
250 if ( bufferAccessibility
)
252 switch ( Okular::Settings::renderMode() )
254 case Okular::Settings::EnumRenderMode::Inverted
:
255 // Invert image pixels using QImage internal function
256 backImage
.invertPixels(QImage::InvertRgb
);
258 case Okular::Settings::EnumRenderMode::Recolor
:
259 // Recolor image using Blitz::flatten with dither:0
260 Blitz::flatten( backImage
, Okular::Settings::recolorForeground(), Okular::Settings::recolorBackground() );
262 case Okular::Settings::EnumRenderMode::BlackWhite
:
263 // Manual Gray and Contrast
264 unsigned int * data
= (unsigned int *)backImage
.bits();
265 int val
, pixels
= backImage
.width() * backImage
.height(),
266 con
= Okular::Settings::bWContrast(), thr
= 255 - Okular::Settings::bWThreshold();
267 for( int i
= 0; i
< pixels
; ++i
)
269 val
= qGray( data
[i
] );
271 val
= 128 + (127 * (val
- thr
)) / (255 - thr
);
272 else if ( val
< thr
)
273 val
= (128 * val
) / thr
;
276 val
= con
* ( val
- thr
) / 2 + thr
;
282 data
[i
] = qRgba( val
, val
, val
, 255 );
287 // 4B.3. highlight rects in page
288 if ( bufferedHighlights
)
290 // draw highlights that are inside the 'limits' paint region
291 QList
< QPair
<QColor
, Okular::NormalizedRect
> >::const_iterator hIt
= bufferedHighlights
->begin(), hEnd
= bufferedHighlights
->end();
292 for ( ; hIt
!= hEnd
; ++hIt
)
294 const Okular::NormalizedRect
& r
= (*hIt
).second
;
295 // find out the rect to highlight on pixmap
296 QRect highlightRect
= r
.geometry( scaledWidth
, scaledHeight
).translated( -scaledCrop
.topLeft() ).intersect( limits
);
297 highlightRect
.translate( -limits
.left(), -limits
.top() );
299 // highlight composition (product: highlight color * destcolor)
300 unsigned int * data
= (unsigned int *)backImage
.bits();
301 int val
, newR
, newG
, newB
,
302 rh
= (*hIt
).first
.red(),
303 gh
= (*hIt
).first
.green(),
304 bh
= (*hIt
).first
.blue(),
305 offset
= highlightRect
.top() * backImage
.width();
306 for( int y
= highlightRect
.top(); y
<= highlightRect
.bottom(); ++y
)
308 for( int x
= highlightRect
.left(); x
<= highlightRect
.right(); ++x
)
310 val
= data
[ x
+ offset
];
311 newR
= (qRed(val
) * rh
) / 255;
312 newG
= (qGreen(val
) * gh
) / 255;
313 newB
= (qBlue(val
) * bh
) / 255;
314 data
[ x
+ offset
] = qRgba( newR
, newG
, newB
, 255 );
316 offset
+= backImage
.width();
320 // 4B.4. paint annotations [COMPOSITED ONES]
321 if ( bufferedAnnotations
)
323 // Albert: This is quite "heavy" but all the backImage that reach here are QImage::Format_ARGB32_Premultiplied
324 // and have to be so that the QPainter::CompositionMode_Multiply works
325 // we could also put a
326 // backImage = backImage.convertToFormat(QImage::Format_ARGB32_Premultiplied)
327 // that would be almost a noop, but we'll leave the assert for now
328 Q_ASSERT(backImage
.format() == QImage::Format_ARGB32_Premultiplied
);
329 // precalc constants for normalizing [0,1] page coordinates into normalized [0,1] limit rect coordinates
330 double pageScale
= (double)croppedWidth
/ page
->width();
331 double xOffset
= (double)limits
.left() / (double)scaledWidth
+ crop
.left
,
332 xScale
= (double)scaledWidth
/ (double)limits
.width(),
333 yOffset
= (double)limits
.top() / (double)scaledHeight
+ crop
.top
,
334 yScale
= (double)scaledHeight
/ (double)limits
.height();
336 // paint all buffered annotations in the page
337 QList
< Okular::Annotation
* >::const_iterator aIt
= bufferedAnnotations
->begin(), aEnd
= bufferedAnnotations
->end();
338 for ( ; aIt
!= aEnd
; ++aIt
)
340 Okular::Annotation
* a
= *aIt
;
341 Okular::Annotation::SubType type
= a
->subType();
342 QColor acolor
= a
->style().color();
343 if ( !acolor
.isValid() )
345 acolor
.setAlphaF( a
->style().opacity() );
347 // draw LineAnnotation MISSING: all
348 if ( type
== Okular::Annotation::ALine
)
350 // get the annotation
351 Okular::LineAnnotation
* la
= (Okular::LineAnnotation
*) a
;
354 // normalize page point to image
355 const QLinkedList
<Okular::NormalizedPoint
> points
= la
->transformedLinePoints();
356 QLinkedList
<Okular::NormalizedPoint
>::const_iterator it
= points
.begin();
357 QLinkedList
<Okular::NormalizedPoint
>::const_iterator itEnd
= points
.end();
358 for ( ; it
!= itEnd
; ++it
)
360 Okular::NormalizedPoint point
;
361 point
.x
= ( (*it
).x
- xOffset
) * xScale
;
362 point
.y
= ( (*it
).y
- yOffset
) * yScale
;
363 path
.append( point
);
366 const QPen linePen
= buildPen( a
, a
->style().width(), a
->style().color() );
368 // draw the line as normalized path into image
369 drawShapeOnImage( backImage
, path
, la
->lineClosed(),
371 QBrush(), pageScale
,Multiply
);
373 if ( path
.count() == 2 && fabs( la
->lineLeadingForwardPoint() ) > 0.1 )
375 Okular::NormalizedPoint
delta( la
->transformedLinePoints().last().x
- la
->transformedLinePoints().first().x
, la
->transformedLinePoints().first().y
- la
->transformedLinePoints().last().y
);
376 double angle
= atan2( delta
.y
, delta
.x
);
380 int sign
= la
->lineLeadingForwardPoint() > 0.0 ? 1 : -1;
381 double LLx
= fabs( la
->lineLeadingForwardPoint() ) * cos( angle
+ sign
* M_PI_2
+ 2 * M_PI
) / page
->width();
382 double LLy
= fabs( la
->lineLeadingForwardPoint() ) * sin( angle
+ sign
* M_PI_2
+ 2 * M_PI
) / page
->height();
384 NormalizedPath path2
;
385 NormalizedPath path3
;
387 Okular::NormalizedPoint point
;
388 point
.x
= ( la
->transformedLinePoints().first().x
+ LLx
- xOffset
) * xScale
;
389 point
.y
= ( la
->transformedLinePoints().first().y
- LLy
- yOffset
) * yScale
;
390 path2
.append( point
);
391 point
.x
= ( la
->transformedLinePoints().last().x
+ LLx
- xOffset
) * xScale
;
392 point
.y
= ( la
->transformedLinePoints().last().y
- LLy
- yOffset
) * yScale
;
393 path3
.append( point
);
394 // do we have the extension on the "back"?
395 if ( fabs( la
->lineLeadingBackwardPoint() ) > 0.1 )
397 double LLEx
= la
->lineLeadingBackwardPoint() * cos( angle
- sign
* M_PI_2
+ 2 * M_PI
) / page
->width();
398 double LLEy
= la
->lineLeadingBackwardPoint() * sin( angle
- sign
* M_PI_2
+ 2 * M_PI
) / page
->height();
399 point
.x
= ( la
->transformedLinePoints().first().x
+ LLEx
- xOffset
) * xScale
;
400 point
.y
= ( la
->transformedLinePoints().first().y
- LLEy
- yOffset
) * yScale
;
401 path2
.append( point
);
402 point
.x
= ( la
->transformedLinePoints().last().x
+ LLEx
- xOffset
) * xScale
;
403 point
.y
= ( la
->transformedLinePoints().last().y
- LLEy
- yOffset
) * yScale
;
404 path3
.append( point
);
408 path2
.append( path
[0] );
409 path3
.append( path
[1] );
412 drawShapeOnImage( backImage
, path2
, false, linePen
, QBrush(), pageScale
, Multiply
);
413 drawShapeOnImage( backImage
, path3
, false, linePen
, QBrush(), pageScale
, Multiply
);
416 // draw HighlightAnnotation MISSING: under/strike width, feather, capping
417 else if ( type
== Okular::Annotation::AHighlight
)
419 // get the annotation
420 Okular::HighlightAnnotation
* ha
= (Okular::HighlightAnnotation
*) a
;
421 Okular::HighlightAnnotation::HighlightType type
= ha
->highlightType();
423 // draw each quad of the annotation
424 int quads
= ha
->highlightQuads().size();
425 for ( int q
= 0; q
< quads
; q
++ )
428 const Okular::HighlightAnnotation::Quad
& quad
= ha
->highlightQuads()[ q
];
429 // normalize page point to image
430 for ( int i
= 0; i
< 4; i
++ )
432 Okular::NormalizedPoint point
;
433 point
.x
= (quad
.transformedPoint( i
).x
- xOffset
) * xScale
;
434 point
.y
= (quad
.transformedPoint( i
).y
- yOffset
) * yScale
;
435 path
.append( point
);
437 // draw the normalized path into image
440 // highlight the whole rect
441 case Okular::HighlightAnnotation::Highlight
:
442 drawShapeOnImage( backImage
, path
, true, Qt::NoPen
, acolor
, pageScale
, Multiply
);
444 // highlight the bottom part of the rect
445 case Okular::HighlightAnnotation::Squiggly
:
446 path
[ 3 ].x
= ( path
[ 0 ].x
+ path
[ 3 ].x
) / 2.0;
447 path
[ 3 ].y
= ( path
[ 0 ].y
+ path
[ 3 ].y
) / 2.0;
448 path
[ 2 ].x
= ( path
[ 1 ].x
+ path
[ 2 ].x
) / 2.0;
449 path
[ 2 ].y
= ( path
[ 1 ].y
+ path
[ 2 ].y
) / 2.0;
450 drawShapeOnImage( backImage
, path
, true, Qt::NoPen
, acolor
, pageScale
, Multiply
);
452 // make a line at 3/4 of the height
453 case Okular::HighlightAnnotation::Underline
:
454 path
[ 0 ].x
= ( 3 * path
[ 0 ].x
+ path
[ 3 ].x
) / 4.0;
455 path
[ 0 ].y
= ( 3 * path
[ 0 ].y
+ path
[ 3 ].y
) / 4.0;
456 path
[ 1 ].x
= ( 3 * path
[ 1 ].x
+ path
[ 2 ].x
) / 4.0;
457 path
[ 1 ].y
= ( 3 * path
[ 1 ].y
+ path
[ 2 ].y
) / 4.0;
460 drawShapeOnImage( backImage
, path
, false, QPen( acolor
, 2 ), QBrush(), pageScale
);
462 // make a line at 1/2 of the height
463 case Okular::HighlightAnnotation::StrikeOut
:
464 path
[ 0 ].x
= ( path
[ 0 ].x
+ path
[ 3 ].x
) / 2.0;
465 path
[ 0 ].y
= ( path
[ 0 ].y
+ path
[ 3 ].y
) / 2.0;
466 path
[ 1 ].x
= ( path
[ 1 ].x
+ path
[ 2 ].x
) / 2.0;
467 path
[ 1 ].y
= ( path
[ 1 ].y
+ path
[ 2 ].y
) / 2.0;
470 drawShapeOnImage( backImage
, path
, false, QPen( acolor
, 2 ), QBrush(), pageScale
);
475 // draw InkAnnotation MISSING:invar width, PENTRACER
476 else if ( type
== Okular::Annotation::AInk
)
478 // get the annotation
479 Okular::InkAnnotation
* ia
= (Okular::InkAnnotation
*) a
;
481 // draw each ink path
482 const QList
< QLinkedList
<Okular::NormalizedPoint
> > transformedInkPaths
= ia
->transformedInkPaths();
484 const QPen inkPen
= buildPen( a
, a
->style().width(), acolor
);
486 int paths
= transformedInkPaths
.size();
487 for ( int p
= 0; p
< paths
; p
++ )
490 const QLinkedList
<Okular::NormalizedPoint
> & inkPath
= transformedInkPaths
[ p
];
492 // normalize page point to image
493 QLinkedList
<Okular::NormalizedPoint
>::const_iterator pIt
= inkPath
.begin(), pEnd
= inkPath
.end();
494 for ( ; pIt
!= pEnd
; ++pIt
)
496 const Okular::NormalizedPoint
& inkPoint
= *pIt
;
497 Okular::NormalizedPoint point
;
498 point
.x
= (inkPoint
.x
- xOffset
) * xScale
;
499 point
.y
= (inkPoint
.y
- yOffset
) * yScale
;
500 path
.append( point
);
502 // draw the normalized path into image
503 drawShapeOnImage( backImage
, path
, false, inkPen
, QBrush(), pageScale
);
506 } // end current annotation drawing
509 // 4B.5. create the back pixmap converting from the local image
510 backPixmap
= new QPixmap( QPixmap::fromImage( backImage
) );
512 // 4B.6. create a painter over the pixmap and set it as the active one
513 mixedPainter
= new QPainter( backPixmap
);
514 mixedPainter
->translate( -limits
.left(), -limits
.top() );
517 /** 5 -- MIXED FLOW. Draw ANNOTATIONS [OPAQUE ONES] on ACTIVE PAINTER **/
518 if ( unbufferedAnnotations
)
520 // iterate over annotations and paint AText, AGeom, AStamp
521 QList
< Okular::Annotation
* >::const_iterator aIt
= unbufferedAnnotations
->begin(), aEnd
= unbufferedAnnotations
->end();
522 for ( ; aIt
!= aEnd
; ++aIt
)
524 Okular::Annotation
* a
= *aIt
;
526 // honor opacity settings on supported types
527 unsigned int opacity
= (unsigned int)( 255.0 * a
->style().opacity() );
528 // skip the annotation drawing if all the annotation is fully
529 // transparent, but not with text annotations
530 if ( opacity
<= 0 && a
->subType() != Okular::Annotation::AText
)
533 QColor acolor
= a
->style().color();
534 if ( !acolor
.isValid() )
536 acolor
.setAlpha( opacity
);
538 // get annotation boundary and drawn rect
539 QRect annotBoundary
= a
->transformedBoundingRectangle().geometry( scaledWidth
, scaledHeight
).translated( -scaledCrop
.topLeft() ).intersect( limits
);
540 QRect annotRect
= annotBoundary
.intersect( limits
);
541 QRect
innerRect( annotRect
.left() - annotBoundary
.left(), annotRect
.top() -
542 annotBoundary
.top(), annotRect
.width(), annotRect
.height() );
544 Okular::Annotation::SubType type
= a
->subType();
546 // draw TextAnnotation
547 if ( type
== Okular::Annotation::AText
)
549 Okular::TextAnnotation
* text
= (Okular::TextAnnotation
*)a
;
550 if ( text
->textType() == Okular::TextAnnotation::InPlace
)
552 QImage
image( annotBoundary
.size(), QImage::Format_ARGB32
);
553 image
.fill( acolor
.rgba() );
554 QPainter
painter( &image
);
555 painter
.setPen( Qt::black
);
556 painter
.setFont( text
->textFont() );
557 Qt::AlignmentFlag halign
= ( text
->inplaceAlignment() == 1 ? Qt::AlignHCenter
: ( text
->inplaceAlignment() == 2 ? Qt::AlignRight
: Qt::AlignLeft
) );
558 painter
.scale( 1.0 * scaledWidth
/ page
->width(), 1.0 * scaledHeight
/ page
->height() );
559 painter
.drawText( 2, 2, image
.width() - 2, image
.height() - 2,
560 Qt::AlignTop
| halign
| Qt::TextWordWrap
,
561 text
->inplaceText() );
562 painter
.resetTransform();
563 painter
.drawRect( 0, 0, image
.width() - 1, image
.height() - 1 );
566 mixedPainter
->drawImage( annotBoundary
.topLeft(), image
);
568 else if ( text
->textType() == Okular::TextAnnotation::Linked
)
570 // get pixmap, colorize and alpha-blend it
572 QPixmap pixmap
= GuiUtils::iconLoader()->loadIcon( text
->textIcon().toLower(), KIconLoader::User
, 32, KIconLoader::DefaultState
, QStringList(), &path
, true );
573 if ( path
.isEmpty() )
574 pixmap
= GuiUtils::iconLoader()->loadIcon( text
->textIcon().toLower(), KIconLoader::NoGroup
, 32 );
576 QRect annotBoundary2
= QRect( annotBoundary
.topLeft(), QSize( TEXTANNOTATION_ICONSIZE
, TEXTANNOTATION_ICONSIZE
) );
577 QRect annotRect2
= annotBoundary2
.intersect( limits
);
578 QRect
innerRect2( annotRect2
.left() - annotBoundary2
.left(), annotRect2
.top() -
579 annotBoundary2
.top(), annotRect2
.width(), annotRect2
.height() );
580 scalePixmapOnImage( scaledImage
, &pixmap
,
581 TEXTANNOTATION_ICONSIZE
, TEXTANNOTATION_ICONSIZE
,
582 innerRect2
, QImage::Format_ARGB32
);
583 // if the annotation color is valid (ie it was set), then
584 // use it to colorize the icon, otherwise the icon will be
586 if ( a
->style().color().isValid() )
587 colorizeImage( scaledImage
, a
->style().color(), opacity
);
588 pixmap
= QPixmap::fromImage( scaledImage
);
590 // draw the mangled image to painter
591 mixedPainter
->drawPixmap( annotRect
.topLeft(), pixmap
);
595 // draw StampAnnotation
596 else if ( type
== Okular::Annotation::AStamp
)
598 Okular::StampAnnotation
* stamp
= (Okular::StampAnnotation
*)a
;
600 // get pixmap and alpha blend it if needed
601 QPixmap pixmap
= GuiUtils::loadStamp( stamp
->stampIconName(), annotBoundary
.size() );
603 scalePixmapOnImage( scaledImage
, &pixmap
, annotBoundary
.width(),
604 annotBoundary
.height(), innerRect
, QImage::Format_ARGB32
);
606 changeImageAlpha( scaledImage
, opacity
);
607 pixmap
= QPixmap::fromImage( scaledImage
);
609 // draw the scaled and al
610 mixedPainter
->drawPixmap( annotRect
.topLeft(), pixmap
);
612 // draw GeomAnnotation
613 else if ( type
== Okular::Annotation::AGeom
)
615 Okular::GeomAnnotation
* geom
= (Okular::GeomAnnotation
*)a
;
616 // check whether there's anything to draw
617 if ( geom
->style().width() || geom
->geometricalInnerColor().isValid() )
619 mixedPainter
->save();
620 const double width
= geom
->style().width() * Okular::Utils::dpiX() / ( 72.0 * 2.0 ) * scaledWidth
/ page
->width();
621 QRectF
r( .0, .0, annotBoundary
.width(), annotBoundary
.height() );
622 r
.adjust( width
, width
, -width
, -width
);
623 r
.translate( annotBoundary
.topLeft() );
624 if ( geom
->geometricalInnerColor().isValid() )
626 r
.adjust( width
, width
, -width
, -width
);
627 const QColor color
= geom
->geometricalInnerColor();
628 mixedPainter
->setPen( Qt::NoPen
);
629 mixedPainter
->setBrush( QColor( color
.red(), color
.green(), color
.blue(), opacity
) );
630 if ( geom
->geometricalType() == Okular::GeomAnnotation::InscribedSquare
)
631 mixedPainter
->drawRect( r
);
633 mixedPainter
->drawEllipse( r
);
634 r
.adjust( -width
, -width
, width
, width
);
636 if ( geom
->style().width() ) // need to check the original size here..
638 mixedPainter
->setPen( buildPen( a
, width
* 2, acolor
) );
639 mixedPainter
->setBrush( Qt::NoBrush
);
640 if ( geom
->geometricalType() == Okular::GeomAnnotation::InscribedSquare
)
641 mixedPainter
->drawRect( r
);
643 mixedPainter
->drawEllipse( r
);
645 mixedPainter
->restore();
649 // draw extents rectangle
650 if ( Okular::Settings::debugDrawAnnotationRect() )
652 mixedPainter
->setPen( a
->style().color() );
653 mixedPainter
->drawRect( annotBoundary
);
658 /** 6 -- MIXED FLOW. Draw LINKS+IMAGES BORDER on ACTIVE PAINTER **/
659 if ( enhanceLinks
|| enhanceImages
)
661 mixedPainter
->save();
662 mixedPainter
->scale( scaledWidth
, scaledHeight
);
663 mixedPainter
->translate( -crop
.left
, -crop
.top
);
665 QColor normalColor
= QApplication::palette().color( QPalette::Active
, QPalette::Highlight
);
666 QColor lightColor
= normalColor
.light( 140 );
667 // enlarging limits for intersection is like growing the 'rectGeometry' below
668 QRect limitsEnlarged
= limits
;
669 limitsEnlarged
.adjust( -2, -2, 2, 2 );
670 // draw rects that are inside the 'limits' paint region as opaque rects
671 QLinkedList
< Okular::ObjectRect
* >::const_iterator lIt
= page
->m_rects
.begin(), lEnd
= page
->m_rects
.end();
672 for ( ; lIt
!= lEnd
; ++lIt
)
674 Okular::ObjectRect
* rect
= *lIt
;
675 if ( (enhanceLinks
&& rect
->objectType() == Okular::ObjectRect::Action
) ||
676 (enhanceImages
&& rect
->objectType() == Okular::ObjectRect::Image
) )
678 if ( limitsEnlarged
.intersects( rect
->boundingRect( scaledWidth
, scaledHeight
).translated( -scaledCrop
.topLeft() ) ) )
680 mixedPainter
->strokePath( rect
->region(), QPen( normalColor
) );
684 mixedPainter
->restore();
687 /** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/
691 destPainter
->drawPixmap( limits
.left(), limits
.top(), *backPixmap
);
695 // delete object containers
696 delete bufferedHighlights
;
697 delete bufferedAnnotations
;
698 delete unbufferedAnnotations
;
702 /** Private Helpers :: Pixmap conversion **/
703 void PagePainter::cropPixmapOnImage( QImage
& dest
, const QPixmap
* src
, const QRect
& r
)
705 // handle quickly the case in which the whole pixmap has to be converted
706 if ( r
== QRect( 0, 0, src
->width(), src
->height() ) )
708 dest
= src
->toImage();
709 dest
= dest
.convertToFormat(QImage::Format_ARGB32_Premultiplied
);
711 // else copy a portion of the src to an internal pixmap (smaller) and convert it
714 QImage
croppedImage( r
.width(), r
.height(), QImage::Format_ARGB32_Premultiplied
);
715 QPainter
p( &croppedImage
);
716 p
.drawPixmap( 0, 0, *src
, r
.left(), r
.top(), r
.width(), r
.height() );
722 void PagePainter::scalePixmapOnImage ( QImage
& dest
, const QPixmap
* src
,
723 int scaledWidth
, int scaledHeight
, const QRect
& cropRect
, QImage::Format format
)
725 // {source, destination, scaling} params
726 int srcWidth
= src
->width(),
727 srcHeight
= src
->height(),
728 destLeft
= cropRect
.left(),
729 destTop
= cropRect
.top(),
730 destWidth
= cropRect
.width(),
731 destHeight
= cropRect
.height();
733 // destination image (same geometry as the pageLimits rect)
734 dest
= QImage( destWidth
, destHeight
, format
);
735 unsigned int * destData
= (unsigned int *)dest
.bits();
737 // source image (1:1 conversion from pixmap)
738 QImage srcImage
= src
->toImage();
739 unsigned int * srcData
= (unsigned int *)srcImage
.bits();
741 // precalc the x correspondancy conversion in a lookup table
742 QVarLengthArray
<unsigned int> xOffset( destWidth
);
743 for ( int x
= 0; x
< destWidth
; x
++ )
744 xOffset
[ x
] = ((x
+ destLeft
) * srcWidth
) / scaledWidth
;
746 // for each pixel of the destination image apply the color of the
747 // corresponsing pixel on the source image (note: keep parenthesis)
748 for ( int y
= 0; y
< destHeight
; y
++ )
750 unsigned int srcOffset
= srcWidth
* (((destTop
+ y
) * srcHeight
) / scaledHeight
);
751 for ( int x
= 0; x
< destWidth
; x
++ )
752 (*destData
++) = srcData
[ srcOffset
+ xOffset
[x
] ];
756 /** Private Helpers :: Image Drawing **/
758 inline int qt_div_255(int x
) { return (x
+ (x
>>8) + 0x80) >> 8; }
760 void PagePainter::changeImageAlpha( QImage
& image
, unsigned int destAlpha
)
762 // iterate over all pixels changing the alpha component value
763 unsigned int * data
= (unsigned int *)image
.bits();
764 unsigned int pixels
= image
.width() * image
.height();
766 int source
, sourceAlpha
;
767 for( register unsigned int i
= 0; i
< pixels
; ++i
)
768 { // optimize this loop keeping byte order into account
770 if ( (sourceAlpha
= qAlpha( source
)) == 255 )
773 data
[i
] = qRgba( qRed(source
), qGreen(source
), qBlue(source
), destAlpha
);
777 // use destAlpha * sourceAlpha product
778 sourceAlpha
= qt_div_255( destAlpha
* sourceAlpha
);
779 data
[i
] = qRgba( qRed(source
), qGreen(source
), qBlue(source
), sourceAlpha
);
784 void PagePainter::colorizeImage( QImage
& grayImage
, const QColor
& color
,
785 unsigned int destAlpha
)
787 // iterate over all pixels changing the alpha component value
788 unsigned int * data
= (unsigned int *)grayImage
.bits();
789 unsigned int pixels
= grayImage
.width() * grayImage
.height();
790 int red
= color
.red(),
791 green
= color
.green(),
794 int source
, sourceSat
, sourceAlpha
;
795 for( register unsigned int i
= 0; i
< pixels
; ++i
)
796 { // optimize this loop keeping byte order into account
798 sourceSat
= qRed( source
);
799 int newR
= qt_div_255( sourceSat
* red
),
800 newG
= qt_div_255( sourceSat
* green
),
801 newB
= qt_div_255( sourceSat
* blue
);
802 if ( (sourceAlpha
= qAlpha( source
)) == 255 )
805 data
[i
] = qRgba( newR
, newG
, newB
, destAlpha
);
809 // use destAlpha * sourceAlpha product
810 if ( destAlpha
< 255 )
811 sourceAlpha
= qt_div_255( destAlpha
* sourceAlpha
);
812 data
[i
] = qRgba( newR
, newG
, newB
, sourceAlpha
);
817 void PagePainter::drawShapeOnImage(
819 const NormalizedPath
& normPath
,
822 const QBrush
& brush
,
823 double penWidthMultiplier
,
825 //float antiAliasRadius
829 int pointsNumber
= normPath
.size();
830 if ( pointsNumber
< 2 )
833 int imageWidth
= image
.width();
834 int imageHeight
= image
.height();
835 double fImageWidth
= (double)imageWidth
;
836 double fImageHeight
= (double)imageHeight
;
839 double penWidth
= (double)pen
.width() * penWidthMultiplier
;
840 QPainter
painter(&image
);
841 painter
.setRenderHint(QPainter::Antialiasing
);
843 pen2
.setWidthF(penWidth
);
844 painter
.setPen(pen2
);
845 painter
.setBrush(brush
);
847 if (op
== Multiply
) {
848 painter
.setCompositionMode(QPainter::CompositionMode_Multiply
);
851 if ( brush
.style() == Qt::NoBrush
)
854 QPolygonF
poly( closeShape
? pointsNumber
+ 1 : pointsNumber
);
855 for ( int i
= 0; i
< pointsNumber
; ++i
)
857 poly
[ i
] = QPointF( normPath
[ i
].x
* fImageWidth
, normPath
[ i
].y
* fImageHeight
);
860 poly
[ pointsNumber
] = poly
[ 0 ];
862 painter
.drawPolyline( poly
);
868 path
.moveTo( normPath
[ 0 ].x
* fImageWidth
, normPath
[ 0 ].y
* fImageHeight
);
869 for ( int i
= 1; i
< pointsNumber
; i
++ )
871 path
.lineTo( normPath
[ i
].x
* fImageWidth
, normPath
[ i
].y
* fImageHeight
);
876 painter
.drawPath( path
);