summaryrefslogtreecommitdiffstats
path: root/krita/core/kis_brush.cc
diff options
context:
space:
mode:
Diffstat (limited to 'krita/core/kis_brush.cc')
-rw-r--r--krita/core/kis_brush.cc1333
1 files changed, 1333 insertions, 0 deletions
diff --git a/krita/core/kis_brush.cc b/krita/core/kis_brush.cc
new file mode 100644
index 00000000..57ee2584
--- /dev/null
+++ b/krita/core/kis_brush.cc
@@ -0,0 +1,1333 @@
+/*
+ * Copyright (c) 1999 Matthias Elter <me@kde.org>
+ * Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
+ * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
+ * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include <netinet/in.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <cfloat>
+
+#include <qfile.h>
+#include <qimage.h>
+#include <qpoint.h>
+#include <qvaluevector.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <kis_meta_registry.h>
+#include "kis_paint_device.h"
+#include "kis_global.h"
+#include "kis_brush.h"
+#include "kis_alpha_mask.h"
+#include "kis_colorspace_factory_registry.h"
+#include "kis_iterators_pixel.h"
+#include "kis_image.h"
+
+
+namespace {
+ struct GimpBrushV1Header {
+ Q_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
+ Q_UINT32 version; /* brush file version # */
+ Q_UINT32 width; /* width of brush */
+ Q_UINT32 height; /* height of brush */
+ Q_UINT32 bytes; /* depth of brush in bytes */
+ };
+
+ /// All fields are in MSB on disk!
+ struct GimpBrushHeader {
+ Q_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
+ Q_UINT32 version; /* brush file version # */
+ Q_UINT32 width; /* width of brush */
+ Q_UINT32 height; /* height of brush */
+ Q_UINT32 bytes; /* depth of brush in bytes */
+
+ /* The following are only defined in version 2 */
+ Q_UINT32 magic_number; /* GIMP brush magic number */
+ Q_UINT32 spacing; /* brush spacing as % of width & height, 0 - 1000 */
+ };
+
+ // Needed, or the GIMP won't open it!
+ Q_UINT32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0);
+}
+
+#define DEFAULT_SPACING 0.25
+#define MAXIMUM_SCALE 2
+
+KisBrush::KisBrush(const QString& filename) : super(filename)
+{
+ m_brushType = INVALID;
+ m_ownData = true;
+ m_useColorAsMask = false;
+ m_hasColor = false;
+ m_spacing = DEFAULT_SPACING;
+ m_boundary = 0;
+}
+
+KisBrush::KisBrush(const QString& filename,
+ const QByteArray& data,
+ Q_UINT32 & dataPos) : super(filename)
+{
+ m_brushType = INVALID;
+ m_ownData = false;
+ m_useColorAsMask = false;
+ m_hasColor = false;
+ m_spacing = DEFAULT_SPACING;
+ m_boundary = 0;
+
+ m_data.setRawData(data.data() + dataPos, data.size() - dataPos);
+ init();
+ m_data.resetRawData(data.data() + dataPos, data.size() - dataPos);
+ dataPos += m_header_size + (width() * height() * m_bytes);
+}
+
+KisBrush::KisBrush(KisPaintDevice* image, int x, int y, int w, int h)
+ : super(QString(""))
+{
+ m_brushType = INVALID;
+ m_ownData = true;
+ m_useColorAsMask = false;
+ m_hasColor = true;
+ m_spacing = DEFAULT_SPACING;
+ m_boundary = 0;
+
+ initFromPaintDev(image, x, y, w, h);
+}
+
+KisBrush::KisBrush(const QImage& image, const QString& name)
+ : super(QString(""))
+{
+ m_ownData = false;
+ m_useColorAsMask = false;
+ m_hasColor = true;
+ m_spacing = DEFAULT_SPACING;
+ m_boundary = 0;
+
+ setImage(image);
+ setName(name);
+ setBrushType(IMAGE);
+}
+
+
+KisBrush::~KisBrush()
+{
+ m_scaledBrushes.clear();
+ delete m_boundary;
+}
+
+bool KisBrush::load()
+{
+ if (m_ownData) {
+ QFile file(filename());
+ file.open(IO_ReadOnly);
+ m_data = file.readAll();
+ file.close();
+ }
+ return init();
+}
+
+bool KisBrush::init()
+{
+ GimpBrushHeader bh;
+
+ if (sizeof(GimpBrushHeader) > m_data.size()) {
+ return false;
+ }
+
+ memcpy(&bh, &m_data[0], sizeof(GimpBrushHeader));
+ bh.header_size = ntohl(bh.header_size);
+ m_header_size = bh.header_size;
+
+ bh.version = ntohl(bh.version);
+ m_version = bh.version;
+
+ bh.width = ntohl(bh.width);
+ bh.height = ntohl(bh.height);
+
+ bh.bytes = ntohl(bh.bytes);
+ m_bytes = bh.bytes;
+
+ bh.magic_number = ntohl(bh.magic_number);
+ m_magic_number = bh.magic_number;
+
+ if (bh.version == 1) {
+ // No spacing in version 1 files so use Gimp default
+ bh.spacing = static_cast<int>(DEFAULT_SPACING * 100);
+ }
+ else {
+ bh.spacing = ntohl(bh.spacing);
+
+ if (bh.spacing > 1000) {
+ return false;
+ }
+ }
+
+ setSpacing(bh.spacing / 100.0);
+
+ if (bh.header_size > m_data.size() || bh.header_size == 0) {
+ return false;
+ }
+
+ QString name;
+
+ if (bh.version == 1) {
+ // Version 1 has no magic number or spacing, so the name
+ // is at a different offset. Character encoding is undefined.
+ const char *text = &m_data[sizeof(GimpBrushV1Header)];
+ name = QString::fromAscii(text, bh.header_size - sizeof(GimpBrushV1Header));
+ } else {
+ // ### Version = 3->cinepaint; may be float16 data!
+ // Version >=2: UTF-8 encoding is used
+ name = QString::fromUtf8(&m_data[sizeof(GimpBrushHeader)],
+ bh.header_size - sizeof(GimpBrushHeader));
+ }
+
+ setName(i18n(name.ascii())); // Ascii? And what with real UTF-8 chars?
+
+ if (bh.width == 0 || bh.height == 0 || !m_img.create(bh.width, bh.height, 32)) {
+ return false;
+ }
+
+ Q_INT32 k = bh.header_size;
+
+ if (bh.bytes == 1) {
+ // Grayscale
+
+ if (static_cast<Q_UINT32>(k + bh.width * bh.height) > m_data.size()) {
+ return false;
+ }
+
+ m_brushType = MASK;
+ m_hasColor = false;
+
+ for (Q_UINT32 y = 0; y < bh.height; y++) {
+ for (Q_UINT32 x = 0; x < bh.width; x++, k++) {
+ Q_INT32 val = 255 - static_cast<uchar>(m_data[k]);
+ m_img.setPixel(x, y, qRgb(val, val, val));
+ }
+ }
+ } else if (bh.bytes == 4) {
+ // RGBA
+
+ if (static_cast<Q_UINT32>(k + (bh.width * bh.height * 4)) > m_data.size()) {
+ return false;
+ }
+
+ m_brushType = IMAGE;
+ m_img.setAlphaBuffer(true);
+ m_hasColor = true;
+
+ for (Q_UINT32 y = 0; y < bh.height; y++) {
+ for (Q_UINT32 x = 0; x < bh.width; x++, k += 4) {
+ m_img.setPixel(x, y, qRgba(m_data[k],
+ m_data[k+1],
+ m_data[k+2],
+ m_data[k+3]));
+ }
+ }
+ } else {
+ return false;
+ }
+
+ setWidth(m_img.width());
+ setHeight(m_img.height());
+ //createScaledBrushes();
+ if (m_ownData) {
+ m_data.resize(0); // Save some memory, we're using enough of it as it is.
+ }
+
+
+ if (m_img.width() == 0 || m_img.height() == 0)
+ setValid(false);
+ else
+ setValid(true);
+
+ return true;
+}
+
+bool KisBrush::initFromPaintDev(KisPaintDevice* image, int x, int y, int w, int h) {
+ // Forcefully convert to RGBA8
+ // XXX profile and exposure?
+ setImage(image->convertToQImage(0, x, y, w, h));
+ setName(image->name());
+
+ m_brushType = IMAGE;
+ m_hasColor = true;
+
+ return true;
+}
+
+bool KisBrush::save()
+{
+ QFile file(filename());
+ file.open(IO_WriteOnly | IO_Truncate);
+ bool ok = saveToDevice(&file);
+ file.close();
+ return ok;
+}
+
+bool KisBrush::saveToDevice(QIODevice* dev) const
+{
+ GimpBrushHeader bh;
+ QCString utf8Name = name().utf8(); // Names in v2 brushes are in UTF-8
+ char const* name = utf8Name.data();
+ int nameLength = qstrlen(name);
+ int wrote;
+
+ bh.header_size = htonl(sizeof(GimpBrushHeader) + nameLength);
+ bh.version = htonl(2); // Only RGBA8 data needed atm, no cinepaint stuff
+ bh.width = htonl(width());
+ bh.height = htonl(height());
+ // Hardcoded, 4 bytes RGBA or 1 byte GREY
+ if (!hasColor())
+ bh.bytes = htonl(1);
+ else
+ bh.bytes = htonl(4);
+ bh.magic_number = htonl(GimpV2BrushMagic);
+ bh.spacing = htonl(static_cast<Q_UINT32>(spacing() * 100.0));
+
+ // Write header: first bh, then the name
+ QByteArray bytes;
+ bytes.setRawData(reinterpret_cast<char*>(&bh), sizeof(GimpBrushHeader));
+ wrote = dev->writeBlock(bytes);
+ bytes.resetRawData(reinterpret_cast<char*>(&bh), sizeof(GimpBrushHeader));
+
+ if (wrote == -1)
+ return false;
+
+ wrote = dev->writeBlock(name, nameLength); // No +1 for the trailing NULL it seems...
+ if (wrote == -1)
+ return false;
+
+ int k = 0;
+
+ if (!hasColor()) {
+ bytes.resize(width() * height());
+ for (Q_INT32 y = 0; y < height(); y++) {
+ for (Q_INT32 x = 0; x < width(); x++) {
+ QRgb c = m_img.pixel(x, y);
+ bytes[k++] = static_cast<char>(255 - qRed(c)); // red == blue == green
+ }
+ }
+ } else {
+ bytes.resize(width() * height() * 4);
+ for (Q_INT32 y = 0; y < height(); y++) {
+ for (Q_INT32 x = 0; x < width(); x++) {
+ // order for gimp brushes, v2 is: RGBA
+ QRgb pixel = m_img.pixel(x,y);
+ bytes[k++] = static_cast<char>(qRed(pixel));
+ bytes[k++] = static_cast<char>(qGreen(pixel));
+ bytes[k++] = static_cast<char>(qBlue(pixel));
+ bytes[k++] = static_cast<char>(qAlpha(pixel));
+ }
+ }
+ }
+
+ wrote = dev->writeBlock(bytes);
+ if (wrote == -1)
+ return false;
+
+ return true;
+}
+
+QImage KisBrush::img()
+{
+ QImage image = m_img;
+
+ if (hasColor() && useColorAsMask()) {
+ image.detach();
+
+ for (int x = 0; x < image.width(); x++) {
+ for (int y = 0; y < image.height(); y++) {
+ QRgb c = image.pixel(x, y);
+ int a = (qGray(c) * qAlpha(c)) / 255;
+ image.setPixel(x, y, qRgba(a, 0, a, a));
+ }
+ }
+ }
+
+ return image;
+}
+
+KisAlphaMaskSP KisBrush::mask(const KisPaintInformation& info, double subPixelX, double subPixelY) const
+{
+ if (m_scaledBrushes.isEmpty()) {
+ createScaledBrushes();
+ }
+
+ double scale = scaleForPressure(info.pressure);
+
+ const ScaledBrush *aboveBrush = 0;
+ const ScaledBrush *belowBrush = 0;
+
+ findScaledBrushes(scale, &aboveBrush, &belowBrush);
+ Q_ASSERT(aboveBrush != 0);
+
+ KisAlphaMaskSP outputMask = 0;
+
+ if (belowBrush != 0) {
+ // We're in between two masks. Interpolate between them.
+
+ KisAlphaMaskSP scaledAboveMask = scaleMask(aboveBrush, scale, subPixelX, subPixelY);
+ KisAlphaMaskSP scaledBelowMask = scaleMask(belowBrush, scale, subPixelX, subPixelY);
+
+ double t = (scale - belowBrush->scale()) / (aboveBrush->scale() - belowBrush->scale());
+
+ outputMask = KisAlphaMask::interpolate(scaledBelowMask, scaledAboveMask, t);
+ } else {
+ if (fabs(scale - aboveBrush->scale()) < DBL_EPSILON) {
+ // Exact match.
+ outputMask = scaleMask(aboveBrush, scale, subPixelX, subPixelY);
+ } else {
+ // We are smaller than the smallest mask, which is always 1x1.
+ double s = scale / aboveBrush->scale();
+ outputMask = scaleSinglePixelMask(s, aboveBrush->mask()->alphaAt(0, 0), subPixelX, subPixelY);
+ }
+ }
+
+ return outputMask;
+}
+
+KisPaintDeviceSP KisBrush::image(KisColorSpace * /*colorSpace*/, const KisPaintInformation& info, double subPixelX, double subPixelY) const
+{
+ if (m_scaledBrushes.isEmpty()) {
+ createScaledBrushes();
+ }
+
+ double scale = scaleForPressure(info.pressure);
+
+ const ScaledBrush *aboveBrush = 0;
+ const ScaledBrush *belowBrush = 0;
+
+ findScaledBrushes(scale, &aboveBrush, &belowBrush);
+ Q_ASSERT(aboveBrush != 0);
+
+ QImage outputImage;
+
+ if (belowBrush != 0) {
+ // We're in between two brushes. Interpolate between them.
+
+ QImage scaledAboveImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY);
+ QImage scaledBelowImage = scaleImage(belowBrush, scale, subPixelX, subPixelY);
+
+ double t = (scale - belowBrush->scale()) / (aboveBrush->scale() - belowBrush->scale());
+
+ outputImage = interpolate(scaledBelowImage, scaledAboveImage, t);
+ } else {
+ if (fabs(scale - aboveBrush->scale()) < DBL_EPSILON) {
+ // Exact match.
+ outputImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY);
+ } else {
+ // We are smaller than the smallest brush, which is always 1x1.
+ double s = scale / aboveBrush->scale();
+ outputImage = scaleSinglePixelImage(s, aboveBrush->image().pixel(0, 0), subPixelX, subPixelY);
+ }
+ }
+
+ int outputWidth = outputImage.width();
+ int outputHeight = outputImage.height();
+
+ KisPaintDevice *layer = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry()->getRGB8(), "brush");
+
+ Q_CHECK_PTR(layer);
+
+ for (int y = 0; y < outputHeight; y++) {
+ KisHLineIterator iter = layer->createHLineIterator( 0, y, outputWidth, true);
+ for (int x = 0; x < outputWidth; x++) {
+ Q_UINT8 * p = iter.rawData();
+
+ QRgb pixel = outputImage.pixel(x, y);
+ int red = qRed(pixel);
+ int green = qGreen(pixel);
+ int blue = qBlue(pixel);
+ int alpha = qAlpha(pixel);
+
+ // Scaled images are in pre-multiplied alpha form so
+ // divide by alpha.
+ // channel order is BGRA
+ if (alpha != 0) {
+ p[2] = (red * 255) / alpha;
+ p[1] = (green * 255) / alpha;
+ p[0] = (blue * 255) / alpha;
+ p[3] = alpha;
+ }
+
+ ++iter;
+ }
+ }
+
+ return layer;
+}
+
+void KisBrush::setHotSpot(KisPoint pt)
+{
+ double x = pt.x();
+ double y = pt.y();
+
+ if (x < 0)
+ x = 0;
+ else if (x >= width())
+ x = width() - 1;
+
+ if (y < 0)
+ y = 0;
+ else if (y >= height())
+ y = height() - 1;
+
+ m_hotSpot = KisPoint(x, y);
+}
+
+KisPoint KisBrush::hotSpot(const KisPaintInformation& info) const
+{
+ double scale = scaleForPressure(info.pressure);
+ double w = width() * scale;
+ double h = height() * scale;
+
+ // The smallest brush we can produce is a single pixel.
+ if (w < 1) {
+ w = 1;
+ }
+
+ if (h < 1) {
+ h = 1;
+ }
+
+ // XXX: This should take m_hotSpot into account, though it
+ // isn't specified by gimp brushes so it would default to the centre
+ // anyway.
+ KisPoint p(w / 2, h / 2);
+ return p;
+}
+
+enumBrushType KisBrush::brushType() const
+{
+ if (m_brushType == IMAGE && useColorAsMask()) {
+ return MASK;
+ }
+ else {
+ return m_brushType;
+ }
+}
+
+bool KisBrush::hasColor() const
+{
+ return m_hasColor;
+}
+
+void KisBrush::createScaledBrushes() const
+{
+ if (!m_scaledBrushes.isEmpty())
+ m_scaledBrushes.clear();
+
+ // Construct a series of brushes where each one's dimensions are
+ // half the size of the previous one.
+ int width = m_img.width() * MAXIMUM_SCALE;
+ int height = m_img.height() * MAXIMUM_SCALE;
+
+ QImage scaledImage;
+
+ while (true) {
+
+ if (width >= m_img.width() && height >= m_img.height()) {
+ scaledImage = scaleImage(m_img, width, height);
+ }
+ else {
+ // Scale down the previous image once we're below 1:1.
+ scaledImage = scaleImage(scaledImage, width, height);
+ }
+
+ KisAlphaMaskSP scaledMask = new KisAlphaMask(scaledImage, hasColor());
+ Q_CHECK_PTR(scaledMask);
+
+ double xScale = static_cast<double>(width) / m_img.width();
+ double yScale = static_cast<double>(height) / m_img.height();
+ double scale = xScale;
+
+ m_scaledBrushes.append(ScaledBrush(scaledMask, hasColor() ? scaledImage : QImage(), scale, xScale, yScale));
+
+ if (width == 1 && height == 1) {
+ break;
+ }
+
+ // Round up so that we never have to scale an image by less than 1/2.
+ width = (width + 1) / 2;
+ height = (height + 1) / 2;
+
+ }
+
+}
+
+double KisBrush::xSpacing(double pressure) const
+{
+ return width() * scaleForPressure(pressure) * m_spacing;
+}
+
+double KisBrush::ySpacing(double pressure) const
+{
+ return height() * scaleForPressure(pressure) * m_spacing;
+}
+
+double KisBrush::scaleForPressure(double pressure)
+{
+ double scale = pressure / PRESSURE_DEFAULT;
+
+ if (scale < 0) {
+ scale = 0;
+ }
+
+ if (scale > MAXIMUM_SCALE) {
+ scale = MAXIMUM_SCALE;
+ }
+
+ return scale;
+}
+
+Q_INT32 KisBrush::maskWidth(const KisPaintInformation& info) const
+{
+ // Add one for sub-pixel shift
+ return static_cast<Q_INT32>(ceil(width() * scaleForPressure(info.pressure)) + 1);
+}
+
+Q_INT32 KisBrush::maskHeight(const KisPaintInformation& info) const
+{
+ // Add one for sub-pixel shift
+ return static_cast<Q_INT32>(ceil(height() * scaleForPressure(info.pressure)) + 1);
+}
+
+KisAlphaMaskSP KisBrush::scaleMask(const ScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const
+{
+ // Add one pixel for sub-pixel shifting
+ int dstWidth = static_cast<int>(ceil(scale * width())) + 1;
+ int dstHeight = static_cast<int>(ceil(scale * height())) + 1;
+
+ KisAlphaMaskSP dstMask = new KisAlphaMask(dstWidth, dstHeight);
+ Q_CHECK_PTR(dstMask);
+
+ KisAlphaMaskSP srcMask = srcBrush->mask();
+
+ // Compute scales to map the scaled brush onto the required scale.
+ double xScale = srcBrush->xScale() / scale;
+ double yScale = srcBrush->yScale() / scale;
+
+ int srcWidth = srcMask->width();
+ int srcHeight = srcMask->height();
+
+ for (int dstY = 0; dstY < dstHeight; dstY++) {
+ for (int dstX = 0; dstX < dstWidth; dstX++) {
+
+ double srcX = (dstX - subPixelX + 0.5) * xScale;
+ double srcY = (dstY - subPixelY + 0.5) * yScale;
+
+ srcX -= 0.5;
+ srcY -= 0.5;
+
+ int leftX = static_cast<int>(srcX);
+
+ if (srcX < 0) {
+ leftX--;
+ }
+
+ double xInterp = srcX - leftX;
+
+ int topY = static_cast<int>(srcY);
+
+ if (srcY < 0) {
+ topY--;
+ }
+
+ double yInterp = srcY - topY;
+
+ Q_UINT8 topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX, topY) : OPACITY_TRANSPARENT;
+ Q_UINT8 bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX, topY + 1) : OPACITY_TRANSPARENT;
+ Q_UINT8 topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX + 1, topY) : OPACITY_TRANSPARENT;
+ Q_UINT8 bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX + 1, topY + 1) : OPACITY_TRANSPARENT;
+
+ double a = 1 - xInterp;
+ double b = 1 - yInterp;
+
+ // Bi-linear interpolation
+ int d = static_cast<int>(a * b * topLeft
+ + a * (1 - b) * bottomLeft
+ + (1 - a) * b * topRight
+ + (1 - a) * (1 - b) * bottomRight + 0.5);
+
+ if (d < OPACITY_TRANSPARENT) {
+ d = OPACITY_TRANSPARENT;
+ }
+ else
+ if (d > OPACITY_OPAQUE) {
+ d = OPACITY_OPAQUE;
+ }
+
+ dstMask->setAlphaAt(dstX, dstY, static_cast<Q_UINT8>(d));
+ }
+ }
+
+ return dstMask;
+}
+
+QImage KisBrush::scaleImage(const ScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const
+{
+ // Add one pixel for sub-pixel shifting
+ int dstWidth = static_cast<int>(ceil(scale * width())) + 1;
+ int dstHeight = static_cast<int>(ceil(scale * height())) + 1;
+
+ QImage dstImage(dstWidth, dstHeight, 32);
+ dstImage.setAlphaBuffer(true);
+
+ const QImage srcImage = srcBrush->image();
+
+ // Compute scales to map the scaled brush onto the required scale.
+ double xScale = srcBrush->xScale() / scale;
+ double yScale = srcBrush->yScale() / scale;
+
+ int srcWidth = srcImage.width();
+ int srcHeight = srcImage.height();
+
+ for (int dstY = 0; dstY < dstHeight; dstY++) {
+ for (int dstX = 0; dstX < dstWidth; dstX++) {
+
+ double srcX = (dstX - subPixelX + 0.5) * xScale;
+ double srcY = (dstY - subPixelY + 0.5) * yScale;
+
+ srcX -= 0.5;
+ srcY -= 0.5;
+
+ int leftX = static_cast<int>(srcX);
+
+ if (srcX < 0) {
+ leftX--;
+ }
+
+ double xInterp = srcX - leftX;
+
+ int topY = static_cast<int>(srcY);
+
+ if (srcY < 0) {
+ topY--;
+ }
+
+ double yInterp = srcY - topY;
+
+ QRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : qRgba(0, 0, 0, 0);
+ QRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : qRgba(0, 0, 0, 0);
+ QRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : qRgba(0, 0, 0, 0);
+ QRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : qRgba(0, 0, 0, 0);
+
+ double a = 1 - xInterp;
+ double b = 1 - yInterp;
+
+ // Bi-linear interpolation. Image is pre-multiplied by alpha.
+ int red = static_cast<int>(a * b * qRed(topLeft)
+ + a * (1 - b) * qRed(bottomLeft)
+ + (1 - a) * b * qRed(topRight)
+ + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5);
+ int green = static_cast<int>(a * b * qGreen(topLeft)
+ + a * (1 - b) * qGreen(bottomLeft)
+ + (1 - a) * b * qGreen(topRight)
+ + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5);
+ int blue = static_cast<int>(a * b * qBlue(topLeft)
+ + a * (1 - b) * qBlue(bottomLeft)
+ + (1 - a) * b * qBlue(topRight)
+ + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5);
+ int alpha = static_cast<int>(a * b * qAlpha(topLeft)
+ + a * (1 - b) * qAlpha(bottomLeft)
+ + (1 - a) * b * qAlpha(topRight)
+ + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5);
+
+ if (red < 0) {
+ red = 0;
+ }
+ else
+ if (red > 255) {
+ red = 255;
+ }
+
+ if (green < 0) {
+ green = 0;
+ }
+ else
+ if (green > 255) {
+ green = 255;
+ }
+
+ if (blue < 0) {
+ blue = 0;
+ }
+ else
+ if (blue > 255) {
+ blue = 255;
+ }
+
+ if (alpha < 0) {
+ alpha = 0;
+ }
+ else
+ if (alpha > 255) {
+ alpha = 255;
+ }
+
+ dstImage.setPixel(dstX, dstY, qRgba(red, green, blue, alpha));
+ }
+ }
+
+ return dstImage;
+}
+
+QImage KisBrush::scaleImage(const QImage& srcImage, int width, int height)
+{
+ QImage scaledImage;
+ //QString filename;
+
+ int srcWidth = srcImage.width();
+ int srcHeight = srcImage.height();
+
+ double xScale = static_cast<double>(srcWidth) / width;
+ double yScale = static_cast<double>(srcHeight) / height;
+
+ if (xScale > 2 + DBL_EPSILON || yScale > 2 + DBL_EPSILON || xScale < 1 - DBL_EPSILON || yScale < 1 - DBL_EPSILON) {
+ // smoothScale gives better results when scaling an image up
+ // or scaling it to less than half size.
+ scaledImage = srcImage.smoothScale(width, height);
+
+ //filename = QString("smoothScale_%1x%2.png").arg(width).arg(height);
+ }
+ else {
+ scaledImage.create(width, height, 32);
+ scaledImage.setAlphaBuffer(srcImage.hasAlphaBuffer());
+
+ for (int dstY = 0; dstY < height; dstY++) {
+ for (int dstX = 0; dstX < width; dstX++) {
+
+ double srcX = (dstX + 0.5) * xScale;
+ double srcY = (dstY + 0.5) * yScale;
+
+ srcX -= 0.5;
+ srcY -= 0.5;
+
+ int leftX = static_cast<int>(srcX);
+
+ if (srcX < 0) {
+ leftX--;
+ }
+
+ double xInterp = srcX - leftX;
+
+ int topY = static_cast<int>(srcY);
+
+ if (srcY < 0) {
+ topY--;
+ }
+
+ double yInterp = srcY - topY;
+
+ QRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : qRgba(0, 0, 0, 0);
+ QRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : qRgba(0, 0, 0, 0);
+ QRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : qRgba(0, 0, 0, 0);
+ QRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : qRgba(0, 0, 0, 0);
+
+ double a = 1 - xInterp;
+ double b = 1 - yInterp;
+
+ int red;
+ int green;
+ int blue;
+ int alpha;
+
+ if (srcImage.hasAlphaBuffer()) {
+ red = static_cast<int>(a * b * qRed(topLeft) * qAlpha(topLeft)
+ + a * (1 - b) * qRed(bottomLeft) * qAlpha(bottomLeft)
+ + (1 - a) * b * qRed(topRight) * qAlpha(topRight)
+ + (1 - a) * (1 - b) * qRed(bottomRight) * qAlpha(bottomRight) + 0.5);
+ green = static_cast<int>(a * b * qGreen(topLeft) * qAlpha(topLeft)
+ + a * (1 - b) * qGreen(bottomLeft) * qAlpha(bottomLeft)
+ + (1 - a) * b * qGreen(topRight) * qAlpha(topRight)
+ + (1 - a) * (1 - b) * qGreen(bottomRight) * qAlpha(bottomRight) + 0.5);
+ blue = static_cast<int>(a * b * qBlue(topLeft) * qAlpha(topLeft)
+ + a * (1 - b) * qBlue(bottomLeft) * qAlpha(bottomLeft)
+ + (1 - a) * b * qBlue(topRight) * qAlpha(topRight)
+ + (1 - a) * (1 - b) * qBlue(bottomRight) * qAlpha(bottomRight) + 0.5);
+ alpha = static_cast<int>(a * b * qAlpha(topLeft)
+ + a * (1 - b) * qAlpha(bottomLeft)
+ + (1 - a) * b * qAlpha(topRight)
+ + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5);
+
+ if (alpha != 0) {
+ red /= alpha;
+ green /= alpha;
+ blue /= alpha;
+ }
+ }
+ else {
+ red = static_cast<int>(a * b * qRed(topLeft)
+ + a * (1 - b) * qRed(bottomLeft)
+ + (1 - a) * b * qRed(topRight)
+ + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5);
+ green = static_cast<int>(a * b * qGreen(topLeft)
+ + a * (1 - b) * qGreen(bottomLeft)
+ + (1 - a) * b * qGreen(topRight)
+ + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5);
+ blue = static_cast<int>(a * b * qBlue(topLeft)
+ + a * (1 - b) * qBlue(bottomLeft)
+ + (1 - a) * b * qBlue(topRight)
+ + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5);
+ alpha = 255;
+ }
+
+ if (red < 0) {
+ red = 0;
+ }
+ else
+ if (red > 255) {
+ red = 255;
+ }
+
+ if (green < 0) {
+ green = 0;
+ }
+ else
+ if (green > 255) {
+ green = 255;
+ }
+
+ if (blue < 0) {
+ blue = 0;
+ }
+ else
+ if (blue > 255) {
+ blue = 255;
+ }
+
+ if (alpha < 0) {
+ alpha = 0;
+ }
+ else
+ if (alpha > 255) {
+ alpha = 255;
+ }
+
+ scaledImage.setPixel(dstX, dstY, qRgba(red, green, blue, alpha));
+ }
+ }
+
+ //filename = QString("bilinear_%1x%2.png").arg(width).arg(height);
+ }
+
+ //scaledImage.save(filename, "PNG");
+
+ return scaledImage;
+}
+
+void KisBrush::findScaledBrushes(double scale, const ScaledBrush **aboveBrush, const ScaledBrush **belowBrush) const
+{
+ uint current = 0;
+
+ while (true) {
+ *aboveBrush = &(m_scaledBrushes[current]);
+
+ if (fabs((*aboveBrush)->scale() - scale) < DBL_EPSILON) {
+ // Scale matches exactly
+ break;
+ }
+
+ if (current == m_scaledBrushes.count() - 1) {
+ // This is the last one
+ break;
+ }
+
+ if (scale > m_scaledBrushes[current + 1].scale() + DBL_EPSILON) {
+ // We fit in between the two.
+ *belowBrush = &(m_scaledBrushes[current + 1]);
+ break;
+ }
+
+ current++;
+ }
+}
+
+KisAlphaMaskSP KisBrush::scaleSinglePixelMask(double scale, Q_UINT8 maskValue, double subPixelX, double subPixelY)
+{
+ int srcWidth = 1;
+ int srcHeight = 1;
+ int dstWidth = 2;
+ int dstHeight = 2;
+ KisAlphaMaskSP outputMask = new KisAlphaMask(dstWidth, dstHeight);
+ Q_CHECK_PTR(outputMask);
+
+ double a = subPixelX;
+ double b = subPixelY;
+
+ for (int y = 0; y < dstHeight; y++) {
+ for (int x = 0; x < dstWidth; x++) {
+
+ Q_UINT8 topLeft = (x > 0 && y > 0) ? maskValue : OPACITY_TRANSPARENT;
+ Q_UINT8 bottomLeft = (x > 0 && y < srcHeight) ? maskValue : OPACITY_TRANSPARENT;
+ Q_UINT8 topRight = (x < srcWidth && y > 0) ? maskValue : OPACITY_TRANSPARENT;
+ Q_UINT8 bottomRight = (x < srcWidth && y < srcHeight) ? maskValue : OPACITY_TRANSPARENT;
+
+ // Bi-linear interpolation
+ int d = static_cast<int>(a * b * topLeft
+ + a * (1 - b) * bottomLeft
+ + (1 - a) * b * topRight
+ + (1 - a) * (1 - b) * bottomRight + 0.5);
+
+ // Multiply by the square of the scale because a 0.5x0.5 pixel
+ // has 0.25 the value of the 1x1.
+ d = static_cast<int>(d * scale * scale + 0.5);
+
+ if (d < OPACITY_TRANSPARENT) {
+ d = OPACITY_TRANSPARENT;
+ }
+ else
+ if (d > OPACITY_OPAQUE) {
+ d = OPACITY_OPAQUE;
+ }
+
+ outputMask->setAlphaAt(x, y, static_cast<Q_UINT8>(d));
+ }
+ }
+
+ return outputMask;
+}
+
+QImage KisBrush::scaleSinglePixelImage(double scale, QRgb pixel, double subPixelX, double subPixelY)
+{
+ int srcWidth = 1;
+ int srcHeight = 1;
+ int dstWidth = 2;
+ int dstHeight = 2;
+
+ QImage outputImage(dstWidth, dstHeight, 32);
+ outputImage.setAlphaBuffer(true);
+
+ double a = subPixelX;
+ double b = subPixelY;
+
+ for (int y = 0; y < dstHeight; y++) {
+ for (int x = 0; x < dstWidth; x++) {
+
+ QRgb topLeft = (x > 0 && y > 0) ? pixel : qRgba(0, 0, 0, 0);
+ QRgb bottomLeft = (x > 0 && y < srcHeight) ? pixel : qRgba(0, 0, 0, 0);
+ QRgb topRight = (x < srcWidth && y > 0) ? pixel : qRgba(0, 0, 0, 0);
+ QRgb bottomRight = (x < srcWidth && y < srcHeight) ? pixel : qRgba(0, 0, 0, 0);
+
+ // Bi-linear interpolation. Images are in pre-multiplied form.
+ int red = static_cast<int>(a * b * qRed(topLeft)
+ + a * (1 - b) * qRed(bottomLeft)
+ + (1 - a) * b * qRed(topRight)
+ + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5);
+ int green = static_cast<int>(a * b * qGreen(topLeft)
+ + a * (1 - b) * qGreen(bottomLeft)
+ + (1 - a) * b * qGreen(topRight)
+ + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5);
+ int blue = static_cast<int>(a * b * qBlue(topLeft)
+ + a * (1 - b) * qBlue(bottomLeft)
+ + (1 - a) * b * qBlue(topRight)
+ + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5);
+ int alpha = static_cast<int>(a * b * qAlpha(topLeft)
+ + a * (1 - b) * qAlpha(bottomLeft)
+ + (1 - a) * b * qAlpha(topRight)
+ + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5);
+
+ // Multiply by the square of the scale because a 0.5x0.5 pixel
+ // has 0.25 the value of the 1x1.
+ alpha = static_cast<int>(alpha * scale * scale + 0.5);
+
+ // Apply to the colour channels too since we are
+ // storing pre-multiplied by alpha.
+ red = static_cast<int>(red * scale * scale + 0.5);
+ green = static_cast<int>(green * scale * scale + 0.5);
+ blue = static_cast<int>(blue * scale * scale + 0.5);
+
+ if (red < 0) {
+ red = 0;
+ }
+ else
+ if (red > 255) {
+ red = 255;
+ }
+
+ if (green < 0) {
+ green = 0;
+ }
+ else
+ if (green > 255) {
+ green = 255;
+ }
+
+ if (blue < 0) {
+ blue = 0;
+ }
+ else
+ if (blue > 255) {
+ blue = 255;
+ }
+
+ if (alpha < 0) {
+ alpha = 0;
+ }
+ else
+ if (alpha > 255) {
+ alpha = 255;
+ }
+
+ outputImage.setPixel(x, y, qRgba(red, green, blue, alpha));
+ }
+ }
+
+ return outputImage;
+}
+
+QImage KisBrush::interpolate(const QImage& image1, const QImage& image2, double t)
+{
+ Q_ASSERT((image1.width() == image2.width()) && (image1.height() == image2.height()));
+ Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON);
+
+ int width = image1.width();
+ int height = image1.height();
+
+ QImage outputImage(width, height, 32);
+ outputImage.setAlphaBuffer(true);
+
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ QRgb image1pixel = image1.pixel(x, y);
+ QRgb image2pixel = image2.pixel(x, y);
+
+ // Images are in pre-multiplied alpha format.
+ int red = static_cast<int>((1 - t) * qRed(image1pixel) + t * qRed(image2pixel) + 0.5);
+ int green = static_cast<int>((1 - t) * qGreen(image1pixel) + t * qGreen(image2pixel) + 0.5);
+ int blue = static_cast<int>((1 - t) * qBlue(image1pixel) + t * qBlue(image2pixel) + 0.5);
+ int alpha = static_cast<int>((1 - t) * qAlpha(image1pixel) + t * qAlpha(image2pixel) + 0.5);
+
+ if (red < 0) {
+ red = 0;
+ }
+ else
+ if (red > 255) {
+ red = 255;
+ }
+
+ if (green < 0) {
+ green = 0;
+ }
+ else
+ if (green > 255) {
+ green = 255;
+ }
+
+ if (blue < 0) {
+ blue = 0;
+ }
+ else
+ if (blue > 255) {
+ blue = 255;
+ }
+
+ if (alpha < 0) {
+ alpha = 0;
+ }
+ else
+ if (alpha > 255) {
+ alpha = 255;
+ }
+
+ outputImage.setPixel(x, y, qRgba(red, green, blue, alpha));
+ }
+ }
+
+ return outputImage;
+}
+
+KisBrush::ScaledBrush::ScaledBrush()
+{
+ m_mask = 0;
+ m_image = QImage();
+ m_scale = 1;
+ m_xScale = 1;
+ m_yScale = 1;
+}
+
+KisBrush::ScaledBrush::ScaledBrush(KisAlphaMaskSP scaledMask, const QImage& scaledImage, double scale, double xScale, double yScale)
+{
+ m_mask = scaledMask;
+ m_image = scaledImage;
+ m_scale = scale;
+ m_xScale = xScale;
+ m_yScale = yScale;
+
+ if (!m_image.isNull()) {
+ // Convert image to pre-multiplied by alpha.
+
+ m_image.detach();
+
+ for (int y = 0; y < m_image.height(); y++) {
+ for (int x = 0; x < m_image.width(); x++) {
+
+ QRgb pixel = m_image.pixel(x, y);
+
+ int red = qRed(pixel);
+ int green = qGreen(pixel);
+ int blue = qBlue(pixel);
+ int alpha = qAlpha(pixel);
+
+ red = (red * alpha) / 255;
+ green = (green * alpha) / 255;
+ blue = (blue * alpha) / 255;
+
+ m_image.setPixel(x, y, qRgba(red, green, blue, alpha));
+ }
+ }
+ }
+}
+
+void KisBrush::setImage(const QImage& img)
+{
+ m_img = img;
+ m_img.detach();
+
+ setWidth(img.width());
+ setHeight(img.height());
+
+ m_scaledBrushes.clear();
+
+ setValid(true);
+}
+
+Q_INT32 KisBrush::width() const
+{
+ return m_width;
+}
+
+void KisBrush::setWidth(Q_INT32 w)
+{
+ m_width = w;
+}
+
+Q_INT32 KisBrush::height() const
+{
+ return m_height;
+}
+
+void KisBrush::setHeight(Q_INT32 h)
+{
+ m_height = h;
+}
+
+/*QImage KisBrush::outline(double pressure) {
+ KisLayerSP layer = image(KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA",""),""),
+ KisPaintInformation(pressure));
+ KisBoundary bounds(layer.data());
+ int w = maskWidth(pressure);
+ int h = maskHeight(pressure);
+
+ bounds.generateBoundary(w, h);
+ QPixmap pix(bounds.pixmap(w, h));
+ QImage result;
+ result = pix;
+ return result;
+}*/
+
+void KisBrush::generateBoundary() {
+ KisPaintDeviceSP dev;
+ int w = maskWidth(KisPaintInformation());
+ int h = maskHeight(KisPaintInformation());
+
+ if (brushType() == IMAGE || brushType() == PIPE_IMAGE) {
+ dev = image(KisMetaRegistry::instance()->csRegistry() ->getColorSpace(KisID("RGBA",""),""), KisPaintInformation());
+ } else {
+ KisAlphaMaskSP amask = mask(KisPaintInformation());
+ KisColorSpace* cs = KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA",""),"");
+ dev = new KisPaintDevice(cs, "tmp for generateBoundary");
+ for (int y = 0; y < h; y++) {
+ KisHLineIteratorPixel it = dev->createHLineIterator(0, y, w, true);
+ int x = 0;
+
+ while(!it.isDone()) {
+ cs->setAlpha(it.rawData(), amask->alphaAt(x++, y), 1);
+ ++it;
+ }
+ }
+ }
+
+ m_boundary = new KisBoundary(dev);
+ m_boundary->generateBoundary(w, h);
+}
+
+KisBoundary KisBrush::boundary() {
+ if (!m_boundary)
+ generateBoundary();
+ return *m_boundary;
+}
+
+void KisBrush::makeMaskImage() {
+ if (!hasColor())
+ return;
+
+ QImage img;
+ img.create(width(), height(), 32);
+
+ if (m_img.width() == img.width() && m_img.height() == img.height()) {
+ for (int x = 0; x < width(); x++) {
+ for (int y = 0; y < height(); y++) {
+ QRgb c = m_img.pixel(x, y);
+ int a = (qGray(c) * qAlpha(c)) / 255; // qGray(black) = 0
+ img.setPixel(x, y, qRgba(a, a, a, 255));
+ }
+ }
+
+ m_img = img;
+ }
+
+ m_brushType = MASK;
+ m_hasColor = false;
+ m_useColorAsMask = false;
+ delete m_boundary;
+ m_boundary = 0;
+ m_scaledBrushes.clear();
+}
+
+KisBrush* KisBrush::clone() const {
+ KisBrush* c = new KisBrush("");
+ c->m_spacing = m_spacing;
+ c->m_useColorAsMask = m_useColorAsMask;
+ c->m_hasColor = m_useColorAsMask;
+ c->m_img = m_img;
+ c->m_width = m_width;
+ c->m_height = m_height;
+ c->m_ownData = false;
+ c->m_hotSpot = m_hotSpot;
+ c->m_brushType = m_brushType;
+ c->setValid(true);
+
+ return c;
+}
+
+#include "kis_brush.moc"
+