mirror of
https://invent.kde.org/graphics/okular
synced 2024-09-19 16:01:41 +00:00
e5eb63ec71
typical usage). PagePainter: Added 2 convenience methods for performing fast cropping and scaling from qpixmaps to qimages. Split buffered graphic flow from the unbuffered one reducing buffers conversions to the minimum. Avoid odd cases in rescaling. Detection of annotations boundaries to check for enabling buffered painting. Sample quick dirty and broken (but not so much..) implementation for painting AStamp annotations. Misc: PresentationWidged and ThumbnailWidget: adapted to PagePainter changes. (PageView mod has already been committed). svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=400310
369 lines
16 KiB
C++
369 lines
16 KiB
C++
/***************************************************************************
|
|
* 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. *
|
|
***************************************************************************/
|
|
|
|
// qt / kde includes
|
|
#include <qrect.h>
|
|
#include <qpainter.h>
|
|
#include <qpixmap.h>
|
|
#include <qimage.h>
|
|
#include <qapplication.h>
|
|
#include <kimageeffect.h>
|
|
#include <kiconloader.h>
|
|
|
|
// local includes
|
|
#include "pagepainter.h"
|
|
#include "core/page.h"
|
|
#include "core/annotations.h"
|
|
#include "conf/settings.h"
|
|
|
|
void PagePainter::paintPageOnPainter( const KPDFPage * page, int id, int flags,
|
|
QPainter * destPainter, int pageWidth, int pageHeight, const QRect & limits )
|
|
{
|
|
/** 1 - RETRIEVE THE 'PAGE+ID' PIXMAP OR A SIMILAR 'PAGE' ONE **/
|
|
const QPixmap * pixmap = 0;
|
|
|
|
// if a pixmap is present for given id, use it
|
|
if ( page->m_pixmaps.contains( id ) )
|
|
pixmap = page->m_pixmaps[ id ];
|
|
|
|
// else find the closest match using pixmaps of other IDs (great optim!)
|
|
else if ( !page->m_pixmaps.isEmpty() )
|
|
{
|
|
int minDistance = -1;
|
|
QMap< int,QPixmap * >::const_iterator it = page->m_pixmaps.begin(), end = page->m_pixmaps.end();
|
|
for ( ; it != end; ++it )
|
|
{
|
|
int pixWidth = (*it)->width(),
|
|
distance = pixWidth > pageWidth ? pixWidth - pageWidth : pageWidth - pixWidth;
|
|
if ( minDistance == -1 || distance < minDistance )
|
|
{
|
|
pixmap = *it;
|
|
minDistance = distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/
|
|
double pixmapRescaleRatio = pixmap ? pageWidth / (double)pixmap->width() : -1;
|
|
if ( !pixmap || pixmapRescaleRatio > 20.0 || pixmapRescaleRatio < 0.25 )
|
|
{
|
|
if ( Settings::changeColors() &&
|
|
Settings::renderMode() == Settings::EnumRenderMode::Paper )
|
|
destPainter->fillRect( limits, Settings::paperColor() );
|
|
else
|
|
destPainter->fillRect( limits, Qt::white );
|
|
|
|
// draw a cross (to that the pixmap as not yet been loaded)
|
|
// helps a lot on pages that take much to render
|
|
destPainter->setPen( Qt::gray );
|
|
destPainter->drawLine( 0, 0, pageWidth-1, pageHeight-1 );
|
|
destPainter->drawLine( 0, pageHeight-1, pageWidth-1, 0 );
|
|
// idea here: draw a hourglass (or kpdf icon :-) on top-left corner
|
|
return;
|
|
}
|
|
|
|
/** 2 - FIND OUT WHAT TO PAINT (Flags + Configuration + Presence) **/
|
|
bool paintAccessibility = (flags & Accessibility) && Settings::changeColors() && (Settings::renderMode() != Settings::EnumRenderMode::Paper);
|
|
bool paintHighlights = (flags & Highlights) && !page->m_highlights.isEmpty();
|
|
bool paintAnnotations = (flags & Annotations) && !page->m_annotations.isEmpty();
|
|
bool enhanceLinks = (flags & EnhanceLinks) && Settings::highlightLinks();
|
|
bool enhanceImages = (flags & EnhanceImages) && Settings::highlightImages();
|
|
// check if there are really some highlightRects to paint
|
|
if ( paintHighlights || paintAnnotations )
|
|
{
|
|
// precalc normalized 'limits rect' for intersection
|
|
double nXMin = (double)limits.left() / (double)pageWidth,
|
|
nXMax = (double)limits.right() / (double)pageWidth,
|
|
nYMin = (double)limits.top() / (double)pageHeight,
|
|
nYMax = (double)limits.bottom() / (double)pageHeight;
|
|
// if no rect intersects limits, disable paintHighlights
|
|
if ( paintHighlights )
|
|
{
|
|
paintHighlights = false;
|
|
QValueList< HighlightRect * >::const_iterator hIt = page->m_highlights.begin(), hEnd = page->m_highlights.end();
|
|
for ( ; hIt != hEnd; ++hIt )
|
|
{
|
|
if ( (*hIt)->intersects( nXMin, nYMin, nXMax, nYMax ) )
|
|
{
|
|
paintHighlights = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// if no annotation intersects limits, disable paintAnnotations
|
|
if ( paintAnnotations )
|
|
{
|
|
paintAnnotations = false;
|
|
QValueList< Annotation * >::const_iterator aIt = page->m_annotations.begin(), aEnd = page->m_annotations.end();
|
|
for ( ; aIt != aEnd; ++aIt )
|
|
{
|
|
if ( (*aIt)->r.intersects( nXMin, nYMin, nXMax, nYMax ) )
|
|
{
|
|
paintAnnotations = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/
|
|
bool useBackBuffer = paintAccessibility || paintHighlights || paintAnnotations;
|
|
QPixmap * backPixmap = 0;
|
|
QPainter * p = 0;
|
|
|
|
/** 4A -- REGULAR FLOW. PAINT PIXMAP NORMAL OR RESCALED USING GIVEN QPAINTER **/
|
|
if ( !useBackBuffer )
|
|
{
|
|
// 4A.1. if size is ok, draw the page pixmap using painter
|
|
if ( pixmap->width() == pageWidth && pixmap->height() == pageHeight )
|
|
destPainter->drawPixmap( limits.topLeft(), *pixmap, limits );
|
|
// else draw a scaled portion of the magnified pixmap
|
|
else
|
|
{
|
|
QImage destImage;
|
|
scalePixmapOnImage( pixmap, destImage, pageWidth, pageHeight, limits );
|
|
destPainter->drawPixmap( limits.left(), limits.top(), destImage, 0, 0,
|
|
limits.width(),limits.height() );
|
|
}
|
|
|
|
// 4A.2. active painter is the one passed to this method
|
|
p = destPainter;
|
|
}
|
|
/** 4B -- BUFFERED FLOW. IMAGE PAINTING + OPERATIONS. QPAINTER OVER PIXMAP **/
|
|
else
|
|
{
|
|
// the image over which we are going to draw
|
|
QImage backImage;
|
|
|
|
// 4B.1. draw the page pixmap: normal or scaled
|
|
if ( pixmap->width() == pageWidth && pixmap->height() == pageHeight )
|
|
cropPixmapOnImage( pixmap, backImage, limits );
|
|
else
|
|
scalePixmapOnImage( pixmap, backImage, pageWidth, pageHeight, limits );
|
|
|
|
// 4B.2. modify pixmap following accessibility settings
|
|
if ( paintAccessibility )
|
|
{
|
|
switch ( Settings::renderMode() )
|
|
{
|
|
case Settings::EnumRenderMode::Inverted:
|
|
// Invert image pixels using QImage internal function
|
|
backImage.invertPixels(false);
|
|
break;
|
|
case Settings::EnumRenderMode::Recolor:
|
|
// Recolor image using KImageEffect::flatten with dither:0
|
|
KImageEffect::flatten( backImage, Settings::recolorForeground(), Settings::recolorBackground() );
|
|
break;
|
|
case Settings::EnumRenderMode::BlackWhite:
|
|
// Manual Gray and Contrast
|
|
unsigned int * data = (unsigned int *)backImage.bits();
|
|
int val, pixels = backImage.width() * backImage.height(),
|
|
con = Settings::bWContrast(), thr = 255 - Settings::bWThreshold();
|
|
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;
|
|
}
|
|
}
|
|
// 4B.3. highlight rects in page
|
|
if ( paintHighlights )
|
|
{
|
|
// draw highlights that are inside the 'limits' paint region
|
|
QValueList< HighlightRect * >::const_iterator hIt = page->m_highlights.begin(), hEnd = page->m_highlights.end();
|
|
for ( ; hIt != hEnd; ++hIt )
|
|
{
|
|
HighlightRect * r = *hIt;
|
|
QRect highlightRect = r->geometry( pageWidth, pageHeight );
|
|
if ( highlightRect.isValid() && highlightRect.intersects( limits ) )
|
|
{
|
|
// find out the rect to highlight on pixmap
|
|
highlightRect = highlightRect.intersect( limits );
|
|
highlightRect.moveBy( -limits.left(), -limits.top() );
|
|
|
|
// highlight composition (product: highlight color * destcolor)
|
|
unsigned int * data = (unsigned int *)backImage.bits();
|
|
int val, newR, newG, newB,
|
|
rh = r->color.red(),
|
|
gh = r->color.green(),
|
|
bh = r->color.blue(),
|
|
offset = highlightRect.top() * backImage.width();
|
|
for( int y = highlightRect.top(); y <= highlightRect.bottom(); ++y )
|
|
{
|
|
for( int x = highlightRect.left(); x <= highlightRect.right(); ++x )
|
|
{
|
|
val = data[ x + offset ];
|
|
newR = (qRed(val) * rh) / 255;
|
|
newG = (qGreen(val) * gh) / 255;
|
|
newB = (qBlue(val) * bh) / 255;
|
|
data[ x + offset ] = qRgba( newR, newG, newB, 255 );
|
|
}
|
|
offset += backImage.width();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 4B.4. paint annotations [COMPOSITED ONES]
|
|
if ( paintAnnotations )
|
|
{
|
|
// TODO draw AText(1), AHighlight
|
|
}
|
|
|
|
// 4B.5. create the back pixmap converting from the local image
|
|
backPixmap = new QPixmap( backImage );
|
|
|
|
// 4B.6. create a painter over the pixmap and set it as the active one
|
|
p = new QPainter( backPixmap );
|
|
p->translate( -limits.left(), -limits.top() );
|
|
}
|
|
|
|
/** 5 -- MIXED FLOW. Draw ANNOTATIONS [OPAQUE ONES] on ACTIVE PAINTER **/
|
|
if ( paintAnnotations )
|
|
{
|
|
// iterate over annotations and paint AText(2), ALine, AGeom, AStamp, AInk
|
|
QValueList< Annotation * >::const_iterator aIt = page->m_annotations.begin(), aEnd = page->m_annotations.end();
|
|
for ( ; aIt != aEnd; ++aIt )
|
|
{
|
|
Annotation * a = *aIt;
|
|
QRect annotRect = a->r.geometry( pageWidth, pageHeight );
|
|
|
|
// if annotation doesn't intersect paint region, skip it
|
|
if ( !annotRect.isValid() || !annotRect.intersects( limits ) )
|
|
continue;
|
|
|
|
// draw extents rectangle
|
|
if ( Settings::debugDrawAnnotationRect() )
|
|
{
|
|
p->setPen( a->color );
|
|
p->drawRect( annotRect );
|
|
}
|
|
|
|
//
|
|
//annotRect = annotRect.intersect( limits );
|
|
Annotation::SubType type = a->subType();
|
|
|
|
// stamp annotation TODO
|
|
if ( type == Annotation::AStamp )
|
|
{
|
|
QPixmap pic = DesktopIcon( "kpdf" );
|
|
//QImage destImage;
|
|
//scalePixmapOnImage( &pic, destImage, annotRect.width(), annotRect.height(), QRect(0,0,annotRect.width(), annotRect.height()) );
|
|
//p->drawPixmap( annotRect.left(), annotRect.top(), destImage, 0, 0, annotRect.width(), annotRect.height() );
|
|
pic = pic.convertToImage().scale( annotRect.width(), annotRect.height() );
|
|
p->drawPixmap( annotRect.left(), annotRect.top(), pic, 0, 0, annotRect.width(), annotRect.height() );
|
|
}
|
|
//else if ( type == Annotation::AText ) TODO
|
|
}
|
|
}
|
|
|
|
/** 6 -- MIXED FLOW. Draw LINKS+IMAGES BORDER on ACTIVE PAINTER **/
|
|
if ( enhanceLinks || enhanceImages )
|
|
{
|
|
QColor normalColor = QApplication::palette().active().highlight();
|
|
QColor lightColor = normalColor.light( 140 );
|
|
// enlarging limits for intersection is like growing the 'rectGeometry' below
|
|
QRect limitsEnlarged = limits;
|
|
limitsEnlarged.addCoords( -2, -2, 2, 2 );
|
|
// draw rects that are inside the 'limits' paint region as opaque rects
|
|
QValueList< ObjectRect * >::const_iterator lIt = page->m_rects.begin(), lEnd = page->m_rects.end();
|
|
for ( ; lIt != lEnd; ++lIt )
|
|
{
|
|
ObjectRect * rect = *lIt;
|
|
if ( (enhanceLinks && rect->objectType() == ObjectRect::Link) ||
|
|
(enhanceImages && rect->objectType() == ObjectRect::Image) )
|
|
{
|
|
QRect rectGeometry = rect->geometry( pageWidth, pageHeight );
|
|
if ( rectGeometry.intersects( limitsEnlarged ) )
|
|
{
|
|
// expand rect and draw inner border
|
|
rectGeometry.addCoords( -1,-1,1,1 );
|
|
p->setPen( lightColor );
|
|
p->drawRect( rectGeometry );
|
|
// expand rect to draw outer border
|
|
rectGeometry.addCoords( -1,-1,1,1 );
|
|
p->setPen( normalColor );
|
|
p->drawRect( rectGeometry );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/
|
|
if ( useBackBuffer )
|
|
{
|
|
delete p;
|
|
destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap );
|
|
delete backPixmap;
|
|
}
|
|
}
|
|
|
|
|
|
void PagePainter::cropPixmapOnImage( const QPixmap * src, QImage & dest, const QRect & r )
|
|
{
|
|
// handle quickly the case in which the whole pixmap has to be converted
|
|
if ( r == QRect( 0, 0, src->width(), src->height() ) )
|
|
{
|
|
dest = src->convertToImage();
|
|
}
|
|
// else copy a portion of the src to an internal pixmap (smaller) and convert it
|
|
else
|
|
{
|
|
QPixmap croppedPixmap( r.width(), r.height() );
|
|
copyBlt( &croppedPixmap, 0, 0, src, r.left(), r.top(), r.width(), r.height() );
|
|
dest = croppedPixmap.convertToImage();
|
|
}
|
|
}
|
|
|
|
void PagePainter::scalePixmapOnImage ( const QPixmap * src, QImage & dest,
|
|
int pageWidth, int pageHeight, const QRect & cropRect )
|
|
{
|
|
// {source, destination, scaling} params
|
|
int srcWidth = src->width(),
|
|
srcHeight = src->height(),
|
|
destLeft = cropRect.left(),
|
|
destTop = cropRect.top(),
|
|
destWidth = cropRect.width(),
|
|
destHeight = cropRect.height();
|
|
|
|
// destination image (same geometry as the pageLimits rect)
|
|
dest = QImage( destWidth, destHeight, 32 );
|
|
unsigned int * destData = (unsigned int *)dest.bits();
|
|
|
|
// source image (1:1 conversion from pixmap)
|
|
QImage srcImage = src->convertToImage();
|
|
unsigned int * srcData = (unsigned int *)srcImage.bits();
|
|
|
|
// precalc the x correspondancy conversion in a lookup table
|
|
unsigned int xOffset[ destWidth ];
|
|
for ( int x = 0; x < destWidth; x++ )
|
|
xOffset[ x ] = ((x + destLeft) * srcWidth) / pageWidth;
|
|
|
|
// for each pixel of the destination image apply the color of the
|
|
// corresponsing pixel on the source image (note: keep parenthesis)
|
|
for ( int y = 0; y < destHeight; y++ )
|
|
{
|
|
unsigned int srcOffset = srcWidth * (((destTop + y) * srcHeight) / pageHeight);
|
|
for ( int x = 0; x < destWidth; x++ )
|
|
(*destData++) = srcData[ srcOffset + xOffset[x] ];
|
|
}
|
|
}
|