|
|
|
/*
|
|
|
|
* 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 <tqfile.h>
|
|
|
|
#include <tqimage.h>
|
|
|
|
#include <tqpoint.h>
|
|
|
|
#include <tqvaluevector.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 {
|
|
|
|
TQ_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
|
|
|
|
TQ_UINT32 version; /* brush file version # */
|
|
|
|
TQ_UINT32 width; /* width of brush */
|
|
|
|
TQ_UINT32 height; /* height of brush */
|
|
|
|
TQ_UINT32 bytes; /* depth of brush in bytes */
|
|
|
|
};
|
|
|
|
|
|
|
|
/// All fields are in MSB on disk!
|
|
|
|
struct GimpBrushHeader {
|
|
|
|
TQ_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
|
|
|
|
TQ_UINT32 version; /* brush file version # */
|
|
|
|
TQ_UINT32 width; /* width of brush */
|
|
|
|
TQ_UINT32 height; /* height of brush */
|
|
|
|
TQ_UINT32 bytes; /* depth of brush in bytes */
|
|
|
|
|
|
|
|
/* The following are only defined in version 2 */
|
|
|
|
TQ_UINT32 magic_number; /* GIMP brush magic number */
|
|
|
|
TQ_UINT32 spacing; /* brush spacing as % of width & height, 0 - 1000 */
|
|
|
|
};
|
|
|
|
|
|
|
|
// Needed, or the GIMP won't open it!
|
|
|
|
TQ_UINT32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define DEFAULT_SPACING 0.25
|
|
|
|
#define MAXIMUM_SCALE 2
|
|
|
|
|
|
|
|
KisBrush::KisBrush(const TQString& 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 TQString& filename,
|
|
|
|
const TQByteArray& data,
|
|
|
|
TQ_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(TQString(""))
|
|
|
|
{
|
|
|
|
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 TQImage& image, const TQString& name)
|
|
|
|
: super(TQString(""))
|
|
|
|
{
|
|
|
|
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) {
|
|
|
|
TQFile 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString 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 = TQString::fromAscii(text, bh.header_size - sizeof(GimpBrushV1Header));
|
|
|
|
} else {
|
|
|
|
// ### Version = 3->cinepaint; may be float16 data!
|
|
|
|
// Version >=2: UTF-8 encoding is used
|
|
|
|
name = TQString::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;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_INT32 k = bh.header_size;
|
|
|
|
|
|
|
|
if (bh.bytes == 1) {
|
|
|
|
// Grayscale
|
|
|
|
|
|
|
|
if (static_cast<TQ_UINT32>(k + bh.width * bh.height) > m_data.size()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_brushType = MASK;
|
|
|
|
m_hasColor = false;
|
|
|
|
|
|
|
|
for (TQ_UINT32 y = 0; y < bh.height; y++) {
|
|
|
|
for (TQ_UINT32 x = 0; x < bh.width; x++, k++) {
|
|
|
|
TQ_INT32 val = 255 - static_cast<uchar>(m_data[k]);
|
|
|
|
m_img.setPixel(x, y, tqRgb(val, val, val));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (bh.bytes == 4) {
|
|
|
|
// RGBA
|
|
|
|
|
|
|
|
if (static_cast<TQ_UINT32>(k + (bh.width * bh.height * 4)) > m_data.size()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_brushType = IMAGE;
|
|
|
|
m_img.setAlphaBuffer(true);
|
|
|
|
m_hasColor = true;
|
|
|
|
|
|
|
|
for (TQ_UINT32 y = 0; y < bh.height; y++) {
|
|
|
|
for (TQ_UINT32 x = 0; x < bh.width; x++, k += 4) {
|
|
|
|
m_img.setPixel(x, y, tqRgba(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->convertToTQImage(0, x, y, w, h));
|
|
|
|
setName(image->name());
|
|
|
|
|
|
|
|
m_brushType = IMAGE;
|
|
|
|
m_hasColor = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KisBrush::save()
|
|
|
|
{
|
|
|
|
TQFile file(filename());
|
|
|
|
file.open(IO_WriteOnly | IO_Truncate);
|
|
|
|
bool ok = saveToDevice(TQT_TQIODEVICE(&file));
|
|
|
|
file.close();
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KisBrush::saveToDevice(TQIODevice* dev) const
|
|
|
|
{
|
|
|
|
GimpBrushHeader bh;
|
|
|
|
TQCString utf8Name = name().utf8(); // Names in v2 brushes are in UTF-8
|
|
|
|
char const* name = utf8Name.data();
|
|
|
|
int nameLength = tqstrlen(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<TQ_UINT32>(spacing() * 100.0));
|
|
|
|
|
|
|
|
// Write header: first bh, then the name
|
|
|
|
TQByteArray 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 (TQ_INT32 y = 0; y < height(); y++) {
|
|
|
|
for (TQ_INT32 x = 0; x < width(); x++) {
|
|
|
|
TQRgb c = m_img.pixel(x, y);
|
|
|
|
bytes[k++] = static_cast<char>(255 - tqRed(c)); // red == blue == green
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bytes.resize(width() * height() * 4);
|
|
|
|
for (TQ_INT32 y = 0; y < height(); y++) {
|
|
|
|
for (TQ_INT32 x = 0; x < width(); x++) {
|
|
|
|
// order for gimp brushes, v2 is: RGBA
|
|
|
|
TQRgb pixel = m_img.pixel(x,y);
|
|
|
|
bytes[k++] = static_cast<char>(tqRed(pixel));
|
|
|
|
bytes[k++] = static_cast<char>(tqGreen(pixel));
|
|
|
|
bytes[k++] = static_cast<char>(tqBlue(pixel));
|
|
|
|
bytes[k++] = static_cast<char>(tqAlpha(pixel));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wrote = dev->writeBlock(bytes);
|
|
|
|
if (wrote == -1)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQImage KisBrush::img()
|
|
|
|
{
|
|
|
|
TQImage image = m_img;
|
|
|
|
|
|
|
|
if (hasColor() && useColorAsMask()) {
|
|
|
|
image.detach();
|
|
|
|
|
|
|
|
for (int x = 0; x < image.width(); x++) {
|
|
|
|
for (int y = 0; y < image.height(); y++) {
|
|
|
|
TQRgb c = image.pixel(x, y);
|
|
|
|
int a = (tqGray(c) * tqAlpha(c)) / 255;
|
|
|
|
image.setPixel(x, y, tqRgba(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);
|
|
|
|
|
|
|
|
TQImage outputImage;
|
|
|
|
|
|
|
|
if (belowBrush != 0) {
|
|
|
|
// We're in between two brushes. Interpolate between them.
|
|
|
|
|
|
|
|
TQImage scaledAboveImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY);
|
|
|
|
TQImage 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++) {
|
|
|
|
TQ_UINT8 * p = iter.rawData();
|
|
|
|
|
|
|
|
TQRgb pixel = outputImage.pixel(x, y);
|
|
|
|
int red = tqRed(pixel);
|
|
|
|
int green = tqGreen(pixel);
|
|
|
|
int blue = tqBlue(pixel);
|
|
|
|
int alpha = tqAlpha(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;
|
|
|
|
|
|
|
|
TQImage 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 : TQImage(), 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_INT32 KisBrush::maskWidth(const KisPaintInformation& info) const
|
|
|
|
{
|
|
|
|
// Add one for sub-pixel shift
|
|
|
|
return static_cast<TQ_INT32>(ceil(width() * scaleForPressure(info.pressure)) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_INT32 KisBrush::maskHeight(const KisPaintInformation& info) const
|
|
|
|
{
|
|
|
|
// Add one for sub-pixel shift
|
|
|
|
return static_cast<TQ_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;
|
|
|
|
|
|
|
|
TQ_UINT8 topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX, topY) : OPACITY_TRANSPARENT;
|
|
|
|
TQ_UINT8 bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX, topY + 1) : OPACITY_TRANSPARENT;
|
|
|
|
TQ_UINT8 topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX + 1, topY) : OPACITY_TRANSPARENT;
|
|
|
|
TQ_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<TQ_UINT8>(d));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dstMask;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQImage 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;
|
|
|
|
|
|
|
|
TQImage dstImage(dstWidth, dstHeight, 32);
|
|
|
|
dstImage.setAlphaBuffer(true);
|
|
|
|
|
|
|
|
const TQImage 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;
|
|
|
|
|
|
|
|
TQRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : tqRgba(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 * tqRed(topLeft)
|
|
|
|
+ a * (1 - b) * tqRed(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqRed(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqRed(bottomRight) + 0.5);
|
|
|
|
int green = static_cast<int>(a * b * tqGreen(topLeft)
|
|
|
|
+ a * (1 - b) * tqGreen(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqGreen(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqGreen(bottomRight) + 0.5);
|
|
|
|
int blue = static_cast<int>(a * b * tqBlue(topLeft)
|
|
|
|
+ a * (1 - b) * tqBlue(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqBlue(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqBlue(bottomRight) + 0.5);
|
|
|
|
int alpha = static_cast<int>(a * b * tqAlpha(topLeft)
|
|
|
|
+ a * (1 - b) * tqAlpha(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqAlpha(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqAlpha(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, tqRgba(red, green, blue, alpha));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dstImage;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQImage KisBrush::scaleImage(const TQImage& srcImage, int width, int height)
|
|
|
|
{
|
|
|
|
TQImage scaledImage;
|
|
|
|
//TQString 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 = TQString("smoothScale_%1x%2.png").tqarg(width).tqarg(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;
|
|
|
|
|
|
|
|
TQRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : tqRgba(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 * tqRed(topLeft) * tqAlpha(topLeft)
|
|
|
|
+ a * (1 - b) * tqRed(bottomLeft) * tqAlpha(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqRed(topRight) * tqAlpha(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqRed(bottomRight) * tqAlpha(bottomRight) + 0.5);
|
|
|
|
green = static_cast<int>(a * b * tqGreen(topLeft) * tqAlpha(topLeft)
|
|
|
|
+ a * (1 - b) * tqGreen(bottomLeft) * tqAlpha(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqGreen(topRight) * tqAlpha(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqGreen(bottomRight) * tqAlpha(bottomRight) + 0.5);
|
|
|
|
blue = static_cast<int>(a * b * tqBlue(topLeft) * tqAlpha(topLeft)
|
|
|
|
+ a * (1 - b) * tqBlue(bottomLeft) * tqAlpha(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqBlue(topRight) * tqAlpha(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqBlue(bottomRight) * tqAlpha(bottomRight) + 0.5);
|
|
|
|
alpha = static_cast<int>(a * b * tqAlpha(topLeft)
|
|
|
|
+ a * (1 - b) * tqAlpha(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqAlpha(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqAlpha(bottomRight) + 0.5);
|
|
|
|
|
|
|
|
if (alpha != 0) {
|
|
|
|
red /= alpha;
|
|
|
|
green /= alpha;
|
|
|
|
blue /= alpha;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
red = static_cast<int>(a * b * tqRed(topLeft)
|
|
|
|
+ a * (1 - b) * tqRed(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqRed(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqRed(bottomRight) + 0.5);
|
|
|
|
green = static_cast<int>(a * b * tqGreen(topLeft)
|
|
|
|
+ a * (1 - b) * tqGreen(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqGreen(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqGreen(bottomRight) + 0.5);
|
|
|
|
blue = static_cast<int>(a * b * tqBlue(topLeft)
|
|
|
|
+ a * (1 - b) * tqBlue(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqBlue(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqBlue(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, tqRgba(red, green, blue, alpha));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//filename = TQString("bilinear_%1x%2.png").tqarg(width).tqarg(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, TQ_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++) {
|
|
|
|
|
|
|
|
TQ_UINT8 topLeft = (x > 0 && y > 0) ? maskValue : OPACITY_TRANSPARENT;
|
|
|
|
TQ_UINT8 bottomLeft = (x > 0 && y < srcHeight) ? maskValue : OPACITY_TRANSPARENT;
|
|
|
|
TQ_UINT8 topRight = (x < srcWidth && y > 0) ? maskValue : OPACITY_TRANSPARENT;
|
|
|
|
TQ_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<TQ_UINT8>(d));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return outputMask;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQImage KisBrush::scaleSinglePixelImage(double scale, TQRgb pixel, double subPixelX, double subPixelY)
|
|
|
|
{
|
|
|
|
int srcWidth = 1;
|
|
|
|
int srcHeight = 1;
|
|
|
|
int dstWidth = 2;
|
|
|
|
int dstHeight = 2;
|
|
|
|
|
|
|
|
TQImage 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++) {
|
|
|
|
|
|
|
|
TQRgb topLeft = (x > 0 && y > 0) ? pixel : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb bottomLeft = (x > 0 && y < srcHeight) ? pixel : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb topRight = (x < srcWidth && y > 0) ? pixel : tqRgba(0, 0, 0, 0);
|
|
|
|
TQRgb bottomRight = (x < srcWidth && y < srcHeight) ? pixel : tqRgba(0, 0, 0, 0);
|
|
|
|
|
|
|
|
// Bi-linear interpolation. Images are in pre-multiplied form.
|
|
|
|
int red = static_cast<int>(a * b * tqRed(topLeft)
|
|
|
|
+ a * (1 - b) * tqRed(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqRed(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqRed(bottomRight) + 0.5);
|
|
|
|
int green = static_cast<int>(a * b * tqGreen(topLeft)
|
|
|
|
+ a * (1 - b) * tqGreen(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqGreen(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqGreen(bottomRight) + 0.5);
|
|
|
|
int blue = static_cast<int>(a * b * tqBlue(topLeft)
|
|
|
|
+ a * (1 - b) * tqBlue(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqBlue(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqBlue(bottomRight) + 0.5);
|
|
|
|
int alpha = static_cast<int>(a * b * tqAlpha(topLeft)
|
|
|
|
+ a * (1 - b) * tqAlpha(bottomLeft)
|
|
|
|
+ (1 - a) * b * tqAlpha(topRight)
|
|
|
|
+ (1 - a) * (1 - b) * tqAlpha(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, tqRgba(red, green, blue, alpha));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return outputImage;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQImage KisBrush::interpolate(const TQImage& image1, const TQImage& 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();
|
|
|
|
|
|
|
|
TQImage outputImage(width, height, 32);
|
|
|
|
outputImage.setAlphaBuffer(true);
|
|
|
|
|
|
|
|
for (int x = 0; x < width; x++) {
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
|
|
TQRgb image1pixel = image1.pixel(x, y);
|
|
|
|
TQRgb image2pixel = image2.pixel(x, y);
|
|
|
|
|
|
|
|
// Images are in pre-multiplied alpha format.
|
|
|
|
int red = static_cast<int>((1 - t) * tqRed(image1pixel) + t * tqRed(image2pixel) + 0.5);
|
|
|
|
int green = static_cast<int>((1 - t) * tqGreen(image1pixel) + t * tqGreen(image2pixel) + 0.5);
|
|
|
|
int blue = static_cast<int>((1 - t) * tqBlue(image1pixel) + t * tqBlue(image2pixel) + 0.5);
|
|
|
|
int alpha = static_cast<int>((1 - t) * tqAlpha(image1pixel) + t * tqAlpha(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, tqRgba(red, green, blue, alpha));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return outputImage;
|
|
|
|
}
|
|
|
|
|
|
|
|
KisBrush::ScaledBrush::ScaledBrush()
|
|
|
|
{
|
|
|
|
m_mask = 0;
|
|
|
|
m_image = TQImage();
|
|
|
|
m_scale = 1;
|
|
|
|
m_xScale = 1;
|
|
|
|
m_yScale = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
KisBrush::ScaledBrush::ScaledBrush(KisAlphaMaskSP scaledMask, const TQImage& 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++) {
|
|
|
|
|
|
|
|
TQRgb pixel = m_image.pixel(x, y);
|
|
|
|
|
|
|
|
int red = tqRed(pixel);
|
|
|
|
int green = tqGreen(pixel);
|
|
|
|
int blue = tqBlue(pixel);
|
|
|
|
int alpha = tqAlpha(pixel);
|
|
|
|
|
|
|
|
red = (red * alpha) / 255;
|
|
|
|
green = (green * alpha) / 255;
|
|
|
|
blue = (blue * alpha) / 255;
|
|
|
|
|
|
|
|
m_image.setPixel(x, y, tqRgba(red, green, blue, alpha));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisBrush::setImage(const TQImage& img)
|
|
|
|
{
|
|
|
|
m_img = img;
|
|
|
|
m_img.detach();
|
|
|
|
|
|
|
|
setWidth(img.width());
|
|
|
|
setHeight(img.height());
|
|
|
|
|
|
|
|
m_scaledBrushes.clear();
|
|
|
|
|
|
|
|
setValid(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_INT32 KisBrush::width() const
|
|
|
|
{
|
|
|
|
return m_width;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisBrush::setWidth(TQ_INT32 w)
|
|
|
|
{
|
|
|
|
m_width = w;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_INT32 KisBrush::height() const
|
|
|
|
{
|
|
|
|
return m_height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisBrush::setHeight(TQ_INT32 h)
|
|
|
|
{
|
|
|
|
m_height = h;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*TQImage 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);
|
|
|
|
TQPixmap pix(bounds.pixmap(w, h));
|
|
|
|
TQImage 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;
|
|
|
|
|
|
|
|
TQImage 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++) {
|
|
|
|
TQRgb c = m_img.pixel(x, y);
|
|
|
|
int a = (tqGray(c) * tqAlpha(c)) / 255; // tqGray(black) = 0
|
|
|
|
img.setPixel(x, y, tqRgba(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"
|
|
|
|
|