Warning, file /office/calligra/libs/flake/KoShapeShadow.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-2009 Jan Hambrecht <jaham@gmx.net>
0003  * Copyright (C) 2010 Thomas Zander <zander@kde.org>
0004  * Copyright (C) 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
0005  * Copyright (C) 2010-2011 Yue Liu <yue.liu@mail.com>
0006  *
0007  * This library is free software; you can redistribute it and/or
0008  * modify it under the terms of the GNU Library General Public
0009  * License as published by the Free Software Foundation; either
0010  * version 2 of the License, or (at your option) any later version.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Library General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Library General Public License
0018  * along with this library; see the file COPYING.LIB.  If not, write to
0019  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020  * Boston, MA 02110-1301, USA.
0021  */
0022 
0023 #include "KoShapeShadow.h"
0024 #include "KoShapeGroup.h"
0025 #include "KoSelection.h"
0026 #include "KoShapeSavingContext.h"
0027 #include "KoShapeStrokeModel.h"
0028 #include "KoShape.h"
0029 #include "KoInsets.h"
0030 #include "KoPathShape.h"
0031 #include <KoGenStyle.h>
0032 #include <KoViewConverter.h>
0033 #include <FlakeDebug.h>
0034 #include <QPainter>
0035 #include <QPainterPath>
0036 #include <QAtomicInt>
0037 #include <QImage>
0038 #include <QRectF>
0039 
0040 class Q_DECL_HIDDEN KoShapeShadow::Private
0041 {
0042 public:
0043     Private()
0044             : offset(2, 2), color(Qt::black), blur(8), visible(true), refCount(0) {
0045     }
0046     QPointF offset;
0047     QColor color;
0048     qreal blur;
0049     bool visible;
0050     QAtomicInt refCount;
0051 
0052     /**
0053      * Paints the shadow of the shape group to the buffer image.
0054      * @param group the shape group to paint around
0055      * @param painter the painter to paint on the image
0056      * @param converter to convert between internal and view coordinates.
0057      */
0058     void paintGroupShadow(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter);
0059     /**
0060      * Paints the shadow of the shape to the buffer image.
0061      * @param shape the shape to paint around
0062      * @param painter the painter to paint on the image
0063      * @param converter to convert between internal and view coordinates.
0064      */
0065     void paintShadow(KoShape *shape, QPainter &painter, const KoViewConverter &converter);
0066     void blurShadow(QImage &image, int radius, const QColor& shadowColor);
0067 };
0068 
0069 void KoShapeShadow::Private::paintGroupShadow(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter)
0070 {
0071     QList<KoShape*> shapes = group->shapes();
0072     foreach(KoShape *child, shapes) {
0073         // we paint recursively here, so we do not have to check recursively for visibility
0074         if (!child->isVisible())
0075             continue;
0076         painter.save();
0077         //apply group child's transformation
0078         painter.setTransform(child->absoluteTransformation(&converter), true);
0079         paintShadow(child, painter, converter);
0080         painter.restore();
0081     }
0082 }
0083 
0084 void KoShapeShadow::Private::paintShadow(KoShape *shape, QPainter &painter, const KoViewConverter &converter)
0085 {
0086     QPainterPath path(shape->shadowOutline());
0087     if (!path.isEmpty()) {
0088         painter.save();
0089         KoShape::applyConversion(painter, converter);
0090         painter.setBrush(QBrush(color));
0091 
0092         // Make sure the shadow has the same fill rule as the shape.
0093         KoPathShape * pathShape = dynamic_cast<KoPathShape*>(shape);
0094         if (pathShape)
0095             path.setFillRule(pathShape->fillRule());
0096 
0097         painter.drawPath(path);
0098         painter.restore();
0099     }
0100 
0101     if (shape->stroke()) {
0102         shape->stroke()->paint(shape, painter, converter);
0103     }
0104 }
0105 
0106 /* You can also find a BSD version to this method from
0107  * http://gitorious.org/ofi-labs/x2/blobs/master/graphics/shadowblur/
0108  */
0109 void KoShapeShadow::Private::blurShadow(QImage &image, int radius, const QColor& shadowColor)
0110 {
0111     static const int BlurSumShift = 15;
0112 
0113     // Check http://www.w3.org/TR/SVG/filters.html#
0114     // As noted in the SVG filter specification, ru
0115     // approximates a real gaussian blur nicely.
0116     // See comments in http://webkit.org/b/40793, it seems sensible
0117     // to follow Skia's limit of 128 pixels for the blur radius.
0118     if (radius > 128)
0119         radius = 128;
0120 
0121     int channels[4] = { 3, 0, 1, 3 };
0122     int dmax = radius >> 1;
0123     int dmin = dmax - 1 + (radius & 1);
0124     if (dmin < 0)
0125         dmin = 0;
0126 
0127     // Two stages: horizontal and vertical
0128     for (int k = 0; k < 2; ++k) {
0129 
0130         unsigned char* pixels = image.bits();
0131         int stride = (k == 0) ? 4 : image.bytesPerLine();
0132         int delta = (k == 0) ? image.bytesPerLine() : 4;
0133         int jfinal = (k == 0) ? image.height() : image.width();
0134         int dim = (k == 0) ? image.width() : image.height();
0135 
0136         for (int j = 0; j < jfinal; ++j, pixels += delta) {
0137 
0138             // For each step, we blur the alpha in a channel and store the result
0139             // in another channel for the subsequent step.
0140             // We use sliding window algorithm to accumulate the alpha values.
0141             // This is much more efficient than computing the sum of each pixels
0142             // covered by the box kernel size for each x.
0143 
0144             for (int step = 0; step < 3; ++step) {
0145                 int side1 = (step == 0) ? dmin : dmax;
0146                 int side2 = (step == 1) ? dmin : dmax;
0147                 int pixelCount = side1 + 1 + side2;
0148                 int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount;
0149                 int ofs = 1 + side2;
0150                 int alpha1 = pixels[channels[step]];
0151                 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
0152                 unsigned char* ptr = pixels + channels[step + 1];
0153                 unsigned char* prev = pixels + stride + channels[step];
0154                 unsigned char* next = pixels + ofs * stride + channels[step];
0155 
0156                 int i;
0157                 int sum = side1 * alpha1 + alpha1;
0158                 int limit = (dim < side2 + 1) ? dim : side2 + 1;
0159                 for (i = 1; i < limit; ++i, prev += stride)
0160                     sum += *prev;
0161                 if (limit <= side2)
0162                     sum += (side2 - limit + 1) * alpha2;
0163 
0164                 limit = (side1 < dim) ? side1 : dim;
0165                 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
0166                     *ptr = (sum * invCount) >> BlurSumShift;
0167                     sum += ((ofs < dim) ? *next : alpha2) - alpha1;
0168                 }
0169                 prev = pixels + channels[step];
0170                 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
0171                     *ptr = (sum * invCount) >> BlurSumShift;
0172                     sum += (*next) - (*prev);
0173                 }
0174                 for (; i < dim; ptr += stride, prev += stride, ++i) {
0175                     *ptr = (sum * invCount) >> BlurSumShift;
0176                     sum += alpha2 - (*prev);
0177                 }
0178             }
0179         }
0180     }
0181 
0182     // "Colorize" with the right shadow color.
0183     QPainter p(&image);
0184     p.setCompositionMode(QPainter::CompositionMode_SourceIn);
0185     p.fillRect(image.rect(), shadowColor);
0186     p.end();
0187 }
0188 
0189 
0190 // ----------------------------------------------------------------
0191 //                         KoShapeShadow
0192 
0193 
0194 KoShapeShadow::KoShapeShadow()
0195         : d(new Private())
0196 {
0197 }
0198 
0199 KoShapeShadow::~KoShapeShadow()
0200 {
0201     delete d;
0202 }
0203 
0204 KoShapeShadow::KoShapeShadow(const KoShapeShadow &rhs)
0205     : d(new Private(*rhs.d))
0206 {
0207     d->refCount = 0;
0208 }
0209 
0210 KoShapeShadow& KoShapeShadow::operator=(const KoShapeShadow &rhs)
0211 {
0212     *d = *rhs.d;
0213     d->refCount = 0;
0214     return *this;
0215 }
0216 
0217 void KoShapeShadow::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
0218 {
0219     Q_UNUSED(context);
0220 
0221     style.addProperty("draw:shadow", d->visible ? "visible" : "hidden", KoGenStyle::GraphicType);
0222     style.addProperty("draw:shadow-color", d->color.name(), KoGenStyle::GraphicType);
0223     if (d->color.alphaF() != 1.0)
0224         style.addProperty("draw:shadow-opacity", QString("%1%").arg(d->color.alphaF() * 100.0), KoGenStyle::GraphicType);
0225     style.addProperty("draw:shadow-offset-x", QString("%1pt").arg(d->offset.x()), KoGenStyle::GraphicType);
0226     style.addProperty("draw:shadow-offset-y", QString("%1pt").arg(d->offset.y()), KoGenStyle::GraphicType);
0227     if (d->blur != 0)
0228         style.addProperty("calligra:shadow-blur-radius", QString("%1pt").arg(d->blur), KoGenStyle::GraphicType);
0229 }
0230 
0231 void KoShapeShadow::paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter)
0232 {
0233     if (! d->visible)
0234         return;
0235 
0236     // So the approach we are taking here is to draw into a buffer image the size of boundingRect
0237     // We offset by the shadow offset at the time we draw into the buffer
0238     // Then we filter the image and draw it at the position of the bounding rect on canvas
0239 
0240     //the boundingRect of the shape or the KoSelection boundingRect of the group
0241     QRectF shadowRect = shape->boundingRect();
0242     QRectF zoomedClipRegion = converter.documentToView(shadowRect);
0243 
0244     // Init the buffer image
0245     QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
0246     sourceGraphic.fill(qRgba(0,0,0,0));
0247     // Init the buffer painter
0248     QPainter imagePainter(&sourceGraphic);
0249     imagePainter.setPen(Qt::NoPen);
0250     imagePainter.setBrush(Qt::NoBrush);
0251     imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
0252     // Since our imagebuffer and the canvas don't align we need to offset our drawings
0253     imagePainter.translate(-1.0f*converter.documentToView(shadowRect.topLeft()));
0254 
0255     // Handle the shadow offset
0256     imagePainter.translate(converter.documentToView(offset()));
0257 
0258     KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
0259     if (group) {
0260         d->paintGroupShadow(group, imagePainter, converter);
0261     } else {
0262         //apply shape's transformation
0263         imagePainter.setTransform(shape->absoluteTransformation(&converter), true);
0264 
0265         d->paintShadow(shape, imagePainter, converter);
0266     }
0267     imagePainter.end();
0268 
0269     // Blur the shadow (well the entire buffer)
0270     d->blurShadow(sourceGraphic, converter.documentToViewX(d->blur), d->color);
0271 
0272     // Paint the result
0273     painter.save();
0274     // The painter is initialized for us with canvas transform 'plus' shape transform
0275     // we are only interested in the canvas transform so 'subtract' the shape transform part
0276     painter.setTransform(shape->absoluteTransformation(&converter).inverted() * painter.transform());
0277     painter.drawImage(zoomedClipRegion.topLeft(), sourceGraphic);
0278     painter.restore();
0279 }
0280 
0281 void KoShapeShadow::setOffset(const QPointF & offset)
0282 {
0283     d->offset = offset;
0284 }
0285 
0286 QPointF KoShapeShadow::offset() const
0287 {
0288     return d->offset;
0289 }
0290 
0291 void KoShapeShadow::setColor(const QColor &color)
0292 {
0293     d->color = color;
0294 }
0295 
0296 QColor KoShapeShadow::color() const
0297 {
0298     return d->color;
0299 }
0300 
0301 void KoShapeShadow::setBlur(qreal blur)
0302 {
0303     // force positive blur radius
0304     d->blur = qAbs(blur);
0305 }
0306 
0307 qreal KoShapeShadow::blur() const
0308 {
0309     return d->blur;
0310 }
0311 
0312 void KoShapeShadow::setVisible(bool visible)
0313 {
0314     d->visible = visible;
0315 }
0316 
0317 bool KoShapeShadow::isVisible() const
0318 {
0319     return d->visible;
0320 }
0321 
0322 void KoShapeShadow::insets(KoInsets &insets) const
0323 {
0324     if (!d->visible) {
0325         insets.top = 0;
0326         insets.bottom = 0;
0327         insets.left = 0;
0328         insets.right = 0;
0329         return;
0330     }
0331 
0332     qreal expand = d->blur;
0333 
0334     insets.left = (d->offset.x() < 0.0) ? qAbs(d->offset.x()) : 0.0;
0335     insets.top = (d->offset.y() < 0.0) ? qAbs(d->offset.y()) : 0.0;
0336     insets.right = (d->offset.x() > 0.0) ? d->offset.x() : 0.0;
0337     insets.bottom = (d->offset.y() > 0.0) ? d->offset.y() : 0.0;
0338 
0339     insets.left += expand;
0340     insets.top += expand;
0341     insets.right += expand;
0342     insets.bottom += expand;
0343 }
0344 
0345 bool KoShapeShadow::ref()
0346 {
0347     return d->refCount.ref();
0348 }
0349 
0350 bool KoShapeShadow::deref()
0351 {
0352     return d->refCount.deref();
0353 }
0354 
0355 int KoShapeShadow::useCount() const
0356 {
0357     return d->refCount;
0358 }