mirror of
https://invent.kde.org/graphics/okular
synced 2024-09-19 16:01:41 +00:00
e8f27fc700
PagePainter: Updated drawShape with RasterOperation (Normal, Multiply) for getting Highlight annotation types to work. All Highlight annotations are now drew using the Multiply raster operation. Painter_AGG2: Replaced the agg_pixfmt_rgba.h pixformat definition template with an internal version that implements different raster operations too. The new version has been shrinked to the minimum, containing the only code needed by kpdf AGG2 rendering engine to work. Thanks for Rob Buis for telling me the way to go and providing sample code with his AGG2 algorithms. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=405338
716 lines
31 KiB
C++
716 lines
31 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>
|
|
|
|
// system includes
|
|
#include <math.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;
|
|
long pixmapPixels = pixmap ? (long)pixmap->width() * (long)pixmap->height() : 0;
|
|
if ( !pixmap || pixmapRescaleRatio > 20.0 || pixmapRescaleRatio < 0.25 ||
|
|
(scaledWidth != pixmap->width() && pixmapPixels > 6000000L) )
|
|
{
|
|
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 canDrawHighlights = (flags & Highlights) && !page->m_highlights.isEmpty();
|
|
bool canDrawAnnotations = (flags & Annotations) && !page->m_annotations.isEmpty();
|
|
bool enhanceLinks = (flags & EnhanceLinks) && Settings::highlightLinks();
|
|
bool enhanceImages = (flags & EnhanceImages) && Settings::highlightImages();
|
|
// vectors containing objects to draw
|
|
QValueList< HighlightRect * > * bufferedHighlights = 0;
|
|
QValueList< Annotation * > * bufferedAnnotations = 0;
|
|
QValueList< Annotation * > * unbufferedAnnotations = 0;
|
|
// fill up lists with visible annotation/highlight objects
|
|
if ( canDrawHighlights || canDrawAnnotations )
|
|
{
|
|
// 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;
|
|
// append all highlights inside limits to their list
|
|
if ( canDrawHighlights )
|
|
{
|
|
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 ) )
|
|
{
|
|
if ( !bufferedHighlights )
|
|
bufferedHighlights = new QValueList< HighlightRect * >();
|
|
bufferedHighlights->append( *hIt );
|
|
}
|
|
}
|
|
// append annotations inside limits to the un/buffered list
|
|
if ( canDrawAnnotations )
|
|
{
|
|
QValueList< Annotation * >::const_iterator aIt = page->m_annotations.begin(), aEnd = page->m_annotations.end();
|
|
for ( ; aIt != aEnd; ++aIt )
|
|
{
|
|
Annotation * ann = *aIt;
|
|
if ( ann->boundary.intersects( nXMin, nYMin, nXMax, nYMax ) )
|
|
{
|
|
Annotation::SubType type = ann->subType();
|
|
if ( type == Annotation::ALine || type == Annotation::AHighlight ||
|
|
type == Annotation::AInk /*|| (type == Annotation::AGeom && ann->style.opacity < 0.99)*/ )
|
|
{
|
|
if ( !bufferedAnnotations )
|
|
bufferedAnnotations = new QValueList< Annotation * >();
|
|
bufferedAnnotations->append( ann );
|
|
}
|
|
else
|
|
{
|
|
if ( !unbufferedAnnotations )
|
|
unbufferedAnnotations = new QValueList< Annotation * >();
|
|
unbufferedAnnotations->append( ann );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// end of intersections checking
|
|
}
|
|
|
|
/** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/
|
|
bool bufferAccessibility = (flags & Accessibility) && Settings::changeColors() && (Settings::renderMode() != Settings::EnumRenderMode::Paper);
|
|
bool useBackBuffer = bufferAccessibility || bufferedHighlights || bufferedAnnotations;
|
|
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 ( bufferAccessibility )
|
|
{
|
|
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 ( bufferedHighlights )
|
|
{
|
|
// draw highlights that are inside the 'limits' paint region
|
|
QValueList< HighlightRect * >::const_iterator hIt = bufferedHighlights->begin(), hEnd = bufferedHighlights->end();
|
|
for ( ; hIt != hEnd; ++hIt )
|
|
{
|
|
HighlightRect * r = *hIt;
|
|
// find out the rect to highlight on pixmap
|
|
QRect highlightRect = r->geometry( scaledWidth, scaledHeight ).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 ( bufferedAnnotations )
|
|
{
|
|
// precalc costants for normalizing the quads to the image
|
|
double xOffset = (float)limits.left() / (float)scaledWidth,
|
|
xScale = (float)scaledWidth / (float)limits.width(),
|
|
yOffset = (float)limits.top() / (float)scaledHeight,
|
|
yScale = (float)scaledHeight / (float)limits.height();
|
|
|
|
// paint all buffered annotations in the page
|
|
QValueList< Annotation * >::const_iterator aIt = bufferedAnnotations->begin(), aEnd = bufferedAnnotations->end();
|
|
for ( ; aIt != aEnd; ++aIt )
|
|
{
|
|
Annotation * a = *aIt;
|
|
Annotation::SubType type = a->subType();
|
|
|
|
// draw TextAnnotation (InPlace) MISSING: all
|
|
if ( type == Annotation::AText )
|
|
{
|
|
// TODO
|
|
}
|
|
// draw LineAnnotation MISSING: all
|
|
else if ( type == Annotation::ALine )
|
|
{
|
|
// TODO
|
|
// get the annotation
|
|
/*LineAnnotation * la = (LineAnnotation *) a;
|
|
|
|
NormalizedPath path;
|
|
// normalize page point to image
|
|
const NormalizedPoint & inkPoint = *pIt;
|
|
NormalizedPoint point;
|
|
point.x = (inkPoint.x - xOffset) * xScale;
|
|
point.y = (inkPoint.y - yOffset) * yScale;
|
|
path.append( point );
|
|
// draw the normalized path into image
|
|
drawShapeOnImage( backImage, path, false, QPen( a->style.color ), QBrush() );
|
|
*/
|
|
}
|
|
// draw GeomAnnotation MISSING: all
|
|
else if ( type == Annotation::AGeom )
|
|
{
|
|
// TODO
|
|
}
|
|
// draw HighlightAnnotation MISSING: under/strike width, feather, capping
|
|
else if ( type == Annotation::AHighlight )
|
|
{
|
|
// get the annotation
|
|
HighlightAnnotation * ha = (HighlightAnnotation *) a;
|
|
HighlightAnnotation::HighlightType type = ha->highlightType;
|
|
|
|
// draw each quad of the annotation
|
|
int quads = ha->highlightQuads.size();
|
|
for ( int q = 0; q < quads; q++ )
|
|
{
|
|
NormalizedPath path;
|
|
const HighlightAnnotation::Quad & quad = ha->highlightQuads[ q ];
|
|
// normalize page point to image
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
NormalizedPoint point;
|
|
point.x = (quad.points[ i ].x - xOffset) * xScale;
|
|
point.y = (quad.points[ i ].y - yOffset) * yScale;
|
|
path.append( point );
|
|
}
|
|
// draw the normalized path into image
|
|
switch ( type )
|
|
{
|
|
// highlight the whole rect
|
|
case HighlightAnnotation::Highlight:
|
|
drawShapeOnImage( backImage, path, true, QPen(), a->style.color, Multiply );
|
|
break;
|
|
// highlight the bottom part of the rect
|
|
case HighlightAnnotation::Squiggly:
|
|
path[ 0 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0;
|
|
path[ 0 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0;
|
|
path[ 1 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0;
|
|
path[ 1 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0;
|
|
drawShapeOnImage( backImage, path, true, QPen(), a->style.color, Multiply );
|
|
break;
|
|
// make a line at 3/4 of the height
|
|
case HighlightAnnotation::Underline:
|
|
path[ 0 ].x = ( path[ 0 ].x + 3*path[ 3 ].x ) / 4.0;
|
|
path[ 0 ].y = ( path[ 0 ].y + 3*path[ 3 ].y ) / 4.0;
|
|
path[ 1 ].x = ( path[ 1 ].x + 3*path[ 2 ].x ) / 4.0;
|
|
path[ 1 ].y = ( path[ 1 ].y + 3*path[ 2 ].y ) / 4.0;
|
|
path.pop_back();
|
|
path.pop_back();
|
|
drawShapeOnImage( backImage, path, false, QPen( a->style.color, 2 ), QBrush() );
|
|
break;
|
|
// make a line at 1/2 of the height
|
|
case HighlightAnnotation::StrikeOut:
|
|
path[ 0 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0;
|
|
path[ 0 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0;
|
|
path[ 1 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0;
|
|
path[ 1 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0;
|
|
path.pop_back();
|
|
path.pop_back();
|
|
drawShapeOnImage( backImage, path, false, QPen( a->style.color, 2 ), QBrush() );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// draw InkAnnotation MISSING:invar width, PENTRACER
|
|
else if ( type == Annotation::AInk )
|
|
{
|
|
// get the annotation
|
|
InkAnnotation * ia = (InkAnnotation *) a;
|
|
|
|
// draw each ink path
|
|
int paths = ia->inkPaths.size();
|
|
for ( int p = 0; p < paths; p++ )
|
|
{
|
|
NormalizedPath path;
|
|
const QValueList<NormalizedPoint> & inkPath = ia->inkPaths[ p ];
|
|
|
|
// normalize page point to image
|
|
QValueList<NormalizedPoint>::const_iterator pIt = inkPath.begin(), pEnd = inkPath.end();
|
|
for ( ; pIt != pEnd; ++pIt )
|
|
{
|
|
const NormalizedPoint & inkPoint = *pIt;
|
|
NormalizedPoint point;
|
|
point.x = (inkPoint.x - xOffset) * xScale;
|
|
point.y = (inkPoint.y - yOffset) * yScale;
|
|
path.append( point );
|
|
}
|
|
// draw the normalized path into image
|
|
drawShapeOnImage( backImage, path, false, QPen( a->style.color, 2 ), QBrush() );
|
|
}
|
|
}
|
|
} // end current annotation drawing
|
|
}
|
|
|
|
// 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 ( unbufferedAnnotations )
|
|
{
|
|
// iterate over annotations and paint AText, AGeom, AStamp
|
|
QValueList< Annotation * >::const_iterator aIt = unbufferedAnnotations->begin(), aEnd = unbufferedAnnotations->end();
|
|
for ( ; aIt != aEnd; ++aIt )
|
|
{
|
|
Annotation * a = *aIt;
|
|
|
|
// honour opacity settings on supported types
|
|
unsigned int opacity = (unsigned int)( 255.0 * a->style.opacity );
|
|
if ( opacity <= 0 )
|
|
continue;
|
|
|
|
// get annotation boundary and drawn rect
|
|
QRect annotBoundary = a->boundary.geometry( scaledWidth, scaledHeight );
|
|
QRect annotRect = annotBoundary.intersect( limits );
|
|
QRect innerRect( annotRect.left() - annotBoundary.left(), annotRect.top() -
|
|
annotBoundary.top(), annotRect.width(), annotRect.height() );
|
|
|
|
Annotation::SubType type = a->subType();
|
|
|
|
// draw TextAnnotation (only the 'Linked' variant)
|
|
if ( type == Annotation::AText )
|
|
{
|
|
TextAnnotation * text = (TextAnnotation *)a;
|
|
if ( text->textType != TextAnnotation::Linked )
|
|
continue;
|
|
|
|
// get pixmap, colorize and alpha-blend it
|
|
QPixmap pixmap = DesktopIcon( text->textIcon );
|
|
QImage scaledImage;
|
|
scalePixmapOnImage( scaledImage, &pixmap, annotBoundary.width(),
|
|
annotBoundary.height(), innerRect );
|
|
colorizeImage( scaledImage, a->style.color, opacity );
|
|
scaledImage.setAlphaBuffer( true );
|
|
pixmap.convertFromImage( scaledImage );
|
|
|
|
// draw the mangled image to painter
|
|
mixedPainter->drawPixmap( annotRect.topLeft(), pixmap );
|
|
}
|
|
// draw StampAnnotation
|
|
else if ( type == Annotation::AStamp )
|
|
{
|
|
StampAnnotation * stamp = (StampAnnotation *)a;
|
|
|
|
// get pixmap and alpha blend it if needed
|
|
QPixmap pixmap = DesktopIcon( stamp->stampIconName );
|
|
QImage scaledImage;
|
|
scalePixmapOnImage( scaledImage, &pixmap, annotBoundary.width(),
|
|
annotBoundary.height(), innerRect );
|
|
if ( opacity < 255 )
|
|
changeImageAlpha( scaledImage, opacity );
|
|
scaledImage.setAlphaBuffer( true );
|
|
pixmap.convertFromImage( scaledImage );
|
|
|
|
// draw the scaled and al
|
|
mixedPainter->drawPixmap( annotRect.topLeft(), pixmap );
|
|
}
|
|
// draw GeomAnnotation
|
|
else // WARNING: TEMPORARY CODE! migrate everything to AGG
|
|
{
|
|
//GeomAnnotation * geom = (GeomAnnotation *)a;
|
|
//if ( geom->geomType == GeomAnnotation::InscribedSquare )
|
|
//{
|
|
QImage rectImage( innerRect.width(), innerRect.height(), 32 );
|
|
const QColor & c = a->style.color;
|
|
unsigned int color = qRgba( c.red(), c.green(), c.blue(), opacity );
|
|
rectImage.fill( color );
|
|
rectImage.setAlphaBuffer( true );
|
|
QPixmap pixmap( rectImage );
|
|
mixedPainter->drawPixmap( annotRect.topLeft(), pixmap );
|
|
//}
|
|
//else if ( geom->geomType == GeomAnnotation::InscribedCircle )
|
|
}
|
|
|
|
// draw extents rectangle
|
|
if ( Settings::debugDrawAnnotationRect() )
|
|
{
|
|
mixedPainter->setPen( a->style.color );
|
|
mixedPainter->drawRect( annotBoundary );
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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;
|
|
}
|
|
|
|
// delete object containers
|
|
delete bufferedHighlights;
|
|
delete bufferedAnnotations;
|
|
delete unbufferedAnnotations;
|
|
}
|
|
|
|
|
|
/** 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 **/
|
|
// from Arthur - qt4
|
|
inline int qt_div_255(int x) { return (x + (x>>8) + 0x80) >> 8; }
|
|
|
|
void PagePainter::changeImageAlpha( QImage & image, unsigned int destAlpha )
|
|
{
|
|
// iterate over all pixels changing the alpha component value
|
|
unsigned int * data = (unsigned int *)image.bits();
|
|
unsigned int pixels = image.width() * image.height();
|
|
|
|
int source, sourceAlpha;
|
|
for( register unsigned int i = 0; i < pixels; ++i )
|
|
{ // optimize this loop keeping byte order into account
|
|
source = data[i];
|
|
if ( (sourceAlpha = qAlpha( source )) == 255 )
|
|
{
|
|
// use destAlpha
|
|
data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), destAlpha );
|
|
}
|
|
else
|
|
{
|
|
// use destAlpha * sourceAlpha product
|
|
sourceAlpha = qt_div_255( destAlpha * sourceAlpha );
|
|
data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), sourceAlpha );
|
|
}
|
|
}
|
|
}
|
|
|
|
void PagePainter::colorizeImage( QImage & grayImage, const QColor & color,
|
|
unsigned int destAlpha )
|
|
{
|
|
// iterate over all pixels changing the alpha component value
|
|
unsigned int * data = (unsigned int *)grayImage.bits();
|
|
unsigned int pixels = grayImage.width() * grayImage.height();
|
|
int red = color.red(),
|
|
green = color.green(),
|
|
blue = color.blue();
|
|
|
|
int source, sourceSat, sourceAlpha;
|
|
for( register unsigned int i = 0; i < pixels; ++i )
|
|
{ // optimize this loop keeping byte order into account
|
|
source = data[i];
|
|
sourceSat = qRed( source );
|
|
int newR = qt_div_255( sourceSat * red ),
|
|
newG = qt_div_255( sourceSat * green ),
|
|
newB = qt_div_255( sourceSat * blue );
|
|
if ( (sourceAlpha = qAlpha( source )) == 255 )
|
|
{
|
|
// use destAlpha
|
|
data[i] = qRgba( newR, newG, newB, destAlpha );
|
|
}
|
|
else
|
|
{
|
|
// use destAlpha * sourceAlpha product
|
|
if ( destAlpha < 255 )
|
|
sourceAlpha = qt_div_255( destAlpha * sourceAlpha );
|
|
data[i] = qRgba( newR, newG, newB, sourceAlpha );
|
|
}
|
|
}
|
|
}
|
|
|
|
//BEGIN of Anti-Grain dependant code
|
|
/** Shape Drawing using Anti-Grain Geometry library **/
|
|
// The following code uses the AGG2.3 lib imported into the "painter_agg2"
|
|
// directory. This is to be replaced by Arthur calls for drawing antialiased
|
|
// primitives, but until that AGG2 does its job very fast and good-looking.
|
|
|
|
#include "kpdf_pixfmt_rgba.h"
|
|
#include "agg_rendering_buffer.h"
|
|
#include "agg_renderer_base.h"
|
|
#include "agg_scanline_u.h"
|
|
#include "agg_rasterizer_scanline_aa.h"
|
|
#include "agg_renderer_scanline.h"
|
|
#include "agg_conv_stroke.h"
|
|
#include "agg_path_storage.h"
|
|
|
|
void PagePainter::drawShapeOnImage(
|
|
QImage & image,
|
|
const NormalizedPath & normPath,
|
|
bool closeShape,
|
|
const QPen & pen,
|
|
const QBrush & brush,
|
|
RasterOperation op
|
|
//float antiAliasRadius
|
|
)
|
|
{
|
|
// safety checks
|
|
int pointsNumber = normPath.size();
|
|
if ( pointsNumber < 2 )
|
|
return;
|
|
|
|
int imageWidth = image.width();
|
|
int imageHeight = image.height();
|
|
double fImageWidth = (double)imageWidth;
|
|
double fImageHeight = (double)imageHeight;
|
|
|
|
// create a 'path'
|
|
agg::path_storage path;
|
|
path.move_to( normPath[ 0 ].x * fImageWidth, normPath[ 0 ].y * fImageHeight );
|
|
for ( int i = 1; i < pointsNumber; i++ )
|
|
path.line_to( normPath[ i ].x * fImageWidth, normPath[ i ].y * fImageHeight );
|
|
if ( closeShape )
|
|
path.close_polygon();
|
|
|
|
// create the 'rendering buffer' over qimage memory
|
|
agg::rendering_buffer buffer( image.bits(), imageWidth, imageHeight, imageWidth << 2 );
|
|
// create 'pixel buffer', 'clipped renderer', 'scanline renderer' on bgra32 format
|
|
typedef agg::pixfmt_bgra32 bgra32;
|
|
typedef agg::renderer_base< bgra32 > rb_bgra32;
|
|
bgra32 pixels( buffer, op == Multiply ? 1 : 0 );
|
|
rb_bgra32 rb( pixels );
|
|
agg::renderer_scanline_aa_solid< rb_bgra32 > render( rb );
|
|
// create rasterizer and scaline
|
|
agg::rasterizer_scanline_aa<> rasterizer;
|
|
agg::scanline_u8 scanline;
|
|
|
|
#if 0
|
|
//draw RAINBOW
|
|
agg::rgba8 span[ imageWidth ];
|
|
for( int x = 0; x < imageWidth; x++ )
|
|
{
|
|
agg::rgba c( 380.0 + 400.0 * x / imageWidth, 0.8 );
|
|
span[ x ] = agg::rgba8(c);
|
|
}
|
|
for( int y = 0; y < imageHeight; y++ )
|
|
pixels.blend_color_hspan( 0, y, imageWidth, span, 0, (255*y)/imageHeight );
|
|
#endif
|
|
|
|
// fill rect
|
|
if ( brush.style() != Qt::NoBrush )
|
|
{
|
|
const QColor & brushColor = brush.color();
|
|
render.color( agg::rgba8( brushColor.red(), brushColor.green(), brushColor.blue() ) );
|
|
rasterizer.add_path( path );
|
|
agg::render_scanlines( rasterizer, scanline, render );
|
|
rasterizer.reset();
|
|
}
|
|
|
|
// stroke outline
|
|
if ( pen.width() != 0 )
|
|
{
|
|
const QColor & penColor = pen.color();
|
|
render.color( agg::rgba8( penColor.red(), penColor.green(), penColor.blue() ) );
|
|
agg::conv_stroke< agg::path_storage > strokedPath( path );
|
|
strokedPath.width( pen.width() );
|
|
rasterizer.add_path( strokedPath );
|
|
agg::render_scanlines( rasterizer, scanline, render );
|
|
}
|
|
}
|
|
|
|
//END of Anti-Grain dependant code
|