Warning, file /office/calligra/libs/flake/KoPatternBackground.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Library General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2 of the License, or (at your option) any later version.
0008  *
0009  * This library is distributed in the hope that it will be useful,
0010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012  * Library General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU Library General Public License
0015  * along with this library; see the file COPYING.LIB.  If not, write to
0016  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018  */
0019 
0020 #include "KoPatternBackground.h"
0021 #include "KoShapeBackground_p.h"
0022 #include "KoShapeSavingContext.h"
0023 #include "KoImageData.h"
0024 #include "KoImageCollection.h"
0025 #include <KoStyleStack.h>
0026 #include <KoGenStyle.h>
0027 #include <KoGenStyles.h>
0028 #include <KoXmlNS.h>
0029 #include <KoOdfLoadingContext.h>
0030 #include <KoOdfGraphicStyles.h>
0031 #include <KoOdfStylesReader.h>
0032 #include <KoUnit.h>
0033 #include <KoViewConverter.h>
0034 
0035 #include <FlakeDebug.h>
0036 
0037 #include <QBrush>
0038 #include <QPainter>
0039 #include <QPainterPath>
0040 
0041 class KoPatternBackgroundPrivate : public KoShapeBackgroundPrivate
0042 {
0043 public:
0044     KoPatternBackgroundPrivate()
0045         : repeat(KoPatternBackground::Tiled)
0046         , refPoint(KoPatternBackground::TopLeft)
0047         , imageCollection(0)
0048         , imageData(0)
0049     {
0050     }
0051 
0052     ~KoPatternBackgroundPrivate() override {
0053         delete imageData;
0054     }
0055 
0056     QSizeF targetSize() const {
0057         QSizeF size = imageData->imageSize();
0058         if (targetImageSizePercent.width() > 0.0)
0059             size.setWidth(0.01 * targetImageSizePercent.width() * size.width());
0060         else if (targetImageSize.width() > 0.0)
0061             size.setWidth(targetImageSize.width());
0062         if (targetImageSizePercent.height() > 0.0)
0063             size.setHeight(0.01 * targetImageSizePercent.height() * size.height());
0064         else if (targetImageSize.height() > 0.0)
0065             size.setHeight(targetImageSize.height());
0066 
0067         return size;
0068     }
0069 
0070     QPointF offsetFromRect(const QRectF &fillRect, const QSizeF &imageSize) const {
0071         QPointF offset;
0072         switch (refPoint) {
0073         case KoPatternBackground::TopLeft:
0074             offset = fillRect.topLeft();
0075             break;
0076         case KoPatternBackground::Top:
0077             offset.setX(fillRect.center().x() - 0.5 * imageSize.width());
0078             offset.setY(fillRect.top());
0079             break;
0080         case KoPatternBackground::TopRight:
0081             offset.setX(fillRect.right() - imageSize.width());
0082             offset.setY(fillRect.top());
0083             break;
0084         case KoPatternBackground::Left:
0085             offset.setX(fillRect.left());
0086             offset.setY(fillRect.center().y() - 0.5 * imageSize.height());
0087             break;
0088         case KoPatternBackground::Center:
0089             offset.setX(fillRect.center().x() - 0.5 * imageSize.width());
0090             offset.setY(fillRect.center().y() - 0.5 * imageSize.height());
0091             break;
0092         case KoPatternBackground::Right:
0093             offset.setX(fillRect.right() - imageSize.width());
0094             offset.setY(fillRect.center().y() - 0.5 * imageSize.height());
0095             break;
0096         case KoPatternBackground::BottomLeft:
0097             offset.setX(fillRect.left());
0098             offset.setY(fillRect.bottom() - imageSize.height());
0099             break;
0100         case KoPatternBackground::Bottom:
0101             offset.setX(fillRect.center().x() - 0.5 * imageSize.width());
0102             offset.setY(fillRect.bottom() - imageSize.height());
0103             break;
0104         case KoPatternBackground::BottomRight:
0105             offset.setX(fillRect.right() - imageSize.width());
0106             offset.setY(fillRect.bottom() - imageSize.height());
0107             break;
0108         default:
0109             break;
0110         }
0111         if (refPointOffsetPercent.x() > 0.0)
0112             offset += QPointF(0.01 * refPointOffsetPercent.x() * imageSize.width(), 0);
0113         if (refPointOffsetPercent.y() > 0.0)
0114             offset += QPointF(0, 0.01 * refPointOffsetPercent.y() * imageSize.height());
0115 
0116         return offset;
0117     }
0118 
0119     QTransform matrix;
0120     KoPatternBackground::PatternRepeat repeat;
0121     KoPatternBackground::ReferencePoint refPoint;
0122     QSizeF targetImageSize;
0123     QSizeF targetImageSizePercent;
0124     QPointF refPointOffsetPercent;
0125     QPointF tileRepeatOffsetPercent;
0126     KoImageCollection * imageCollection;
0127     KoImageData * imageData;
0128 };
0129 
0130 
0131 // ----------------------------------------------------------------
0132 
0133 
0134 KoPatternBackground::KoPatternBackground(KoImageCollection * imageCollection)
0135         : KoShapeBackground(*(new KoPatternBackgroundPrivate()))
0136 {
0137     Q_D(KoPatternBackground);
0138     d->imageCollection = imageCollection;
0139     Q_ASSERT(d->imageCollection);
0140 }
0141 
0142 KoPatternBackground::~KoPatternBackground()
0143 {
0144     //Q_D(KoPatternBackground);
0145 }
0146 
0147 void KoPatternBackground::setTransform(const QTransform &matrix)
0148 {
0149     Q_D(KoPatternBackground);
0150     d->matrix = matrix;
0151 }
0152 
0153 QTransform KoPatternBackground::transform() const
0154 {
0155     Q_D(const KoPatternBackground);
0156     return d->matrix;
0157 }
0158 
0159 void KoPatternBackground::setPattern(const QImage &pattern)
0160 {
0161     Q_D(KoPatternBackground);
0162     delete d->imageData;
0163     d->imageData = d->imageCollection->createImageData(pattern);
0164 }
0165 
0166 void KoPatternBackground::setPattern(KoImageData *imageData)
0167 {
0168     Q_D(KoPatternBackground);
0169     delete d->imageData;
0170 
0171     d->imageData = imageData;
0172 }
0173 
0174 QImage KoPatternBackground::pattern() const
0175 {
0176     Q_D(const KoPatternBackground);
0177     if (d->imageData)
0178         return d->imageData->image();
0179     return QImage();
0180 }
0181 
0182 void KoPatternBackground::setRepeat(PatternRepeat repeat)
0183 {
0184     Q_D(KoPatternBackground);
0185     d->repeat = repeat;
0186 }
0187 
0188 KoPatternBackground::PatternRepeat KoPatternBackground::repeat() const
0189 {
0190     Q_D(const KoPatternBackground);
0191     return d->repeat;
0192 }
0193 
0194 KoPatternBackground::ReferencePoint KoPatternBackground::referencePoint() const
0195 {
0196     Q_D(const KoPatternBackground);
0197     return d->refPoint;
0198 }
0199 
0200 void KoPatternBackground::setReferencePoint(ReferencePoint referencePoint)
0201 {
0202     Q_D(KoPatternBackground);
0203     d->refPoint = referencePoint;
0204 }
0205 
0206 QPointF KoPatternBackground::referencePointOffset() const
0207 {
0208     Q_D(const KoPatternBackground);
0209     return d->refPointOffsetPercent;
0210 }
0211 
0212 void KoPatternBackground::setReferencePointOffset(const QPointF &offset)
0213 {
0214     Q_D(KoPatternBackground);
0215     qreal ox = qMax(qreal(0.0), qMin(qreal(100.0), offset.x()));
0216     qreal oy = qMax(qreal(0.0), qMin(qreal(100.0), offset.y()));
0217 
0218     d->refPointOffsetPercent = QPointF(ox, oy);
0219 }
0220 
0221 QPointF KoPatternBackground::tileRepeatOffset() const
0222 {
0223     Q_D(const KoPatternBackground);
0224     return d->tileRepeatOffsetPercent;
0225 }
0226 
0227 void KoPatternBackground::setTileRepeatOffset(const QPointF &offset)
0228 {
0229     Q_D(KoPatternBackground);
0230     d->tileRepeatOffsetPercent = offset;
0231 }
0232 
0233 QSizeF KoPatternBackground::patternDisplaySize() const
0234 {
0235     Q_D(const KoPatternBackground);
0236     return d->targetSize();
0237 }
0238 
0239 void KoPatternBackground::setPatternDisplaySize(const QSizeF &size)
0240 {
0241     Q_D(KoPatternBackground);
0242     d->targetImageSizePercent = QSizeF();
0243     d->targetImageSize = size;
0244 }
0245 
0246 QSizeF KoPatternBackground::patternOriginalSize() const
0247 {
0248     Q_D(const KoPatternBackground);
0249     return d->imageData->imageSize();
0250 }
0251 
0252 void KoPatternBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
0253 {
0254     Q_D(const KoPatternBackground);
0255     if (! d->imageData)
0256         return;
0257 
0258     painter.save();
0259 
0260     if (d->repeat == Tiled) {
0261         // calculate scaling of pixmap
0262         QSizeF targetSize = d->targetSize();
0263         QSizeF imageSize = d->imageData->imageSize();
0264         qreal scaleX = targetSize.width() / imageSize.width();
0265         qreal scaleY = targetSize.height() / imageSize.height();
0266 
0267         QRectF targetRect = fillPath.boundingRect();
0268         // undo scaling on target rectangle
0269         targetRect.setWidth(targetRect.width() / scaleX);
0270         targetRect.setHeight(targetRect.height() / scaleY);
0271 
0272         // determine pattern offset
0273         QPointF offset = d->offsetFromRect(targetRect, imageSize);
0274 
0275         // create matrix for pixmap scaling
0276         QTransform matrix;
0277         matrix.scale(scaleX, scaleY);
0278 
0279         painter.setClipPath(fillPath);
0280         painter.setWorldTransform(matrix, true);
0281         painter.drawTiledPixmap(targetRect, d->imageData->pixmap(imageSize.toSize()), -offset);
0282     } else if (d->repeat == Original) {
0283         QRectF sourceRect(QPointF(0, 0), d->imageData->imageSize());
0284         QRectF targetRect(QPoint(0, 0), d->targetSize());
0285         targetRect.moveCenter(fillPath.boundingRect().center());
0286         painter.setClipPath(fillPath);
0287         painter.drawPixmap(targetRect, d->imageData->pixmap(sourceRect.size().toSize()), sourceRect);
0288     } else if (d->repeat == Stretched) {
0289         painter.setClipPath(fillPath);
0290         // undo conversion of the scaling so that we can use a nicely scaled image of the correct size
0291         qreal zoomX, zoomY;
0292         converter.zoom(&zoomX, &zoomY);
0293         zoomX = zoomX ? 1 / zoomX : zoomX;
0294         zoomY = zoomY ? 1 / zoomY : zoomY;
0295         painter.scale(zoomX, zoomY);
0296 
0297         QRectF targetRect = converter.documentToView(fillPath.boundingRect());
0298         painter.drawPixmap(targetRect.topLeft(), d->imageData->pixmap(targetRect.size().toSize()));
0299     }
0300 
0301     painter.restore();
0302 }
0303 
0304 void KoPatternBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
0305 {
0306     Q_D(KoPatternBackground);
0307     if (! d->imageData)
0308         return;
0309 
0310     switch (d->repeat) {
0311     case Original:
0312         style.addProperty("style:repeat", "no-repeat");
0313         break;
0314     case Tiled:
0315         style.addProperty("style:repeat", "repeat");
0316         break;
0317     case Stretched:
0318         style.addProperty("style:repeat", "stretch");
0319         break;
0320     }
0321 
0322     if (d->repeat == Tiled) {
0323         QString refPointId = "top-left";
0324         switch (d->refPoint) {
0325         case TopLeft: refPointId = "top-left"; break;
0326         case Top: refPointId = "top"; break;
0327         case TopRight: refPointId = "top-right"; break;
0328         case Left: refPointId = "left"; break;
0329         case Center: refPointId = "center"; break;
0330         case Right: refPointId = "right"; break;
0331         case BottomLeft: refPointId = "bottom-left"; break;
0332         case Bottom: refPointId = "bottom"; break;
0333         case BottomRight: refPointId = "bottom-right"; break;
0334         }
0335         style.addProperty("draw:fill-image-ref-point", refPointId);
0336         if (d->refPointOffsetPercent.x() > 0.0)
0337             style.addProperty("draw:fill-image-ref-point-x", QString("%1%").arg(d->refPointOffsetPercent.x()));
0338         if (d->refPointOffsetPercent.y() > 0.0)
0339             style.addProperty("draw:fill-image-ref-point-y", QString("%1%").arg(d->refPointOffsetPercent.y()));
0340     }
0341 
0342     if (d->repeat != Stretched) {
0343         QSizeF targetSize = d->targetSize();
0344         QSizeF imageSize = d->imageData->imageSize();
0345         if (targetSize.height() != imageSize.height())
0346             style.addPropertyPt("draw:fill-image-height", targetSize.height());
0347         if (targetSize.width() != imageSize.width())
0348             style.addPropertyPt("draw:fill-image-width", targetSize.width());
0349     }
0350 
0351     KoGenStyle patternStyle(KoGenStyle::FillImageStyle /*no family name*/);
0352     patternStyle.addAttribute("xlink:show", "embed");
0353     patternStyle.addAttribute("xlink:actuate", "onLoad");
0354     patternStyle.addAttribute("xlink:type", "simple");
0355     patternStyle.addAttribute("xlink:href", context.imageHref(d->imageData));
0356 
0357     QString patternStyleName = context.mainStyles().insert(patternStyle, "picture");
0358     style.addProperty("draw:fill", "bitmap");
0359     style.addProperty("draw:fill-image-name", patternStyleName);
0360 
0361     context.addDataCenter(d->imageCollection);
0362 }
0363 
0364 bool KoPatternBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &)
0365 {
0366     Q_D(KoPatternBackground);
0367     KoStyleStack &styleStack = context.styleStack();
0368     if (! styleStack.hasProperty(KoXmlNS::draw, "fill"))
0369         return false;
0370 
0371     QString fillStyle = styleStack.property(KoXmlNS::draw, "fill");
0372     if (fillStyle != "bitmap")
0373         return false;
0374 
0375     QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name");
0376 
0377     KoXmlElement* e = context.stylesReader().drawStyles("fill-image").value(styleName);
0378     if (! e)
0379         return false;
0380 
0381     const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString());
0382     if (href.isEmpty())
0383         return false;
0384 
0385     delete d->imageData;
0386     d->imageData = d->imageCollection->createImageData(href, context.store());
0387     if (! d->imageData)
0388         return false;
0389 
0390     // read the pattern repeat style
0391     QString style = styleStack.property(KoXmlNS::style, "repeat");
0392     if (style == "stretch")
0393         d->repeat = Stretched;
0394     else if (style == "no-repeat")
0395         d->repeat = Original;
0396     else
0397         d->repeat = Tiled;
0398 
0399     if (style != "stretch") {
0400         // optional attributes which can override original image size
0401         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height")) {
0402             QString height = styleStack.property(KoXmlNS::draw, "fill-image-height");
0403             if (height.endsWith('%'))
0404                 d->targetImageSizePercent.setHeight(height.remove('%').toDouble());
0405             else
0406                 d->targetImageSize.setHeight(KoUnit::parseValue(height));
0407         }
0408         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) {
0409             QString width = styleStack.property(KoXmlNS::draw, "fill-image-width");
0410             if (width.endsWith('%'))
0411                 d->targetImageSizePercent.setWidth(width.remove('%').toDouble());
0412             else
0413                 d->targetImageSize.setWidth(KoUnit::parseValue(width));
0414         }
0415     }
0416 
0417     if (style == "repeat") {
0418         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) {
0419             // align pattern to the given size
0420             QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point");
0421             if (align == "top-left")
0422                 d->refPoint = TopLeft;
0423             else if (align == "top")
0424                 d->refPoint = Top;
0425             else if (align == "top-right")
0426                 d->refPoint = TopRight;
0427             else if (align == "left")
0428                 d->refPoint = Left;
0429             else if (align == "center")
0430                 d->refPoint = Center;
0431             else if (align == "right")
0432                 d->refPoint = Right;
0433             else if (align == "bottom-left")
0434                 d->refPoint = BottomLeft;
0435             else if (align == "bottom")
0436                 d->refPoint = Bottom;
0437             else if (align == "bottom-right")
0438                 d->refPoint = BottomRight;
0439         }
0440         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) {
0441             QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x");
0442             d->refPointOffsetPercent.setX(pointX.remove('%').toDouble());
0443         }
0444         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) {
0445             QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y");
0446             d->refPointOffsetPercent.setY(pointY.remove('%').toDouble());
0447         }
0448         if (styleStack.hasProperty(KoXmlNS::draw, "tile-repeat-offset")) {
0449             QString repeatOffset = styleStack.property(KoXmlNS::draw, "tile-repeat-offset");
0450             QStringList tokens = repeatOffset.split('%');
0451             if (tokens.count() == 2) {
0452                 QString direction = tokens[1].simplified();
0453                 if (direction == "horizontal")
0454                     d->tileRepeatOffsetPercent.setX(tokens[0].toDouble());
0455                 else if (direction == "vertical")
0456                     d->tileRepeatOffsetPercent.setY(tokens[0].toDouble());
0457             }
0458         }
0459     }
0460 
0461     return true;
0462 }
0463 
0464 QRectF KoPatternBackground::patternRectFromFillSize(const QSizeF &size)
0465 {
0466     Q_D(KoPatternBackground);
0467     QRectF rect;
0468 
0469     switch (d->repeat) {
0470     case Tiled:
0471         rect.setTopLeft(d->offsetFromRect(QRectF(QPointF(), size), d->targetSize()));
0472         rect.setSize(d->targetSize());
0473         break;
0474     case Original:
0475         rect.setLeft(0.5 * (size.width() - d->targetSize().width()));
0476         rect.setTop(0.5 * (size.height() - d->targetSize().height()));
0477         rect.setSize(d->targetSize());
0478         break;
0479     case Stretched:
0480         rect.setTopLeft(QPointF(0.0, 0.0));
0481         rect.setSize(size);
0482         break;
0483     }
0484 
0485     return rect;
0486 }