compile
[kdegraphics.git] / okular / ui / pagepainter.cpp
bloba1beafa1d65ebc48b4cb7e48817b9991d821f199
1 /***************************************************************************
2 * Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> *
3 * *
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"
12 // qt / kde includes
13 #include <qrect.h>
14 #include <qpainter.h>
15 #include <qpalette.h>
16 #include <qpixmap.h>
17 #include <qvarlengtharray.h>
18 #include <kiconloader.h>
19 #include <kdebug.h>
20 #include <QApplication>
21 #include <qimageblitz.h>
23 // system includes
24 #include <math.h>
26 // local includes
27 #include "core/area.h"
28 #include "core/page.h"
29 #include "core/annotations.h"
30 #include "core/utils.h"
31 #include "guiutils.h"
32 #include "settings.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 )
40 QPen p(
41 QBrush( color ),
42 width,
43 ann->style().lineStyle() == Okular::Annotation::Dashed ? Qt::DashLine : Qt::SolidLine,
44 Qt::SquareCap,
45 Qt::MiterJoin
47 return p;
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:
75 color = Qt::black;
76 break;
77 case Okular::Settings::EnumRenderMode::Paper:
78 color = Okular::Settings::paperColor();
79 break;
80 case Okular::Settings::EnumRenderMode::Recolor:
81 color = Okular::Settings::recolorBackground();
82 break;
83 default: ;
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 );
99 else
101 destPainter->setPen( Qt::gray );
102 destPainter->drawLine( 0, 0, croppedWidth-1, croppedHeight-1 );
103 destPainter->drawLine( 0, croppedHeight-1, croppedWidth-1, 0 );
105 return;
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> >();
133 /* else
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) );
145 delete limitRect;
148 if ( canDrawTextSelection )
150 if ( !bufferedHighlights )
151 bufferedHighlights = new QList< QPair<QColor, Okular::NormalizedRect> >();
152 /* else
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 ) );
162 delete limitRect;
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*/ ) )
173 continue;
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 );
188 if ( intersects )
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 );
198 else
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
226 else
228 QImage destImage;
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 **/
238 else
240 // the image over which we are going to draw
241 QImage backImage;
243 // 4B.1. draw the page pixmap: normal or scaled
244 if ( pixmap->width() == scaledWidth && pixmap->height() == scaledHeight )
245 cropPixmapOnImage( backImage, pixmap, limitsInPixmap );
246 else
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);
257 break;
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() );
261 break;
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] );
270 if ( val > thr )
271 val = 128 + (127 * (val - thr)) / (255 - thr);
272 else if ( val < thr )
273 val = (128 * val) / thr;
274 if ( con > 2 )
276 val = con * ( val - thr ) / 2 + thr;
277 if ( val > 255 )
278 val = 255;
279 else if ( val < 0 )
280 val = 0;
282 data[i] = qRgba( val, val, val, 255 );
284 break;
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() )
344 acolor = Qt::yellow;
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;
353 NormalizedPath path;
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(),
370 linePen,
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 );
377 if ( delta.y < 0 )
378 angle += 2 * M_PI;
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 );
406 else
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++ )
427 NormalizedPath path;
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
438 switch ( type )
440 // highlight the whole rect
441 case Okular::HighlightAnnotation::Highlight:
442 drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply );
443 break;
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 );
451 break;
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;
458 path.pop_back();
459 path.pop_back();
460 drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale );
461 break;
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;
468 path.pop_back();
469 path.pop_back();
470 drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale );
471 break;
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++ )
489 NormalizedPath path;
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 )
531 continue;
533 QColor acolor = a->style().color();
534 if ( !acolor.isValid() )
535 acolor = Qt::yellow;
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 );
564 painter.end();
566 mixedPainter->drawImage( annotBoundary.topLeft(), image );
568 else if ( text->textType() == Okular::TextAnnotation::Linked )
570 // get pixmap, colorize and alpha-blend it
571 QString path;
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 );
575 QImage scaledImage;
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
585 // "gray"
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() );
602 QImage scaledImage;
603 scalePixmapOnImage( scaledImage, &pixmap, annotBoundary.width(),
604 annotBoundary.height(), innerRect, QImage::Format_ARGB32 );
605 if ( opacity < 255 )
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 );
632 else
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 );
642 else
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 **/
688 if ( useBackBuffer )
690 delete mixedPainter;
691 destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap );
692 delete 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
712 else
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() );
717 p.end();
718 dest = croppedImage;
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 **/
757 // from Arthur - qt4
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
769 source = data[i];
770 if ( (sourceAlpha = qAlpha( source )) == 255 )
772 // use destAlpha
773 data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), destAlpha );
775 else
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(),
792 blue = color.blue();
794 int source, sourceSat, sourceAlpha;
795 for( register unsigned int i = 0; i < pixels; ++i )
796 { // optimize this loop keeping byte order into account
797 source = data[i];
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 )
804 // use destAlpha
805 data[i] = qRgba( newR, newG, newB, destAlpha );
807 else
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(
818 QImage & image,
819 const NormalizedPath & normPath,
820 bool closeShape,
821 const QPen & pen,
822 const QBrush & brush,
823 double penWidthMultiplier,
824 RasterOperation op
825 //float antiAliasRadius
828 // safety checks
829 int pointsNumber = normPath.size();
830 if ( pointsNumber < 2 )
831 return;
833 int imageWidth = image.width();
834 int imageHeight = image.height();
835 double fImageWidth = (double)imageWidth;
836 double fImageHeight = (double)imageHeight;
838 // stroke outline
839 double penWidth = (double)pen.width() * penWidthMultiplier;
840 QPainter painter(&image);
841 painter.setRenderHint(QPainter::Antialiasing);
842 QPen pen2 = pen;
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 )
853 // create a polygon
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 );
859 if ( closeShape )
860 poly[ pointsNumber ] = poly[ 0 ];
862 painter.drawPolyline( poly );
864 else
866 // create a 'path'
867 QPainterPath path;
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 );
873 if ( closeShape )
874 path.closeSubpath();
876 painter.drawPath( path );