/*************************************************************************** * Copyright (C) 2005 by Enrico Ros * * * * 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 #include #include #include #include #include #include // 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] ]; } }