File indexing completed on 2024-04-28 04:33:06
0001 /* 0002 SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "pagepainter.h" 0008 0009 // qt / kde includes 0010 #include <QApplication> 0011 #include <QDebug> 0012 #include <QIcon> 0013 #include <QPainter> 0014 #include <QPalette> 0015 #include <QPixmap> 0016 #include <QRect> 0017 #include <QTransform> 0018 #include <QVarLengthArray> 0019 0020 // system includes 0021 #include <math.h> 0022 0023 // local includes 0024 #include "core/annotations.h" 0025 #include "core/observer.h" 0026 #include "core/page.h" 0027 #include "core/page_p.h" 0028 #include "core/tile.h" 0029 #include "core/utils.h" 0030 #include "debug_ui.h" 0031 #include "guiutils.h" 0032 #include "settings.h" 0033 #include "settings_core.h" 0034 0035 Q_GLOBAL_STATIC_WITH_ARGS(QPixmap, busyPixmap, (QIcon::fromTheme(QLatin1String("okular")).pixmap(48))) 0036 0037 #define TEXTANNOTATION_ICONSIZE 24 0038 0039 inline QPen buildPen(const Okular::Annotation *ann, double width, const QColor &color) 0040 { 0041 QColor c = color; 0042 c.setAlphaF(ann->style().opacity()); 0043 QPen p(QBrush(c), width, ann->style().lineStyle() == Okular::Annotation::Dashed ? Qt::DashLine : Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); 0044 return p; 0045 } 0046 0047 void PagePainter::paintPageOnPainter(QPainter *destPainter, const Okular::Page *page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect limits) 0048 { 0049 paintCroppedPageOnPainter(destPainter, page, observer, flags, scaledWidth, scaledHeight, limits, Okular::NormalizedRect(0, 0, 1, 1), nullptr); 0050 } 0051 0052 void PagePainter::paintCroppedPageOnPainter(QPainter *destPainter, 0053 const Okular::Page *page, 0054 Okular::DocumentObserver *observer, 0055 int flags, 0056 int scaledWidth, 0057 int scaledHeight, 0058 const QRect limits, 0059 const Okular::NormalizedRect &crop, 0060 Okular::NormalizedPoint *viewPortPoint) 0061 { 0062 qreal dpr = destPainter->device()->devicePixelRatioF(); 0063 0064 /* Calculate the cropped geometry of the page */ 0065 QRect scaledCrop = crop.geometry(scaledWidth, scaledHeight); 0066 0067 /* variables prefixed with d are in the device pixels coordinate system, which translates to the rendered output - that means, 0068 * multiplied with the device pixel ratio of the target PaintDevice */ 0069 const QRect dScaledCrop(QRectF(scaledCrop.x() * dpr, scaledCrop.y() * dpr, scaledCrop.width() * dpr, scaledCrop.height() * dpr).toAlignedRect()); 0070 0071 int croppedWidth = scaledCrop.width(); 0072 int croppedHeight = scaledCrop.height(); 0073 0074 int dScaledWidth = ceil(scaledWidth * dpr); 0075 int dScaledHeight = ceil(scaledHeight * dpr); 0076 const QRect dLimits(QRectF(limits.x() * dpr, limits.y() * dpr, limits.width() * dpr, limits.height() * dpr).toAlignedRect()); 0077 0078 QColor paperColor = Qt::white; 0079 QColor backgroundColor = paperColor; 0080 if (Okular::SettingsCore::changeColors()) { 0081 switch (Okular::SettingsCore::renderMode()) { 0082 case Okular::SettingsCore::EnumRenderMode::Inverted: 0083 case Okular::SettingsCore::EnumRenderMode::InvertLightness: 0084 case Okular::SettingsCore::EnumRenderMode::InvertLuma: 0085 case Okular::SettingsCore::EnumRenderMode::InvertLumaSymmetric: 0086 backgroundColor = Qt::black; 0087 break; 0088 case Okular::SettingsCore::EnumRenderMode::Paper: 0089 paperColor = Okular::SettingsCore::paperColor(); 0090 backgroundColor = paperColor; 0091 break; 0092 case Okular::SettingsCore::EnumRenderMode::Recolor: 0093 backgroundColor = Okular::Settings::recolorBackground(); 0094 break; 0095 default:; 0096 } 0097 } 0098 destPainter->fillRect(limits, backgroundColor); 0099 0100 const bool hasTilesManager = page->hasTilesManager(observer); 0101 QPixmap pixmap; 0102 0103 if (!hasTilesManager) { 0104 /** 1 - RETRIEVE THE 'PAGE+ID' PIXMAP OR A SIMILAR 'PAGE' ONE **/ 0105 const QPixmap *p = page->_o_nearestPixmap(observer, dScaledWidth, dScaledHeight); 0106 0107 if (p != nullptr) { 0108 pixmap = *p; 0109 } 0110 0111 /** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/ 0112 double pixmapRescaleRatio = !pixmap.isNull() ? dScaledWidth / (double)pixmap.width() : -1; 0113 long pixmapPixels = !pixmap.isNull() ? (long)pixmap.width() * (long)pixmap.height() : 0; 0114 if (pixmap.isNull() || pixmapRescaleRatio > 20.0 || pixmapRescaleRatio < 0.25 || (dScaledWidth > pixmap.width() && pixmapPixels > 60000000L)) { 0115 // draw something on the blank page: the okular icon or a cross (as a fallback) 0116 if (!busyPixmap()->isNull()) { 0117 busyPixmap->setDevicePixelRatio(dpr); 0118 destPainter->drawPixmap(QPoint(10, 10), *busyPixmap()); 0119 } else { 0120 destPainter->setPen(Qt::gray); 0121 destPainter->drawLine(0, 0, croppedWidth - 1, croppedHeight - 1); 0122 destPainter->drawLine(0, croppedHeight - 1, croppedWidth - 1, 0); 0123 } 0124 return; 0125 } 0126 } 0127 0128 /** 2 - FIND OUT WHAT TO PAINT (Flags + Configuration + Presence) **/ 0129 bool canDrawHighlights = (flags & Highlights) && !page->m_highlights.isEmpty(); 0130 bool canDrawTextSelection = (flags & TextSelection) && page->textSelection(); 0131 bool canDrawAnnotations = (flags & Annotations) && !page->m_annotations.isEmpty(); 0132 bool enhanceLinks = (flags & EnhanceLinks) && Okular::Settings::highlightLinks(); 0133 bool enhanceImages = (flags & EnhanceImages) && Okular::Settings::highlightImages(); 0134 0135 // vectors containing objects to draw 0136 // make this a qcolor, rect map, since we don't need 0137 // to know s_id here! we are only drawing this right? 0138 QList<QPair<QColor, Okular::NormalizedRect>> *bufferedHighlights = nullptr; 0139 QList<Okular::Annotation *> *bufferedAnnotations = nullptr; 0140 QList<Okular::Annotation *> *unbufferedAnnotations = nullptr; 0141 Okular::Annotation *boundingRectOnlyAnn = nullptr; // Paint the bounding rect of this annotation 0142 // fill up lists with visible annotation/highlight objects/text selections 0143 if (canDrawHighlights || canDrawTextSelection || canDrawAnnotations) { 0144 // precalc normalized 'limits rect' for intersection 0145 double nXMin = ((double)limits.left() / scaledWidth) + crop.left, nXMax = ((double)limits.right() / scaledWidth) + crop.left, nYMin = ((double)limits.top() / scaledHeight) + crop.top, 0146 nYMax = ((double)limits.bottom() / scaledHeight) + crop.top; 0147 // append all highlights inside limits to their list 0148 if (canDrawHighlights) { 0149 if (!bufferedHighlights) { 0150 bufferedHighlights = new QList<QPair<QColor, Okular::NormalizedRect>>(); 0151 } 0152 /* else 0153 {*/ 0154 0155 Okular::NormalizedRect *limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax); 0156 Okular::HighlightAreaRect::const_iterator hIt; 0157 for (const Okular::HighlightAreaRect *highlight : page->m_highlights) { 0158 for (hIt = highlight->constBegin(); hIt != highlight->constEnd(); ++hIt) { 0159 if ((*hIt).intersects(limitRect)) { 0160 bufferedHighlights->append(qMakePair(highlight->color, *hIt)); 0161 } 0162 } 0163 } 0164 delete limitRect; 0165 //} 0166 } 0167 if (canDrawTextSelection) { 0168 if (!bufferedHighlights) { 0169 bufferedHighlights = new QList<QPair<QColor, Okular::NormalizedRect>>(); 0170 } 0171 /* else 0172 {*/ 0173 Okular::NormalizedRect *limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax); 0174 const Okular::RegularAreaRect *textSelection = page->textSelection(); 0175 Okular::HighlightAreaRect::const_iterator hIt = textSelection->constBegin(), hEnd = textSelection->constEnd(); 0176 for (; hIt != hEnd; ++hIt) { 0177 if ((*hIt).intersects(limitRect)) { 0178 bufferedHighlights->append(qMakePair(page->textSelectionColor(), *hIt)); 0179 } 0180 } 0181 delete limitRect; 0182 //} 0183 } 0184 // append annotations inside limits to the un/buffered list 0185 if (canDrawAnnotations) { 0186 for (Okular::Annotation *ann : page->m_annotations) { 0187 int flags = ann->flags(); 0188 0189 if (flags & Okular::Annotation::Hidden) { 0190 continue; 0191 } 0192 0193 if (flags & Okular::Annotation::ExternallyDrawn) { 0194 // ExternallyDrawn annots are never rendered by PagePainter. 0195 // Just paint the boundingRect if the annot is moved or resized. 0196 if (flags & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized)) { 0197 boundingRectOnlyAnn = ann; 0198 } 0199 continue; 0200 } 0201 0202 bool intersects = ann->transformedBoundingRectangle().intersects(nXMin, nYMin, nXMax, nYMax); 0203 if (ann->subType() == Okular::Annotation::AText) { 0204 Okular::TextAnnotation *ta = static_cast<Okular::TextAnnotation *>(ann); 0205 if (ta->textType() == Okular::TextAnnotation::Linked) { 0206 Okular::NormalizedRect iconrect(ann->transformedBoundingRectangle().left, 0207 ann->transformedBoundingRectangle().top, 0208 ann->transformedBoundingRectangle().left + TEXTANNOTATION_ICONSIZE / page->width(), 0209 ann->transformedBoundingRectangle().top + TEXTANNOTATION_ICONSIZE / page->height()); 0210 intersects = iconrect.intersects(nXMin, nYMin, nXMax, nYMax); 0211 } 0212 } 0213 if (intersects) { 0214 Okular::Annotation::SubType type = ann->subType(); 0215 if (type == Okular::Annotation::ALine || type == Okular::Annotation::AHighlight || type == Okular::Annotation::AInk /*|| (type == Annotation::AGeom && ann->style().opacity() < 0.99)*/) { 0216 if (!bufferedAnnotations) { 0217 bufferedAnnotations = new QList<Okular::Annotation *>(); 0218 } 0219 bufferedAnnotations->append(ann); 0220 } else { 0221 if (!unbufferedAnnotations) { 0222 unbufferedAnnotations = new QList<Okular::Annotation *>(); 0223 } 0224 unbufferedAnnotations->append(ann); 0225 } 0226 } 0227 } 0228 } 0229 // end of intersections checking 0230 } 0231 0232 /** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/ 0233 bool bufferAccessibility = (flags & Accessibility) && Okular::SettingsCore::changeColors() && (Okular::SettingsCore::renderMode() != Okular::SettingsCore::EnumRenderMode::Paper); 0234 bool useBackBuffer = bufferAccessibility || bufferedHighlights || bufferedAnnotations || viewPortPoint; 0235 QPixmap *backPixmap = nullptr; 0236 QPainter *mixedPainter = nullptr; 0237 QRect limitsInPixmap = limits.translated(scaledCrop.topLeft()); 0238 QRect dLimitsInPixmap = dLimits.translated(dScaledCrop.topLeft()); 0239 0240 // limits within full (scaled but uncropped) pixmap 0241 0242 /** 4A -- REGULAR FLOW. PAINT PIXMAP NORMAL OR RESCALED USING GIVEN QPAINTER **/ 0243 if (!useBackBuffer) { 0244 if (hasTilesManager) { 0245 const Okular::NormalizedRect normalizedLimits(limitsInPixmap, scaledWidth, scaledHeight); 0246 const QList<Okular::Tile> tiles = page->tilesAt(observer, normalizedLimits); 0247 QList<Okular::Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); 0248 while (tIt != tEnd) { 0249 const Okular::Tile &tile = *tIt; 0250 QRectF tileRect = tile.rect().geometryF(scaledWidth, scaledHeight).translated(-scaledCrop.topLeft()); 0251 QRect dTileRect = tile.rect().geometry(dScaledWidth, dScaledHeight).translated(-dScaledCrop.topLeft()); 0252 QRectF limitsInTile = QRectF(limits) & tileRect; 0253 QRect dLimitsInTile = dLimits & dTileRect; 0254 0255 if (!limitsInTile.isEmpty()) { 0256 QPixmap *tilePixmap = tile.pixmap(); 0257 0258 if (tilePixmap->width() == dTileRect.width() && tilePixmap->height() == dTileRect.height()) { 0259 destPainter->drawPixmap(limitsInTile, *tilePixmap, dLimitsInTile.translated(-dTileRect.topLeft())); 0260 } else { 0261 destPainter->drawPixmap(tileRect, *tilePixmap, tilePixmap->rect()); 0262 } 0263 } 0264 tIt++; 0265 } 0266 } else { 0267 destPainter->drawPixmap(limits, pixmap.scaled(dScaledWidth, dScaledHeight), dLimitsInPixmap); 0268 } 0269 0270 // 4A.2. active painter is the one passed to this method 0271 mixedPainter = destPainter; 0272 } 0273 /** 4B -- BUFFERED FLOW. IMAGE PAINTING + OPERATIONS. QPAINTER OVER PIXMAP **/ 0274 else { 0275 // the image over which we are going to draw 0276 QImage backImage = QImage(dLimits.width(), dLimits.height(), QImage::Format_ARGB32_Premultiplied); 0277 backImage.setDevicePixelRatio(dpr); 0278 backImage.fill(paperColor); 0279 QPainter p(&backImage); 0280 0281 if (hasTilesManager) { 0282 const Okular::NormalizedRect normalizedLimits(limitsInPixmap, scaledWidth, scaledHeight); 0283 const QList<Okular::Tile> tiles = page->tilesAt(observer, normalizedLimits); 0284 QList<Okular::Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); 0285 while (tIt != tEnd) { 0286 const Okular::Tile &tile = *tIt; 0287 QRectF tileRect = tile.rect().geometryF(scaledWidth, scaledHeight).translated(-scaledCrop.topLeft()); 0288 QRect dTileRect = tile.rect().geometry(dScaledWidth, dScaledHeight).translated(-dScaledCrop.topLeft()); 0289 QRectF limitsInTile = QRectF(limits) & tileRect; 0290 QRect dLimitsInTile = dLimits & dTileRect; 0291 0292 if (!limitsInTile.isEmpty()) { 0293 QPixmap *tilePixmap = tile.pixmap(); 0294 0295 if (tilePixmap->width() == dTileRect.width() && tilePixmap->height() == dTileRect.height()) { 0296 p.drawPixmap(limitsInTile.translated(-limits.topLeft()), *tilePixmap, dLimitsInTile.translated(-dTileRect.topLeft())); 0297 } else { 0298 double xScale = tilePixmap->width() / (double)dTileRect.width(); 0299 double yScale = tilePixmap->height() / (double)dTileRect.height(); 0300 QTransform transform(xScale, 0, 0, yScale, 0, 0); 0301 p.drawPixmap(limitsInTile.translated(-limits.topLeft()), *tilePixmap, transform.mapRect(dLimitsInTile).translated(-transform.mapRect(dTileRect).topLeft())); 0302 } 0303 } 0304 ++tIt; 0305 } 0306 } else { 0307 // 4B.1. draw the page pixmap: normal or scaled 0308 0309 p.drawPixmap(QRectF(0, 0, limits.width(), limits.height()), pixmap.scaled(dScaledWidth, dScaledHeight), dLimitsInPixmap); 0310 } 0311 0312 p.end(); 0313 0314 // 4B.2. modify pixmap following accessibility settings 0315 if (bufferAccessibility) { 0316 switch (Okular::SettingsCore::renderMode()) { 0317 case Okular::SettingsCore::EnumRenderMode::Inverted: 0318 // Invert image pixels using QImage internal function 0319 backImage.invertPixels(QImage::InvertRgb); 0320 break; 0321 case Okular::SettingsCore::EnumRenderMode::Recolor: 0322 recolor(&backImage, Okular::Settings::recolorForeground(), Okular::Settings::recolorBackground()); 0323 break; 0324 case Okular::SettingsCore::EnumRenderMode::BlackWhite: 0325 blackWhite(&backImage, Okular::Settings::bWContrast(), Okular::Settings::bWThreshold()); 0326 break; 0327 case Okular::SettingsCore::EnumRenderMode::InvertLightness: 0328 invertLightness(&backImage); 0329 break; 0330 case Okular::SettingsCore::EnumRenderMode::InvertLuma: 0331 invertLuma(&backImage, 0.2126, 0.7152, 0.0722); // sRGB / Rec. 709 luma coefficients 0332 break; 0333 case Okular::SettingsCore::EnumRenderMode::InvertLumaSymmetric: 0334 invertLuma(&backImage, 0.3333, 0.3334, 0.3333); // Symmetric coefficients, to keep colors saturated. 0335 break; 0336 case Okular::SettingsCore::EnumRenderMode::HueShiftPositive: 0337 hueShiftPositive(&backImage); 0338 break; 0339 case Okular::SettingsCore::EnumRenderMode::HueShiftNegative: 0340 hueShiftNegative(&backImage); 0341 break; 0342 } 0343 } 0344 0345 // 4B.3. highlight rects in page 0346 if (bufferedHighlights) { 0347 // draw highlights that are inside the 'limits' paint region 0348 for (const auto &highlight : std::as_const(*bufferedHighlights)) { 0349 const Okular::NormalizedRect &r = highlight.second; 0350 // find out the rect to highlight on pixmap 0351 QRect highlightRect = r.geometry(scaledWidth, scaledHeight).translated(-scaledCrop.topLeft()).intersected(limits); 0352 highlightRect.translate(-limits.left(), -limits.top()); 0353 0354 const QColor highlightColor = highlight.first; 0355 QPainter painter(&backImage); 0356 painter.setCompositionMode(QPainter::CompositionMode_Multiply); 0357 painter.fillRect(highlightRect, highlightColor); 0358 0359 auto frameColor = highlightColor.darker(150); 0360 const QRect frameRect = r.geometry(scaledWidth, scaledHeight).translated(-scaledCrop.topLeft()).translated(-limits.left(), -limits.top()); 0361 painter.setPen(frameColor); 0362 painter.drawRect(frameRect); 0363 } 0364 } 0365 0366 // 4B.4. paint annotations [COMPOSITED ONES] 0367 if (bufferedAnnotations) { 0368 // Albert: This is quite "heavy" but all the backImage that reach here are QImage::Format_ARGB32_Premultiplied 0369 // and have to be so that the QPainter::CompositionMode_Multiply works 0370 // we could also put a 0371 // backImage = backImage.convertToFormat(QImage::Format_ARGB32_Premultiplied) 0372 // that would be almost a noop, but we'll leave the assert for now 0373 Q_ASSERT(backImage.format() == QImage::Format_ARGB32_Premultiplied); 0374 // precalc constants for normalizing [0,1] page coordinates into normalized [0,1] limit rect coordinates 0375 double pageScale = (double)croppedWidth / page->width(); 0376 double xOffset = (double)limits.left() / (double)scaledWidth + crop.left, xScale = (double)scaledWidth / (double)limits.width(), yOffset = (double)limits.top() / (double)scaledHeight + crop.top, 0377 yScale = (double)scaledHeight / (double)limits.height(); 0378 0379 // paint all buffered annotations in the page 0380 QList<Okular::Annotation *>::const_iterator aIt = bufferedAnnotations->constBegin(), aEnd = bufferedAnnotations->constEnd(); 0381 for (; aIt != aEnd; ++aIt) { 0382 Okular::Annotation *a = *aIt; 0383 Okular::Annotation::SubType type = a->subType(); 0384 QColor acolor = a->style().color(); 0385 if (!acolor.isValid()) { 0386 acolor = Qt::yellow; 0387 } 0388 acolor.setAlphaF(a->style().opacity()); 0389 0390 // draw LineAnnotation MISSING: caption, dash pattern, endings for multipoint lines 0391 if (type == Okular::Annotation::ALine) { 0392 LineAnnotPainter linepainter {(Okular::LineAnnotation *)a, {page->width(), page->height()}, pageScale, {xScale, 0., 0., yScale, -xOffset * xScale, -yOffset * yScale}}; 0393 linepainter.draw(backImage); 0394 } 0395 // draw HighlightAnnotation MISSING: under/strike width, feather, capping 0396 else if (type == Okular::Annotation::AHighlight) { 0397 // get the annotation 0398 Okular::HighlightAnnotation *ha = (Okular::HighlightAnnotation *)a; 0399 Okular::HighlightAnnotation::HighlightType type = ha->highlightType(); 0400 0401 // draw each quad of the annotation 0402 int quads = ha->highlightQuads().size(); 0403 for (int q = 0; q < quads; q++) { 0404 NormalizedPath path; 0405 const Okular::HighlightAnnotation::Quad &quad = ha->highlightQuads()[q]; 0406 // normalize page point to image 0407 for (int i = 0; i < 4; i++) { 0408 Okular::NormalizedPoint point; 0409 point.x = (quad.transformedPoint(i).x - xOffset) * xScale; 0410 point.y = (quad.transformedPoint(i).y - yOffset) * yScale; 0411 path.append(point); 0412 } 0413 // draw the normalized path into image 0414 switch (type) { 0415 // highlight the whole rect 0416 case Okular::HighlightAnnotation::Highlight: 0417 drawShapeOnImage(backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply); 0418 break; 0419 // highlight the bottom part of the rect 0420 case Okular::HighlightAnnotation::Squiggly: 0421 path[3].x = (path[0].x + path[3].x) / 2.0; 0422 path[3].y = (path[0].y + path[3].y) / 2.0; 0423 path[2].x = (path[1].x + path[2].x) / 2.0; 0424 path[2].y = (path[1].y + path[2].y) / 2.0; 0425 drawShapeOnImage(backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply); 0426 break; 0427 // make a line at 3/4 of the height 0428 case Okular::HighlightAnnotation::Underline: 0429 path[0].x = (3 * path[0].x + path[3].x) / 4.0; 0430 path[0].y = (3 * path[0].y + path[3].y) / 4.0; 0431 path[1].x = (3 * path[1].x + path[2].x) / 4.0; 0432 path[1].y = (3 * path[1].y + path[2].y) / 4.0; 0433 path.pop_back(); 0434 path.pop_back(); 0435 drawShapeOnImage(backImage, path, false, QPen(acolor, 2), QBrush(), pageScale); 0436 break; 0437 // make a line at 1/2 of the height 0438 case Okular::HighlightAnnotation::StrikeOut: 0439 path[0].x = (path[0].x + path[3].x) / 2.0; 0440 path[0].y = (path[0].y + path[3].y) / 2.0; 0441 path[1].x = (path[1].x + path[2].x) / 2.0; 0442 path[1].y = (path[1].y + path[2].y) / 2.0; 0443 path.pop_back(); 0444 path.pop_back(); 0445 drawShapeOnImage(backImage, path, false, QPen(acolor, 2), QBrush(), pageScale); 0446 break; 0447 } 0448 } 0449 } 0450 // draw InkAnnotation MISSING:invar width, PENTRACER 0451 else if (type == Okular::Annotation::AInk) { 0452 // get the annotation 0453 Okular::InkAnnotation *ia = (Okular::InkAnnotation *)a; 0454 0455 // draw each ink path 0456 const QList<QList<Okular::NormalizedPoint>> transformedInkPaths = ia->transformedInkPaths(); 0457 0458 const QPen inkPen = buildPen(a, a->style().width(), acolor); 0459 0460 for (const QList<Okular::NormalizedPoint> &inkPath : transformedInkPaths) { 0461 // normalize page point to image 0462 NormalizedPath path; 0463 for (const Okular::NormalizedPoint &inkPoint : inkPath) { 0464 Okular::NormalizedPoint point; 0465 point.x = (inkPoint.x - xOffset) * xScale; 0466 point.y = (inkPoint.y - yOffset) * yScale; 0467 path.append(point); 0468 } 0469 // draw the normalized path into image 0470 drawShapeOnImage(backImage, path, false, inkPen, QBrush(), pageScale); 0471 } 0472 } 0473 } // end current annotation drawing 0474 } 0475 if (viewPortPoint) { 0476 QPainter painter(&backImage); 0477 painter.translate(-limits.left(), -limits.top()); 0478 painter.setPen(QApplication::palette().color(QPalette::Active, QPalette::Highlight)); 0479 painter.drawLine(0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1, viewPortPoint->y * scaledHeight + 1); 0480 // ROTATION CURRENTLY NOT IMPLEMENTED 0481 /* 0482 if( page->rotation() == Okular::Rotation0) 0483 { 0484 0485 } 0486 else if(page->rotation() == Okular::Rotation270) 0487 { 0488 painter.drawLine( viewPortPoint->y * scaledHeight + 1, 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1); 0489 } 0490 else if(page->rotation() == Okular::Rotation180) 0491 { 0492 painter.drawLine( 0, (1.0 - viewPortPoint->y) * scaledHeight - 1, scaledWidth - 1, (1.0 - viewPortPoint->y) * scaledHeight - 1 ); 0493 } 0494 else if(page->rotation() == Okular::Rotation90) // not right, rotation clock-wise 0495 { 0496 painter.drawLine( scaledWidth - (viewPortPoint->y * scaledHeight + 1), 0, scaledWidth - (viewPortPoint->y * scaledHeight + 1), scaledWidth - 1); 0497 } 0498 */ 0499 } 0500 0501 // 4B.5. create the back pixmap converting from the local image 0502 backPixmap = new QPixmap(QPixmap::fromImage(backImage)); 0503 backPixmap->setDevicePixelRatio(dpr); 0504 0505 // 4B.6. create a painter over the pixmap and set it as the active one 0506 mixedPainter = new QPainter(backPixmap); 0507 mixedPainter->translate(-limits.left(), -limits.top()); 0508 } 0509 0510 /** 5 -- MIXED FLOW. Draw ANNOTATIONS [OPAQUE ONES] on ACTIVE PAINTER **/ 0511 if (unbufferedAnnotations) { 0512 // iterate over annotations and paint AText, AGeom, AStamp 0513 QList<Okular::Annotation *>::const_iterator aIt = unbufferedAnnotations->constBegin(), aEnd = unbufferedAnnotations->constEnd(); 0514 for (; aIt != aEnd; ++aIt) { 0515 Okular::Annotation *a = *aIt; 0516 0517 // honor opacity settings on supported types 0518 unsigned int opacity = (unsigned int)(a->style().color().alpha() * a->style().opacity()); 0519 // skip the annotation drawing if all the annotation is fully 0520 // transparent, but not with text annotations 0521 if (opacity <= 0 && a->subType() != Okular::Annotation::AText) { 0522 continue; 0523 } 0524 0525 QColor acolor = a->style().color(); 0526 if (!acolor.isValid()) { 0527 acolor = Qt::yellow; 0528 } 0529 acolor.setAlpha(opacity); 0530 0531 // Annotation boundary in destPainter coordinates: 0532 QRect annotBoundary = a->transformedBoundingRectangle().geometry(scaledWidth, scaledHeight).translated(-scaledCrop.topLeft()); 0533 QRect annotRect = annotBoundary.intersected(limits); 0534 // Visible portion of the annotation at annotBoundary size: 0535 QRect innerRect = annotRect.translated(-annotBoundary.topLeft()); 0536 QRectF dInnerRect(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr); 0537 0538 Okular::Annotation::SubType type = a->subType(); 0539 0540 // draw TextAnnotation 0541 if (type == Okular::Annotation::AText) { 0542 Okular::TextAnnotation *text = (Okular::TextAnnotation *)a; 0543 if (text->textType() == Okular::TextAnnotation::InPlace) { 0544 QImage image(annotBoundary.size(), QImage::Format_ARGB32); 0545 image.fill(acolor.rgba()); 0546 QPainter painter(&image); 0547 painter.setFont(text->textFont()); 0548 painter.setPen(text->textColor()); 0549 Qt::AlignmentFlag halign = (text->inplaceAlignment() == 1 ? Qt::AlignHCenter : (text->inplaceAlignment() == 2 ? Qt::AlignRight : Qt::AlignLeft)); 0550 const double invXScale = (double)page->width() / scaledWidth; 0551 const double invYScale = (double)page->height() / scaledHeight; 0552 const double borderWidth = text->style().width(); 0553 painter.scale(1 / invXScale, 1 / invYScale); 0554 painter.drawText( 0555 borderWidth * invXScale, borderWidth * invYScale, (image.width() - 2 * borderWidth) * invXScale, (image.height() - 2 * borderWidth) * invYScale, Qt::AlignTop | halign | Qt::TextWordWrap, text->contents()); 0556 painter.resetTransform(); 0557 // Required as asking for a zero width pen results 0558 // in a default width pen (1.0) being created 0559 if (borderWidth != 0) { 0560 QPen pen(Qt::black, borderWidth); 0561 painter.setPen(pen); 0562 painter.drawRect(0, 0, image.width() - 1, image.height() - 1); 0563 } 0564 painter.end(); 0565 0566 mixedPainter->drawImage(annotBoundary.topLeft(), image); 0567 } else if (text->textType() == Okular::TextAnnotation::Linked) { 0568 // get pixmap, colorize and alpha-blend it 0569 QPixmap pixmap = QIcon::fromTheme(text->textIcon().toLower()).pixmap(32); 0570 0571 QPixmap scaledCroppedPixmap = pixmap.scaled(TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr).copy(dInnerRect.toAlignedRect()); 0572 scaledCroppedPixmap.setDevicePixelRatio(dpr); 0573 QImage scaledCroppedImage = scaledCroppedPixmap.toImage(); 0574 0575 // if the annotation color is valid (ie it was set), then 0576 // use it to colorize the icon, otherwise the icon will be 0577 // "gray" 0578 if (a->style().color().isValid()) { 0579 GuiUtils::colorizeImage(scaledCroppedImage, a->style().color(), opacity); 0580 } 0581 pixmap = QPixmap::fromImage(scaledCroppedImage); 0582 0583 // draw the mangled image to painter 0584 mixedPainter->drawPixmap(annotRect.topLeft(), pixmap); 0585 } 0586 0587 } 0588 // draw StampAnnotation 0589 else if (type == Okular::Annotation::AStamp) { 0590 Okular::StampAnnotation *stamp = (Okular::StampAnnotation *)a; 0591 0592 // get pixmap and alpha blend it if needed 0593 QPixmap pixmap = Okular::AnnotationUtils::loadStamp(stamp->stampIconName(), qMax(annotBoundary.width(), annotBoundary.height()) * dpr); 0594 if (!pixmap.isNull()) // should never happen but can happen on huge sizes 0595 { 0596 // Draw pixmap with opacity: 0597 mixedPainter->save(); 0598 mixedPainter->setOpacity(mixedPainter->opacity() * opacity / 255.0); 0599 0600 mixedPainter->drawPixmap(annotRect.topLeft(), pixmap.scaled(annotBoundary.width() * dpr, annotBoundary.height() * dpr), dInnerRect.toAlignedRect()); 0601 0602 mixedPainter->restore(); 0603 } 0604 } 0605 // draw GeomAnnotation 0606 else if (type == Okular::Annotation::AGeom) { 0607 Okular::GeomAnnotation *geom = (Okular::GeomAnnotation *)a; 0608 // check whether there's anything to draw 0609 if (geom->style().width() || geom->geometricalInnerColor().isValid()) { 0610 mixedPainter->save(); 0611 const double width = geom->style().width() * Okular::Utils::realDpi(nullptr).width() / (72.0 * 2.0) * scaledWidth / page->width(); 0612 QRectF r(.0, .0, annotBoundary.width(), annotBoundary.height()); 0613 r.adjust(width, width, -width, -width); 0614 r.translate(annotBoundary.topLeft()); 0615 if (geom->geometricalInnerColor().isValid()) { 0616 r.adjust(width, width, -width, -width); 0617 const QColor color = geom->geometricalInnerColor(); 0618 mixedPainter->setPen(Qt::NoPen); 0619 mixedPainter->setBrush(QColor(color.red(), color.green(), color.blue(), opacity)); 0620 if (geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare) { 0621 mixedPainter->drawRect(r); 0622 } else { 0623 mixedPainter->drawEllipse(r); 0624 } 0625 r.adjust(-width, -width, width, width); 0626 } 0627 if (geom->style().width()) // need to check the original size here.. 0628 { 0629 mixedPainter->setPen(buildPen(a, width * 2, acolor)); 0630 mixedPainter->setBrush(Qt::NoBrush); 0631 if (geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare) { 0632 mixedPainter->drawRect(r); 0633 } else { 0634 mixedPainter->drawEllipse(r); 0635 } 0636 } 0637 mixedPainter->restore(); 0638 } 0639 } 0640 0641 // draw extents rectangle 0642 if (Okular::Settings::debugDrawAnnotationRect()) { 0643 mixedPainter->setPen(a->style().color()); 0644 mixedPainter->drawRect(annotBoundary); 0645 } 0646 } 0647 } 0648 0649 if (boundingRectOnlyAnn) { 0650 QRect annotBoundary = boundingRectOnlyAnn->transformedBoundingRectangle().geometry(scaledWidth, scaledHeight).translated(-scaledCrop.topLeft()); 0651 mixedPainter->setPen(Qt::DashLine); 0652 mixedPainter->drawRect(annotBoundary); 0653 } 0654 0655 /** 6 -- MIXED FLOW. Draw LINKS+IMAGES BORDER on ACTIVE PAINTER **/ 0656 if (enhanceLinks || enhanceImages) { 0657 mixedPainter->save(); 0658 mixedPainter->scale(scaledWidth, scaledHeight); 0659 mixedPainter->translate(-crop.left, -crop.top); 0660 0661 QColor normalColor = QApplication::palette().color(QPalette::Active, QPalette::Highlight); 0662 // enlarging limits for intersection is like growing the 'rectGeometry' below 0663 QRect limitsEnlarged = limits; 0664 limitsEnlarged.adjust(-2, -2, 2, 2); 0665 // draw rects that are inside the 'limits' paint region as opaque rects 0666 for (Okular::ObjectRect *rect : page->m_rects) { 0667 if ((enhanceLinks && rect->objectType() == Okular::ObjectRect::Action) || (enhanceImages && rect->objectType() == Okular::ObjectRect::Image)) { 0668 if (limitsEnlarged.intersects(rect->boundingRect(scaledWidth, scaledHeight).translated(-scaledCrop.topLeft()))) { 0669 mixedPainter->strokePath(rect->region(), QPen(normalColor, 0)); 0670 } 0671 } 0672 } 0673 mixedPainter->restore(); 0674 } 0675 0676 /** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/ 0677 if (useBackBuffer) { 0678 delete mixedPainter; 0679 destPainter->drawPixmap(limits.left(), limits.top(), *backPixmap); 0680 delete backPixmap; 0681 } 0682 0683 // delete object containers 0684 delete bufferedHighlights; 0685 delete bufferedAnnotations; 0686 delete unbufferedAnnotations; 0687 } 0688 0689 void PagePainter::recolor(QImage *image, const QColor &foreground, const QColor &background) 0690 { 0691 if (image->format() != QImage::Format_ARGB32_Premultiplied) { 0692 qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; 0693 *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); 0694 } 0695 0696 Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); 0697 0698 const float scaleRed = background.redF() - foreground.redF(); 0699 const float scaleGreen = background.greenF() - foreground.greenF(); 0700 const float scaleBlue = background.blueF() - foreground.blueF(); 0701 0702 const int foreground_red = foreground.red(); 0703 const int foreground_green = foreground.green(); 0704 const int foreground_blue = foreground.blue(); 0705 0706 QRgb *data = reinterpret_cast<QRgb *>(image->bits()); 0707 const int pixels = image->width() * image->height(); 0708 0709 for (int i = 0; i < pixels; ++i) { 0710 const int lightness = qGray(data[i]); 0711 0712 const float r = scaleRed * lightness + foreground_red; 0713 const float g = scaleGreen * lightness + foreground_green; 0714 const float b = scaleBlue * lightness + foreground_blue; 0715 0716 const unsigned a = qAlpha(data[i]); 0717 data[i] = qRgba(r, g, b, a); 0718 } 0719 } 0720 0721 void PagePainter::blackWhite(QImage *image, int contrast, int threshold) 0722 { 0723 unsigned int *data = reinterpret_cast<unsigned int *>(image->bits()); 0724 int con = contrast; 0725 int thr = 255 - threshold; 0726 0727 int pixels = image->width() * image->height(); 0728 for (int i = 0; i < pixels; ++i) { 0729 // Piecewise linear function of val, through (0, 0), (thr, 128), (255, 255) 0730 int val = qGray(data[i]); 0731 if (val > thr) { 0732 val = 128 + (127 * (val - thr)) / (255 - thr); 0733 } else if (val < thr) { 0734 val = (128 * val) / thr; 0735 } 0736 0737 // Linear contrast stretching through (thr, thr) 0738 if (con > 2) { 0739 val = thr + (val - thr) * con / 2; 0740 val = qBound(0, val, 255); 0741 } 0742 0743 const unsigned a = qAlpha(data[i]); 0744 data[i] = qRgba(val, val, val, a); 0745 } 0746 } 0747 0748 void PagePainter::invertLightness(QImage *image) 0749 { 0750 if (image->format() != QImage::Format_ARGB32_Premultiplied) { 0751 qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; 0752 *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); 0753 } 0754 0755 Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); 0756 0757 QRgb *data = reinterpret_cast<QRgb *>(image->bits()); 0758 int pixels = image->width() * image->height(); 0759 for (int i = 0; i < pixels; ++i) { 0760 // Invert lightness of the pixel using the cylindric HSL color model. 0761 // Algorithm is based on https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB (2019-03-17). 0762 // Important simplifications are that inverting lightness does not change chroma and hue. 0763 // This means the sector (of the chroma/hue plane) is not changed, 0764 // so we can use a linear calculation after determining the sector using qMin() and qMax(). 0765 uchar R = qRed(data[i]); 0766 uchar G = qGreen(data[i]); 0767 uchar B = qBlue(data[i]); 0768 0769 // Get only the needed HSL components. These are chroma C and the common component m. 0770 // Get common component m 0771 uchar m = qMin(R, qMin(G, B)); 0772 // Remove m from color components 0773 R -= m; 0774 G -= m; 0775 B -= m; 0776 // Get chroma C 0777 uchar C = qMax(R, qMax(G, B)); 0778 0779 // Get common component m' after inverting lightness L. 0780 // Hint: Lightness L = m + C / 2; L' = 255 - L = 255 - (m + C / 2) => m' = 255 - C - m 0781 uchar m_ = 255 - C - m; 0782 0783 // Add m' to color compontents 0784 R += m_; 0785 G += m_; 0786 B += m_; 0787 0788 // Save new color 0789 const unsigned A = qAlpha(data[i]); 0790 data[i] = qRgba(R, G, B, A); 0791 } 0792 } 0793 0794 void PagePainter::invertLuma(QImage *image, float Y_R, float Y_G, float Y_B) 0795 { 0796 if (image->format() != QImage::Format_ARGB32_Premultiplied) { 0797 qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; 0798 *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); 0799 } 0800 0801 Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); 0802 0803 QRgb *data = reinterpret_cast<QRgb *>(image->bits()); 0804 int pixels = image->width() * image->height(); 0805 for (int i = 0; i < pixels; ++i) { 0806 uchar R = qRed(data[i]); 0807 uchar G = qGreen(data[i]); 0808 uchar B = qBlue(data[i]); 0809 0810 invertLumaPixel(R, G, B, Y_R, Y_G, Y_B); 0811 0812 // Save new color 0813 const unsigned A = qAlpha(data[i]); 0814 data[i] = qRgba(R, G, B, A); 0815 } 0816 } 0817 0818 void PagePainter::invertLumaPixel(uchar &R, uchar &G, uchar &B, float Y_R, float Y_G, float Y_B) 0819 { 0820 // Invert luma of the pixel using the bicone HCY color model, stretched to cylindric HSY. 0821 // Algorithm is based on https://en.wikipedia.org/wiki/HSL_and_HSV#Luma,_chroma_and_hue_to_RGB (2019-03-19). 0822 // For an illustration see https://experilous.com/1/product/make-it-colorful/ (2019-03-19). 0823 0824 // Special case: The algorithm does not work when hue is undefined. 0825 if (R == G && G == B) { 0826 R = 255 - R; 0827 G = 255 - G; 0828 B = 255 - B; 0829 return; 0830 } 0831 0832 // Get input and output luma Y, Y_inv in range 0..255 0833 float Y = R * Y_R + G * Y_G + B * Y_B; 0834 float Y_inv = 255 - Y; 0835 0836 // Get common component m and remove from color components. 0837 // This moves us to the bottom faces of the HCY bicone, i. e. we get C and X in R, G, B. 0838 uint_fast8_t m = qMin(R, qMin(G, B)); 0839 R -= m; 0840 G -= m; 0841 B -= m; 0842 0843 // We operate in a hue plane of the luma/chroma/hue bicone. 0844 // The hue plane is a triangle. 0845 // This bicone is distorted, so we can not simply mirror the triangle. 0846 // We need to stretch it to a luma/saturation rectangle, so we need to stretch chroma C and the proportional X. 0847 0848 // First, we need to calculate luma Y_full_C for the outer corner of the triangle. 0849 // Then we can interpolate the max chroma C_max, C_inv_max for our luma Y, Y_inv. 0850 // Then we calculate C_inv and X_inv by scaling them by the ratio of C_max and C_inv_max. 0851 0852 // Calculate luma Y_full_C (in range equivalent to gray 0..255) for chroma = 1 at this hue. 0853 // Piecewise linear, with the corners of the bicone at the sum of one or two luma coefficients. 0854 float Y_full_C; 0855 if (R >= B && B >= G) { 0856 Y_full_C = 255 * Y_R + 255 * Y_B * B / R; 0857 } else if (R >= G && G >= B) { 0858 Y_full_C = 255 * Y_R + 255 * Y_G * G / R; 0859 } else if (G >= R && R >= B) { 0860 Y_full_C = 255 * Y_G + 255 * Y_R * R / G; 0861 } else if (G >= B && B >= R) { 0862 Y_full_C = 255 * Y_G + 255 * Y_B * B / G; 0863 } else if (B >= G && G >= R) { 0864 Y_full_C = 255 * Y_B + 255 * Y_G * G / B; 0865 } else { 0866 Y_full_C = 255 * Y_B + 255 * Y_R * R / B; 0867 } 0868 0869 // Calculate C_max, C_inv_max, to scale C and X. 0870 float C_max, C_inv_max; 0871 if (Y >= Y_full_C) { 0872 C_max = Y_inv / (255 - Y_full_C); 0873 } else { 0874 C_max = Y / Y_full_C; 0875 } 0876 if (Y_inv >= Y_full_C) { 0877 C_inv_max = Y / (255 - Y_full_C); 0878 } else { 0879 C_inv_max = Y_inv / Y_full_C; 0880 } 0881 0882 // Scale C and X. C and X already lie in R, G, B. 0883 float C_scale = C_inv_max / C_max; 0884 float R_ = R * C_scale; 0885 float G_ = G * C_scale; 0886 float B_ = B * C_scale; 0887 0888 // Calculate missing luma (in range 0..255), to get common component m_inv 0889 float m_inv = Y_inv - (Y_R * R_ + Y_G * G_ + Y_B * B_); 0890 0891 // Add m_inv to color compontents 0892 R_ += m_inv; 0893 G_ += m_inv; 0894 B_ += m_inv; 0895 0896 // Return colors rounded 0897 R = R_ + 0.5; 0898 G = G_ + 0.5; 0899 B = B_ + 0.5; 0900 } 0901 0902 void PagePainter::hueShiftPositive(QImage *image) 0903 { 0904 if (image->format() != QImage::Format_ARGB32_Premultiplied) { 0905 qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; 0906 *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); 0907 } 0908 0909 Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); 0910 0911 QRgb *data = reinterpret_cast<QRgb *>(image->bits()); 0912 int pixels = image->width() * image->height(); 0913 for (int i = 0; i < pixels; ++i) { 0914 uchar R = qRed(data[i]); 0915 uchar G = qGreen(data[i]); 0916 uchar B = qBlue(data[i]); 0917 0918 // Save new color 0919 const unsigned A = qAlpha(data[i]); 0920 data[i] = qRgba(B, R, G, A); 0921 } 0922 } 0923 0924 void PagePainter::hueShiftNegative(QImage *image) 0925 { 0926 if (image->format() != QImage::Format_ARGB32_Premultiplied) { 0927 qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; 0928 *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); 0929 } 0930 0931 Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); 0932 0933 QRgb *data = reinterpret_cast<QRgb *>(image->bits()); 0934 int pixels = image->width() * image->height(); 0935 for (int i = 0; i < pixels; ++i) { 0936 uchar R = qRed(data[i]); 0937 uchar G = qGreen(data[i]); 0938 uchar B = qBlue(data[i]); 0939 0940 // Save new color 0941 const unsigned A = qAlpha(data[i]); 0942 data[i] = qRgba(G, B, R, A); 0943 } 0944 } 0945 0946 void PagePainter::drawShapeOnImage(QImage &image, const NormalizedPath &normPath, bool closeShape, const QPen &pen, const QBrush &brush, double penWidthMultiplier, RasterOperation op 0947 // float antiAliasRadius 0948 ) 0949 { 0950 // safety checks 0951 int pointsNumber = normPath.size(); 0952 if (pointsNumber < 2) { 0953 return; 0954 } 0955 0956 const double dpr = image.devicePixelRatio(); 0957 const double fImageWidth = image.width() / dpr; 0958 const double fImageHeight = image.height() / dpr; 0959 0960 // stroke outline 0961 double penWidth = (double)pen.width() * penWidthMultiplier; 0962 QPainter painter(&image); 0963 painter.setRenderHint(QPainter::Antialiasing); 0964 QPen pen2 = pen; 0965 pen2.setWidthF(penWidth); 0966 painter.setPen(pen2); 0967 painter.setBrush(brush); 0968 0969 if (op == Multiply) { 0970 painter.setCompositionMode(QPainter::CompositionMode_Multiply); 0971 } 0972 0973 if (brush.style() == Qt::NoBrush) { 0974 // create a polygon 0975 QPolygonF poly(closeShape ? pointsNumber + 1 : pointsNumber); 0976 for (int i = 0; i < pointsNumber; ++i) { 0977 poly[i] = QPointF(normPath[i].x * fImageWidth, normPath[i].y * fImageHeight); 0978 } 0979 if (closeShape) { 0980 poly[pointsNumber] = poly[0]; 0981 } 0982 0983 painter.drawPolyline(poly); 0984 } else { 0985 // create a 'path' 0986 QPainterPath path; 0987 path.setFillRule(Qt::WindingFill); 0988 0989 path.moveTo(normPath[0].x * fImageWidth, normPath[0].y * fImageHeight); 0990 for (int i = 1; i < pointsNumber; i++) { 0991 path.lineTo(normPath[i].x * fImageWidth, normPath[i].y * fImageHeight); 0992 } 0993 if (closeShape) { 0994 path.closeSubpath(); 0995 } 0996 0997 painter.drawPath(path); 0998 } 0999 } 1000 1001 void PagePainter::drawEllipseOnImage(QImage &image, const NormalizedPath &rect, const QPen &pen, const QBrush &brush, double penWidthMultiplier, RasterOperation op) 1002 { 1003 const double dpr = image.devicePixelRatio(); 1004 const double fImageWidth = image.width() / dpr; 1005 const double fImageHeight = image.height() / dpr; 1006 1007 // stroke outline 1008 const double penWidth = (double)pen.width() * penWidthMultiplier; 1009 QPainter painter(&image); 1010 painter.setRenderHint(QPainter::Antialiasing); 1011 QPen pen2 = pen; 1012 pen2.setWidthF(penWidth); 1013 painter.setPen(pen2); 1014 painter.setBrush(brush); 1015 1016 if (op == Multiply) { 1017 painter.setCompositionMode(QPainter::CompositionMode_Multiply); 1018 } 1019 1020 const QPointF &topLeft {rect[0].x * fImageWidth, rect[0].y * fImageHeight}; 1021 const QSizeF &size {(rect[1].x - rect[0].x) * fImageWidth, (rect[1].y - rect[0].y) * fImageHeight}; 1022 const QRectF imgRect {topLeft, size}; 1023 if (brush.style() == Qt::NoBrush) { 1024 painter.drawArc(imgRect, 0, 16 * 360); 1025 } else { 1026 painter.drawEllipse(imgRect); 1027 } 1028 } 1029 1030 LineAnnotPainter::LineAnnotPainter(const Okular::LineAnnotation *a, QSizeF pageSize, double pageScale, const QTransform &toNormalizedImage) 1031 : la {a} 1032 , pageSize {pageSize} 1033 , pageScale {pageScale} 1034 , toNormalizedImage {toNormalizedImage} 1035 , aspectRatio {pageSize.height() / pageSize.width()} 1036 , linePen {buildPen(a, a->style().width(), a->style().color())} 1037 { 1038 if ((la->lineClosed() || la->transformedLinePoints().count() == 2) && la->lineInnerColor().isValid()) { 1039 fillBrush = QBrush(la->lineInnerColor()); 1040 } 1041 } 1042 1043 void LineAnnotPainter::draw(QImage &image) const 1044 { 1045 const QList<Okular::NormalizedPoint> transformedLinePoints = la->transformedLinePoints(); 1046 if (transformedLinePoints.count() == 2) { 1047 const Okular::NormalizedPoint delta {transformedLinePoints.last().x - transformedLinePoints.first().x, transformedLinePoints.first().y - transformedLinePoints.last().y}; 1048 const double angle {atan2(delta.y * aspectRatio, delta.x)}; 1049 const double cosA {cos(-angle)}; 1050 const double sinA {sin(-angle)}; 1051 const QTransform tmpMatrix = QTransform {cosA, sinA / aspectRatio, -sinA, cosA / aspectRatio, transformedLinePoints.first().x, transformedLinePoints.first().y}; 1052 const double deaspectedY {delta.y * aspectRatio}; 1053 const double mainSegmentLength {sqrt(delta.x * delta.x + deaspectedY * deaspectedY)}; 1054 const double lineendSize {std::min(6. * la->style().width() / pageSize.width(), mainSegmentLength / 2.)}; 1055 1056 drawShortenedLine(mainSegmentLength, lineendSize, image, tmpMatrix); 1057 drawLineEnds(mainSegmentLength, lineendSize, image, tmpMatrix); 1058 drawLeaderLine(0., image, tmpMatrix); 1059 drawLeaderLine(mainSegmentLength, image, tmpMatrix); 1060 } else if (transformedLinePoints.count() > 2) { 1061 drawMainLine(image); 1062 } 1063 } 1064 1065 void LineAnnotPainter::drawMainLine(QImage &image) const 1066 { 1067 // draw the line as normalized path into image 1068 PagePainter::drawShapeOnImage(image, transformPath(la->transformedLinePoints(), toNormalizedImage), la->lineClosed(), linePen, fillBrush, pageScale); 1069 } 1070 1071 void LineAnnotPainter::drawShortenedLine(double mainSegmentLength, double size, QImage &image, const QTransform &toNormalizedPage) const 1072 { 1073 const QTransform combinedTransform {toNormalizedPage * toNormalizedImage}; 1074 const QList<Okular::NormalizedPoint> path {{shortenForArrow(size, la->lineStartStyle()), 0}, {mainSegmentLength - shortenForArrow(size, la->lineEndStyle()), 0}}; 1075 PagePainter::drawShapeOnImage(image, transformPath(path, combinedTransform), la->lineClosed(), linePen, fillBrush, pageScale); 1076 } 1077 1078 void LineAnnotPainter::drawLineEnds(double mainSegmentLength, double size, QImage &image, const QTransform &transform) const 1079 { 1080 switch (la->lineStartStyle()) { 1081 case Okular::LineAnnotation::Square: 1082 drawLineEndSquare(0, -size, transform, image); 1083 break; 1084 case Okular::LineAnnotation::Circle: 1085 drawLineEndCircle(0, -size, transform, image); 1086 break; 1087 case Okular::LineAnnotation::Diamond: 1088 drawLineEndDiamond(0, -size, transform, image); 1089 break; 1090 case Okular::LineAnnotation::OpenArrow: 1091 drawLineEndArrow(0, -size, 1., false, transform, image); 1092 break; 1093 case Okular::LineAnnotation::ClosedArrow: 1094 drawLineEndArrow(0, -size, 1., true, transform, image); 1095 break; 1096 case Okular::LineAnnotation::None: 1097 break; 1098 case Okular::LineAnnotation::Butt: 1099 drawLineEndButt(0, size, transform, image); 1100 break; 1101 case Okular::LineAnnotation::ROpenArrow: 1102 drawLineEndArrow(0, size, 1., false, transform, image); 1103 break; 1104 case Okular::LineAnnotation::RClosedArrow: 1105 drawLineEndArrow(0, size, 1., true, transform, image); 1106 break; 1107 case Okular::LineAnnotation::Slash: 1108 drawLineEndSlash(0, -size, transform, image); 1109 break; 1110 } 1111 switch (la->lineEndStyle()) { 1112 case Okular::LineAnnotation::Square: 1113 drawLineEndSquare(mainSegmentLength, size, transform, image); 1114 break; 1115 case Okular::LineAnnotation::Circle: 1116 drawLineEndCircle(mainSegmentLength, size, transform, image); 1117 break; 1118 case Okular::LineAnnotation::Diamond: 1119 drawLineEndDiamond(mainSegmentLength, size, transform, image); 1120 break; 1121 case Okular::LineAnnotation::OpenArrow: 1122 drawLineEndArrow(mainSegmentLength, size, 1., false, transform, image); 1123 break; 1124 case Okular::LineAnnotation::ClosedArrow: 1125 drawLineEndArrow(mainSegmentLength, size, 1., true, transform, image); 1126 break; 1127 case Okular::LineAnnotation::None: 1128 break; 1129 case Okular::LineAnnotation::Butt: 1130 drawLineEndButt(mainSegmentLength, size, transform, image); 1131 break; 1132 case Okular::LineAnnotation::ROpenArrow: 1133 drawLineEndArrow(mainSegmentLength, size, -1., false, transform, image); 1134 break; 1135 case Okular::LineAnnotation::RClosedArrow: 1136 drawLineEndArrow(mainSegmentLength, size, -1., true, transform, image); 1137 break; 1138 case Okular::LineAnnotation::Slash: 1139 drawLineEndSlash(mainSegmentLength, size, transform, image); 1140 break; 1141 } 1142 } 1143 1144 void LineAnnotPainter::drawLineEndArrow(double xEndPos, double size, double flipX, bool close, const QTransform &toNormalizedPage, QImage &image) const 1145 { 1146 const QTransform combinedTransform {toNormalizedPage * toNormalizedImage}; 1147 const QList<Okular::NormalizedPoint> path { 1148 {xEndPos - size * flipX, size / 2.}, 1149 {xEndPos, 0}, 1150 {xEndPos - size * flipX, -size / 2.}, 1151 }; 1152 PagePainter::drawShapeOnImage(image, transformPath(path, combinedTransform), close, linePen, fillBrush, pageScale); 1153 } 1154 1155 void LineAnnotPainter::drawLineEndButt(double xEndPos, double size, const QTransform &toNormalizedPage, QImage &image) const 1156 { 1157 const QTransform combinedTransform {toNormalizedPage * toNormalizedImage}; 1158 const double halfSize {size / 2.}; 1159 const QList<Okular::NormalizedPoint> path { 1160 {xEndPos, halfSize}, 1161 {xEndPos, -halfSize}, 1162 }; 1163 PagePainter::drawShapeOnImage(image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale); 1164 } 1165 1166 void LineAnnotPainter::drawLineEndCircle(double xEndPos, double size, const QTransform &toNormalizedPage, QImage &image) const 1167 { 1168 /* transform the circle midpoint to intermediate normalized coordinates 1169 * where it's easy to construct the bounding rect of the circle */ 1170 Okular::NormalizedPoint center; 1171 toNormalizedPage.map(xEndPos - size / 2., 0, ¢er.x, ¢er.y); 1172 const double halfSize {size / 2.}; 1173 const QList<Okular::NormalizedPoint> path { 1174 {center.x - halfSize, center.y - halfSize / aspectRatio}, 1175 {center.x + halfSize, center.y + halfSize / aspectRatio}, 1176 }; 1177 1178 /* then transform bounding rect with toNormalizedImage */ 1179 PagePainter::drawEllipseOnImage(image, transformPath(path, toNormalizedImage), linePen, fillBrush, pageScale); 1180 } 1181 1182 void LineAnnotPainter::drawLineEndSquare(double xEndPos, double size, const QTransform &toNormalizedPage, QImage &image) const 1183 { 1184 const QTransform combinedTransform {toNormalizedPage * toNormalizedImage}; 1185 const QList<Okular::NormalizedPoint> path {{xEndPos, size / 2.}, {xEndPos - size, size / 2.}, {xEndPos - size, -size / 2.}, {xEndPos, -size / 2.}}; 1186 PagePainter::drawShapeOnImage(image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale); 1187 } 1188 1189 void LineAnnotPainter::drawLineEndDiamond(double xEndPos, double size, const QTransform &toNormalizedPage, QImage &image) const 1190 { 1191 const QTransform combinedTransform {toNormalizedPage * toNormalizedImage}; 1192 const QList<Okular::NormalizedPoint> path {{xEndPos, 0}, {xEndPos - size / 2., size / 2.}, {xEndPos - size, 0}, {xEndPos - size / 2., -size / 2.}}; 1193 PagePainter::drawShapeOnImage(image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale); 1194 } 1195 1196 void LineAnnotPainter::drawLineEndSlash(double xEndPos, double size, const QTransform &toNormalizedPage, QImage &image) const 1197 { 1198 const QTransform combinedTransform {toNormalizedPage * toNormalizedImage}; 1199 const double halfSize {size / 2.}; 1200 const double xOffset {cos(M_PI / 3.) * halfSize}; 1201 const QList<Okular::NormalizedPoint> path { 1202 {xEndPos - xOffset, halfSize}, 1203 {xEndPos + xOffset, -halfSize}, 1204 }; 1205 PagePainter::drawShapeOnImage(image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale); 1206 } 1207 1208 void LineAnnotPainter::drawLeaderLine(double xEndPos, QImage &image, const QTransform &toNormalizedPage) const 1209 { 1210 const QTransform combinedTransform = toNormalizedPage * toNormalizedImage; 1211 const double ll = aspectRatio * la->lineLeadingForwardPoint() / pageSize.height(); 1212 const double lle = aspectRatio * la->lineLeadingBackwardPoint() / pageSize.height(); 1213 const int sign {ll > 0 ? -1 : 1}; 1214 QList<Okular::NormalizedPoint> path; 1215 1216 if (fabs(ll) > 0) { 1217 path.append({xEndPos, ll}); 1218 // do we have the extension on the "back"? 1219 if (fabs(lle) > 0) { 1220 path.append({xEndPos, sign * lle}); 1221 } else { 1222 path.append({xEndPos, 0}); 1223 } 1224 } 1225 PagePainter::drawShapeOnImage(image, transformPath(path, combinedTransform), false, linePen, fillBrush, pageScale); 1226 } 1227 1228 double LineAnnotPainter::shortenForArrow(double size, Okular::LineAnnotation::TermStyle endStyle) 1229 { 1230 double shortenBy {0}; 1231 1232 if (endStyle == Okular::LineAnnotation::Square || endStyle == Okular::LineAnnotation::Circle || endStyle == Okular::LineAnnotation::Diamond || endStyle == Okular::LineAnnotation::ClosedArrow) { 1233 shortenBy = size; 1234 } 1235 1236 return shortenBy; 1237 } 1238 1239 /* kate: replace-tabs on; indent-width 4; */