summaryrefslogtreecommitdiffstats
path: root/chalk/core/kis_gradient_painter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'chalk/core/kis_gradient_painter.cpp')
-rw-r--r--chalk/core/kis_gradient_painter.cpp723
1 files changed, 723 insertions, 0 deletions
diff --git a/chalk/core/kis_gradient_painter.cpp b/chalk/core/kis_gradient_painter.cpp
new file mode 100644
index 000000000..7c2b00b18
--- /dev/null
+++ b/chalk/core/kis_gradient_painter.cpp
@@ -0,0 +1,723 @@
+/*
+ * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <cfloat>
+
+#include "tqbrush.h"
+#include "tqcolor.h"
+#include "tqfontinfo.h"
+#include "tqfontmetrics.h"
+#include "tqpen.h"
+#include "tqregion.h"
+#include "tqwmatrix.h"
+#include <tqimage.h>
+#include <tqmap.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpointarray.h>
+#include <tqrect.h>
+#include <tqstring.h>
+
+#include <kdebug.h>
+#include <tdelocale.h>
+
+#include "kis_brush.h"
+#include "kis_debug_areas.h"
+#include "kis_gradient.h"
+#include "kis_image.h"
+#include "kis_iterators_pixel.h"
+#include "kis_layer.h"
+#include "kis_paint_device.h"
+#include "kis_pattern.h"
+#include "kis_rect.h"
+#include "kis_colorspace.h"
+#include "kis_types.h"
+#include "kis_vec.h"
+#include "kis_selection.h"
+#include "kis_gradient_painter.h"
+#include "kis_meta_registry.h"
+#include "kis_colorspace_factory_registry.h"
+
+namespace {
+
+ class GradientShapeStrategy {
+ public:
+ GradientShapeStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
+ virtual ~GradientShapeStrategy() {}
+
+ virtual double valueAt(double x, double y) const = 0;
+
+ protected:
+ KisPoint m_gradientVectorStart;
+ KisPoint m_gradientVectorEnd;
+ };
+
+ GradientShapeStrategy::GradientShapeStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
+ : m_gradientVectorStart(gradientVectorStart), m_gradientVectorEnd(gradientVectorEnd)
+ {
+ }
+
+
+ class LinearGradientStrategy : public GradientShapeStrategy {
+ typedef GradientShapeStrategy super;
+ public:
+ LinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
+
+ virtual double valueAt(double x, double y) const;
+
+ protected:
+ double m_normalisedVectorX;
+ double m_normalisedVectorY;
+ double m_vectorLength;
+ };
+
+ LinearGradientStrategy::LinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
+ : super(gradientVectorStart, gradientVectorEnd)
+ {
+ double dx = gradientVectorEnd.x() - gradientVectorStart.x();
+ double dy = gradientVectorEnd.y() - gradientVectorStart.y();
+
+ m_vectorLength = sqrt((dx * dx) + (dy * dy));
+
+ if (m_vectorLength < DBL_EPSILON) {
+ m_normalisedVectorX = 0;
+ m_normalisedVectorY = 0;
+ }
+ else {
+ m_normalisedVectorX = dx / m_vectorLength;
+ m_normalisedVectorY = dy / m_vectorLength;
+ }
+ }
+
+ double LinearGradientStrategy::valueAt(double x, double y) const
+ {
+ double vx = x - m_gradientVectorStart.x();
+ double vy = y - m_gradientVectorStart.y();
+
+ // Project the vector onto the normalised gradient vector.
+ double t = vx * m_normalisedVectorX + vy * m_normalisedVectorY;
+
+ if (m_vectorLength < DBL_EPSILON) {
+ t = 0;
+ }
+ else {
+ // Scale to 0 to 1 over the gradient vector length.
+ t /= m_vectorLength;
+ }
+
+ return t;
+ }
+
+
+ class BiLinearGradientStrategy : public LinearGradientStrategy {
+ typedef LinearGradientStrategy super;
+ public:
+ BiLinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
+
+ virtual double valueAt(double x, double y) const;
+ };
+
+ BiLinearGradientStrategy::BiLinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
+ : super(gradientVectorStart, gradientVectorEnd)
+ {
+ }
+
+ double BiLinearGradientStrategy::valueAt(double x, double y) const
+ {
+ double t = super::valueAt(x, y);
+
+ // Reflect
+ if (t < -DBL_EPSILON) {
+ t = -t;
+ }
+
+ return t;
+ }
+
+
+ class RadialGradientStrategy : public GradientShapeStrategy {
+ typedef GradientShapeStrategy super;
+ public:
+ RadialGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
+
+ virtual double valueAt(double x, double y) const;
+
+ protected:
+ double m_radius;
+ };
+
+ RadialGradientStrategy::RadialGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
+ : super(gradientVectorStart, gradientVectorEnd)
+ {
+ double dx = gradientVectorEnd.x() - gradientVectorStart.x();
+ double dy = gradientVectorEnd.y() - gradientVectorStart.y();
+
+ m_radius = sqrt((dx * dx) + (dy * dy));
+ }
+
+ double RadialGradientStrategy::valueAt(double x, double y) const
+ {
+ double dx = x - m_gradientVectorStart.x();
+ double dy = y - m_gradientVectorStart.y();
+
+ double distance = sqrt((dx * dx) + (dy * dy));
+
+ double t;
+
+ if (m_radius < DBL_EPSILON) {
+ t = 0;
+ }
+ else {
+ t = distance / m_radius;
+ }
+
+ return t;
+ }
+
+
+ class SquareGradientStrategy : public GradientShapeStrategy {
+ typedef GradientShapeStrategy super;
+ public:
+ SquareGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
+
+ virtual double valueAt(double x, double y) const;
+
+ protected:
+ double m_normalisedVectorX;
+ double m_normalisedVectorY;
+ double m_vectorLength;
+ };
+
+ SquareGradientStrategy::SquareGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
+ : super(gradientVectorStart, gradientVectorEnd)
+ {
+ double dx = gradientVectorEnd.x() - gradientVectorStart.x();
+ double dy = gradientVectorEnd.y() - gradientVectorStart.y();
+
+ m_vectorLength = sqrt((dx * dx) + (dy * dy));
+
+ if (m_vectorLength < DBL_EPSILON) {
+ m_normalisedVectorX = 0;
+ m_normalisedVectorY = 0;
+ }
+ else {
+ m_normalisedVectorX = dx / m_vectorLength;
+ m_normalisedVectorY = dy / m_vectorLength;
+ }
+ }
+
+ double SquareGradientStrategy::valueAt(double x, double y) const
+ {
+ double px = x - m_gradientVectorStart.x();
+ double py = y - m_gradientVectorStart.y();
+
+ double distance1 = 0;
+ double distance2 = 0;
+
+ if (m_vectorLength > DBL_EPSILON) {
+
+ // Point to line distance is:
+ // distance = ((l0.y() - l1.y()) * p.x() + (l1.x() - l0.x()) * p.y() + l0.x() * l1.y() - l1.x() * l0.y()) / m_vectorLength;
+ //
+ // Here l0 = (0, 0) and |l1 - l0| = 1
+
+ distance1 = -m_normalisedVectorY * px + m_normalisedVectorX * py;
+ distance1 = fabs(distance1);
+
+ // Rotate point by 90 degrees and get the distance to the perpendicular
+ distance2 = -m_normalisedVectorY * -py + m_normalisedVectorX * px;
+ distance2 = fabs(distance2);
+ }
+
+ double t = TQMAX(distance1, distance2) / m_vectorLength;
+
+ return t;
+ }
+
+
+ class ConicalGradientStrategy : public GradientShapeStrategy {
+ typedef GradientShapeStrategy super;
+ public:
+ ConicalGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
+
+ virtual double valueAt(double x, double y) const;
+
+ protected:
+ double m_vectorAngle;
+ };
+
+ ConicalGradientStrategy::ConicalGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
+ : super(gradientVectorStart, gradientVectorEnd)
+ {
+ double dx = gradientVectorEnd.x() - gradientVectorStart.x();
+ double dy = gradientVectorEnd.y() - gradientVectorStart.y();
+
+ // Get angle from 0 to 2 PI.
+ m_vectorAngle = atan2(dy, dx) + M_PI;
+ }
+
+ double ConicalGradientStrategy::valueAt(double x, double y) const
+ {
+ double px = x - m_gradientVectorStart.x();
+ double py = y - m_gradientVectorStart.y();
+
+ double angle = atan2(py, px) + M_PI;
+
+ angle -= m_vectorAngle;
+
+ if (angle < 0) {
+ angle += 2 * M_PI;
+ }
+
+ double t = angle / (2 * M_PI);
+
+ return t;
+ }
+
+
+ class ConicalSymetricGradientStrategy : public GradientShapeStrategy {
+ typedef GradientShapeStrategy super;
+ public:
+ ConicalSymetricGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
+
+ virtual double valueAt(double x, double y) const;
+
+ protected:
+ double m_vectorAngle;
+ };
+
+ ConicalSymetricGradientStrategy::ConicalSymetricGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
+ : super(gradientVectorStart, gradientVectorEnd)
+ {
+ double dx = gradientVectorEnd.x() - gradientVectorStart.x();
+ double dy = gradientVectorEnd.y() - gradientVectorStart.y();
+
+ // Get angle from 0 to 2 PI.
+ m_vectorAngle = atan2(dy, dx) + M_PI;
+ }
+
+ double ConicalSymetricGradientStrategy::valueAt(double x, double y) const
+ {
+ double px = x - m_gradientVectorStart.x();
+ double py = y - m_gradientVectorStart.y();
+
+ double angle = atan2(py, px) + M_PI;
+
+ angle -= m_vectorAngle;
+
+ if (angle < 0) {
+ angle += 2 * M_PI;
+ }
+
+ double t;
+
+ if (angle < M_PI) {
+ t = angle / M_PI;
+ }
+ else {
+ t = 1 - ((angle - M_PI) / M_PI);
+ }
+
+ return t;
+ }
+
+
+ class GradientRepeatStrategy {
+ public:
+ GradientRepeatStrategy() {}
+ virtual ~GradientRepeatStrategy() {}
+
+ virtual double valueAt(double t) const = 0;
+ };
+
+
+ class GradientRepeatNoneStrategy : public GradientRepeatStrategy {
+ public:
+ static GradientRepeatNoneStrategy *instance();
+
+ virtual double valueAt(double t) const;
+
+ private:
+ GradientRepeatNoneStrategy() {}
+
+ static GradientRepeatNoneStrategy *m_instance;
+ };
+
+ GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::m_instance = 0;
+
+ GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::instance()
+ {
+ if (m_instance == 0) {
+ m_instance = new GradientRepeatNoneStrategy();
+ TQ_CHECK_PTR(m_instance);
+ }
+
+ return m_instance;
+ }
+
+ // Output is clamped to 0 to 1.
+ double GradientRepeatNoneStrategy::valueAt(double t) const
+ {
+ double value = t;
+
+ if (t < DBL_EPSILON) {
+ value = 0;
+ }
+ else
+ if (t > 1 - DBL_EPSILON) {
+ value = 1;
+ }
+
+ return value;
+ }
+
+
+ class GradientRepeatForwardsStrategy : public GradientRepeatStrategy {
+ public:
+ static GradientRepeatForwardsStrategy *instance();
+
+ virtual double valueAt(double t) const;
+
+ private:
+ GradientRepeatForwardsStrategy() {}
+
+ static GradientRepeatForwardsStrategy *m_instance;
+ };
+
+ GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::m_instance = 0;
+
+ GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::instance()
+ {
+ if (m_instance == 0) {
+ m_instance = new GradientRepeatForwardsStrategy();
+ TQ_CHECK_PTR(m_instance);
+ }
+
+ return m_instance;
+ }
+
+ // Output is 0 to 1, 0 to 1, 0 to 1...
+ double GradientRepeatForwardsStrategy::valueAt(double t) const
+ {
+ int i = static_cast<int>(t);
+
+ if (t < DBL_EPSILON) {
+ i--;
+ }
+
+ double value = t - i;
+
+ return value;
+ }
+
+
+ class GradientRepeatAlternateStrategy : public GradientRepeatStrategy {
+ public:
+ static GradientRepeatAlternateStrategy *instance();
+
+ virtual double valueAt(double t) const;
+
+ private:
+ GradientRepeatAlternateStrategy() {}
+
+ static GradientRepeatAlternateStrategy *m_instance;
+ };
+
+ GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::m_instance = 0;
+
+ GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::instance()
+ {
+ if (m_instance == 0) {
+ m_instance = new GradientRepeatAlternateStrategy();
+ TQ_CHECK_PTR(m_instance);
+ }
+
+ return m_instance;
+ }
+
+ // Output is 0 to 1, 1 to 0, 0 to 1, 1 to 0...
+ double GradientRepeatAlternateStrategy::valueAt(double t) const
+ {
+ if (t < 0) {
+ t = -t;
+ }
+
+ int i = static_cast<int>(t);
+
+ double value = t - i;
+
+ if (i % 2 == 1) {
+ value = 1 - value;
+ }
+
+ return value;
+ }
+}
+
+KisGradientPainter::KisGradientPainter()
+ : super()
+{
+ m_gradient = 0;
+}
+
+KisGradientPainter::KisGradientPainter(KisPaintDeviceSP device) : super(device), m_gradient(0)
+{
+}
+
+bool KisGradientPainter::paintGradient(const KisPoint& gradientVectorStart,
+ const KisPoint& gradientVectorEnd,
+ enumGradientShape shape,
+ enumGradientRepeat repeat,
+ double antiAliasThreshold,
+ bool reverseGradient,
+ TQ_INT32 startx,
+ TQ_INT32 starty,
+ TQ_INT32 width,
+ TQ_INT32 height)
+{
+ m_cancelRequested = false;
+
+ if (!m_gradient) return false;
+
+ GradientShapeStrategy *shapeStrategy = 0;
+
+ switch (shape) {
+ case GradientShapeLinear:
+ shapeStrategy = new LinearGradientStrategy(gradientVectorStart, gradientVectorEnd);
+ break;
+ case GradientShapeBiLinear:
+ shapeStrategy = new BiLinearGradientStrategy(gradientVectorStart, gradientVectorEnd);
+ break;
+ case GradientShapeRadial:
+ shapeStrategy = new RadialGradientStrategy(gradientVectorStart, gradientVectorEnd);
+ break;
+ case GradientShapeSquare:
+ shapeStrategy = new SquareGradientStrategy(gradientVectorStart, gradientVectorEnd);
+ break;
+ case GradientShapeConical:
+ shapeStrategy = new ConicalGradientStrategy(gradientVectorStart, gradientVectorEnd);
+ break;
+ case GradientShapeConicalSymetric:
+ shapeStrategy = new ConicalSymetricGradientStrategy(gradientVectorStart, gradientVectorEnd);
+ break;
+ }
+ TQ_CHECK_PTR(shapeStrategy);
+
+ GradientRepeatStrategy *repeatStrategy = 0;
+
+ switch (repeat) {
+ case GradientRepeatNone:
+ repeatStrategy = GradientRepeatNoneStrategy::instance();
+ break;
+ case GradientRepeatForwards:
+ repeatStrategy = GradientRepeatForwardsStrategy::instance();
+ break;
+ case GradientRepeatAlternate:
+ repeatStrategy = GradientRepeatAlternateStrategy::instance();
+ break;
+ }
+ Q_ASSERT(repeatStrategy != 0);
+
+
+ //If the device has a selection only iterate over that selection
+ TQRect r;
+ if( m_device->hasSelection() ) {
+ r = m_device->selection()->selectedExactRect();
+ startx = r.x();
+ starty = r.y();
+ width = r.width();
+ height = r.height();
+ }
+
+ TQ_INT32 endx = startx + width - 1;
+ TQ_INT32 endy = starty + height - 1;
+
+ TQImage layer (width, height, 32);
+ layer.setAlphaBuffer(true);
+
+ int pixelsProcessed = 0;
+ int lastProgressPercent = 0;
+
+ emit notifyProgressStage(i18n("Rendering gradient..."), 0);
+
+ int totalPixels = width * height;
+
+ if (antiAliasThreshold < 1 - DBL_EPSILON) {
+ totalPixels *= 2;
+ }
+
+ for (int y = starty; y <= endy; y++) {
+ for (int x = startx; x <= endx; x++) {
+
+ double t = shapeStrategy->valueAt( x, y);
+ t = repeatStrategy->valueAt(t);
+
+ if (reverseGradient) {
+ t = 1 - t;
+ }
+
+ TQColor color;
+ TQ_UINT8 opacity;
+
+ m_gradient->colorAt(t, &color, &opacity);
+
+ layer.setPixel(x - startx, y - starty,
+ tqRgba(color.red(), color.green(), color.blue(), opacity));
+
+ pixelsProcessed++;
+
+ int progressPercent = (pixelsProcessed * 100) / totalPixels;
+
+ if (progressPercent > lastProgressPercent) {
+ emit notifyProgress(progressPercent);
+ lastProgressPercent = progressPercent;
+
+ if (m_cancelRequested) {
+ break;
+ }
+ }
+ if (m_cancelRequested) {
+ break;
+ }
+ }
+ }
+
+ if (!m_cancelRequested && antiAliasThreshold < 1 - DBL_EPSILON) {
+
+ TQColor color;
+ emit notifyProgressStage(i18n("Anti-aliasing gradient..."), lastProgressPercent);
+ TQ_UINT8 * layerPointer = layer.bits();
+ for (int y = starty; y <= endy; y++) {
+ for (int x = startx; x <= endx; x++) {
+
+ double maxDistance = 0;
+
+ TQ_UINT8 redThis = layerPointer[2];
+ TQ_UINT8 greenThis = layerPointer[1];
+ TQ_UINT8 blueThis = layerPointer[0];
+ TQ_UINT8 thisPixelOpacity = layerPointer[3];
+
+ for (int yOffset = -1; yOffset < 2; yOffset++) {
+ for (int xOffset = -1; xOffset < 2; xOffset++) {
+
+ if (xOffset != 0 || yOffset != 0) {
+ int sampleX = x + xOffset;
+ int sampleY = y + yOffset;
+
+ if (sampleX >= startx && sampleX <= endx && sampleY >= starty && sampleY <= endy) {
+ uint x = sampleX - startx;
+ uint y = sampleY - starty;
+ TQ_UINT8 * pixelPos = layer.bits() + (y * width * 4) + (x * 4);
+ TQ_UINT8 red = *(pixelPos +2);
+ TQ_UINT8 green = *(pixelPos + 1);
+ TQ_UINT8 blue = *pixelPos;
+ TQ_UINT8 opacity = *(pixelPos + 3);
+
+ double dRed = (red * opacity - redThis * thisPixelOpacity) / 65535.0;
+ double dGreen = (green * opacity - greenThis * thisPixelOpacity) / 65535.0;
+ double dBlue = (blue * opacity - blueThis * thisPixelOpacity) / 65535.0;
+
+ #define SQRT_3 1.7320508
+
+ double distance =/* sqrt(*/dRed * dRed + dGreen * dGreen + dBlue * dBlue/*) / SQRT_3*/;
+
+ if (distance > maxDistance) {
+ maxDistance = distance;
+ }
+ }
+ }
+ }
+ }
+
+ if (maxDistance > 3.*antiAliasThreshold*antiAliasThreshold) {
+ const int numSamples = 4;
+
+ int totalRed = 0;
+ int totalGreen = 0;
+ int totalBlue = 0;
+ int totalOpacity = 0;
+
+ for (int ySample = 0; ySample < numSamples; ySample++) {
+ for (int xSample = 0; xSample < numSamples; xSample++) {
+
+ double sampleWidth = 1.0 / numSamples;
+
+ double sampleX = x - 0.5 + (sampleWidth / 2) + xSample * sampleWidth;
+ double sampleY = y - 0.5 + (sampleWidth / 2) + ySample * sampleWidth;
+
+ double t = shapeStrategy->valueAt(sampleX, sampleY);
+ t = repeatStrategy->valueAt(t);
+
+ if (reverseGradient) {
+ t = 1 - t;
+ }
+
+ TQ_UINT8 opacity;
+
+ m_gradient->colorAt(t, &color, &opacity);
+
+ totalRed += color.red();
+ totalGreen += color.green();
+ totalBlue += color.blue();
+ totalOpacity += opacity;
+ }
+ }
+
+ int red = totalRed / (numSamples * numSamples);
+ int green = totalGreen / (numSamples * numSamples);
+ int blue = totalBlue / (numSamples * numSamples);
+ int opacity = totalOpacity / (numSamples * numSamples);
+
+ layer.setPixel(x - startx, y - starty, tqRgba(red, green, blue, opacity));
+ }
+
+ pixelsProcessed++;
+
+ int progressPercent = (pixelsProcessed * 100) / totalPixels;
+
+ if (progressPercent > lastProgressPercent) {
+ emit notifyProgress(progressPercent);
+ lastProgressPercent = progressPercent;
+
+ if (m_cancelRequested) {
+ break;
+ }
+ }
+ layerPointer += 4;
+ }
+
+ if (m_cancelRequested) {
+ break;
+ }
+ }
+ }
+
+ if (!m_cancelRequested) {
+ kdDebug() << "Have we got a selection? " << m_device->hasSelection() << endl;
+ KisPaintDeviceSP dev = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry()->getRGB8(), "temporary device for gradient");
+ dev->writeBytes(layer.bits(), startx, starty, width, height);
+ bltSelection(startx, starty, m_compositeOp, dev, m_opacity, startx, starty, width, height);
+ }
+ delete shapeStrategy;
+
+ emit notifyProgressDone();
+
+ return !m_cancelRequested;
+}