okular/ui/pagepainter.cpp
Enrico Ros b2b74d91c8 Adapted to annotations.h changes.
annotations.cpp is all commented out, need to work on that now.

svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=403222
2005-04-04 23:55:40 +00:00

374 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( QPainter * destPainter, const KPDFPage * page,
int pixID, int flags, int scaledWidth, int scaledHeight, 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( pixID ) )
pixmap = page->m_pixmaps[ pixID ];
// 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 > scaledWidth ? pixWidth - scaledWidth : scaledWidth - pixWidth;
if ( minDistance == -1 || distance < minDistance )
{
pixmap = *it;
minDistance = distance;
}
}
}
/** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/
double pixmapRescaleRatio = pixmap ? scaledWidth / (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, scaledWidth-1, scaledHeight-1 );
destPainter->drawLine( 0, scaledHeight-1, scaledWidth-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)scaledWidth,
nXMax = (double)limits.right() / (double)scaledWidth,
nYMin = (double)limits.top() / (double)scaledHeight,
nYMax = (double)limits.bottom() / (double)scaledHeight;
// 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)->boundary.intersects( nXMin, nYMin, nXMax, nYMax ) )
{
paintAnnotations = true;
break;
}
}
}
}
/** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/
// FIXME: NOT ALL ANNOTATIONS REQUIRES BACKBUFFER
bool useBackBuffer = paintAccessibility || paintHighlights || paintAnnotations;
QPixmap * backPixmap = 0;
QPainter * mixedPainter = 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() == scaledWidth && pixmap->height() == scaledHeight )
destPainter->drawPixmap( limits.topLeft(), *pixmap, limits );
// else draw a scaled portion of the magnified pixmap
else
{
QImage destImage;
scalePixmapOnImage( destImage, pixmap, scaledWidth, scaledHeight, 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
mixedPainter = 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() == scaledWidth && pixmap->height() == scaledHeight )
cropPixmapOnImage( backImage, pixmap, limits );
else
scalePixmapOnImage( backImage, pixmap, scaledWidth, scaledHeight, 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( scaledWidth, scaledHeight );
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
mixedPainter = new QPainter( backPixmap );
mixedPainter->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->boundary.geometry( scaledWidth, scaledHeight );
// if annotation doesn't intersect paint region, skip it
if ( !annotRect.isValid() || !annotRect.intersects( limits ) )
continue;
// draw extents rectangle
if ( Settings::debugDrawAnnotationRect() )
{
mixedPainter->setPen( a->style.color );
mixedPainter->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( destImage, &pic, annotRect.width(), annotRect.height(), QRect(0,0,annotRect.width(), annotRect.height()) );
//mixedPainter->drawPixmap( annotRect.left(), annotRect.top(), destImage, 0, 0, annotRect.width(), annotRect.height() );
pic = pic.convertToImage().scale( annotRect.width(), annotRect.height() );
mixedPainter->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( scaledWidth, scaledHeight );
if ( rectGeometry.intersects( limitsEnlarged ) )
{
// expand rect and draw inner border
rectGeometry.addCoords( -1,-1,1,1 );
mixedPainter->setPen( lightColor );
mixedPainter->drawRect( rectGeometry );
// expand rect to draw outer border
rectGeometry.addCoords( -1,-1,1,1 );
mixedPainter->setPen( normalColor );
mixedPainter->drawRect( rectGeometry );
}
}
}
}
/** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/
if ( useBackBuffer )
{
delete mixedPainter;
destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap );
delete backPixmap;
}
}
/** Private Helpers :: Pixmap conversion **/
void PagePainter::cropPixmapOnImage( QImage & dest, const QPixmap * src, 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 ( QImage & dest, const QPixmap * src,
int scaledWidth, int scaledHeight, 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) / scaledWidth;
// 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) / scaledHeight);
for ( int x = 0; x < destWidth; x++ )
(*destData++) = srcData[ srcOffset + xOffset[x] ];
}
}
/** Private Helpers :: Image Drawing **/
//void image_draw_line( const QImage & img, bool antiAlias = true ) {}