diff --git a/libs/brush/kis_abr_brush_collection.cpp b/libs/brush/kis_abr_brush_collection.cpp index cf1a48404d..e0d08671aa 100644 --- a/libs/brush/kis_abr_brush_collection.cpp +++ b/libs/brush/kis_abr_brush_collection.cpp @@ -1,627 +1,628 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2007 Eric Lamarque * * 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 #include "kis_abr_brush_collection.h" #include "kis_abr_brush.h" #include #include #include #include #include #include #include #include #include #include #include struct AbrInfo { //big endian short version; short subversion; // count of the images (brushes) in the abr file short count; }; /// save the QImages as png files to directory image_tests static QImage convertToQImage(char * buffer, qint32 width, qint32 height) { // create 8-bit indexed image QImage img(width, height, QImage::Format_RGB32); int pos = 0; int value = 0; for (int y = 0; y < height; y++) { QRgb *pixel = reinterpret_cast(img.scanLine(y)); for (int x = 0; x < width; x++, pos++) { value = 255 - buffer[pos]; pixel[x] = qRgb(value, value , value); } } return img; } static qint32 rle_decode(QDataStream & abr, char *buffer, qint32 height) { qint32 n; char ptmp; char ch; int i, j, c; short *cscanline_len; char *data = buffer; // read compressed size foreach scanline cscanline_len = new short[ height ]; for (i = 0; i < height; i++) { // short abr >> cscanline_len[i]; } // unpack each scanline data for (i = 0; i < height; i++) { for (j = 0; j < cscanline_len[i];) { // char if (!abr.device()->getChar(&ptmp)) { break; } n = ptmp; j++; if (n >= 128) // force sign n -= 256; if (n < 0) { // copy the following char -n + 1 times if (n == -128) // it's a nop continue; n = -n + 1; // char if (!abr.device()->getChar(&ch)) { break; } j++; for (c = 0; c < n; c++, data++) { *data = ch; } } else { // read the following n + 1 chars (no compr) for (c = 0; c < n + 1; c++, j++, data++) { // char if (!abr.device()->getChar(data)) { break; } } } } } delete [] cscanline_len; return 0; } static QString abr_v1_brush_name(const QString filename, qint32 id) { QString result = filename; int pos = filename.lastIndexOf('.'); result.remove(pos, 4); QTextStream(&result) << "_" << id; return result; } static bool abr_supported_content(AbrInfo *abr_hdr) { switch (abr_hdr->version) { case 1: case 2: return true; break; case 6: if (abr_hdr->subversion == 1 || abr_hdr->subversion == 2) return true; break; } return false; } static bool abr_reach_8BIM_section(QDataStream & abr, const QString name) { char tag[4]; char tagname[5]; qint32 section_size = 0; int r; // find 8BIMname section while (!abr.atEnd()) { r = abr.readRawData(tag, 4); if (r != 4) { warnKrita << "Error: Cannot read 8BIM tag "; return false; } if (strncmp(tag, "8BIM", 4)) { warnKrita << "Error: Start tag not 8BIM but " << (int)tag[0] << (int)tag[1] << (int)tag[2] << (int)tag[3] << " at position " << abr.device()->pos(); return false; } r = abr.readRawData(tagname, 4); if (r != 4) { warnKrita << "Error: Cannot read 8BIM tag name"; return false; } tagname[4] = '\0'; QString s1 = QString::fromLatin1(tagname, 4); if (!s1.compare(name)) { return true; } // long abr >> section_size; abr.device()->seek(abr.device()->pos() + section_size); } return true; } static qint32 find_sample_count_v6(QDataStream & abr, AbrInfo *abr_info) { qint64 origin; qint32 sample_section_size; qint32 sample_section_end; qint32 samples = 0; qint32 data_start; qint32 brush_size; qint32 brush_end; if (!abr_supported_content(abr_info)) return 0; origin = abr.device()->pos(); if (!abr_reach_8BIM_section(abr, "samp")) { // reset to origin abr.device()->seek(origin); return 0; } // long abr >> sample_section_size; sample_section_end = sample_section_size + abr.device()->pos(); if(sample_section_end < 0 || sample_section_end > abr.device()->size()) return 0; data_start = abr.device()->pos(); while ((!abr.atEnd()) && (abr.device()->pos() < sample_section_end)) { // read long abr >> brush_size; brush_end = brush_size; // complement to 4 while (brush_end % 4 != 0) brush_end++; qint64 newPos = abr.device()->pos() + brush_end; if(newPos > 0 && newPos < abr.device()->size()) { abr.device()->seek(newPos); } else return 0; samples++; } // set stream to samples data abr.device()->seek(data_start); //dbgKrita <<"samples : "<< samples; return samples; } static bool abr_read_content(QDataStream & abr, AbrInfo *abr_hdr) { abr >> abr_hdr->version; abr_hdr->subversion = 0; abr_hdr->count = 0; switch (abr_hdr->version) { case 1: case 2: abr >> abr_hdr->count; break; case 6: abr >> abr_hdr->subversion; abr_hdr->count = find_sample_count_v6(abr, abr_hdr); break; default: // unknown versions break; } // next bytes in abr are samples data return true; } static QString abr_read_ucs2_text(QDataStream & abr) { quint32 name_size; quint32 buf_size; uint i; /* two-bytes characters encoded (UCS-2) * format: * long : size - number of characters in string * data : zero terminated UCS-2 string */ // long abr >> name_size; if (name_size == 0) { return QString(); } //buf_size = name_size * 2; buf_size = name_size; //name_ucs2 = (char*) malloc (buf_size * sizeof (char)); //name_ucs2 = new char[buf_size]; ushort * name_ucs2 = new ushort[buf_size]; for (i = 0; i < buf_size ; i++) { //* char*/ //abr >> name_ucs2[i]; // I will use ushort as that is input to fromUtf16 abr >> name_ucs2[i]; } QString name_utf8 = QString::fromUtf16(name_ucs2, buf_size); delete [] name_ucs2; return name_utf8; } quint32 KisAbrBrushCollection::abr_brush_load_v6(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id) { Q_UNUSED(image_ID); qint32 brush_size = 0; qint32 brush_end = 0; qint32 next_brush = 0; qint32 top, left, bottom, right; top = left = bottom = right = 0; short depth; char compression; qint32 width = 0; qint32 height = 0; qint32 size = 0; qint32 layer_ID = -1; char *buffer; abr >> brush_size; brush_end = brush_size; // complement to 4 while (brush_end % 4 != 0) { brush_end++; } next_brush = abr.device()->pos() + brush_end; // discard key abr.device()->seek(abr.device()->pos() + 37); if (abr_hdr->subversion == 1) // discard short coordinates and unknown short abr.device()->seek(abr.device()->pos() + 10); else // discard unknown bytes abr.device()->seek(abr.device()->pos() + 264); // long abr >> top; abr >> left; abr >> bottom; abr >> right; // short abr >> depth; // char abr.device()->getChar(&compression); width = right - left; height = bottom - top; size = width * (depth >> 3) * height; // remove .abr and add some id, so something like test.abr -> test_12345 QString name = abr_v1_brush_name(filename, id); buffer = (char*)malloc(size); // data decoding if (!compression) { // not compressed - read raw bytes as brush data //fread (buffer, size, 1, abr); abr.readRawData(buffer, size); } else { rle_decode(abr, buffer, height); } if (width < quint16_MAX && height < quint16_MAX) { // filename - filename of the file , e.g. test.abr // name - test_number_of_the_brush, e.g test_1, test_2 KisAbrBrush* abrBrush = 0; if (m_abrBrushes.contains(name)) { abrBrush = m_abrBrushes[name]; } else { abrBrush = new KisAbrBrush(name, this); abrBrush->setMD5(md5()); } abrBrush->setBrushTipImage(convertToQImage(buffer, width, height)); // XXX: call extra setters on abrBrush for other options of ABR brushes abrBrush->setValid(true); abrBrush->setName(name); m_abrBrushes[name] = abrBrush; } free(buffer); abr.device()->seek(next_brush); layer_ID = id; return layer_ID; } qint32 KisAbrBrushCollection::abr_brush_load_v12(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id) { Q_UNUSED(image_ID); short brush_type; qint32 brush_size; qint32 next_brush; qint32 top, left, bottom, right; qint16 depth; char compression; QString name; qint32 width, height; qint32 size; qint32 layer_ID = -1; char *buffer; // short abr >> brush_type; // long abr >> brush_size; next_brush = abr.device()->pos() + brush_size; if (brush_type == 1) { // computed brush // FIXME: support it! warnKrita << "WARNING: computed brush unsupported, skipping."; abr.device()->seek(abr.device()->pos() + next_brush); // TODO: test also this one abr.skipRawData(next_brush); } else if (brush_type == 2) { // sampled brush // discard 4 misc bytes and 2 spacing bytes abr.device()->seek(abr.device()->pos() + 6); if (abr_hdr->version == 2) name = abr_read_ucs2_text(abr); if (name.isNull()) { name = abr_v1_brush_name(filename, id); } // discard 1 byte for antialiasing and 4 x short for short bounds abr.device()->seek(abr.device()->pos() + 9); // long abr >> top; abr >> left; abr >> bottom; abr >> right; // short abr >> depth; // char abr.device()->getChar(&compression); width = right - left; height = bottom - top; size = width * (depth >> 3) * height; /* FIXME: support wide brushes */ if (height > 16384) { warnKrita << "WARNING: wide brushes not supported"; abr.device()->seek(next_brush); } else { buffer = (char*)malloc(size); if (!compression) { // not compressed - read raw bytes as brush data abr.readRawData(buffer, size); } else { rle_decode(abr, buffer, height); } KisAbrBrush* abrBrush = 0; if (m_abrBrushes.contains(name)) { abrBrush = m_abrBrushes[name]; } else { abrBrush = new KisAbrBrush(name, this); abrBrush->setMD5(md5()); } abrBrush->setBrushTipImage(convertToQImage(buffer, width, height)); // XXX: call extra setters on abrBrush for other options of ABR brushes free (buffer); abrBrush->setValid(true); abrBrush->setName(name); m_abrBrushes[name] = abrBrush; layer_ID = 1; } } else { warnKrita << "Unknown ABR brush type, skipping."; abr.device()->seek(next_brush); } return layer_ID; } qint32 KisAbrBrushCollection::abr_brush_load(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id) { qint32 layer_ID = -1; switch (abr_hdr->version) { case 1: + Q_FALLTHROUGH(); // fall through, version 1 and 2 are compatible case 2: layer_ID = abr_brush_load_v12(abr, abr_hdr, filename, image_ID, id); break; case 6: layer_ID = abr_brush_load_v6(abr, abr_hdr, filename, image_ID, id); break; } return layer_ID; } KisAbrBrushCollection::KisAbrBrushCollection(const QString& filename) : KisScalingSizeBrush(filename) { } KisAbrBrushCollection::KisAbrBrushCollection(const KisAbrBrushCollection& rhs) : KisScalingSizeBrush(rhs) { for (auto it = rhs.m_abrBrushes.begin(); it != rhs.m_abrBrushes.end(); ++it) { m_abrBrushes.insert(it.key(), new KisAbrBrush(*it.value(), this)); } } KisBrush* KisAbrBrushCollection::clone() const { return new KisAbrBrushCollection(*this); } bool KisAbrBrushCollection::load() { QFile file(filename()); // check if the file is open correctly if (!file.open(QIODevice::ReadOnly)) { warnKrita << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KisAbrBrushCollection::loadFromDevice(QIODevice *dev) { AbrInfo abr_hdr; qint32 image_ID; int i; qint32 layer_ID; QByteArray ba = dev->readAll(); QBuffer buf(&ba); buf.open(QIODevice::ReadOnly); QDataStream abr(&buf); if (!abr_read_content(abr, &abr_hdr)) { warnKrita << "Error: cannot parse ABR file: " << filename(); return false; } if (!abr_supported_content(&abr_hdr)) { warnKrita << "ERROR: unable to decode abr format version " << abr_hdr.version << "(subver " << abr_hdr.subversion << ")"; return false; } if (abr_hdr.count == 0) { errKrita << "ERROR: no sample brush found in " << filename(); return false; } image_ID = 123456; for (i = 0; i < abr_hdr.count; i++) { layer_ID = abr_brush_load(abr, &abr_hdr, shortFilename(), image_ID, i + 1); if (layer_ID == -1) { warnKrita << "Warning: problem loading brush #" << i << " in " << filename(); } } return true; } bool KisAbrBrushCollection::save() { return false; } bool KisAbrBrushCollection::saveToDevice(QIODevice */*dev*/) const { return false; } QImage KisAbrBrushCollection::image() const { return QImage(); } void KisAbrBrushCollection::toXML(QDomDocument& d, QDomElement& e) const { Q_UNUSED(d); Q_UNUSED(e); // Do nothing... } QString KisAbrBrushCollection::defaultFileExtension() const { return QString(".abr"); } diff --git a/libs/flake/KoImageData.cpp b/libs/flake/KoImageData.cpp index a0d08369e4..006aa419fd 100644 --- a/libs/flake/KoImageData.cpp +++ b/libs/flake/KoImageData.cpp @@ -1,379 +1,379 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 C. Boemann * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoImageData.h" #include "KoImageData_p.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include /// the maximum amount of bytes the image can be while we store it in memory instead of /// spooling it to disk in a temp-file. #define MAX_MEMORY_IMAGESIZE 90000 KoImageData::KoImageData() : d(0) { } KoImageData::KoImageData(const KoImageData &imageData) : KoShapeUserData(), d(imageData.d) { if (d) d->refCount.ref(); } KoImageData::KoImageData(KoImageDataPrivate *priv) : d(priv) { d->refCount.ref(); } KoImageData::~KoImageData() { if (d && !d->refCount.deref()) delete d; } QPixmap KoImageData::pixmap(const QSize &size) { if (!d) return QPixmap(); QSize wantedSize = size; if (! wantedSize.isValid()) { if (d->pixmap.isNull()) // we have a problem, Houston.. wantedSize = QSize(100, 100); else wantedSize = d->pixmap.size(); } if (d->pixmap.isNull() || d->pixmap.size() != wantedSize) { switch (d->dataStoreState) { case KoImageDataPrivate::StateEmpty: { #if 0 // this is not possible as it gets called during the paint method // and will crash. Therefore create a tmp pixmap and return it. d->pixmap = QPixmap(1, 1); QPainter p(&d->pixmap); p.setPen(QPen(Qt::gray, 0)); p.drawPoint(0, 0); p.end(); break; #endif QPixmap tmp(1, 1); tmp.fill(Qt::gray); return tmp; } case KoImageDataPrivate::StateNotLoaded: image(); // forces load - // fall through + Q_FALLTHROUGH(); case KoImageDataPrivate::StateImageLoaded: case KoImageDataPrivate::StateImageOnly: if (!d->image.isNull()) { // create pixmap from image. // this is the highest quality and lowest memory usage way of doing the conversion. d->pixmap = QPixmap::fromImage(d->image.scaled(wantedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } } if (d->dataStoreState == KoImageDataPrivate::StateImageLoaded) { if (d->cleanCacheTimer.isActive()) d->cleanCacheTimer.stop(); // schedule an auto-unload of the big QImage in a second. d->cleanCacheTimer.start(); } } return d->pixmap; } bool KoImageData::hasCachedPixmap() const { return d && !d->pixmap.isNull(); } QSizeF KoImageData::imageSize() { if (!d->imageSize.isValid()) { // The imagesize have not yet been calculated if (image().isNull()) // auto loads the image return QSizeF(100, 100); if (d->image.dotsPerMeterX()) d->imageSize.setWidth(DM_TO_POINT(d->image.width() / (qreal) d->image.dotsPerMeterX() * 10.0)); else d->imageSize.setWidth(d->image.width() / 72.0); if (d->image.dotsPerMeterY()) d->imageSize.setHeight(DM_TO_POINT(d->image.height() / (qreal) d->image.dotsPerMeterY() * 10.0)); else d->imageSize.setHeight(d->image.height() / 72.0); } return d->imageSize; } QImage KoImageData::image() const { if (d->dataStoreState == KoImageDataPrivate::StateNotLoaded) { // load image if (d->temporaryFile) { bool r = d->temporaryFile->open(); if (!r) { d->errorCode = OpenFailed; } else if (d->errorCode == Success && !d->image.load(d->temporaryFile->fileName(), d->suffix.toLatin1())) { d->errorCode = OpenFailed; } d->temporaryFile->close(); } else { if (d->errorCode == Success && !d->image.load(d->imageLocation.toLocalFile())) { d->errorCode = OpenFailed; } } if (d->errorCode == Success) { d->dataStoreState = KoImageDataPrivate::StateImageLoaded; } } return d->image; } bool KoImageData::hasCachedImage() const { return d && !d->image.isNull(); } void KoImageData::setImage(const QImage &image, KoImageCollection *collection) { qint64 oldKey = 0; if (d) { oldKey = d->key; } Q_ASSERT(!image.isNull()); if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and well go to the other clause KoImageData *other = collection->createImageData(image); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } delete d->temporaryFile; d->temporaryFile = 0; d->clear(); d->suffix = "png"; // good default for non-lossy storage. if (image.byteCount() > MAX_MEMORY_IMAGESIZE) { // store image QBuffer buffer; buffer.open(QIODevice::WriteOnly); if (!image.save(&buffer, d->suffix.toLatin1())) { warnFlake << "Write temporary file failed"; d->errorCode = StorageFailed; delete d->temporaryFile; d->temporaryFile = 0; return; } buffer.close(); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } else { d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // use .png for images we get as QImage QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(ba); d->key = KoImageDataPrivate::generateKey(md5.result()); } if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } void KoImageData::setImage(const QString &url, KoStore *store, KoImageCollection *collection) { if (collection) { // Let the collection first check if it already has one. If it // doesn't it'll call this method again and we'll go to the // other clause. KoImageData *other = collection->createImageData(url, store); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } else { d->clear(); } d->setSuffix(url); if (store->open(url)) { struct Finalizer { ~Finalizer() { store->close(); } KoStore *store; }; Finalizer closer; closer.store = store; KoStoreDevice device(store); const bool lossy = url.endsWith(".jpg", Qt::CaseInsensitive) || url.endsWith(".gif", Qt::CaseInsensitive); if (!lossy && device.size() < MAX_MEMORY_IMAGESIZE) { QByteArray data = device.readAll(); if (d->image.loadFromData(data)) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(data); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } d->dataStoreState = KoImageDataPrivate::StateImageOnly; return; } } if (!device.open(QIODevice::ReadOnly)) { warnFlake << "open file from store " << url << "failed"; d->errorCode = OpenFailed; return; } d->copyToTemporary(device); } else { warnFlake << "Find file in store " << url << "failed"; d->errorCode = OpenFailed; return; } } } void KoImageData::setImage(const QByteArray &imageData, KoImageCollection *collection) { if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and we'll go to the other clause KoImageData *other = collection->createImageData(imageData); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } d->suffix = "png"; // good default for non-lossy storage. if (imageData.size() <= MAX_MEMORY_IMAGESIZE) { QImage image; if (!image.loadFromData(imageData)) { // mark the image as invalid, but keep the data in memory // even if Calligra cannot handle the format, the data should // be retained d->errorCode = OpenFailed; } d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; } if (imageData.size() > MAX_MEMORY_IMAGESIZE || d->errorCode == OpenFailed) { d->image = QImage(); // store image data QBuffer buffer; buffer.setData(imageData); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(imageData); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } bool KoImageData::isValid() const { return d && d->dataStoreState != KoImageDataPrivate::StateEmpty && d->errorCode == Success; } bool KoImageData::operator==(const KoImageData &other) const { return other.d == d; } KoImageData &KoImageData::operator=(const KoImageData &other) { if (other.d) other.d->refCount.ref(); if (d && !d->refCount.deref()) delete d; d = other.d; return *this; } KoShapeUserData *KoImageData::clone() const { return new KoImageData(*this); } qint64 KoImageData::key() const { return d->key; } QString KoImageData::suffix() const { return d->suffix; } KoImageData::ErrorCode KoImageData::errorCode() const { return d->errorCode; } bool KoImageData::saveData(QIODevice &device) { return d->saveData(device); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoImageData.cpp" diff --git a/libs/flake/KoPathShapeLoader.cpp b/libs/flake/KoPathShapeLoader.cpp index f8307b9087..a3478344b5 100644 --- a/libs/flake/KoPathShapeLoader.cpp +++ b/libs/flake/KoPathShapeLoader.cpp @@ -1,648 +1,648 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathShapeLoader.h" #include "KoPathShape.h" #include #include class KoPathShapeLoaderPrivate { public: KoPathShapeLoaderPrivate(KoPathShape * p) : path(p) { Q_ASSERT(path); path->clear(); } void parseSvg(const QString &svgInputData, bool process = false); void svgMoveTo(qreal x1, qreal y1, bool abs = true); void svgLineTo(qreal x1, qreal y1, bool abs = true); void svgLineToHorizontal(qreal x, bool abs = true); void svgLineToVertical(qreal y, bool abs = true); void svgCurveToCubic(qreal x1, qreal y1, qreal x2, qreal y2, qreal x, qreal y, bool abs = true); void svgCurveToCubicSmooth(qreal x, qreal y, qreal x2, qreal y2, bool abs = true); void svgCurveToQuadratic(qreal x, qreal y, qreal x1, qreal y1, bool abs = true); void svgCurveToQuadraticSmooth(qreal x, qreal y, bool abs = true); void svgArcTo(qreal x, qreal y, qreal r1, qreal r2, qreal angle, bool largeArcFlag, bool sweepFlag, bool abs = true); void svgClosePath(); const char *getCoord(const char *, qreal &); void calculateArc(bool relative, qreal &curx, qreal &cury, qreal angle, qreal x, qreal y, qreal r1, qreal r2, bool largeArcFlag, bool sweepFlag); KoPathShape * path; ///< the path shape to work on QPointF lastPoint; }; void KoPathShapeLoaderPrivate::parseSvg(const QString &s, bool process) { if (!s.isEmpty()) { QString d = s; d.replace(',', ' '); d = d.simplified(); const QByteArray buffer = d.toLatin1(); const char *ptr = buffer.constData(); const char *end = buffer.constData() + buffer.length() + 1; qreal curx = 0.0; qreal cury = 0.0; qreal contrlx, contrly, subpathx, subpathy, tox, toy, x1, y1, x2, y2, xc, yc; qreal px1, py1, px2, py2, px3, py3; bool relative; char command = *(ptr++), lastCommand = ' '; subpathx = subpathy = curx = cury = contrlx = contrly = 0.0; while (ptr < end) { if (*ptr == ' ') ++ptr; relative = false; switch (command) { case 'm': relative = true; - /* Falls through. */ + Q_FALLTHROUGH(); case 'M': { ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (process) { subpathx = curx = relative ? curx + tox : tox; subpathy = cury = relative ? cury + toy : toy; svgMoveTo(curx, cury); } else svgMoveTo(tox, toy, !relative); break; } case 'l': relative = true; - /* Falls through. */ + Q_FALLTHROUGH(); case 'L': { ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (process) { curx = relative ? curx + tox : tox; cury = relative ? cury + toy : toy; svgLineTo(curx, cury); } else svgLineTo(tox, toy, !relative); break; } case 'h': { ptr = getCoord(ptr, tox); if (process) { curx = curx + tox; svgLineTo(curx, cury); } else svgLineToHorizontal(tox, false); break; } case 'H': { ptr = getCoord(ptr, tox); if (process) { curx = tox; svgLineTo(curx, cury); } else svgLineToHorizontal(tox); break; } case 'v': { ptr = getCoord(ptr, toy); if (process) { cury = cury + toy; svgLineTo(curx, cury); } else svgLineToVertical(toy, false); break; } case 'V': { ptr = getCoord(ptr, toy); if (process) { cury = toy; svgLineTo(curx, cury); } else svgLineToVertical(toy); break; } case 'z': - /* Falls through. */ + Q_FALLTHROUGH(); case 'Z': { // reset curx, cury for next path if (process) { curx = subpathx; cury = subpathy; } svgClosePath(); break; } case 'c': relative = true; - /* Falls through. */ + Q_FALLTHROUGH(); case 'C': { ptr = getCoord(ptr, x1); ptr = getCoord(ptr, y1); ptr = getCoord(ptr, x2); ptr = getCoord(ptr, y2); ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (process) { px1 = relative ? curx + x1 : x1; py1 = relative ? cury + y1 : y1; px2 = relative ? curx + x2 : x2; py2 = relative ? cury + y2 : y2; px3 = relative ? curx + tox : tox; py3 = relative ? cury + toy : toy; svgCurveToCubic(px1, py1, px2, py2, px3, py3); contrlx = relative ? curx + x2 : x2; contrly = relative ? cury + y2 : y2; curx = relative ? curx + tox : tox; cury = relative ? cury + toy : toy; } else svgCurveToCubic(x1, y1, x2, y2, tox, toy, !relative); break; } case 's': relative = true; - /* Falls through. */ + Q_FALLTHROUGH(); case 'S': { ptr = getCoord(ptr, x2); ptr = getCoord(ptr, y2); ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (!(lastCommand == 'c' || lastCommand == 'C' || lastCommand == 's' || lastCommand == 'S')) { contrlx = curx; contrly = cury; } if (process) { px1 = 2 * curx - contrlx; py1 = 2 * cury - contrly; px2 = relative ? curx + x2 : x2; py2 = relative ? cury + y2 : y2; px3 = relative ? curx + tox : tox; py3 = relative ? cury + toy : toy; svgCurveToCubic(px1, py1, px2, py2, px3, py3); contrlx = relative ? curx + x2 : x2; contrly = relative ? cury + y2 : y2; curx = relative ? curx + tox : tox; cury = relative ? cury + toy : toy; } else svgCurveToCubicSmooth(x2, y2, tox, toy, !relative); break; } case 'q': relative = true; - /* Falls through. */ + Q_FALLTHROUGH(); case 'Q': { ptr = getCoord(ptr, x1); ptr = getCoord(ptr, y1); ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (process) { px1 = relative ? (curx + 2 * (x1 + curx)) * (1.0 / 3.0) : (curx + 2 * x1) * (1.0 / 3.0); py1 = relative ? (cury + 2 * (y1 + cury)) * (1.0 / 3.0) : (cury + 2 * y1) * (1.0 / 3.0); px2 = relative ? ((curx + tox) + 2 * (x1 + curx)) * (1.0 / 3.0) : (tox + 2 * x1) * (1.0 / 3.0); py2 = relative ? ((cury + toy) + 2 * (y1 + cury)) * (1.0 / 3.0) : (toy + 2 * y1) * (1.0 / 3.0); px3 = relative ? curx + tox : tox; py3 = relative ? cury + toy : toy; svgCurveToCubic(px1, py1, px2, py2, px3, py3); contrlx = relative ? curx + x1 : x1; contrly = relative ? cury + y1 : y1; curx = relative ? curx + tox : tox; cury = relative ? cury + toy : toy; } else svgCurveToQuadratic(x1, y1, tox, toy, !relative); break; } case 't': relative = true; - /* Falls through. */ + Q_FALLTHROUGH(); case 'T': { ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (!(lastCommand == 'q' || lastCommand == 'Q' || lastCommand == 't' || lastCommand == 'T')) { contrlx = curx; contrly = cury; } if (process) { xc = 2 * curx - contrlx; yc = 2 * cury - contrly; px1 = (curx + 2 * xc) * (1.0 / 3.0); py1 = (cury + 2 * yc) * (1.0 / 3.0); px2 = relative ? ((curx + tox) + 2 * xc) * (1.0 / 3.0) : (tox + 2 * xc) * (1.0 / 3.0); py2 = relative ? ((cury + toy) + 2 * yc) * (1.0 / 3.0) : (toy + 2 * yc) * (1.0 / 3.0); px3 = relative ? curx + tox : tox; py3 = relative ? cury + toy : toy; svgCurveToCubic(px1, py1, px2, py2, px3, py3); contrlx = xc; contrly = yc; curx = relative ? curx + tox : tox; cury = relative ? cury + toy : toy; } else svgCurveToQuadraticSmooth(tox, toy, !relative); break; } case 'a': relative = true; - /* Falls through. */ + Q_FALLTHROUGH(); case 'A': { bool largeArc, sweep; qreal angle, rx, ry; ptr = getCoord(ptr, rx); ptr = getCoord(ptr, ry); ptr = getCoord(ptr, angle); ptr = getCoord(ptr, tox); largeArc = tox == 1; ptr = getCoord(ptr, tox); sweep = tox == 1; ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); // Spec: radii are nonnegative numbers rx = fabs(rx); ry = fabs(ry); if (process) calculateArc(relative, curx, cury, angle, tox, toy, rx, ry, largeArc, sweep); else svgArcTo(tox, toy, rx, ry, angle, largeArc, sweep, !relative); break; } default: { // when svg parser is used for a parsing an odf path an unknown command // can be encountered, so we stop parsing here debugFlake << "KoSvgPathParser::parseSVG(): unknown command \"" << command << "\""; return; } } lastCommand = command; if (*ptr == '+' || *ptr == '-' || (*ptr >= '0' && *ptr <= '9')) { // there are still coords in this command if (command == 'M') command = 'L'; else if (command == 'm') command = 'l'; } else command = *(ptr++); if (lastCommand != 'C' && lastCommand != 'c' && lastCommand != 'S' && lastCommand != 's' && lastCommand != 'Q' && lastCommand != 'q' && lastCommand != 'T' && lastCommand != 't') { contrlx = curx; contrly = cury; } } } } // parses the coord into number and forwards to the next token const char * KoPathShapeLoaderPrivate::getCoord(const char *ptr, qreal &number) { int integer, exponent; qreal decimal, frac; int sign, expsign; exponent = 0; integer = 0; frac = 1.0; decimal = 0; sign = 1; expsign = 1; // read the sign if (*ptr == '+') ++ptr; else if (*ptr == '-') { ++ptr; sign = -1; } // read the integer part while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') integer = (integer * 10) + *(ptr++) - '0'; if (*ptr == '.') { // read the decimals ++ptr; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') decimal += (*(ptr++) - '0') * (frac *= 0.1); } if (*ptr == 'e' || *ptr == 'E') { // read the exponent part ++ptr; // read the sign of the exponent if (*ptr == '+') ++ptr; else if (*ptr == '-') { ++ptr; expsign = -1; } exponent = 0; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') { exponent *= 10; exponent += *ptr - '0'; ++ptr; } } number = integer + decimal; number *= sign * pow((qreal)10, qreal(expsign * exponent)); // skip the following space if (*ptr == ' ') ++ptr; return ptr; } // This works by converting the SVG arc to "simple" beziers. // For each bezier found a svgToCurve call is done. // Adapted from Niko's code in kdelibs/kdecore/svgicons. // Maybe this can serve in some shared lib? (Rob) void KoPathShapeLoaderPrivate::calculateArc(bool relative, qreal &curx, qreal &cury, qreal angle, qreal x, qreal y, qreal r1, qreal r2, bool largeArcFlag, bool sweepFlag) { qreal sin_th, cos_th; qreal a00, a01, a10, a11; qreal x0, y0, x1, y1, xc, yc; qreal d, sfactor, sfactor_sq; qreal th0, th1, th_arc; int i, n_segs; sin_th = sin(angle * (M_PI / 180.0)); cos_th = cos(angle * (M_PI / 180.0)); qreal dx; if (!relative) dx = (curx - x) / 2.0; else dx = -x / 2.0; qreal dy; if (!relative) dy = (cury - y) / 2.0; else dy = -y / 2.0; qreal _x1 = cos_th * dx + sin_th * dy; qreal _y1 = -sin_th * dx + cos_th * dy; qreal Pr1 = r1 * r1; qreal Pr2 = r2 * r2; qreal Px = _x1 * _x1; qreal Py = _y1 * _y1; // Spec : check if radii are large enough qreal check = Px / Pr1 + Py / Pr2; if (check > 1) { r1 = r1 * sqrt(check); r2 = r2 * sqrt(check); } a00 = cos_th / r1; a01 = sin_th / r1; a10 = -sin_th / r2; a11 = cos_th / r2; x0 = a00 * curx + a01 * cury; y0 = a10 * curx + a11 * cury; if (!relative) x1 = a00 * x + a01 * y; else x1 = a00 * (curx + x) + a01 * (cury + y); if (!relative) y1 = a10 * x + a11 * y; else y1 = a10 * (curx + x) + a11 * (cury + y); /* (x0, y0) is current point in transformed coordinate space. (x1, y1) is new point in transformed coordinate space. The arc fits a unit-radius circle in this space. */ d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); sfactor_sq = 1.0 / d - 0.25; if (sfactor_sq < 0) sfactor_sq = 0; sfactor = sqrt(sfactor_sq); if (sweepFlag == largeArcFlag) sfactor = -sfactor; xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); /* (xc, yc) is center of the circle. */ th0 = atan2(y0 - yc, x0 - xc); th1 = atan2(y1 - yc, x1 - xc); th_arc = th1 - th0; if (th_arc < 0 && sweepFlag) th_arc += 2 * M_PI; else if (th_arc > 0 && !sweepFlag) th_arc -= 2 * M_PI; n_segs = (int)(int) ceil(fabs(th_arc / (M_PI * 0.5 + 0.001))); for (i = 0; i < n_segs; ++i) { { qreal sin_th, cos_th; qreal a00, a01, a10, a11; qreal x1, y1, x2, y2, x3, y3; qreal t; qreal th_half; qreal _th0 = th0 + i * th_arc / n_segs; qreal _th1 = th0 + (i + 1) * th_arc / n_segs; sin_th = sin(angle * (M_PI / 180.0)); cos_th = cos(angle * (M_PI / 180.0)); /* inverse transform compared with rsvg_path_arc */ a00 = cos_th * r1; a01 = -sin_th * r2; a10 = sin_th * r1; a11 = cos_th * r2; th_half = 0.5 * (_th1 - _th0); t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half); x1 = xc + cos(_th0) - t * sin(_th0); y1 = yc + sin(_th0) + t * cos(_th0); x3 = xc + cos(_th1); y3 = yc + sin(_th1); x2 = x3 + t * sin(_th1); y2 = y3 - t * cos(_th1); svgCurveToCubic(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3); } } if (!relative) curx = x; else curx += x; if (!relative) cury = y; else cury += y; } void KoPathShapeLoaderPrivate::svgMoveTo(qreal x1, qreal y1, bool abs) { if (abs) lastPoint = QPointF(x1, y1); else lastPoint += QPointF(x1, y1); path->moveTo(lastPoint); } void KoPathShapeLoaderPrivate::svgLineTo(qreal x1, qreal y1, bool abs) { if (abs) lastPoint = QPointF(x1, y1); else lastPoint += QPointF(x1, y1); path->lineTo(lastPoint); } void KoPathShapeLoaderPrivate::svgLineToHorizontal(qreal x, bool abs) { if (abs) lastPoint.setX(x); else lastPoint.rx() += x; path->lineTo(lastPoint); } void KoPathShapeLoaderPrivate::svgLineToVertical(qreal y, bool abs) { if (abs) lastPoint.setY(y); else lastPoint.ry() += y; path->lineTo(lastPoint); } void KoPathShapeLoaderPrivate::svgCurveToCubic(qreal x1, qreal y1, qreal x2, qreal y2, qreal x, qreal y, bool abs) { QPointF p1, p2; if (abs) { p1 = QPointF(x1, y1); p2 = QPointF(x2, y2); lastPoint = QPointF(x, y); } else { p1 = lastPoint + QPointF(x1, y1); p2 = lastPoint + QPointF(x2, y2); lastPoint += QPointF(x, y); } path->curveTo(p1, p2, lastPoint); } void KoPathShapeLoaderPrivate::svgCurveToCubicSmooth(qreal x, qreal y, qreal x2, qreal y2, bool abs) { Q_UNUSED(x); Q_UNUSED(y); Q_UNUSED(x2); Q_UNUSED(y2); Q_UNUSED(abs); // TODO implement } void KoPathShapeLoaderPrivate::svgCurveToQuadratic(qreal x, qreal y, qreal x1, qreal y1, bool abs) { Q_UNUSED(x); Q_UNUSED(y); Q_UNUSED(x1); Q_UNUSED(y1); Q_UNUSED(abs); // TODO implement } void KoPathShapeLoaderPrivate::svgCurveToQuadraticSmooth(qreal x, qreal y, bool abs) { Q_UNUSED(x); Q_UNUSED(y); Q_UNUSED(abs); // TODO implement } void KoPathShapeLoaderPrivate::svgArcTo(qreal x, qreal y, qreal r1, qreal r2, qreal angle, bool largeArcFlag, bool sweepFlag, bool abs) { Q_UNUSED(x); Q_UNUSED(y); Q_UNUSED(r1); Q_UNUSED(r2); Q_UNUSED(angle); Q_UNUSED(largeArcFlag); Q_UNUSED(sweepFlag); Q_UNUSED(abs); // TODO implement } void KoPathShapeLoaderPrivate::svgClosePath() { path->closeMerge(); } KoPathShapeLoader::KoPathShapeLoader(KoPathShape *path) : d(new KoPathShapeLoaderPrivate(path)) { } KoPathShapeLoader::~KoPathShapeLoader() { delete d; } void KoPathShapeLoader::parseSvg(const QString &s, bool process) { d->parseSvg(s, process); } diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 2a523c80d0..89a4acb18a 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2543 +1,2541 @@ /* This file is part of the KDE project Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2006-2010 Thorsten Zachmann Copyright (C) 2007-2009,2011 Jan Hambrecht CopyRight (C) 2010 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeLayer.h" #include "KoShapeContainerModel.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "KoInsets.h" #include "KoShapeStrokeModel.h" #include "KoShapeBackground.h" #include "KoColorBackground.h" #include "KoHatchBackground.h" #include "KoGradientBackground.h" #include "KoPatternBackground.h" #include "KoShapeManager.h" #include "KoShapeUserData.h" #include "KoShapeApplicationData.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoViewConverter.h" #include "KoShapeStroke.h" #include "KoShapeShadow.h" #include "KoClipPath.h" #include "KoPathShape.h" #include "KoOdfWorkaround.h" #include "KoFilterEffectStack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include "KoOdfGradientBackground.h" #include // KoShapePrivate KoShapePrivate::KoShapePrivate(KoShape *shape) : q_ptr(shape), size(50, 50), parent(0), shadow(0), border(0), filterEffectStack(0), transparency(0.0), zIndex(0), runThrough(0), visible(true), printable(true), geometryProtected(false), keepAspect(false), selectable(true), detectCollision(false), protectContent(false), textRunAroundSide(KoShape::BiggestRunAroundSide), textRunAroundDistanceLeft(0.0), textRunAroundDistanceTop(0.0), textRunAroundDistanceRight(0.0), textRunAroundDistanceBottom(0.0), textRunAroundThreshold(0.0), textRunAroundContour(KoShape::ContourFull) { connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint); connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint); connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint); connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint); connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q) : q_ptr(q), size(rhs.size), shapeId(rhs.shapeId), name(rhs.name), localMatrix(rhs.localMatrix), connectors(rhs.connectors), parent(0), // to be initialized later shapeManagers(), // to be initialized later toolDelegates(), // FIXME: how to initialize them? userData(rhs.userData ? rhs.userData->clone() : 0), stroke(rhs.stroke), fill(rhs.fill), inheritBackground(rhs.inheritBackground), inheritStroke(rhs.inheritStroke), dependees(), // FIXME: how to initialize them? shadow(0), // WARNING: not implemented in Krita border(0), // WARNING: not implemented in Krita clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0), clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0), additionalAttributes(rhs.additionalAttributes), additionalStyleAttributes(rhs.additionalStyleAttributes), filterEffectStack(0), // WARNING: not implemented in Krita transparency(rhs.transparency), hyperLink(rhs.hyperLink), zIndex(rhs.zIndex), runThrough(rhs.runThrough), visible(rhs.visible), printable(rhs.visible), geometryProtected(rhs.geometryProtected), keepAspect(rhs.keepAspect), selectable(rhs.selectable), detectCollision(rhs.detectCollision), protectContent(rhs.protectContent), textRunAroundSide(rhs.textRunAroundSide), textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft), textRunAroundDistanceTop(rhs.textRunAroundDistanceTop), textRunAroundDistanceRight(rhs.textRunAroundDistanceRight), textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom), textRunAroundThreshold(rhs.textRunAroundThreshold), textRunAroundContour(rhs.textRunAroundContour) { } KoShapePrivate::~KoShapePrivate() { Q_Q(KoShape); /** * The shape must have already been detached from all the parents and * shape managers. Otherwise we migh accidentally request some RTTI * information, which is not available anymore (we are in d-tor). * * TL;DR: fix the code that caused this destruction without unparenting * instead of trying to remove these assert! */ KIS_SAFE_ASSERT_RECOVER (!parent) { parent->removeShape(q); } KIS_SAFE_ASSERT_RECOVER (shapeManagers.isEmpty()) { Q_FOREACH (KoShapeManager *manager, shapeManagers) { manager->shapeInterface()->notifyShapeDestructed(q); } shapeManagers.clear(); } if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShapePrivate::shapeChanged(KoShape::ChangeType type) { Q_Q(KoShape); if (parent) parent->model()->childChanged(q, type); q->shapeChanged(type); Q_FOREACH (KoShape * shape, dependees) { shape->shapeChanged(type, q); } Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) { listener->notifyShapeChangedImpl(type, q); } } void KoShapePrivate::addShapeManager(KoShapeManager *manager) { shapeManagers.insert(manager); } void KoShapePrivate::removeShapeManager(KoShapeManager *manager) { shapeManagers.remove(manager); } void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toRelative(point.position, shapeSize); point.position.rx() = qBound(0.0, point.position.x(), 1.0); point.position.ry() = qBound(0.0, point.position.y(), 1.0); break; case KoConnectionPoint::AlignRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() -= shapeSize.width(); point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() -= 0.5 * shapeSize.width(); point.position.ry() -= 0.5 * shapeSize.height(); break; } } void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toAbsolute(point.position, shapeSize); break; case KoConnectionPoint::AlignRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() += shapeSize.width(); point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() += 0.5 * shapeSize.width(); point.position.ry() += 0.5 * shapeSize.height(); break; } } // static QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString value; if (styleStack.hasProperty(KoXmlNS::draw, property)) { value = styleStack.property(KoXmlNS::draw, property); } return value; } // ======== KoShape const qint16 KoShape::maxZIndex = std::numeric_limits::max(); const qint16 KoShape::minZIndex = std::numeric_limits::min(); KoShape::KoShape() : d_ptr(new KoShapePrivate(this)) { notifyChanged(); } KoShape::KoShape(KoShapePrivate *dd) : d_ptr(dd) { } KoShape::~KoShape() { Q_D(KoShape); d->shapeChanged(Deleted); d->listeners.clear(); delete d_ptr; } KoShape *KoShape::cloneShape() const { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "not implemented!"); qWarning() << shapeId() << "cannot be cloned"; return 0; } void KoShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { Q_UNUSED(paintcontext); if (stroke()) { stroke()->paint(this, painter, converter); } } void KoShape::scale(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * scaleMatrix; notifyChanged(); d->shapeChanged(ScaleChanged); } void KoShape::rotate(qreal angle) { Q_D(KoShape); QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); d->localMatrix = d->localMatrix * rotateMatrix; notifyChanged(); d->shapeChanged(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * shearMatrix; notifyChanged(); d->shapeChanged(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { Q_D(KoShape); QSizeF oldSize(size()); // always set size, as d->size and size() may vary d->size = newSize; if (oldSize == newSize) return; notifyChanged(); d->shapeChanged(SizeChanged); } void KoShape::setPosition(const QPointF &newPosition) { Q_D(KoShape); QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); d->localMatrix = d->localMatrix * translateMatrix; notifyChanged(); d->shapeChanged(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { Q_D(const KoShape); if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! d->shadow) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - d->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { Q_D(const KoShape); QTransform transform = absoluteTransformation(0); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (d->shadow) { KoInsets insets; d->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (d->filterEffectStack) { QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } QRectF KoShape::boundingRect(const QList &shapes) { QRectF boundingRect; Q_FOREACH (KoShape *shape, shapes) { boundingRect |= shape->boundingRect(); } return boundingRect; } QRectF KoShape::absoluteOutlineRect(KoViewConverter *converter) const { return absoluteTransformation(converter).map(outline()).boundingRect(); } QRectF KoShape::absoluteOutlineRect(const QList &shapes, KoViewConverter *converter) { QRectF absoluteOutlineRect; Q_FOREACH (KoShape *shape, shapes) { absoluteOutlineRect |= shape->absoluteOutlineRect(converter); } return absoluteOutlineRect; } QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const { Q_D(const KoShape); QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { // We do need to pass the converter here, otherwise the parent's // translation is not inherited. matrix = container->absoluteTransformation(converter); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); if (converter) containerPos = converter->documentToView(containerPos); matrix.translate(containerPos.x(), containerPos.y()); } } if (converter) { QPointF pos = d->localMatrix.map(QPointF()); QPointF trans = converter->documentToView(pos) - pos; matrix.translate(trans.x(), trans.y()); } return d->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(0); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix * d->localMatrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } QTransform KoShape::transformation() const { Q_D(const KoShape); return d->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { /** * WARNING: Our definition of zIndex is not yet compatible with SVG2's * definition. In SVG stacking context of groups with the same * zIndex are **merged**, while in Krita the contents of groups * is never merged. One group will always below than the other. * Therefore, when zIndex of two groups inside the same parent * coincide, the resulting painting order in Krita is * **UNDEFINED**. * * To avoid this trouble we use KoShapeReorderCommand::mergeInShape() * inside KoShapeCreateCommand. */ /** * The algorithm below doesn't correctly handle the case when the two pointers actually * point to the same shape. So just check it in advance to guarantee strict weak ordering * relation requirement */ if (s1 == s2) return false; // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { Q_D(KoShape); if (d->parent == parent) { return; } KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) { oldParent->shapeInterface()->removeShape(this); } KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this); if (parent && parent != this) { d->parent = parent; parent->shapeInterface()->addShape(this); } notifyChanged(); d->shapeChanged(ParentChanged); } bool KoShape::inheritsTransformFromAny(const QList ancestorsInQuestion) const { bool result = false; KoShape *shape = const_cast(this); while (shape) { KoShapeContainer *parent = shape->parent(); if (parent && !parent->inheritsTransform(shape)) { break; } if (ancestorsInQuestion.contains(shape)) { result = true; break; } shape = parent; } return result; } bool KoShape::hasCommonParent(const KoShape *shape) const { const KoShape *thisShape = this; while (thisShape) { const KoShape *otherShape = shape; while (otherShape) { if (thisShape == otherShape) { return true; } otherShape = otherShape->parent(); } thisShape = thisShape->parent(); } return false; } qint16 KoShape::zIndex() const { Q_D(const KoShape); return d->zIndex; } void KoShape::update() const { Q_D(const KoShape); if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::updateAbsolute(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } Q_D(const KoShape); if (!d->shapeManagers.empty() && isVisible()) { Q_FOREACH (KoShapeManager *manager, d->shapeManagers) { manager->update(rect); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { if (background()) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const { const QRectF rc = outlineRect(); QPointF point = rc.topLeft(); bool valid = false; QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid); if (valid) { point = anchoredPoint; } return absoluteTransformation(0).map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { Q_D(KoShape); QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); d->shapeChanged(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { Q_D(KoShape); d->size = shape->size(); d->connectors.clear(); Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints()) addConnectionPoint(point); d->zIndex = shape->zIndex(); d->visible = shape->isVisible(false); // Ensure printable is true by default if (!d->visible) d->printable = true; else d->printable = shape->isPrintable(); d->geometryProtected = shape->isGeometryProtected(); d->protectContent = shape->isContentProtected(); d->selectable = shape->isSelectable(); d->keepAspect = shape->keepAspectRatio(); d->localMatrix = shape->d_ptr->localMatrix; } void KoShape::notifyChanged() { Q_D(KoShape); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { Q_D(KoShape); d->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { Q_D(const KoShape); return d->userData.data(); } bool KoShape::hasTransparency() const { Q_D(const KoShape); QSharedPointer bg = background(); return !bg || bg->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { Q_D(KoShape); d->transparency = qBound(0.0, transparency, 1.0); d->shapeChanged(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { Q_D(const KoShape); if (!recursive || !parent()) { return d->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-d->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { Q_D(const KoShape); KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { Q_D(const KoShape); // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { Q_D(const KoShape); return d->size; } QPointF KoShape::position() const { Q_D(const KoShape); QPointF center = outlineRect().center(); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { Q_D(KoShape); // get next glue point id int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint; if (d->connectors.size()) nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1); KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[nextConnectionPointId] = p; return nextConnectionPointId; } bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point) { Q_D(KoShape); if (connectionPointId < 0) return false; const bool insertPoint = !hasConnectionPoint(connectionPointId); switch(connectionPointId) { case KoConnectionPoint::TopConnectionPoint: case KoConnectionPoint::RightConnectionPoint: case KoConnectionPoint::BottomConnectionPoint: case KoConnectionPoint::LeftConnectionPoint: { KoConnectionPoint::PointId id = static_cast(connectionPointId); d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id); break; } default: { KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[connectionPointId] = p; break; } } if(!insertPoint) d->shapeChanged(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { Q_D(const KoShape); return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { Q_D(const KoShape); KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { Q_D(const KoShape); QSizeF s = size(); KoConnectionPoints points = d->connectors; KoConnectionPoints::iterator point = points.begin(); KoConnectionPoints::iterator lastPoint = points.end(); // convert glue points to shape coordinates for(; point != lastPoint; ++point) { d->convertToShapeCoordinates(point.value(), s); } return points; } void KoShape::removeConnectionPoint(int connectionPointId) { Q_D(KoShape); d->connectors.remove(connectionPointId); d->shapeChanged(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { Q_D(KoShape); d->connectors.clear(); } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { Q_D(const KoShape); return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { Q_D(KoShape); if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); d->shapeChanged(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { Q_D(const KoShape); return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { Q_D(const KoShape); return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { Q_D(const KoShape); return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { Q_D(const KoShape); return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { Q_D(const KoShape); return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { Q_D(KoShape); d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { Q_D(const KoShape); return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { Q_D(KoShape); d->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { Q_D(KoShape); d->inheritBackground = false; d->fill = fill; d->shapeChanged(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { Q_D(const KoShape); QSharedPointer bg; if (!d->inheritBackground) { bg = d->fill; } else if (parent()) { bg = parent()->background(); } return bg; } void KoShape::setInheritBackground(bool value) { Q_D(KoShape); d->inheritBackground = value; if (d->inheritBackground) { d->fill.clear(); } } bool KoShape::inheritBackground() const { Q_D(const KoShape); return d->inheritBackground; } void KoShape::setZIndex(qint16 zIndex) { Q_D(KoShape); if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { Q_D(const KoShape); return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { Q_D(KoShape); d->runThrough = runThrough; } void KoShape::setVisible(bool on) { Q_D(KoShape); int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { Q_D(const KoShape); if (!recursive) return d->visible; if (!d->visible) return false; KoShapeContainer * parentShape = parent(); if (parentShape) { return parentShape->isVisible(true); } return true; } void KoShape::setPrintable(bool on) { Q_D(KoShape); d->printable = on; } bool KoShape::isPrintable() const { Q_D(const KoShape); if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { Q_D(KoShape); d->selectable = selectable; } bool KoShape::isSelectable() const { Q_D(const KoShape); return d->selectable; } void KoShape::setGeometryProtected(bool on) { Q_D(KoShape); d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { Q_D(const KoShape); return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { Q_D(KoShape); d->protectContent = protect; } bool KoShape::isContentProtected() const { Q_D(const KoShape); return d->protectContent; } KoShapeContainer *KoShape::parent() const { Q_D(const KoShape); return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { Q_D(KoShape); d->keepAspect = keepAspect; d->shapeChanged(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { Q_D(const KoShape); return d->keepAspect; } QString KoShape::shapeId() const { Q_D(const KoShape); return d->shapeId; } void KoShape::setShapeId(const QString &id) { Q_D(KoShape); d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { Q_D(KoShape); d->detectCollision = detect; } bool KoShape::collisionDetection() { Q_D(KoShape); return d->detectCollision; } KoShapeStrokeModelSP KoShape::stroke() const { Q_D(const KoShape); KoShapeStrokeModelSP stroke; if (!d->inheritStroke) { stroke = d->stroke; } else if (parent()) { stroke = parent()->stroke(); } return stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { Q_D(KoShape); d->inheritStroke = false; d->stroke = stroke; d->shapeChanged(StrokeChanged); notifyChanged(); } void KoShape::setInheritStroke(bool value) { Q_D(KoShape); d->inheritStroke = value; if (d->inheritStroke) { d->stroke.clear(); } } bool KoShape::inheritStroke() const { Q_D(const KoShape); return d->inheritStroke; } void KoShape::setShadow(KoShapeShadow *shadow) { Q_D(KoShape); if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } d->shapeChanged(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { Q_D(const KoShape); return d->shadow; } void KoShape::setBorder(KoBorder *border) { Q_D(KoShape); if (d->border) { // The shape owns the border. delete d->border; } d->border = border; d->shapeChanged(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { Q_D(const KoShape); return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { Q_D(KoShape); d->clipPath.reset(clipPath); d->shapeChanged(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { Q_D(const KoShape); return d->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { Q_D(KoShape); d->clipMask.reset(clipMask); } KoClipMask* KoShape::clipMask() const { Q_D(const KoShape); return d->clipMask.data(); } QTransform KoShape::transform() const { Q_D(const KoShape); return d->localMatrix; } QString KoShape::name() const { Q_D(const KoShape); return d->name; } void KoShape::setName(const QString &name) { Q_D(KoShape); d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isShapeEditable(bool recursive) const { Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (recursive && d->parent) { return d->parent->isShapeEditable(true); } return true; } // painting void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KoBorder *bd = border(); if (!bd) { return; } QRectF borderRect = QRectF(QPointF(0, 0), size()); // Paint the border. bd->paint(painter, borderRect, KoBorder::PaintInsideLine); } // loading & saving methods QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoShape); // and fill the style KoShapeStrokeModelSP sm = stroke(); if (sm) { sm->fillStyle(style, context); } else { style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); } KoShapeShadow *s = shadow(); if (s) s->fillStyle(style, context); QSharedPointer bg = background(); if (bg) { bg->fillStyle(style, context); } else { style.addProperty("draw:fill", "none", KoGenStyle::GraphicType); } KoBorder *b = border(); if (b) { b->saveOdf(style); } if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) { style.setAutoStyleInStylesDotXml(true); } QString value; if (isGeometryProtected()) { value = "position size"; } if (isContentProtected()) { if (! value.isEmpty()) value += ' '; value += "content"; } if (!value.isEmpty()) { style.addProperty("style:protect", value, KoGenStyle::GraphicType); } QMap::const_iterator it(d->additionalStyleAttributes.constBegin()); for (; it != d->additionalStyleAttributes.constEnd(); ++it) { style.addProperty(it.key(), it.value()); } if (parent() && parent()->isClipped(this)) { /* * In Calligra clipping is done using a parent shape which can be rotated, sheared etc * and even non-square. So the ODF interoperability version we write here is really * just a very simple version of that... */ qreal top = -position().y(); qreal left = -position().x(); qreal right = parent()->size().width() - size().width() - left; qreal bottom = parent()->size().height() - size().height() - top; style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(top, 10, 'f').arg(right, 10, 'f') .arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType); } QString wrap; switch (textRunAroundSide()) { case BiggestRunAroundSide: wrap = "biggest"; break; case LeftRunAroundSide: wrap = "left"; break; case RightRunAroundSide: wrap = "right"; break; case EnoughRunAroundSide: wrap = "dynamic"; break; case BothRunAroundSide: wrap = "parallel"; break; case NoRunAround: wrap = "none"; break; case RunThrough: wrap = "run-through"; break; } style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType); switch (textRunAroundContour()) { case ContourBox: style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType); break; case ContourFull: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType); break; case ContourOutside: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType); break; } style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType); if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight()) && (textRunAroundDistanceTop() == textRunAroundDistanceBottom()) && (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) { style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); } else { style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType); } return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr"); } void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); d->fill.clear(); d->stroke.clear(); if (d->shadow && !d->shadow->deref()) { delete d->shadow; d->shadow = 0; } setBackground(loadOdfFill(context)); setStroke(loadOdfStroke(element, context)); setShadow(d->loadOdfShadow(context)); setBorder(d->loadOdfBorder(context)); QString protect(styleStack.property(KoXmlNS::style, "protect")); setGeometryProtected(protect.contains("position") || protect.contains("size")); setContentProtected(protect.contains("content")); QString margin = styleStack.property(KoXmlNS::fo, "margin"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-left"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-top"); if (!margin.isEmpty()) { setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-right"); if (!margin.isEmpty()) { setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-bottom"); if (!margin.isEmpty()) { setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } QString wrap; if (styleStack.hasProperty(KoXmlNS::style, "wrap")) { wrap = styleStack.property(KoXmlNS::style, "wrap"); } else { // no value given in the file, but guess biggest wrap = "biggest"; } if (wrap == "none") { setTextRunAroundSide(KoShape::NoRunAround); } else if (wrap == "run-through") { QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background"); if (runTrought == "background") { setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } else { setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); } } else { if (wrap == "biggest") setTextRunAroundSide(KoShape::BiggestRunAroundSide); else if (wrap == "left") setTextRunAroundSide(KoShape::LeftRunAroundSide); else if (wrap == "right") setTextRunAroundSide(KoShape::RightRunAroundSide); else if (wrap == "dynamic") setTextRunAroundSide(KoShape::EnoughRunAroundSide); else if (wrap == "parallel") setTextRunAroundSide(KoShape::BothRunAroundSide); } if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) { QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold"); if (!wrapThreshold.isEmpty()) { setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold)); } } if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") { if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") { setTextRunAroundContour(KoShape::ContourFull); } else { setTextRunAroundContour(KoShape::ContourOutside); } } else { setTextRunAroundContour(KoShape::ContourBox); } } bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes) { if (attributes & OdfPosition) { QPointF pos(position()); if (element.hasAttributeNS(KoXmlNS::svg, "x")) pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "y")) pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos); } if (attributes & OdfSize) { QSizeF s(size()); if (element.hasAttributeNS(KoXmlNS::svg, "width")) s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "height")) s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); setSize(s); } if (attributes & OdfLayer) { if (element.hasAttributeNS(KoXmlNS::draw, "layer")) { KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer")); if (layer) { setParent(layer); } } } if (attributes & OdfId) { KoElementReference ref; ref.loadOdf(element); if (ref.isValid()) { context.addShapeId(this, ref.toString()); } } if (attributes & OdfZIndex) { if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt()); } else { setZIndex(context.zIndex()); } } if (attributes & OdfName) { if (element.hasAttributeNS(KoXmlNS::draw, "name")) { setName(element.attributeNS(KoXmlNS::draw, "name")); } } if (attributes & OdfStyle) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); } if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation"); } loadStyle(element, context); styleStack.restore(); } if (attributes & OdfTransformation) { QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString()); if (! transform.isEmpty()) applyAbsoluteTransformation(parseOdfTransform(transform)); } if (attributes & OdfAdditionalAttributes) { QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData(); Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) { if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) { QString value = element.attributeNS(attributeData.ns, attributeData.tag); //debugFlake << "load additional attribute" << attributeData.tag << value; setAdditionalAttribute(attributeData.name, value); } } } if (attributes & OdfCommonChildElements) { // load glue points (connection points) loadOdfGluePoints(element, context); } return true; } QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const { QString fill = KoShapePrivate::getStyleProperty("fill", context); QSharedPointer bg; if (fill == "solid") { bg = QSharedPointer(new KoColorBackground()); } else if (fill == "hatch") { bg = QSharedPointer(new KoHatchBackground()); } else if (fill == "gradient") { QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context); KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName]; QString style; if (e) { style = e->attributeNS(KoXmlNS::draw, "style", QString()); } if ((style == "rectangular") || (style == "square")) { bg = QSharedPointer(new KoOdfGradientBackground()); } else { QGradient *gradient = new QLinearGradient(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); bg = QSharedPointer(new KoGradientBackground(gradient)); } } else if (fill == "bitmap") { bg = QSharedPointer(new KoPatternBackground(context.imageCollection())); #ifndef NWORKAROUND_ODF_BUGS } else if (fill.isEmpty()) { bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context)); return bg; #endif } else { return QSharedPointer(0); } if (!bg->loadStyle(context.odfLoadingContext(), size())) { return QSharedPointer(0); } return bg; } KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); QString stroke = KoShapePrivate::getStyleProperty("stroke", context); if (stroke == "solid" || stroke == "dash") { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader); QSharedPointer stroke(new KoShapeStroke()); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) { QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient"); QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size()); stroke->setLineBrush(brush); } else { stroke->setColor(pen.color()); } #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); return stroke; #ifndef NWORKAROUND_ODF_BUGS } else if (stroke.isEmpty()) { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader); if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) { QSharedPointer stroke(new KoShapeStroke()); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); stroke->setColor(pen.color()); return stroke; } #endif } return KoShapeStrokeModelSP(); } KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); if (shadowStyle == "visible" || shadowStyle == "hidden") { KoShapeShadow *shadow = new KoShapeShadow(); QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color")); qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x")); qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y")); shadow->setOffset(QPointF(offsetX, offsetY)); qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius")); shadow->setBlur(blur); QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0); shadow->setColor(shadowColor); shadow->setVisible(shadowStyle == "visible"); return shadow; } return 0; } KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoBorder *border = new KoBorder(); if (border->loadOdf(styleStack)) { return border; } delete border; return 0; } void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoXmlElement child; bool hasCenterGluePoint = false; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "glue-point") continue; // NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated // all use of draw:id for xml:id, it didn't specify that here, so it // doesn't support xml:id (and so, maybe, shouldn't use KoElementReference. const QString id = child.attributeNS(KoXmlNS::draw, "id", QString()); const int index = id.toInt(); // connection point in center should be default but odf doesn't support, // in new shape, first custom point is in center, it's okay to replace that point // with point from xml now, we'll add it back later if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint || (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) { warnFlake << "glue-point with no or invalid id"; continue; } QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified(); QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified(); if(xStr.isEmpty() || yStr.isEmpty()) { warnFlake << "glue-point with invald position"; continue; } KoConnectionPoint connector; const QString align = child.attributeNS(KoXmlNS::draw, "align", QString()); if (align.isEmpty()) { #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixGluePointPosition(xStr, context); KoOdfWorkaround::fixGluePointPosition(yStr, context); #endif if(!xStr.endsWith('%') || !yStr.endsWith('%')) { warnFlake << "glue-point with invald position"; continue; } // x and y are relative to drawing object center connector.position.setX(xStr.remove('%').toDouble()/100.0); connector.position.setY(yStr.remove('%').toDouble()/100.0); // convert position to be relative to top-left corner connector.position += QPointF(0.5, 0.5); connector.position.rx() = qBound(0.0, connector.position.x(), 1.0); connector.position.ry() = qBound(0.0, connector.position.y(), 1.0); } else { // absolute distances to the edge specified by align connector.position.setX(KoUnit::parseValue(xStr)); connector.position.setY(KoUnit::parseValue(yStr)); if (align == "top-left") { connector.alignment = KoConnectionPoint::AlignTopLeft; } else if (align == "top") { connector.alignment = KoConnectionPoint::AlignTop; } else if (align == "top-right") { connector.alignment = KoConnectionPoint::AlignTopRight; } else if (align == "left") { connector.alignment = KoConnectionPoint::AlignLeft; } else if (align == "center") { connector.alignment = KoConnectionPoint::AlignCenter; } else if (align == "right") { connector.alignment = KoConnectionPoint::AlignRight; } else if (align == "bottom-left") { connector.alignment = KoConnectionPoint::AlignBottomLeft; } else if (align == "bottom") { connector.alignment = KoConnectionPoint::AlignBottom; } else if (align == "bottom-right") { connector.alignment = KoConnectionPoint::AlignBottomRight; } debugFlake << "using alignment" << align; } const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString()); if (!escape.isEmpty()) { if (escape == "horizontal") { connector.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (escape == "vertical") { connector.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (escape == "left") { connector.escapeDirection = KoConnectionPoint::LeftDirection; } else if (escape == "right") { connector.escapeDirection = KoConnectionPoint::RightDirection; } else if (escape == "up") { connector.escapeDirection = KoConnectionPoint::UpDirection; } else if (escape == "down") { connector.escapeDirection = KoConnectionPoint::DownDirection; } debugFlake << "using escape direction" << escape; } d->connectors[index] = connector; debugFlake << "loaded glue-point" << index << "at position" << connector.position; if (d->connectors[index].position == QPointF(0.5, 0.5)) { hasCenterGluePoint = true; debugFlake << "center glue-point found at id " << index; } } if (!hasCenterGluePoint) { d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } debugFlake << "shape has now" << d->connectors.count() << "glue-points"; } void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) { Q_D(KoShape); KoXmlElement child; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "contour-polygon") continue; debugFlake << "shape loads contour-polygon"; KoPathShape *ps = new KoPathShape(); ps->loadContourOdf(child, context, scaleFactor); ps->setTransformation(transformation()); KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse); d->clipPath.reset(clipPath); } } QTransform KoShape::parseOdfTransform(const QString &transform) { QTransform matrix; // Split string for handling 1 transform statement at a time QStringList subtransforms = transform.split(')', QString::SkipEmptyParts); QStringList::ConstIterator it = subtransforms.constBegin(); QStringList::ConstIterator end = subtransforms.constEnd(); for (; it != end; ++it) { QStringList subtransform = (*it).split('(', QString::SkipEmptyParts); subtransform[0] = subtransform[0].trimmed().toLower(); subtransform[1] = subtransform[1].simplified(); QRegExp reg("[,( ]"); QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts); if (subtransform[0].startsWith(';') || subtransform[0].startsWith(',')) subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); QString cmd = subtransform[0].toLower(); if (cmd == "rotate") { QTransform rotMatrix; if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); rotMatrix.translate(x, y); // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); rotMatrix.translate(-x, -y); } else { // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); } matrix = matrix * rotMatrix; } else if (cmd == "translate") { QTransform moveMatrix; if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); moveMatrix.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 moveMatrix.translate(KoUnit::parseValue(params[0]) , 0); matrix = matrix * moveMatrix; } else if (cmd == "scale") { QTransform scaleMatrix; if (params.count() == 2) scaleMatrix.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling scaleMatrix.scale(params[0].toDouble(), params[0].toDouble()); matrix = matrix * scaleMatrix; } else if (cmd == "skewx") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(tan(-params[0].toDouble()), 0.0F); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "skewy") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(0.0F, tan(-params[0].toDouble())); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "matrix") { QTransform m; if (params.count() >= 6) { m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } matrix = matrix * m; } } return matrix; } void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const { Q_D(const KoShape); if (attributes & OdfStyle) { KoGenStyle style; // all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape if (context.isSet(KoShapeSavingContext::PresentationShape)) { style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation"); context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context)); } else { style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context)); } } if (attributes & OdfId) { if (context.isSet(KoShapeSavingContext::DrawId)) { KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter); ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId); } } if (attributes & OdfName) { if (! name().isEmpty()) context.xmlWriter().addAttribute("draw:name", name()); } if (attributes & OdfLayer) { KoShape *parent = d->parent; while (parent) { if (dynamic_cast(parent)) { context.xmlWriter().addAttribute("draw:layer", parent->name()); break; } parent = parent->parent(); } } if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) { context.xmlWriter().addAttribute("draw:z-index", zIndex()); } if (attributes & OdfSize) { QSizeF s(size()); if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size // clipping in ODF is done using a combination of visual size and content cliprect. // A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out // of proportion in this case). If we then add a fo:clip like; // fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left) // our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box. // TODO do this properly by subtracting rects s = parent()->size(); } context.xmlWriter().addAttribute("svg:width", s.width()); context.xmlWriter().addAttribute("svg:height", s.height()); } // The position is implicitly stored in the transformation matrix // if the transformation is saved as well if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) { const QPointF p(position() * context.shapeOffset(this)); context.xmlWriter().addAttribute("svg:x", p.x()); context.xmlWriter().addAttribute("svg:y", p.y()); } if (attributes & OdfTransformation) { QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this); if (! matrix.isIdentity()) { if (qAbs(matrix.m11() - 1) < 1E-5 // 1 && qAbs(matrix.m12()) < 1E-5 // 0 && qAbs(matrix.m21()) < 1E-5 // 0 && qAbs(matrix.m22() - 1) < 1E-5) { // 1 context.xmlWriter().addAttribute("svg:x", matrix.dx()); context.xmlWriter().addAttribute("svg:y", matrix.dy()); } else { QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(matrix.m11(), 0, 'f', 11) .arg(matrix.m12(), 0, 'f', 11) .arg(matrix.m21(), 0, 'f', 11) .arg(matrix.m22(), 0, 'f', 11) .arg(matrix.dx(), 0, 'f', 11) .arg(matrix.dy(), 0, 'f', 11); context.xmlWriter().addAttribute("draw:transform", m); } } } if (attributes & OdfViewbox) { const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); } if (attributes & OdfAdditionalAttributes) { QMap::const_iterator it(d->additionalAttributes.constBegin()); for (; it != d->additionalAttributes.constEnd(); ++it) { context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); } } } void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const { Q_D(const KoShape); // save glue points see ODF 9.2.19 Glue Points if(d->connectors.count()) { KoConnectionPoints::const_iterator cp = d->connectors.constBegin(); KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd(); for(; cp != lastCp; ++cp) { // do not save default glue points if(cp.key() < 4) continue; context.xmlWriter().startElement("draw:glue-point"); context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key())); if (cp.value().alignment == KoConnectionPoint::AlignNone) { // convert to percent from center const qreal x = cp.value().position.x() * 100.0 - 50.0; const qreal y = cp.value().position.y() * 100.0 - 50.0; context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x)); context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y)); } else { context.xmlWriter().addAttribute("svg:x", cp.value().position.x()); context.xmlWriter().addAttribute("svg:y", cp.value().position.y()); } QString escapeDirection; switch(cp.value().escapeDirection) { case KoConnectionPoint::HorizontalDirections: escapeDirection = "horizontal"; break; case KoConnectionPoint::VerticalDirections: escapeDirection = "vertical"; break; case KoConnectionPoint::LeftDirection: escapeDirection = "left"; break; case KoConnectionPoint::RightDirection: escapeDirection = "right"; break; case KoConnectionPoint::UpDirection: escapeDirection = "up"; break; case KoConnectionPoint::DownDirection: escapeDirection = "down"; break; default: - // fall through break; } if(!escapeDirection.isEmpty()) { context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection); } QString alignment; switch(cp.value().alignment) { case KoConnectionPoint::AlignTopLeft: alignment = "top-left"; break; case KoConnectionPoint::AlignTop: alignment = "top"; break; case KoConnectionPoint::AlignTopRight: alignment = "top-right"; break; case KoConnectionPoint::AlignLeft: alignment = "left"; break; case KoConnectionPoint::AlignCenter: alignment = "center"; break; case KoConnectionPoint::AlignRight: alignment = "right"; break; case KoConnectionPoint::AlignBottomLeft: alignment = "bottom-left"; break; case KoConnectionPoint::AlignBottom: alignment = "bottom"; break; case KoConnectionPoint::AlignBottomRight: alignment = "bottom-right"; break; default: - // fall through break; } if(!alignment.isEmpty()) { context.xmlWriter().addAttribute("draw:align", alignment); } context.xmlWriter().endElement(); } } } void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const { Q_D(const KoShape); debugFlake << "shape saves contour-polygon"; if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) { // This will loose data as odf can only save one set of contour whereas // svg loading and at least karbon editing can produce more than one // TODO, FIXME see if we can save more than one clipshape to odf d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); } } // end loading & saving methods // static void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter) { qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); } KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform()); KoShape::applyConversion(*painter, converter); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation(0).map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation(0).mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation(0).inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation(0).inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { Q_D(KoShape); if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { Q_D(KoShape); int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { Q_D(const KoShape); return d->dependees.contains(shape); } QList KoShape::dependees() const { Q_D(const KoShape); return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { Q_D(KoShape); d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { Q_D(KoShape); d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { Q_D(KoShape); d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { Q_D(KoShape); d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { Q_D(const KoShape); return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { Q_D(KoShape); if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { Q_D(const KoShape); return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { Q_D(KoShape); d->toolDelegates = delegates; } QString KoShape::hyperLink () const { Q_D(const KoShape); return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { Q_D(KoShape); d->hyperLink = hyperLink; } KoShapePrivate *KoShape::priv() { Q_D(KoShape); return d; } KoShape::ShapeChangeListener::~ShapeChangeListener() { Q_FOREACH(KoShape *shape, m_registeredShapes) { shape->removeShapeChangeListener(this); } } void KoShape::ShapeChangeListener::registerShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape)); m_registeredShapes.append(shape); } void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); m_registeredShapes.removeAll(shape); } void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); notifyShapeChanged(type, shape); if (type == KoShape::Deleted) { unregisterShape(shape); } } void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::linearizeSubtree(const QList &shapes) { QList result; Q_FOREACH (KoShape *shape, shapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtree(container->shapes()); } } return result; } diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index 7f8036058b..5b341d4084 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,2985 +1,2985 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * Copyright (c) 2011 Silvio Heinrich * * 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 "kis_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" #include #include #include "kis_lod_transform.h" #include "kis_algebra_2d.h" #include "krita_utils.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #include "kis_painter_p.h" KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->pattern = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->gradient = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontally = false; d->mirrorVertically = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const QRect srcExtent = src->extent(); const QRect dstExtent = dst->extent(); const QRect srcSampleRect = srcExtent & srcRect; const QRect dstSampleRect = dstExtent & dstRect; const bool srcEmpty = srcSampleRect.isEmpty(); const bool dstEmpty = dstSampleRect.isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { QRect srcCopyRect = srcRect; QRect dstCopyRect = dstRect; if (!srcExtent.contains(srcRect)) { if (src->defaultPixel() == dst->defaultPixel()) { const QRect dstSampleInSrcCoords = dstSampleRect.translated(srcRect.topLeft() - dstPt); if (dstSampleInSrcCoords.isEmpty() || srcSampleRect.contains(dstSampleInSrcCoords)) { srcCopyRect = srcSampleRect; } else { srcCopyRect = srcSampleRect | dstSampleInSrcCoords; } dstCopyRect = QRect(dstPt + srcCopyRect.topLeft() - srcRect.topLeft(), srcCopyRect.size()); } } KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstCopyRect.topLeft(), src, srcCopyRect); } else { gc.bitBlt(dstCopyRect.topLeft(), src, srcCopyRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); const quint8 white = srcCS->intensity8(srcPtr); const quint8 alpha = srcCS->opacityU8(srcPtr); *alpha8Ptr = KoColorSpaceMaths::multiply(alpha, KoColorSpaceMathsTraits::unitValue - white); } return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->intensity8(srcPtr); } return dst; } bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev) { const QRect deviceBounds = dev->exactBounds(); const QRect imageBounds = dev->defaultBounds()->bounds(); if (deviceBounds.isEmpty() || (deviceBounds & imageBounds) != imageBounds) { return true; } const KoColorSpace *cs = dev->colorSpace(); KisSequentialConstIterator it(dev, deviceBounds); while(it.nextPixel()) { if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) { return true; } } return false; } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } void KisPainter::addDirtyRects(const QVector &rects) { d->dirtyRects.reserve(d->dirtyRects.size() + rects.size()); Q_FOREACH (const QRect &rc, rects) { const QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && compositeOp->id() != COMPOSITE_DESTINATION_IN && compositeOp->id() != COMPOSITE_DESTINATION_ATOP && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ KIS_SAFE_ASSERT_RECOVER_RETURN(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection(d->selection->projection()); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (const std::bad_alloc&) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (index >= points.count()) return; if (numPoints < 0) numPoints = points.count(); if (index + numPoints > points.count()) numPoints = points.count() - index; if (numPoints > 1) { KisDistanceInformation saveDist(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontally || d->mirrorVertically) { KisLodTransform lod(d->device); QPointF effectiveAxesCenter = lod.map(d->axesCenter); QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y()); QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontally) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVertically) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontally && d->mirrorVertically) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: - /* Falls through. */ + Q_FALLTHROUGH(); case FillStyleGradient: // Currently unsupported - /* Falls through. */ + Q_FALLTHROUGH(); case FillStyleStrokes: // Currently unsupported warnImage << "Unknown or unsupported fill style in fillPolygon\n"; - /* Falls through. */ + Q_FALLTHROUGH(); case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... fillPainter->fillRect(fillRect, pattern); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect) { // we are drawing mask, it has to be white // color of the path is given by paintColor() Q_ASSERT(pen.color() == Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; if (x2device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = qFloor(start.x()); int y = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = (float)yd / (float)xd; float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KoColor mycolor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = qFloor(fx + 1) - fx; float br2 = fx - qFloor(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = qFloor(fy + 1) - fy; float br2 = fy - qFloor(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KoColor lineColor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; ix1 = x1; ix2 = x2; iy1 = y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; iy1 = y1; iy2 = y2; ix1 = x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y integer coordinates xend = x1; yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = x1; iy1 = qFloor(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = x2; yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = x2; iy2 = qFloor(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, qFloor(yf)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, qFloor(yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y integer coordinates yend = y1; xend = x1 + grad * (yend - y1); ygap = y1; ix1 = qFloor(xend); iy1 = y1; // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = y2; xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = qFloor(xend); iy2 = y2; brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(qFloor(xf), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(qFloor(xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - qFloor(yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - qFloor(yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, qFloor(yfa)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, qFloor(yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = qFloor(yfa) + 1; i <= qFloor(yfb); i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(yfa) + 1; i >= qFloor(yfb); i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - qFloor(xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - qFloor(xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(qFloor(xfa), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(qFloor(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = qFloor(xfa) + 1; i <= qFloor(xfb); i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(xfb); i <= qFloor(xfa) + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { // Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPattern * pattern) { d->pattern = pattern; } const KoPattern * KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(KisFilterConfigurationSP generator) { d->generator = generator; } const KisFilterConfigurationSP KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setAverageOpacity(qreal averageOpacity) { d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity); } qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity) { const float exponent = 0.1; return averageOpacity < opacity ? opacity : exponent * opacity + (1.0 - exponent) * (averageOpacity); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } /** * TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h */ void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradient* gradient) { d->gradient = gradient; } const KoAbstractGradient* KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically) { d->axesCenter = axesCenter; d->mirrorHorizontally = mirrorHorizontally; d->mirrorVertically = mirrorVertically; } void KisPainter::copyMirrorInformationFrom(const KisPainter *other) { d->axesCenter = other->d->axesCenter; d->mirrorHorizontally = other->d->mirrorHorizontally; d->mirrorVertically = other->d->mirrorVertically; } bool KisPainter::hasMirroring() const { return d->mirrorHorizontally || d->mirrorVertically; } bool KisPainter::hasHorizontalMirroring() const { return d->mirrorHorizontally; } bool KisPainter::hasVerticalMirroring() const { return d->mirrorVertically; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface) { d->runnableStrokeJobsInterface = interface; } KisRunnableStrokeJobsInterface *KisPainter::runnableStrokeJobsInterface() const { if (!d->runnableStrokeJobsInterface) { if (!d->fakeRunnableStrokeJobsInterface) { d->fakeRunnableStrokeJobsInterface.reset(new KisFakeRunnableStrokeJobsExecutor()); } return d->fakeRunnableStrokeJobsInterface.data(); } return d->runnableStrokeJobsInterface; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontally){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVertically){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontally){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVertically){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); rects << rc; if (d->mirrorHorizontally && d->mirrorVertically){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontally) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVertically) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } Q_FOREACH (const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } Q_FOREACH (const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } Q_FOREACH (const QRect &rc, rects) { renderMirrorMask(rc, dab); } } bool KisPainter::hasDirtyRegion() const { return !d->dirtyRects.isEmpty(); } void KisPainter::mirrorRect(Qt::Orientation direction, QRect *rc) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorRect(direction, effectiveAxesCenter, rc); } void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab); } const QVector KisPainter::calculateAllMirroredRects(const QRect &rc) { QVector rects; KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); QRect baseRect = rc; rects << baseRect; if (d->mirrorHorizontally && d->mirrorVertically){ KritaUtils::mirrorRect(Qt::Horizontal, effectiveAxesCenter, &baseRect); rects << baseRect; KritaUtils::mirrorRect(Qt::Vertical, effectiveAxesCenter, &baseRect); rects << baseRect; KritaUtils::mirrorRect(Qt::Horizontal, effectiveAxesCenter, &baseRect); rects << baseRect; } else if (d->mirrorHorizontally) { KritaUtils::mirrorRect(Qt::Horizontal, effectiveAxesCenter, &baseRect); rects << baseRect; } else if (d->mirrorVertically) { KritaUtils::mirrorRect(Qt::Vertical, effectiveAxesCenter, &baseRect); rects << baseRect; } return rects; } diff --git a/libs/libqml/DocumentListModel.cpp b/libs/libqml/DocumentListModel.cpp index eeef8bc785..780c3a1389 100644 --- a/libs/libqml/DocumentListModel.cpp +++ b/libs/libqml/DocumentListModel.cpp @@ -1,188 +1,188 @@ /* This file is part of the KDE project * Copyright (C) 2012 KO GmbH. Contact: Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DocumentListModel.h" #include #include #include class DocumentListModel::Private { public: Private( DocumentListModel *qq) : q(qq), filter(DocumentListModel::UnknownType) { } void relayout(); DocumentListModel* q; QList allDocumentInfos; QList currentDocumentInfos; DocumentType filter; QString searchPattern; QTimer *timer; }; QHash DocumentListModel::sm_extensions = QHash(); DocumentListModel::DocumentListModel(QObject *parent) : QAbstractListModel(parent), d(new Private(this)) { qRegisterMetaType(); } DocumentListModel::~DocumentListModel() { } QHash DocumentListModel::roleNames() const { QHash roleNames = QAbstractListModel::roleNames(); roleNames[FileNameRole] = "fileName"; roleNames[FilePathRole] = "filePath"; roleNames[DocTypeRole] = "docType"; roleNames[FileSizeRole] = "fileSize"; roleNames[AuthorNameRole] = "authorName"; roleNames[AccessedTimeRole] = "accessedTime"; roleNames[ModifiedTimeRole] = "modifiedTime"; roleNames[UUIDRole] = "uuid"; return roleNames; } void DocumentListModel::addDocument(const DocumentInfo &info) { if (d->allDocumentInfos.contains(info)) { return; } d->allDocumentInfos.append(info); } int DocumentListModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return d->currentDocumentInfos.count(); } int DocumentListModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } QVariant DocumentListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const int row = index.row(); const DocumentInfo &info = d->currentDocumentInfos[row]; switch (role) { - case FileNameRole: // intentional fall through + case FileNameRole: Q_FALLTHROUGH(); case Qt::DisplayRole: return info.fileName; case FilePathRole: return info.filePath; case DocTypeRole: return info.docType; case FileSizeRole: return info.fileSize; case AuthorNameRole: return info.authorName; case AccessedTimeRole: return prettyTime(info.accessedTime); case ModifiedTimeRole: return prettyTime(info.modifiedTime); case UUIDRole: return info.uuid; default: return QVariant(); } } QString DocumentListModel::prettyTime( const QDateTime& theTime) { return QLocale().toString(theTime, QLocale::LongFormat); } QVariant DocumentListModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(role) return QVariant(); } DocumentListModel::DocumentType DocumentListModel::filter() { return d->filter; } void DocumentListModel::setFilter( DocumentListModel::DocumentType newFilter) { d->filter = newFilter; d->relayout(); } DocumentListModel::DocumentType DocumentListModel::typeForFile ( const QString& file ) { if (sm_extensions.isEmpty()) { sm_extensions["odt"] = TextDocumentType; sm_extensions["fodt"] = TextDocumentType; sm_extensions["doc"] = TextDocumentType; sm_extensions["docx"] = TextDocumentType; sm_extensions["txt"] = TextDocumentType; sm_extensions["odp"] = PresentationType; sm_extensions["fodp"] = PresentationType; sm_extensions["ppt"] = PresentationType; sm_extensions["pptx"] = PresentationType; sm_extensions["ods"] = SpreadsheetType; sm_extensions["fods"] = SpreadsheetType; sm_extensions["xls"] = SpreadsheetType; sm_extensions["xlsx"] = SpreadsheetType; sm_extensions["pdf"] = PDFDocumentType; } QString ext = file.split('.').last().toLower(); if (sm_extensions.contains(ext)) { return sm_extensions.value(ext); } return UnknownType; } void DocumentListModel::Private::relayout() { emit q->layoutAboutToBeChanged(); QList newList; Q_FOREACH (const DocumentInfo &docInfo, allDocumentInfos) { if (filter == UnknownType || docInfo.docType == filter) { if (searchPattern.isEmpty() || docInfo.fileName.contains(searchPattern, Qt::CaseInsensitive)) { newList.append(docInfo); } } } currentDocumentInfos = newList; emit q->layoutChanged(); q->beginResetModel(); q->endResetModel(); } diff --git a/libs/ui/dialogs/kis_dlg_generator_layer.cpp b/libs/ui/dialogs/kis_dlg_generator_layer.cpp index 4f0dff2f44..61c75f05fc 100644 --- a/libs/ui/dialogs/kis_dlg_generator_layer.cpp +++ b/libs/ui/dialogs/kis_dlg_generator_layer.cpp @@ -1,76 +1,120 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_dlg_generator_layer.h" #include #include #include #include #include #include #include #include #include +#include +#include #include +#include -KisDlgGeneratorLayer::KisDlgGeneratorLayer(const QString & name, KisViewManager *view, QWidget *parent) +KisDlgGeneratorLayer::KisDlgGeneratorLayer(const QString defaultName, KisViewManager *view, QWidget *parent, KisGeneratorLayerSP glayer = 0, const KisFilterConfigurationSP previousConfig = 0) : KoDialog(parent) , m_customName(false) , m_freezeName(false) { setButtons(Ok | Cancel); setDefaultButton(Ok); - QWidget * page = new QWidget(this); + isEditing = glayer && previousConfig; + + if(isEditing){ + setModal(false); + layer = glayer; + configBefore = previousConfig; + } + + QWidget *page = new QWidget(this); + + m_view = view; dlgWidget.setupUi(page); - dlgWidget.wdgGenerator->initialize(view); + dlgWidget.wdgGenerator->initialize(m_view); setMainWidget(page); - dlgWidget.txtLayerName->setText(name); + dlgWidget.txtLayerName->setText( isEditing ? layer->name() : defaultName ); connect(dlgWidget.txtLayerName, SIGNAL(textChanged(QString)), this, SLOT(slotNameChanged(QString))); } +KisDlgGeneratorLayer::~KisDlgGeneratorLayer() +{ + /*Editing a layer should be using the show function with automatic deletion on close. + *Because of this, the action should be taken care of when the window is closed and + *the user has accepted the changes.*/ + if(isEditing && result() == QDialog::Accepted) { + + layer->setName(layerName()); + + KisFilterConfigurationSP configAfter(configuration()); + Q_ASSERT(configAfter); + QString xmlBefore = configBefore->toXML(); + QString xmlAfter = configAfter->toXML(); + + if(xmlBefore != xmlAfter) { + KisChangeFilterCmd *cmd + = new KisChangeFilterCmd(layer, + configBefore->name(), + xmlBefore, + configAfter->name(), + xmlAfter, + true); + // FIXME: check whether is needed + cmd->redo(); + m_view->undoAdapter()->addCommand(cmd); + m_view->document()->setModified(true); + } + + } +} + void KisDlgGeneratorLayer::slotNameChanged(const QString & text) { if (m_freezeName) return; m_customName = !text.isEmpty(); enableButtonOk(m_customName); } void KisDlgGeneratorLayer::setConfiguration(const KisFilterConfigurationSP config) { dlgWidget.wdgGenerator->setConfiguration(config); } KisFilterConfigurationSP KisDlgGeneratorLayer::configuration() const { return dlgWidget.wdgGenerator->configuration(); } QString KisDlgGeneratorLayer::layerName() const { return dlgWidget.txtLayerName->text(); } diff --git a/libs/ui/dialogs/kis_dlg_generator_layer.h b/libs/ui/dialogs/kis_dlg_generator_layer.h index 252d3bd247..0d27ae4e73 100644 --- a/libs/ui/dialogs/kis_dlg_generator_layer.h +++ b/libs/ui/dialogs/kis_dlg_generator_layer.h @@ -1,64 +1,70 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_DLG_GENERATORLAYER_H #define KIS_DLG_GENERATORLAYER_H #include #include class KisFilterConfiguration; class KisViewManager; #include "ui_wdgdlggeneratorlayer.h" #include /** * Create a new generator layer */ class KisDlgGeneratorLayer : public KoDialog { public: Q_OBJECT public: /** * Create a new generator layer * @param name the proposed name for this layer * @param view the view manager * @param parent the widget parent of this dialog + * @param glayer optional generator layer for editing + * @param previousConfig optional configuration of layer being edited. */ - KisDlgGeneratorLayer(const QString & name, KisViewManager *view, QWidget *parent); + KisDlgGeneratorLayer(const QString defaultLayerName, KisViewManager *arg_view, QWidget *parent, KisGeneratorLayerSP glayer, const KisFilterConfigurationSP previousConfig); + ~KisDlgGeneratorLayer() override; void setConfiguration(const KisFilterConfigurationSP config); KisFilterConfigurationSP configuration() const; QString layerName() const; protected Q_SLOTS: - void slotNameChanged(const QString &); private: - Ui_WdgDlgGeneratorLayer dlgWidget; + KisGeneratorLayerSP layer; + KisFilterConfigurationSP configBefore; + KisViewManager *m_view; + bool isEditing; + bool m_customName; bool m_freezeName; }; #endif diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index b49cfadbad..efd6c8990a 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,977 +1,959 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * 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 "kis_layer_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cursor.h" #include "dialogs/kis_dlg_adj_layer_props.h" #include "dialogs/kis_dlg_adjustment_layer.h" #include "dialogs/kis_dlg_layer_properties.h" #include "dialogs/kis_dlg_generator_layer.h" #include "dialogs/kis_dlg_file_layer.h" #include "dialogs/kis_dlg_layer_style.h" #include "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_node_commands.h" #include "kis_change_file_layer_command.h" #include "kis_canvas_resource_provider.h" #include "kis_selection_manager.h" #include "kis_statusbar.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "canvas/kis_canvas2.h" #include "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include "widgets/kis_wdg_generator.h" #include "kis_progress_widget.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_raster_keyframe_channel.h" #include "KisImportExportManager.h" #include "kis_signal_compressor_with_param.h" #include "kis_abstract_projection_plane.h" #include "commands_new/kis_set_layer_style_command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "lazybrush/kis_colorize_mask.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) , m_imageView(0) , m_imageFlatten(0) , m_imageMergeLayer(0) , m_groupLayersSave(0) , m_imageResizeToLayer(0) , m_flattenLayer(0) , m_rasterizeLayer(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) , m_layerStyle(0) { } KisLayerManager::~KisLayerManager() { delete m_commandsAdapter; } void KisLayerManager::setView(QPointerview) { m_imageView = view; } KisLayerSP KisLayerManager::activeLayer() { if (m_imageView) { return m_imageView->currentLayer(); } return 0; } KisPaintDeviceSP KisLayerManager::activeDevice() { if (activeLayer()) { return activeLayer()->paintDevice(); } return 0; } void KisLayerManager::activateLayer(KisLayerSP layer) { if (m_imageView) { emit sigLayerActivated(layer); layersUpdated(); if (layer) { m_view->resourceProvider()->slotNodeActivated(layer.data()); } } } void KisLayerManager::setup(KisActionManager* actionManager) { m_imageFlatten = actionManager->createAction("flatten_image"); connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage())); m_imageMergeLayer = actionManager->createAction("merge_layer"); connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer())); m_flattenLayer = actionManager->createAction("flatten_layer"); connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer())); m_rasterizeLayer = actionManager->createAction("rasterize_layer"); connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer())); m_groupLayersSave = actionManager->createAction("save_groups_as_images"); connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers())); m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated"); connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated())); m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer"); connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer())); KisAction *action = actionManager->createAction("trim_to_image"); connect(action, SIGNAL(triggered()), this, SLOT(trimToImage())); m_layerStyle = actionManager->createAction("layer_style"); connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle())); } void KisLayerManager::updateGUI() { KisImageSP image = m_view->image(); KisLayerSP layer = activeLayer(); const bool isGroupLayer = layer && layer->inherits("KisGroupLayer"); m_imageMergeLayer->setText( isGroupLayer ? i18nc("@action:inmenu", "Merge Group") : i18nc("@action:inmenu", "Merge with Layer Below")); m_flattenLayer->setVisible(!isGroupLayer); if (m_view->statusBar()) m_view->statusBar()->setProfile(image); } void KisLayerManager::imageResizeToActiveLayer() { KisLayerSP layer; KisImageWSP image = m_view->image(); if (image && (layer = activeLayer())) { QRect cropRect = layer->projection()->nonDefaultPixelArea(); if (!cropRect.isEmpty()) { image->cropImage(cropRect); } else { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is empty "), QIcon(), 2000, KisFloatingMessage::Low); } } } void KisLayerManager::trimToImage() { KisImageWSP image = m_view->image(); if (image) { image->cropImage(image->bounds()); } } void KisLayerManager::layerProperties() { if (!m_view) return; if (!m_view->document()) return; KisLayerSP layer = activeLayer(); if (!layer) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; KisAdjustmentLayerSP alayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); KisGeneratorLayerSP glayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); KisFileLayerSP flayer = KisFileLayerSP(dynamic_cast(layer.data())); if (alayer && !multipleLayersSelected) { KisPaintDeviceSP dev = alayer->projection(); KisDlgAdjLayerProps dlg(alayer, alayer.data(), dev, m_view, alayer->filter().data(), alayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops"); dlg.resize(dlg.minimumSizeHint()); KisFilterConfigurationSP configBefore(alayer->filter()); KIS_ASSERT_RECOVER_RETURN(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { alayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(alayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { alayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); alayer->setDirty(); } } } else if (glayer && !multipleLayersSelected) { - - KisDlgGeneratorLayer dlg(glayer->name(), m_view, m_view->mainWindow()); - dlg.setCaption(i18n("Fill Layer Properties")); - KisFilterConfigurationSP configBefore(glayer->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); - dlg.setConfiguration(configBefore.data()); - dlg.resize(dlg.minimumSizeHint()); + KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(glayer->name(), m_view, m_view->mainWindow(), glayer, configBefore); + dlg->setCaption(i18n("Fill Layer Properties")); + dlg->setAttribute(Qt::WA_DeleteOnClose); - if (dlg.exec() == QDialog::Accepted) { + dlg->setConfiguration(configBefore.data()); + dlg->resize(dlg->minimumSizeHint()); - glayer->setName(dlg.layerName()); + Qt::WindowFlags flags = dlg->windowFlags(); + dlg->setWindowFlags(flags | Qt::WindowStaysOnTopHint | Qt::Dialog); + dlg->show(); - KisFilterConfigurationSP configAfter(dlg.configuration()); - Q_ASSERT(configAfter); - QString xmlAfter = configAfter->toXML(); - - if(xmlBefore != xmlAfter) { - KisChangeFilterCmd *cmd - = new KisChangeFilterCmd(glayer, - configBefore->name(), - xmlBefore, - configAfter->name(), - xmlAfter, - true); - // FIXME: check whether is needed - cmd->redo(); - m_view->undoAdapter()->addCommand(cmd); - m_view->document()->setModified(true); - } - - } } else if (flayer && !multipleLayersSelected){ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString fileNameOld = flayer->fileName(); KisFileLayer::ScalingMethod scalingMethodOld = flayer->scalingMethod(); KisDlgFileLayer dlg(basePath, flayer->name(), m_view->mainWindow()); dlg.setCaption(i18n("File Layer Properties")); dlg.setFileName(fileNameOld); dlg.setScalingMethod(scalingMethodOld); if (dlg.exec() == QDialog::Accepted) { const QString fileNameNew = dlg.fileName(); KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution(); if(fileNameNew.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } flayer->setName(dlg.layerName()); if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) { KisChangeFileLayerCmd *cmd = new KisChangeFileLayerCmd(flayer, basePath, fileNameOld, scalingMethodOld, basePath, fileNameNew, scalingMethodNew); m_view->undoAdapter()->addCommand(cmd); } } } else { // If layer == normal painting layer, vector layer, or group layer QList selectedNodes = m_view->nodeManager()->selectedNodes(); KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::WindowStaysOnTopHint | Qt::Dialog); dialog->show(); } } void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source) { KisImageWSP image = m_view->image(); if (!image) return; KisLayer *srcLayer = qobject_cast(source.data()); if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) { image->flattenLayer(srcLayer); return; } KisPaintDeviceSP srcDevice = source->paintDevice() ? source->projection() : source->original(); bool putBehind = false; QString newCompositeOp = source->compositeOpId(); KisColorizeMask *colorizeMask = dynamic_cast(source.data()); if (colorizeMask) { srcDevice = colorizeMask->coloringProjection(); putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND; if (putBehind) { newCompositeOp = COMPOSITE_OVER; } } if (!srcDevice) return; KisPaintDeviceSP clone; if (*srcDevice->colorSpace() != *srcDevice->compositionSourceColorSpace()) { clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace()); QRect rc(srcDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc); } else { clone = new KisPaintDevice(*srcDevice); } KisLayerSP layer = new KisPaintLayer(image, source->name(), source->opacity(), clone); layer->setCompositeOpId(newCompositeOp); KisNodeSP parent = source->parent(); KisNodeSP above = source->prevSibling(); while (parent && !parent->allowAsChild(layer)) { above = above ? above->parent() : source->parent(); parent = above ? above->parent() : 0; } if (putBehind && above == source->parent()) { above = above->prevSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(layer, parent, above); m_commandsAdapter->endMacro(); } void KisLayerManager::convertGroupToAnimated() { KisGroupLayerSP group = dynamic_cast(activeLayer().data()); if (group.isNull()) return; KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8); animatedLayer->enableAnimation(); KisRasterKeyframeChannel *contentChannel = dynamic_cast( animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); KIS_ASSERT_RECOVER_RETURN(contentChannel); KisNodeSP child = group->firstChild(); int time = 0; while (child) { contentChannel->importFrame(time, child->projection(), NULL); time++; child = child->nextSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer")); m_commandsAdapter->addNode(animatedLayer, group->parent(), group); m_commandsAdapter->removeNode(group); m_commandsAdapter->endMacro(); } void KisLayerManager::convertLayerToFileLayer(KisNodeSP source) { KisImageSP image = m_view->image(); if (!image) return; QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); dlg.setWindowTitle(i18n("Save layers to...")); QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location.")); lbl->setWordWrap(true); layout->addWidget(lbl); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); if (m_view->document()->url().isLocalFile()) { QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).baseName(); location.setFile(location.dir(), location.baseName() + "_" + source->name() + ".png"); urlRequester->setFileName(location.absoluteFilePath()); } else { const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png"); urlRequester->setFileName(proposedFileName); } // We don't want .kra files as file layers, Krita cannot handle the load. QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); int i = mimes.indexOf(KIS_MIME_TYPE); if (i >= 0 && i < mimes.size()) { mimes.removeAt(i); } urlRequester->setMimeTypeFilters(mimes); layout->addWidget(urlRequester); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName()); if (mimeType.isEmpty()) { mimeType = "image/png"; } QScopedPointer doc(KisPart::instance()->createDocument()); QRect bounds = source->exactBounds(); KisImageSP dst = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->projection()->compositionSourceColorSpace(), source->name()); dst->setResolution(image->xRes(), image->yRes()); doc->setFileBatchMode(false); doc->setCurrentImage(dst); KisNodeSP node = source->clone(); dst->addNode(node); dst->initialRefreshGraph(); dst->cropImage(bounds); dst->waitForDone(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1()); if (!r) { qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << doc->errorMessage(); } else { QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString relativePath = QDir(basePath).relativeFilePath(path); KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8); fileLayer->setX(bounds.x()); fileLayer->setY(bounds.y()); KisNodeSP dstParent = source->parent(); KisNodeSP dstAboveThis = source->prevSibling(); m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis); m_commandsAdapter->endMacro(); } doc->closeUrl(false); } void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(activeNode); parent = activeNode; above = parent->lastChild(); if (parent->inherits("KisGroupLayer") && parent->collapsed()) { above = parent; parent = parent->parent(); return; } while (parent && (!parent->allowAsChild(node) || parent->userLocked())) { above = parent; parent = parent->parent(); } if (!parent) { warnKrita << "KisLayerManager::adjustLayerPosition:" << "No node accepted newly created node"; parent = m_view->image()->root(); above = parent->lastChild(); } } void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage) { KisNodeSP parent; KisNodeSP above; adjustLayerPosition(layer, activeNode, parent, above); KisGroupLayer *group = dynamic_cast(parent.data()); const bool parentForceUpdate = group && !group->projectionIsValid(); updateImage |= parentForceUpdate; m_commandsAdapter->addNode(layer, parent, above, updateImage, updateImage); } KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode) { KisLayerSP layer = KisLayerUtils::constructDefaultLayer(m_view->image()); addLayerCommon(activeNode, layer, false); return layer; } KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, group, false); return group; } KisNodeSP KisLayerManager::addCloneLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisNodeSP node = new KisCloneLayer(activeLayer(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node); return node; } KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode) { if (!m_view) return 0; if (!m_view->document()) return 0; KisImageWSP image = m_view->image(); KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, layer, false); return layer; } KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisSelectionSP selection = m_view->selection(); KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection); image->refreshGraph(); KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original()); KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view); dlg.resize(dlg.minimumSizeHint()); // ensure that the device may be free'd by the dialog // when it is not needed anymore previewDevice = 0; if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) { // XXX: add messagebox warning if there's no filter set! m_commandsAdapter->undoLastCommand(); } else { adjl->setName(dlg.layerName()); } return adjl; } KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection) { KisImageWSP image = m_view->image(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection); addLayerCommon(activeNode, layer); return layer; } KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); QColor currentForeground = m_view->resourceProvider()->fgColor().toQColor(); - KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow()); + + KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0); KisFilterConfigurationSP defaultConfig = dlg.configuration(); defaultConfig->setProperty("color", currentForeground); dlg.setConfiguration(defaultConfig); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { KisSelectionSP selection = m_view->selection(); KisFilterConfigurationSP generator = dlg.configuration(); QString name = dlg.layerName(); KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection); addLayerCommon(activeNode, node ); return node; } return 0; } void KisLayerManager::flattenImage() { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) return; if (image) { bool doIt = true; if (image->nHiddenLayers() > 0) { int answer = QMessageBox::warning(m_view->mainWindow(), i18nc("@title:window", "Flatten Image"), i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer != QMessageBox::Yes) { doIt = false; } } if (doIt) { image->flatten(m_view->activeNode()); } } } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image) { bool result = false; KisNodeSP prevNode = currentNode->prevSibling(); if (isSelectionMask(currentNode) && prevNode && isSelectionMask(prevNode)) { QList mergedNodes; mergedNodes.append(currentNode); mergedNodes.append(prevNode); image->mergeMultipleLayers(mergedNodes, currentNode); result = true; } return result; } bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image) { bool result = false; if (currentNode->inherits("KisGroupLayer")) { KisGroupLayer *layer = qobject_cast(currentNode.data()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false); image->flattenLayer(layer); result = true; } return result; } void KisLayerManager::mergeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.size() > 1) { image->mergeMultipleLayers(selectedNodes, m_view->activeNode()); } else if (tryMergeSelectionMasks(m_view->activeNode(), image)) { // already done! } else if (tryFlattenGroupLayer(m_view->activeNode(), image)) { // already done! } else { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (prevLayer->userLocked()) { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is locked "), QIcon(), 2000, KisFloatingMessage::Low); } else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) { image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); } else { const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow()); if (!strategy) return; image->mergeDown(layer, strategy); } } m_view->updateGUI(); } void KisLayerManager::flattenLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; convertNodeToPaintLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity()); KisPainter gc(paintLayer->paintDevice()); QRect rc = layer->projection()->exactBounds(); gc.bitBlt(rc.topLeft(), layer->projection(), rc); m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer")); m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); int childCount = layer->childCount(); for (int i = 0; i < childCount; i++) { m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild()); } m_commandsAdapter->removeNode(layer); m_commandsAdapter->endMacro(); updateGUI(); } void KisLayerManager::layersUpdated() { KisLayerSP layer = activeLayer(); if (!layer) return; m_view->updateGUI(); } void KisLayerManager::saveGroupLayers() { QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); if (m_view->document()->url().isLocalFile()) { urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath()); } urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); layout->addWidget(urlRequester); QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page); chkInvisible->setChecked(false); layout->addWidget(chkInvisible); QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page); chkDepth->setChecked(true); layout->addWidget(chkDepth); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName(), false); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); QString basename = f.baseName(); KisImageSP image = m_view->image(); if (!image) return; KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType); image->rootLayer()->accept(v); } bool KisLayerManager::activeLayerHasSelection() { return (activeLayer()->selection() != 0); } KisNodeSP KisLayerManager::addFileLayer(KisNodeSP activeNode) { QString basePath; QUrl url = m_view->document()->url(); if (url.isLocalFile()) { basePath = QFileInfo(url.toLocalFile()).absolutePath(); } KisImageWSP image = m_view->image(); KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { QString name = dlg.layerName(); QString fileName = dlg.fileName(); if(fileName.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return 0; } KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution(); KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node); return node; } return 0; } void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg) { KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone()); } void KisLayerManager::layerStyle() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPSDLayerStyleSP oldStyle; if (layer->layerStyle()) { oldStyle = layer->layerStyle()->clone(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone(), m_view->resourceProvider()); std::function updateCall(std::bind(updateLayerStyles, layer, &dlg)); SignalToFunctionProxy proxy(updateCall); connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start())); if (dlg.exec() == QDialog::Accepted) { KisPSDLayerStyleSP newStyle = dlg.style(); KUndo2CommandSP command = toQShared( new KisSetLayerStyleCommand(layer, oldStyle, newStyle)); image->postExecutionUndoAdapter()->addCommand(command); } } diff --git a/libs/ui/kisexiv2/kis_exiv2.cpp b/libs/ui/kisexiv2/kis_exiv2.cpp index 87b6fa88a0..7635e21847 100644 --- a/libs/ui/kisexiv2/kis_exiv2.cpp +++ b/libs/ui/kisexiv2/kis_exiv2.cpp @@ -1,296 +1,296 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "kis_exiv2.h" #include #include "kis_iptc_io.h" #include "kis_exif_io.h" #include "kis_xmp_io.h" #include #include // ---- Generic conversion functions ---- // // Convert an exiv value to a KisMetaData value KisMetaData::Value exivValueToKMDValue(const Exiv2::Value::AutoPtr value, bool forceSeq, KisMetaData::Value::ValueType arrayType) { switch (value->typeId()) { case Exiv2::signedByte: case Exiv2::invalidTypeId: case Exiv2::lastTypeId: case Exiv2::directory: dbgMetaData << "Invalid value :" << value->typeId() << " value =" << value->toString().c_str(); return KisMetaData::Value(); case Exiv2::undefined: { dbgMetaData << "Undefined value :" << value->typeId() << " value =" << value->toString().c_str(); QByteArray array(value->count() , 0); value->copy((Exiv2::byte*)array.data(), Exiv2::invalidByteOrder); return KisMetaData::Value(QString(array.toBase64())); } case Exiv2::unsignedByte: case Exiv2::unsignedShort: case Exiv2::unsignedLong: case Exiv2::signedShort: case Exiv2::signedLong: { if (value->count() == 1 && !forceSeq) { return KisMetaData::Value((int)value->toLong()); } else { QList array; for (int i = 0; i < value->count(); i++) array.push_back(KisMetaData::Value((int)value->toLong(i))); return KisMetaData::Value(array, arrayType); } } case Exiv2::asciiString: case Exiv2::string: case Exiv2::comment: // look at kexiv2 for the problem about decoding correctly that tag return KisMetaData::Value(value->toString().c_str()); case Exiv2::unsignedRational: if(value->size() < 2) { dbgMetaData << "Invalid size :" << value->size() << " value =" << value->toString().c_str(); return KisMetaData::Value(); } return KisMetaData::Value(KisMetaData::Rational(value->toRational().first , value->toRational().second)); case Exiv2::signedRational: if(value->size() < 2) { dbgMetaData << "Invalid size :" << value->size() << " value =" << value->toString().c_str(); return KisMetaData::Value(); } return KisMetaData::Value(KisMetaData::Rational(value->toRational().first , value->toRational().second)); case Exiv2::date: case Exiv2::time: return KisMetaData::Value(QDateTime::fromString(value->toString().c_str(), Qt::ISODate)); case Exiv2::xmpText: case Exiv2::xmpAlt: case Exiv2::xmpBag: case Exiv2::xmpSeq: case Exiv2::langAlt: default: { dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str(); //Q_ASSERT(false); // This point must never be reached ! return KisMetaData::Value(); } } dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str(); //Q_ASSERT(false); // This point must never be reached ! return KisMetaData::Value(); } // Convert a QtVariant to an Exiv value Exiv2::Value* variantToExivValue(const QVariant& variant, Exiv2::TypeId type) { switch (type) { case Exiv2::undefined: { QByteArray arr = QByteArray::fromBase64(variant.toString().toLatin1()); return new Exiv2::DataValue((Exiv2::byte*)arr.data(), arr.size()); } case Exiv2::unsignedByte: return new Exiv2::ValueType(variant.toUInt(0)); case Exiv2::unsignedShort: return new Exiv2::ValueType(variant.toUInt(0)); case Exiv2::unsignedLong: return new Exiv2::ValueType(variant.toUInt(0)); case Exiv2::signedShort: return new Exiv2::ValueType(variant.toInt(0)); case Exiv2::signedLong: return new Exiv2::ValueType(variant.toInt(0)); case Exiv2::date: { QDate date = variant.toDate(); return new Exiv2::DateValue(date.year(), date.month(), date.day()); } case Exiv2::asciiString: if (variant.type() == QVariant::DateTime) { return new Exiv2::AsciiValue(qPrintable(variant.toDateTime().toString("yyyy:MM:dd hh:mm:ss"))); } else return new Exiv2::AsciiValue(qPrintable(variant.toString())); case Exiv2::string: { if (variant.type() == QVariant::DateTime) { return new Exiv2::StringValue(qPrintable(variant.toDateTime().toString("yyyy:MM:dd hh:mm:ss"))); } else return new Exiv2::StringValue(qPrintable(variant.toString())); } case Exiv2::comment: return new Exiv2::CommentValue(qPrintable(variant.toString())); default: dbgMetaData << "Unhandled type:" << type; //Q_ASSERT(false); return 0; } } template Exiv2::Value* arrayToExivValue(const KisMetaData::Value& value) { Exiv2::ValueType<_TYPE_>* ev = new Exiv2::ValueType<_TYPE_>(); for (int i = 0; i < value.asArray().size(); ++i) { ev->value_.push_back(qvariant_cast<_TYPE_>(value.asArray()[i].asVariant())); } return ev; } // Convert a KisMetaData to an Exiv value Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& value, Exiv2::TypeId type) { switch (value.type()) { case KisMetaData::Value::Invalid: return &*Exiv2::Value::create(Exiv2::invalidTypeId); case KisMetaData::Value::Variant: { return variantToExivValue(value.asVariant(), type); } case KisMetaData::Value::Rational: //Q_ASSERT(type == Exiv2::signedRational || type == Exiv2::unsignedRational); if (type == Exiv2::signedRational) { return new Exiv2::ValueType(Exiv2::Rational(value.asRational().numerator, value.asRational().denominator)); } else { return new Exiv2::ValueType(Exiv2::URational(value.asRational().numerator, value.asRational().denominator)); } case KisMetaData::Value::OrderedArray: - /* Falls through */ + Q_FALLTHROUGH(); case KisMetaData::Value::UnorderedArray: - /* Falls through */ + Q_FALLTHROUGH(); case KisMetaData::Value::AlternativeArray: { switch (type) { case Exiv2::unsignedByte: return arrayToExivValue(value); case Exiv2::unsignedShort: return arrayToExivValue(value); case Exiv2::unsignedLong: return arrayToExivValue(value); case Exiv2::signedShort: return arrayToExivValue(value); case Exiv2::signedLong: return arrayToExivValue(value); case Exiv2::string: { Exiv2::StringValue* ev = new Exiv2::StringValue(); for (int i = 0; i < value.asArray().size(); ++i) { ev->value_ += qvariant_cast(value.asArray()[i].asVariant()).toLatin1().constData(); if (i != value.asArray().size() - 1) ev->value_ += ','; } return ev; } break; default: dbgMetaData << type << " " << value; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 && "Unknown alternative array type", 0); break; } break; } default: dbgMetaData << type << " " << value; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 && "Unknown array type", 0); break; } return 0; } Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& value) { //Q_ASSERT(value.type() != KisMetaData::Value::Structure); switch (value.type()) { case KisMetaData::Value::Invalid: return new Exiv2::DataValue(Exiv2::invalidTypeId); case KisMetaData::Value::Variant: { QVariant var = value.asVariant(); if (var.type() == QVariant::Bool) { if (var.toBool()) { return new Exiv2::XmpTextValue("True"); } else { return new Exiv2::XmpTextValue("False"); } } else { //Q_ASSERT(var.canConvert(QVariant::String)); return new Exiv2::XmpTextValue(var.toString().toLatin1().constData()); } } case KisMetaData::Value::Rational: { QString rat = "%1 / %2"; rat = rat.arg(value.asRational().numerator); rat = rat.arg(value.asRational().denominator); return new Exiv2::XmpTextValue(rat.toLatin1().constData()); } case KisMetaData::Value::AlternativeArray: case KisMetaData::Value::OrderedArray: case KisMetaData::Value::UnorderedArray: { Exiv2::XmpArrayValue* arrV = new Exiv2::XmpArrayValue; switch (value.type()) { case KisMetaData::Value::OrderedArray: arrV->setXmpArrayType(Exiv2::XmpValue::xaSeq); break; case KisMetaData::Value::UnorderedArray: arrV->setXmpArrayType(Exiv2::XmpValue::xaBag); break; case KisMetaData::Value::AlternativeArray: arrV->setXmpArrayType(Exiv2::XmpValue::xaAlt); break; default: // Cannot happen ; } Q_FOREACH (const KisMetaData::Value& v, value.asArray()) { Exiv2::Value* ev = kmdValueToExivXmpValue(v); if (ev) { arrV->read(ev->toString()); delete ev; } } return arrV; } case KisMetaData::Value::LangArray: { Exiv2::Value* arrV = new Exiv2::LangAltValue; QMap langArray = value.asLangArray(); for (QMap::iterator it = langArray.begin(); it != langArray.end(); ++it) { QString exivVal; if (it.key() != "x-default") { exivVal = "lang=" + it.key() + ' '; } //Q_ASSERT(it.value().type() == KisMetaData::Value::Variant); QVariant var = it.value().asVariant(); //Q_ASSERT(var.type() == QVariant::String); exivVal += var.toString(); arrV->read(exivVal.toLatin1().constData()); } return arrV; } case KisMetaData::Value::Structure: default: { warnKrita << "KisExiv2: Unhandled value type"; return 0; } } warnKrita << "KisExiv2: Unhandled value type"; return 0; } void KisExiv2::initialize() { // XXX_EXIV: make the exiv io backends real plugins KisMetaData::IOBackendRegistry* ioreg = KisMetaData::IOBackendRegistry::instance(); ioreg->add(new KisIptcIO); ioreg->add(new KisExifIO); ioreg->add(new KisXMPIO); } diff --git a/libs/ui/tool/kis_tool_paint.cc b/libs/ui/tool/kis_tool_paint.cc index 9a3e72b9b1..c8cc16d39c 100644 --- a/libs/ui/tool/kis_tool_paint.cc +++ b/libs/ui/tool/kis_tool_paint.cc @@ -1,782 +1,782 @@ /* * Copyright (c) 2003-2009 Boudewijn Rempt * Copyright (c) 2015 Moritz Molch * * 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 "kis_tool_paint.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_display_color_converter.h" #include #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_slider_spin_box.h" #include "kis_canvas_resource_provider.h" #include "kis_tool_utils.h" #include #include #include #include #include "strokes/kis_color_picker_stroke_strategy.h" KisToolPaint::KisToolPaint(KoCanvasBase *canvas, const QCursor &cursor) : KisTool(canvas, cursor), m_showColorPreview(false), m_colorPreviewShowComparePlate(false), m_colorPickerDelayTimer(), m_isOutlineEnabled(true) { m_specialHoverModifier = false; m_optionsWidgetLayout = 0; m_opacity = OPACITY_OPAQUE_U8; m_supportOutline = false; { int maxSize = KisConfig(true).readEntry("maximumBrushSize", 1000); int brushSize = 1; do { m_standardBrushSizes.push_back(brushSize); int increment = qMax(1, int(std::ceil(qreal(brushSize) / 15))); brushSize += increment; } while (brushSize < maxSize); m_standardBrushSizes.push_back(maxSize); } KisCanvas2 *kiscanvas = dynamic_cast(canvas); connect(this, SIGNAL(sigPaintingFinished()), kiscanvas->viewManager()->resourceProvider(), SLOT(slotPainting())); m_colorPickerDelayTimer.setSingleShot(true); connect(&m_colorPickerDelayTimer, SIGNAL(timeout()), this, SLOT(activatePickColorDelayed())); using namespace std::placeholders; // For _1 placeholder std::function callback = std::bind(&KisToolPaint::addPickerJob, this, _1); m_colorPickingCompressor.reset( new PickingCompressor(100, callback, KisSignalCompressor::FIRST_ACTIVE)); } KisToolPaint::~KisToolPaint() { } int KisToolPaint::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP; } void KisToolPaint::canvasResourceChanged(int key, const QVariant& v) { KisTool::canvasResourceChanged(key, v); switch(key) { case(KisCanvasResourceProvider::Opacity): setOpacity(v.toDouble()); break; default: //nothing break; } connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle()), Qt::UniqueConnection); } void KisToolPaint::activate(ToolActivation toolActivation, const QSet &shapes) { if (currentPaintOpPreset()) { QString formattedBrushName = currentPaintOpPreset()->name().replace("_", " "); emit statusTextChanged(formattedBrushName); } KisTool::activate(toolActivation, shapes); if (flags() & KisTool::FLAG_USES_CUSTOM_SIZE) { connect(action("increase_brush_size"), SIGNAL(triggered()), SLOT(increaseBrushSize()), Qt::UniqueConnection); connect(action("decrease_brush_size"), SIGNAL(triggered()), SLOT(decreaseBrushSize()), Qt::UniqueConnection); } KisCanvasResourceProvider *provider = qobject_cast(canvas())->viewManager()->resourceProvider(); m_oldOpacity = provider->opacity(); provider->setOpacity(m_localOpacity); } void KisToolPaint::deactivate() { if (flags() & KisTool::FLAG_USES_CUSTOM_SIZE) { disconnect(action("increase_brush_size"), 0, this, 0); disconnect(action("decrease_brush_size"), 0, this, 0); } KisCanvasResourceProvider *provider = qobject_cast(canvas())->viewManager()->resourceProvider(); m_localOpacity = provider->opacity(); provider->setOpacity(m_oldOpacity); KisTool::deactivate(); } QPainterPath KisToolPaint::tryFixBrushOutline(const QPainterPath &originalOutline) { KisConfig cfg(true); if (cfg.newOutlineStyle() == OUTLINE_NONE) return originalOutline; const qreal minThresholdSize = cfg.outlineSizeMinimum(); /** * If the brush outline is bigger than the canvas itself (which * would make it invisible for a user in most of the cases) just * add a cross in the center of it */ QSize widgetSize = canvas()->canvasWidget()->size(); const int maxThresholdSum = widgetSize.width() + widgetSize.height(); QPainterPath outline = originalOutline; QRectF boundingRect = outline.boundingRect(); const qreal sum = boundingRect.width() + boundingRect.height(); QPointF center = boundingRect.center(); if (sum > maxThresholdSum) { const int hairOffset = 7; outline.moveTo(center.x(), center.y() - hairOffset); outline.lineTo(center.x(), center.y() + hairOffset); outline.moveTo(center.x() - hairOffset, center.y()); outline.lineTo(center.x() + hairOffset, center.y()); } else if (sum < minThresholdSize && !outline.isEmpty()) { outline = QPainterPath(); outline.addEllipse(center, 0.5 * minThresholdSize, 0.5 * minThresholdSize); } return outline; } void KisToolPaint::paint(QPainter &gc, const KoViewConverter &converter) { Q_UNUSED(converter); QPainterPath path = tryFixBrushOutline(pixelToView(m_currentOutline)); paintToolOutline(&gc, path); if (m_showColorPreview) { QRectF viewRect = converter.documentToView(m_oldColorPreviewRect); gc.fillRect(viewRect, m_colorPreviewCurrentColor); if (m_colorPreviewShowComparePlate) { QRectF baseColorRect = viewRect.translated(viewRect.width(), 0); gc.fillRect(baseColorRect, m_colorPreviewBaseColor); } } } void KisToolPaint::setMode(ToolMode mode) { if(this->mode() == KisTool::PAINT_MODE && mode != KisTool::PAINT_MODE) { // Let's add history information about recently used colors emit sigPaintingFinished(); } KisTool::setMode(mode); } void KisToolPaint::activatePickColor(AlternateAction action) { m_showColorPreview = true; requestUpdateOutline(m_outlineDocPoint, 0); int resource = colorPreviewResourceId(action); KoColor color = canvas()->resourceManager()->koColorResource(resource); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); m_colorPreviewCurrentColor = kisCanvas->displayColorConverter()->toQColor(color); if (!m_colorPreviewBaseColor.isValid()) { m_colorPreviewBaseColor = m_colorPreviewCurrentColor; } } void KisToolPaint::deactivatePickColor(AlternateAction action) { Q_UNUSED(action); m_showColorPreview = false; m_oldColorPreviewRect = QRect(); m_oldColorPreviewUpdateRect = QRect(); m_colorPreviewCurrentColor = QColor(); } void KisToolPaint::pickColorWasOverridden() { m_colorPreviewShowComparePlate = false; m_colorPreviewBaseColor = QColor(); } void KisToolPaint::activateAlternateAction(AlternateAction action) { switch (action) { case PickFgNode: - /* Falls through */ + Q_FALLTHROUGH(); case PickBgNode: - /* Falls through */ + Q_FALLTHROUGH(); case PickFgImage: - /* Falls through */ + Q_FALLTHROUGH(); case PickBgImage: delayedAction = action; m_colorPickerDelayTimer.start(100); - /* Falls through */ + Q_FALLTHROUGH(); default: pickColorWasOverridden(); KisTool::activateAlternateAction(action); }; } void KisToolPaint::activatePickColorDelayed() { switch (delayedAction) { case PickFgNode: useCursor(KisCursor::pickerLayerForegroundCursor()); activatePickColor(delayedAction); break; case PickBgNode: useCursor(KisCursor::pickerLayerBackgroundCursor()); activatePickColor(delayedAction); break; case PickFgImage: useCursor(KisCursor::pickerImageForegroundCursor()); activatePickColor(delayedAction); break; case PickBgImage: useCursor(KisCursor::pickerImageBackgroundCursor()); activatePickColor(delayedAction); break; default: break; }; repaintDecorations(); } bool KisToolPaint::isPickingAction(AlternateAction action) { return action == PickFgNode || action == PickBgNode || action == PickFgImage || action == PickBgImage; } void KisToolPaint::deactivateAlternateAction(AlternateAction action) { if (!isPickingAction(action)) { KisTool::deactivateAlternateAction(action); return; } delayedAction = KisTool::NONE; m_colorPickerDelayTimer.stop(); resetCursorStyle(); deactivatePickColor(action); } void KisToolPaint::addPickerJob(const PickingJob &pickingJob) { /** * The actual picking is delayed by a compressor, so we can get this * event when the stroke is already closed */ if (!m_pickerStrokeId) return; KIS_ASSERT_RECOVER_RETURN(isPickingAction(pickingJob.action)); const QPoint imagePoint = image()->documentToImagePixelFloored(pickingJob.documentPixel); const bool fromCurrentNode = pickingJob.action == PickFgNode || pickingJob.action == PickBgNode; m_pickingResource = colorPreviewResourceId(pickingJob.action); if (!fromCurrentNode) { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas); KisSharedPtr referencesLayer = kisCanvas->imageView()->document()->referenceImagesLayer(); if (referencesLayer && kisCanvas->referenceImagesDecoration()->visible()) { QColor color = referencesLayer->getPixel(imagePoint); if (color.isValid() && color.alpha() != 0) { slotColorPickingFinished(KoColor(color, image()->colorSpace())); return; } } } KisPaintDeviceSP device = fromCurrentNode ? currentNode()->colorPickSourceDevice() : image()->projection(); // Used for color picker blending. KoColor currentColor = canvas()->resourceManager()->foregroundColor(); if( pickingJob.action == PickBgNode || pickingJob.action == PickBgImage ){ currentColor = canvas()->resourceManager()->backgroundColor(); } image()->addJob(m_pickerStrokeId, new KisColorPickerStrokeStrategy::Data(device, imagePoint, currentColor)); } void KisToolPaint::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(!m_pickerStrokeId); setMode(SECONDARY_PAINT_MODE); KisColorPickerStrokeStrategy *strategy = new KisColorPickerStrokeStrategy(); connect(strategy, &KisColorPickerStrokeStrategy::sigColorUpdated, this, &KisToolPaint::slotColorPickingFinished); m_pickerStrokeId = image()->startStroke(strategy); m_colorPickingCompressor->start(PickingJob(event->point, action)); requestUpdateOutline(event->point, event); } else { KisTool::beginAlternateAction(event, action); } } void KisToolPaint::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId); m_colorPickingCompressor->start(PickingJob(event->point, action)); requestUpdateOutline(event->point, event); } else { KisTool::continueAlternateAction(event, action); } } void KisToolPaint::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId); image()->endStroke(m_pickerStrokeId); m_pickerStrokeId.clear(); requestUpdateOutline(event->point, event); setMode(HOVER_MODE); } else { KisTool::endAlternateAction(event, action); } } int KisToolPaint::colorPreviewResourceId(AlternateAction action) { bool toForegroundColor = action == PickFgNode || action == PickFgImage; int resource = toForegroundColor ? KoCanvasResourceProvider::ForegroundColor : KoCanvasResourceProvider::BackgroundColor; return resource; } void KisToolPaint::slotColorPickingFinished(const KoColor &color) { canvas()->resourceManager()->setResource(m_pickingResource, color); if (!m_showColorPreview) return; KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); QColor previewColor = kisCanvas->displayColorConverter()->toQColor(color); m_colorPreviewShowComparePlate = true; m_colorPreviewCurrentColor = previewColor; requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } void KisToolPaint::mouseMoveEvent(KoPointerEvent *event) { KisTool::mouseMoveEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } void KisToolPaint::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } QWidget * KisToolPaint::createOptionWidget() { QWidget *optionWidget = new QWidget(); optionWidget->setObjectName(toolId()); QVBoxLayout *verticalLayout = new QVBoxLayout(optionWidget); verticalLayout->setObjectName("KisToolPaint::OptionWidget::VerticalLayout"); verticalLayout->setContentsMargins(0,0,0,0); verticalLayout->setSpacing(5); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(optionWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); verticalLayout->addWidget(specialSpacer); verticalLayout->addWidget(specialSpacer); m_optionsWidgetLayout = new QGridLayout(); m_optionsWidgetLayout->setColumnStretch(1, 1); verticalLayout->addLayout(m_optionsWidgetLayout); m_optionsWidgetLayout->setContentsMargins(0,0,0,0); m_optionsWidgetLayout->setSpacing(5); if (!quickHelp().isEmpty()) { QPushButton *push = new QPushButton(KisIconUtils::loadIcon("help-contents"), QString(), optionWidget); connect(push, SIGNAL(clicked()), this, SLOT(slotPopupQuickHelp())); QHBoxLayout *hLayout = new QHBoxLayout(); hLayout->addWidget(push); hLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed)); verticalLayout->addLayout(hLayout); } return optionWidget; } QWidget* findLabelWidget(QGridLayout *layout, QWidget *control) { QWidget *result = 0; int index = layout->indexOf(control); int row, col, rowSpan, colSpan; layout->getItemPosition(index, &row, &col, &rowSpan, &colSpan); if (col > 0) { QLayoutItem *item = layout->itemAtPosition(row, col - 1); if (item) { result = item->widget(); } } else { QLayoutItem *item = layout->itemAtPosition(row, col + 1); if (item) { result = item->widget(); } } return result; } void KisToolPaint::showControl(QWidget *control, bool value) { control->setVisible(value); QWidget *label = findLabelWidget(m_optionsWidgetLayout, control); if (label) { label->setVisible(value); } } void KisToolPaint::enableControl(QWidget *control, bool value) { control->setEnabled(value); QWidget *label = findLabelWidget(m_optionsWidgetLayout, control); if (label) { label->setEnabled(value); } } void KisToolPaint::addOptionWidgetLayout(QLayout *layout) { Q_ASSERT(m_optionsWidgetLayout != 0); int rowCount = m_optionsWidgetLayout->rowCount(); m_optionsWidgetLayout->addLayout(layout, rowCount, 0, 1, 2); } void KisToolPaint::addOptionWidgetOption(QWidget *control, QWidget *label) { Q_ASSERT(m_optionsWidgetLayout != 0); if (label) { m_optionsWidgetLayout->addWidget(label, m_optionsWidgetLayout->rowCount(), 0); m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount() - 1, 1); } else { m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount(), 0, 1, 2); } } void KisToolPaint::setOpacity(qreal opacity) { m_opacity = quint8(opacity * OPACITY_OPAQUE_U8); } const KoCompositeOp* KisToolPaint::compositeOp() { if (currentNode()) { KisPaintDeviceSP device = currentNode()->paintDevice(); if (device) { QString op = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentCompositeOp).toString(); return device->colorSpace()->compositeOp(op); } } return 0; } void KisToolPaint::slotPopupQuickHelp() { QWhatsThis::showText(QCursor::pos(), quickHelp()); } KisToolPaint::NodePaintAbility KisToolPaint::nodePaintAbility() { KisNodeSP node = currentNode(); if (!node) { return NONE; } if (node->inherits("KisShapeLayer")) { return VECTOR; } if (node->inherits("KisCloneLayer")) { return CLONE; } if (node->paintDevice()) { return PAINT; } return NONE; } void KisToolPaint::activatePrimaryAction() { pickColorWasOverridden(); setOutlineEnabled(true); KisTool::activatePrimaryAction(); } void KisToolPaint::deactivatePrimaryAction() { setOutlineEnabled(false); KisTool::deactivatePrimaryAction(); } bool KisToolPaint::isOutlineEnabled() const { return m_isOutlineEnabled; } void KisToolPaint::setOutlineEnabled(bool value) { m_isOutlineEnabled = value; requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::increaseBrushSize() { qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize(); std::vector::iterator result = std::upper_bound(m_standardBrushSizes.begin(), m_standardBrushSizes.end(), qRound(paintopSize)); int newValue = result != m_standardBrushSizes.end() ? *result : m_standardBrushSizes.back(); currentPaintOpPreset()->settings()->setPaintOpSize(newValue); requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::decreaseBrushSize() { qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize(); std::vector::reverse_iterator result = std::upper_bound(m_standardBrushSizes.rbegin(), m_standardBrushSizes.rend(), (int)paintopSize, std::greater()); int newValue = result != m_standardBrushSizes.rend() ? *result : m_standardBrushSizes.front(); currentPaintOpPreset()->settings()->setPaintOpSize(newValue); requestUpdateOutline(m_outlineDocPoint, 0); } QRectF KisToolPaint::colorPreviewDocRect(const QPointF &outlineDocPoint) { if (!m_showColorPreview) return QRect(); KisConfig cfg(true); const QRectF colorPreviewViewRect = cfg.colorPreviewRect(); const QRectF colorPreviewDocumentRect = canvas()->viewConverter()->viewToDocument(colorPreviewViewRect); return colorPreviewDocumentRect.translated(outlineDocPoint); } void KisToolPaint::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event) { if (!m_supportOutline) return; KisConfig cfg(true); KisPaintOpSettings::OutlineMode outlineMode; if (isOutlineEnabled() && (mode() == KisTool::GESTURE_MODE || ((cfg.newOutlineStyle() == OUTLINE_FULL || cfg.newOutlineStyle() == OUTLINE_CIRCLE || cfg.newOutlineStyle() == OUTLINE_TILT) && ((mode() == HOVER_MODE) || (mode() == PAINT_MODE && cfg.showOutlineWhilePainting()))))) { // lisp forever! outlineMode.isVisible = true; if (cfg.newOutlineStyle() == OUTLINE_CIRCLE) { outlineMode.forceCircle = true; } else if(cfg.newOutlineStyle() == OUTLINE_TILT) { outlineMode.forceCircle = true; outlineMode.showTiltDecoration = true; } else { // noop } } outlineMode.forceFullSize = cfg.forceAlwaysFullSizedOutline(); m_outlineDocPoint = outlineDocPoint; m_currentOutline = getOutlinePath(m_outlineDocPoint, event, outlineMode); QRectF outlinePixelRect = m_currentOutline.boundingRect(); QRectF outlineDocRect = currentImage()->pixelToDocument(outlinePixelRect); // This adjusted call is needed as we paint with a 3 pixel wide brush and the pen is outside the bounds of the path // Pen uses view coordinates so we have to zoom the document value to match 2 pixel in view coordinates // See BUG 275829 qreal zoomX; qreal zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); qreal xoffset = 2.0/zoomX; qreal yoffset = 2.0/zoomY; if (!outlineDocRect.isEmpty()) { outlineDocRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } QRectF colorPreviewDocRect = this->colorPreviewDocRect(m_outlineDocPoint); QRectF colorPreviewDocUpdateRect; if (!colorPreviewDocRect.isEmpty()) { colorPreviewDocUpdateRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } // DIRTY HACK ALERT: we should fetch the assistant's dirty rect when requesting // the update, instead of just dumbly update the entire canvas! KisCanvas2 * kiscanvas = dynamic_cast(canvas()); KisPaintingAssistantsDecorationSP decoration = kiscanvas->paintingAssistantsDecoration(); if (decoration && decoration->visible()) { kiscanvas->updateCanvas(); } else { // TODO: only this branch should be present! if (!m_oldColorPreviewUpdateRect.isEmpty()) { canvas()->updateCanvas(m_oldColorPreviewUpdateRect); } if (!m_oldOutlineRect.isEmpty()) { canvas()->updateCanvas(m_oldOutlineRect); } if (!outlineDocRect.isEmpty()) { canvas()->updateCanvas(outlineDocRect); } if (!colorPreviewDocUpdateRect.isEmpty()) { canvas()->updateCanvas(colorPreviewDocUpdateRect); } } m_oldOutlineRect = outlineDocRect; m_oldColorPreviewRect = colorPreviewDocRect; m_oldColorPreviewUpdateRect = colorPreviewDocUpdateRect; } QPainterPath KisToolPaint::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { Q_UNUSED(event); QPointF imagePos = currentImage()->documentToPixel(documentPos); QPainterPath path = currentPaintOpPreset()->settings()-> brushOutline(KisPaintInformation(imagePos), outlineMode); return path; } diff --git a/libs/widgetutils/xmlgui/kcheckaccelerators.cpp b/libs/widgetutils/xmlgui/kcheckaccelerators.cpp index 596af2f4ba..49cbae1f1e 100644 --- a/libs/widgetutils/xmlgui/kcheckaccelerators.cpp +++ b/libs/widgetutils/xmlgui/kcheckaccelerators.cpp @@ -1,317 +1,317 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) Copyright (C) 1998, 1999, 2000 KDE Team Copyright (C) 2008 Nick Shaforostoff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcheckaccelerators.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KCheckAcceleratorsInitializer : public QObject { Q_OBJECT public: explicit KCheckAcceleratorsInitializer(QObject *parent = Q_NULLPTR) : QObject(parent) { } public Q_SLOTS: void initiateIfNeeded() { KConfigGroup cg(KSharedConfig::openConfig(), "Development"); QString sKey = cg.readEntry("CheckAccelerators").trimmed(); int key = 0; if (!sKey.isEmpty()) { QList cuts = QKeySequence::listFromString(sKey); if (!cuts.isEmpty()) { key = cuts.first()[0]; } } const bool autoCheck = cg.readEntry("AutoCheckAccelerators", true); const bool copyWidgetText = cg.readEntry("CopyWidgetText", false); if (!copyWidgetText && key == 0 && !autoCheck) { deleteLater(); return; } new KCheckAccelerators(qApp, key, autoCheck, copyWidgetText); deleteLater(); } }; static void startupFunc() { // Call initiateIfNeeded once we're in the event loop // This is to prevent using KSharedConfig before main() can set the app name QCoreApplication *app = QCoreApplication::instance(); KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app); QMetaObject::invokeMethod(initializer, "initiateIfNeeded", Qt::QueuedConnection); } Q_COREAPP_STARTUP_FUNCTION(startupFunc) KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_, bool copyWidgetText_) : QObject(parent) , key(key_) , block(false) , autoCheck(autoCheck_) , copyWidgetText(copyWidgetText_) , drklash(0) { setObjectName(QStringLiteral("kapp_accel_filter")); KConfigGroup cg(KSharedConfig::openConfig(), "Development"); alwaysShow = cg.readEntry("AlwaysShowCheckAccelerators", false); copyWidgetTextCommand = cg.readEntry("CopyWidgetTextCommand", QString()); parent->installEventFilter(this); connect(&autoCheckTimer, SIGNAL(timeout()), SLOT(autoCheckSlot())); } bool KCheckAccelerators::eventFilter(QObject *obj, QEvent *e) { if (block) { return false; } switch (e->type()) { // just simplify debuggin case QEvent::ShortcutOverride: if (key && (static_cast(e)->key() == key)) { block = true; checkAccelerators(false); block = false; e->accept(); return true; } break; case QEvent::ChildAdded: case QEvent::ChildRemoved: // Only care about widgets; this also avoids starting the timer in other threads if (!static_cast(e)->child()->isWidgetType()) { break; } - // fall-through + Q_FALLTHROUGH(); case QEvent::Resize: case QEvent::LayoutRequest: case QEvent::WindowActivate: case QEvent::WindowDeactivate: if (autoCheck) { autoCheckTimer.setSingleShot(true); autoCheckTimer.start(20); // 20 ms } break; //case QEvent::MouseButtonDblClick: case QEvent::MouseButtonPress: if (copyWidgetText && static_cast(e)->button() == Qt::MidButton) { //kWarning()<<"obj"<(obj)->childAt(static_cast(e)->pos()); if (!w) { w = static_cast(obj); } if (!w) { return false; } //kWarning()<<"MouseButtonDblClick"<(w)) { text = static_cast(w)->text(); } else if (qobject_cast(w)) { text = static_cast(w)->text(); } else if (qobject_cast(w)) { text = static_cast(w)->currentText(); } else if (qobject_cast(w)) { text = static_cast(w)->tabText(static_cast(w)->tabAt(static_cast(e)->pos())); } else if (qobject_cast(w)) { text = static_cast(w)->title(); } else if (qobject_cast(obj)) { QAction *a = static_cast(obj)->actionAt(static_cast(e)->pos()); if (!a) { return false; } text = a->text(); if (text.isEmpty()) { text = a->iconText(); } } if (text.isEmpty()) { return false; } if (static_cast(e)->modifiers() == Qt::ControlModifier) { text.remove(QChar::fromLatin1('&')); } //kWarning()<setText(text); } else { QProcess *script = new QProcess(this); script->start(copyWidgetTextCommand.arg(text).arg(QFile::decodeName(KLocalizedString::applicationDomain()))); connect(script, SIGNAL(finished(int,QProcess::ExitStatus)), script, SLOT(deleteLater())); } e->accept(); return true; //kWarning()<<"MouseButtonDblClick"<(obj)->childAt(static_cast(e)->globalPos()); } return false; case QEvent::Timer: case QEvent::MouseMove: case QEvent::Paint: return false; default: // qDebug() << "KCheckAccelerators::eventFilter " << e->type() << " " << autoCheck; break; } return false; } void KCheckAccelerators::autoCheckSlot() { if (block) { autoCheckTimer.setSingleShot(true); autoCheckTimer.start(20); return; } block = true; checkAccelerators(!alwaysShow); block = false; } void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic) { if (drklash) { return; } drklash = new QDialog(actWin); drklash->setAttribute(Qt::WA_DeleteOnClose); drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg")); drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis")); drklash->resize(500, 460); QVBoxLayout *layout = new QVBoxLayout(drklash); drklash_view = new QTextBrowser(drklash); layout->addWidget(drklash_view); QCheckBox *disableAutoCheck = 0; if (automatic) { disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash); connect(disableAutoCheck, SIGNAL(toggled(bool)), SLOT(slotDisableCheck(bool))); layout->addWidget(disableAutoCheck); } QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash); layout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), drklash, SLOT(close())); if (disableAutoCheck) { disableAutoCheck->setFocus(); } else { drklash_view->setFocus(); } } void KCheckAccelerators::slotDisableCheck(bool on) { autoCheck = !on; if (!on) { autoCheckSlot(); } } void KCheckAccelerators::checkAccelerators(bool automatic) { QWidget *actWin = qApp->activeWindow(); if (!actWin) { return; } KAcceleratorManager::manage(actWin); QString a, c, r; KAcceleratorManager::last_manage(a, c, r); if (automatic) { // for now we only show dialogs on F12 checks return; } if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) { return; } QString s; if (! c.isEmpty()) { s += i18n("

Accelerators changed

"); s += QStringLiteral(""); s += c; s += QStringLiteral("
"); s += i18n("Old Text"); s += QStringLiteral(""); s += i18n("New Text"); s += QStringLiteral("
"); } if (! r.isEmpty()) { s += i18n("

Accelerators removed

"); s += QStringLiteral(""); s += r; s += QStringLiteral("
"); s += i18n("Old Text"); s += QStringLiteral("
"); } if (! a.isEmpty()) { s += i18n("

Accelerators added (just for your info)

"); s += QStringLiteral(""); s += a; s += QStringLiteral("
"); s += i18n("New Text"); s += QStringLiteral("
"); } createDialog(actWin, automatic); drklash_view->setHtml(s); drklash->show(); drklash->raise(); // dlg will be destroyed before returning } #include "kcheckaccelerators.moc" diff --git a/plugins/flake/textshape/dialogs/ParagraphBulletsNumbers.cpp b/plugins/flake/textshape/dialogs/ParagraphBulletsNumbers.cpp index a16d57d513..e25c232ac4 100644 --- a/plugins/flake/textshape/dialogs/ParagraphBulletsNumbers.cpp +++ b/plugins/flake/textshape/dialogs/ParagraphBulletsNumbers.cpp @@ -1,387 +1,387 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009-2010 Thomas Zander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2012 Gopalakrishna Bhat A * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ParagraphBulletsNumbers.h" #include #include #include #include #include #include #include #include #include #include ParagraphBulletsNumbers::ParagraphBulletsNumbers(QWidget *parent) : QWidget(parent) , m_alignmentMode(false) , m_imageCollection(0) , m_data(0) , m_fontSize(0) { widget.setupUi(this); Q_FOREACH (const Lists::ListStyleItem &item, Lists::genericListStyleItems()) { addStyle(item); } addStyle(Lists::ListStyleItem(i18n("Custom Bullet"), KoListStyle::CustomCharItem)); m_blankCharIndex = addStyle(Lists::ListStyleItem(i18n("No Bullet"), KoListStyle::CustomCharItem)); Q_FOREACH (const Lists::ListStyleItem &item, Lists::otherListStyleItems()) { addStyle(item); } widget.alignment->addItem(i18nc("Automatic horizontal alignment", "Auto")); widget.alignment->addItem(i18nc("Text alignment", "Left")); widget.alignment->addItem(i18nc("Text alignment", "Right")); widget.alignment->addItem(i18nc("Text alignment", "Centered")); widget.labelFollowedBy->addItem(i18nc("Tab follows the bullet or number", "Tab Stop")); widget.labelFollowedBy->addItem(i18nc("Space", "Space")); widget.labelFollowedBy->addItem(i18nc("None", "Nothing")); widget.doubleSpinBox->setSingleStep(0.05); widget.doubleSpinBox_2->setSingleStep(0.05); widget.doubleSpinBox_3->setSingleStep(0.05); connect(widget.labelFollowedBy, SIGNAL(currentIndexChanged(int)), this, SLOT(labelFollowedByIndexChanged(int))); connect(widget.listTypes, SIGNAL(currentRowChanged(int)), this, SLOT(styleChanged(int))); connect(widget.customCharacter, SIGNAL(clicked(bool)), this, SLOT(customCharButtonPressed())); connect(widget.letterSynchronization, SIGNAL(toggled(bool)), widget.startValue, SLOT(setLetterSynchronization(bool))); connect(widget.prefix, SIGNAL(textChanged(QString)), this, SLOT(recalcPreview())); connect(widget.suffix, SIGNAL(textChanged(QString)), this, SLOT(recalcPreview())); connect(widget.depth, SIGNAL(valueChanged(int)), this, SLOT(recalcPreview())); connect(widget.levels, SIGNAL(valueChanged(int)), this, SLOT(recalcPreview())); connect(widget.startValue, SIGNAL(valueChanged(int)), this, SLOT(recalcPreview())); connect(widget.insertImage, SIGNAL(clicked()), this, SLOT(selectListImage())); connect(widget.imageHeight, SIGNAL(valueChanged(double)), this, SLOT(recalcPreview())); connect(widget.imageWidth, SIGNAL(valueChanged(double)), this, SLOT(recalcPreview())); connect(widget.restartNumbering, SIGNAL(clicked()), this, SLOT(recalcPreview())); } int ParagraphBulletsNumbers::addStyle(const Lists::ListStyleItem &lsi) { m_mapping.insert(widget.listTypes->count(), lsi.style); widget.listTypes->addItem(lsi.name); return widget.listTypes->count() - 1; } void ParagraphBulletsNumbers::setDisplay(KoParagraphStyle *style, int level) { KoListStyle *listStyle = style->listStyle(); widget.listPropertiesPane->setEnabled(listStyle != 0); widget.customCharacter->setText("-"); if (listStyle == 0) { widget.listTypes->setCurrentRow(0); return; } KoListLevelProperties llp = listStyle->levelProperties(level); m_previousLevel = llp.level(); widget.prefix->setText(llp.listItemPrefix()); widget.suffix->setText(llp.listItemSuffix()); widget.letterSynchronization->setChecked(llp.letterSynchronization()); KoListStyle::Style s = llp.style(); Q_FOREACH (int row, m_mapping.keys()) { if (m_mapping[row] == s) { widget.listTypes->setCurrentRow(row); break; } } int align; if (llp.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) { align = 1; } else if (llp.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) { align = 2; } else if (llp.alignment() == Qt::AlignCenter) { align = 3; } else { align = 0; } widget.alignment->setCurrentIndex(align); widget.depth->setValue(llp.level()); widget.levels->setValue(llp.displayLevel()); widget.startValue->setValue(llp.startValue()); if (s == KoListStyle::CustomCharItem) { widget.customCharacter->setText(llp.bulletCharacter()); } if (s == KoListStyle::ImageItem) { m_data = llp.bulletImage(); widget.imageHeight->setValue(llp.height()); widget.imageWidth->setValue(llp.width()); } else { m_data = 0; widget.imageHeight->setValue(0); widget.imageWidth->setValue(0); } if (llp.alignmentMode() == false) { //for list-level-position-and-space-mode=label-width-and-position disable the following options widget.label_8->setEnabled(false); widget.label_9->setEnabled(false); widget.label_10->setEnabled(false); widget.label_11->setEnabled(false); widget.labelFollowedBy->setEnabled(false); widget.doubleSpinBox->setEnabled(false); widget.doubleSpinBox_2->setEnabled(false); widget.doubleSpinBox_3->setEnabled(false); } else { m_alignmentMode = true; switch (llp.labelFollowedBy()) { case KoListStyle::ListTab: widget.doubleSpinBox->setEnabled(true); widget.labelFollowedBy->setCurrentIndex(0); widget.doubleSpinBox->setValue(KoUnit::toCentimeter(llp.tabStopPosition())); break; case KoListStyle::Space: widget.doubleSpinBox->setEnabled(false); widget.labelFollowedBy->setCurrentIndex(1); break; case KoListStyle::Nothing: widget.doubleSpinBox->setEnabled(false); widget.labelFollowedBy->setCurrentIndex(2); break; default: Q_ASSERT(false); } widget.doubleSpinBox_2->setValue(KoUnit::toCentimeter(llp.margin())); widget.doubleSpinBox_3->setValue(KoUnit::toCentimeter(llp.margin()) + KoUnit::toCentimeter(llp.textIndent())); } // *** features not in GUI; // character style // relative bullet size (percent) // minimum label width recalcPreview(); } void ParagraphBulletsNumbers::save(KoParagraphStyle *savingStyle) { Q_ASSERT(savingStyle); KoUnit unit(KoUnit::Centimeter); const int currentRow = widget.listTypes->currentRow(); KoListStyle::Style style = m_mapping[currentRow]; if (style == KoListStyle::None) { savingStyle->setListStyle(0); return; } if (savingStyle->listStyle() == 0) { KoListStyle *listStyle = new KoListStyle(savingStyle); savingStyle->setListStyle(listStyle); } KoListStyle *listStyle = savingStyle->listStyle(); KoListLevelProperties llp = listStyle->levelProperties(widget.depth->value()); llp.setStyle(style); llp.setLevel(widget.depth->value()); llp.setDisplayLevel(widget.levels->value()); llp.setStartValue(widget.startValue->value()); llp.setListItemPrefix(widget.prefix->text()); llp.setListItemSuffix(widget.suffix->text()); llp.setLetterSynchronization(widget.letterSynchronization->isVisible() && widget.letterSynchronization->isChecked()); if (m_alignmentMode == true) { llp.setAlignmentMode(true); switch (widget.labelFollowedBy->currentIndex()) { case 0: llp.setLabelFollowedBy(KoListStyle::ListTab); llp.setTabStopPosition(unit.fromUserValue(widget.doubleSpinBox->value())); break; case 1: llp.setLabelFollowedBy(KoListStyle::Space); break; case 2: llp.setLabelFollowedBy(KoListStyle::Nothing); break; default: Q_ASSERT(false); } llp.setMargin(unit.fromUserValue(widget.doubleSpinBox_2->value())); llp.setTextIndent(unit.fromUserValue(widget.doubleSpinBox_3->value()) - unit.fromUserValue(widget.doubleSpinBox_2->value())); } if (style == KoListStyle::ImageItem) { if (m_data) { llp.setBulletImage(m_data); } llp.setWidth(widget.imageWidth->value()); llp.setHeight(widget.imageHeight->value()); } else if (style == KoListStyle::CustomCharItem) { llp.setBulletCharacter((currentRow == m_blankCharIndex) ? QChar() : widget.customCharacter->text().remove('&').at(0)); } // it is important to not use 45 for CustomCharItem as it is also char based else if (!KoListStyle::isNumberingStyle(style)) { llp.setRelativeBulletSize(45); //for non-numbering bullets the default relative bullet size is 45%(The spec does not say it; we take it) } Qt::Alignment align; switch (widget.alignment->currentIndex()) { case 0: align = Qt::AlignLeft; break; case 1: align = Qt::AlignLeft | Qt::AlignAbsolute; break; case 2: align = Qt::AlignRight | Qt::AlignAbsolute; break; case 3: align = Qt::AlignCenter; break; default: Q_ASSERT(false); } llp.setAlignment(align); if (llp.level() != m_previousLevel) { listStyle->removeLevelProperties(m_previousLevel); } listStyle->setLevelProperties(llp); } void ParagraphBulletsNumbers::styleChanged(int index) { KoListStyle::Style style = m_mapping[index]; bool showLetterSynchronization = false; if (style == KoListStyle::ImageItem) { widget.startValue->setValue(1); widget.startValue->setEnabled(false); widget.levels->setValue(1); widget.levels->setEnabled(false); widget.insertImage->setEnabled(true); widget.imageHeight->setEnabled(true); widget.imageWidth->setEnabled(true); if (widget.imageHeight->value() == 0 && widget.imageWidth->value() == 0) { widget.imageHeight->setValue(m_fontSize); widget.imageWidth->setValue(m_fontSize); } } else if (!KoListStyle::isNumberingStyle(style)) { widget.startValue->setCounterType(KoListStyle::DecimalItem); widget.startValue->setValue(1); widget.startValue->setEnabled(false); widget.levels->setValue(1); widget.levels->setEnabled(false); widget.insertImage->setEnabled(false); widget.imageHeight->setEnabled(false); widget.imageWidth->setEnabled(false); widget.imageHeight->setValue(0); widget.imageWidth->setValue(0); } else { switch (style) { case KoListStyle::AlphaLowerItem: case KoListStyle::UpperAlphaItem: showLetterSynchronization = true; - // fall through + Q_FALLTHROUGH(); default: widget.levels->setEnabled(true); widget.startValue->setEnabled(true); widget.startValue->setCounterType(style); int value = widget.startValue->value(); widget.startValue->setValue(value + 1); widget.startValue->setValue(value); // surely to trigger a change event. widget.insertImage->setEnabled(false); widget.imageHeight->setEnabled(false); widget.imageWidth->setEnabled(false); } widget.imageHeight->setValue(0); widget.imageWidth->setValue(0); } widget.customCharacter->setEnabled(style == KoListStyle::CustomCharItem && index != m_blankCharIndex); widget.letterSynchronization->setVisible(showLetterSynchronization); widget.listPropertiesPane->setEnabled(style != KoListStyle::None); recalcPreview(); } void ParagraphBulletsNumbers::customCharButtonPressed() { KoDialog *dialog = new KoDialog(this); dialog->setModal(true); dialog->setButtons(KoDialog::Ok | KoDialog::Cancel); dialog->setDefaultButton(KoDialog::Ok); KCharSelect *kcs = new KCharSelect(dialog, 0, KCharSelect::SearchLine | KCharSelect::FontCombo | KCharSelect::BlockCombos | KCharSelect::CharacterTable | KCharSelect::DetailBrowser); dialog->setMainWidget(kcs); if (dialog->exec() == KoDialog::Accepted) { QChar character = kcs->currentChar(); widget.customCharacter->setText(character); // also switch to the custom list style. Q_FOREACH (int row, m_mapping.keys()) { if (m_mapping[row] == KoListStyle::CustomCharItem) { widget.listTypes->setCurrentRow(row); break; } } } delete dialog; recalcPreview(); } void ParagraphBulletsNumbers::recalcPreview() { emit parStyleChanged(); } void ParagraphBulletsNumbers::labelFollowedByIndexChanged(int index) { if (index == 1 || index == 2) { widget.doubleSpinBox->setEnabled(false); } else { widget.doubleSpinBox->setEnabled(true); } emit parStyleChanged(); emit recalcPreview(); } void ParagraphBulletsNumbers::setImageCollection(KoImageCollection *imageCollection) { m_imageCollection = imageCollection; } void ParagraphBulletsNumbers::selectListImage() { if (!m_imageCollection) { return; } KoFileDialog dlg(0, KoFileDialog::OpenFile, "bullets"); dlg.setCaption(i18n("Select a list image")); if (!dlg.filename().isEmpty()) { QFile f(dlg.filename()); if (f.exists()) { f.open(QIODevice::ReadOnly); QByteArray ba = f.readAll(); f.close(); if (m_imageCollection) { m_data = m_imageCollection->createImageData(ba); } emit recalcPreview(); } } } void ParagraphBulletsNumbers::setFontSize(const KoCharacterStyle *style) { m_fontSize = style->fontPointSize(); } diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp index c47e7026e3..6feedd40bc 100644 --- a/plugins/impex/csv/csv_loader.cpp +++ b/plugins/impex/csv/csv_loader.cpp @@ -1,489 +1,489 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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 "csv_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_read_line.h" #include "csv_layer_record.h" CSVLoader::CSVLoader(KisDocument *doc, bool batchMode) : m_image(0) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVLoader::~CSVLoader() { } KisImageBuilder_Result CSVLoader::decode(QIODevice *io, const QString &filename) { QString field; int idx; int frame = 0; QString projName; int width = 0; int height = 0; int frameCount = 1; float framerate = 24.0; float pixelRatio = 1.0; int projNameIdx = -1; int widthIdx = -1; int heightIdx = -1; int frameCountIdx = -1; int framerateIdx = -1; int pixelRatioIdx = -1; QVector layers; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); idx = filename.lastIndexOf(QRegExp("[\\/]")); QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator QString path = filename; if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); //according to the QT docs, the slash is a universal directory separator path.append(".frames/"); KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; dbgFile << "pos:" << io->pos(); CSVReadLine readLine; QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setInfiniteAutoSaveInterval(); importDoc->setFileBatchMode(true); KisView *setView(0); if (!m_batchMode) { // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() // //show the statusbar message even if no view // Q_FOREACH (KisView* view, KisPart::instance()->views()) { // if (view && view->document() == m_doc) { // setView = view; // break; // } // } // if (!setView) { // QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); // if (sb) { // sb->showMessage(i18n("Loading CSV file...")); // } // } else { // emit m_doc->statusBarMessage(i18n("Loading CSV file...")); // } // emit m_doc->sigProgress(0); // connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int step = 0; do { qApp->processEvents(); if (m_stop) { retval = KisImageBuilder_RESULT_CANCEL; break; } if ((idx = readLine.nextLine(io)) <= 0) { if ((idx < 0) ||(step < 5)) retval = KisImageBuilder_RESULT_FAILURE; break; } field = readLine.nextField(); //first field of the line if (field.isNull()) continue; //empty row switch (step) { case 0 : //skip first row step = 1; break; case 1 : //scene header names step = 2; for (idx = 0; !field.isNull(); idx++) { if (field == "Project Name") { projNameIdx = idx; } else if (field == "Width") { widthIdx = idx; } else if (field == "Height") { heightIdx = idx; } else if (field == "Frame Count") { frameCountIdx = idx; } else if (field == "Frame Rate") { framerateIdx = idx; } else if (field == "Pixel Aspect Ratio") { pixelRatioIdx = idx; } field= readLine.nextField(); } break; case 2 : //scene header values step= 3; for (idx= 0; !field.isNull(); idx++) { if (idx == projNameIdx) { projName = field; } else if (idx == widthIdx) { width = field.toInt(); } else if (idx == heightIdx) { height = field.toInt(); } else if (idx == frameCountIdx) { frameCount = field.toInt(); if (frameCount < 1) frameCount= 1; } else if (idx == framerateIdx) { framerate = field.toFloat(); } else if (idx == pixelRatioIdx) { pixelRatio = field.toFloat(); } field= readLine.nextField(); } if ((width < 1) || (height < 1)) { retval = KisImageBuilder_RESULT_FAILURE; break; } retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName); break; case 3 : //create level headers if (field[0] != '#') break; for (; !(field = readLine.nextField()).isNull(); ) { CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.append(layerRecord); } readLine.rewind(); field = readLine.nextField(); step = 4; - /* Falls through */ + Q_FALLTHROUGH(); case 4 : //level header if (field == "#Layers") { //layer name for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->name = field; break; } if (field == "#Density") { //layer opacity for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->density = field.toFloat(); break; } if (field == "#Blending") { //layer blending mode for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->blending = field; break; } if (field == "#Visible") { //layer visibility for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->visible = field.toInt(); break; } if (field == "#Folder") { //CSV 1.1 folder location for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->path = validPath(field, base); break; } if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; step = 5; - /* Falls through */ + Q_FALLTHROUGH(); case 5 : //frames if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) { CSVLayerRecord* layer = layers.at(idx); if (layer->last != field) { if (!m_batchMode) { //emit m_doc->sigProgress((frame * layers.size() + idx) * 100 / // (frameCount * layers.size())); } retval = setLayer(layer, importDoc.data(), path); layer->last = field; layer->frame = frame; } } frame++; break; } } while (retval == KisImageBuilder_RESULT_OK); //finish the layers if (retval == KisImageBuilder_RESULT_OK) { if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); if (frame > frameCount) frameCount = frame; animation->setFullClipRange(KisTimeRange::fromTime(0,frameCount - 1)); animation->setFramerate((int)framerate); } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord* layer = layers.at(idx); //empty layers without any pictures are dropped if ((layer->frame > 0) || !layer->last.isEmpty()) { retval = setLayer(layer, importDoc.data(), path); if (retval != KisImageBuilder_RESULT_OK) break; } } } if (m_image) { //insert the existing layers by the right order for (idx = layers.size() - 1; idx >= 0; idx--) { CSVLayerRecord* layer = layers.at(idx); if (layer->layer) { m_image->addNode(layer->layer, m_image->root()); } } m_image->unlock(); } qDeleteAll(layers); io->close(); if (!m_batchMode) { // disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); // emit m_doc->sigProgress(100); if (!setView) { QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); if (sb) { sb->clearMessage(); } } else { emit m_doc->clearStatusBarMessage(); } } QApplication::restoreOverrideCursor(); return retval; } QString CSVLoader::convertBlending(const QString &blending) { if (blending == "Color") return COMPOSITE_OVER; if (blending == "Behind") return COMPOSITE_BEHIND; if (blending == "Erase") return COMPOSITE_ERASE; // "Shade" if (blending == "Light") return COMPOSITE_LINEAR_LIGHT; if (blending == "Colorize") return COMPOSITE_COLORIZE; if (blending == "Hue") return COMPOSITE_HUE; if (blending == "Add") return COMPOSITE_ADD; if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT; if (blending == "Multiply") return COMPOSITE_MULT; if (blending == "Screen") return COMPOSITE_SCREEN; // "Replace" // "Substitute" if (blending == "Difference") return COMPOSITE_DIFF; if (blending == "Divide") return COMPOSITE_DIVIDE; if (blending == "Overlay") return COMPOSITE_OVERLAY; if (blending == "Light2") return COMPOSITE_DODGE; if (blending == "Shade2") return COMPOSITE_BURN; if (blending == "HardLight") return COMPOSITE_HARD_LIGHT; if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP; if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT; if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE; if (blending == "Sub2") return COMPOSITE_SUBTRACT; if (blending == "Darken") return COMPOSITE_DARKEN; if (blending == "Lighten") return COMPOSITE_LIGHTEN; if (blending == "Saturation") return COMPOSITE_SATURATION; return COMPOSITE_OVER; } QString CSVLoader::validPath(const QString &path,const QString &base) { //replace Windows directory separators with the universal / QString tryPath= QString(path).replace(QString("\\"), QString("/")); int i = tryPath.lastIndexOf("/"); if (i == (tryPath.size() - 1)) tryPath= tryPath.left(i); //remove the ending separator if exists if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); QString scan(tryPath); i = -1; while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) { //avoid testing if the next level will be the default xxxx.layers folder if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue; tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a / if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); } return QString(); //NULL string } KisImageBuilder_Result CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path) { bool result = true; if (layer->channel == 0) { //create a new document layer float opacity = layer->density; if (opacity > 1.0) opacity = 1.0; else if (opacity < 0.0) opacity = 0.0; const KoColorSpace* cs = m_image->colorSpace(); const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name; KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName, (quint8)(opacity * OPACITY_OPAQUE_U8), cs); paintLayer->setCompositeOpId(convertBlending(layer->blending)); paintLayer->setVisible(layer->visible); paintLayer->enableAnimation(); layer->layer = paintLayer; layer->channel = qobject_cast (paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (!layer->last.isEmpty()) { //png image QString filename = layer->path.isNull() ? path : layer->path; filename.append(layer->last); result = importDoc->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); if (result) layer->channel->importFrame(layer->frame, importDoc->image()->projection(), 0); } else { //blank layer->channel->addKeyframe(layer->frame); } return (result) ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageBuilder_Result CSVLoader::createNewImage(int width, int height, float ratio, const QString &name) { //the CSV is RGBA 8bits, sRGB if (!m_image) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0); if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name); if (!m_image) return KisImageBuilder_RESULT_FAILURE; m_image->setResolution(ratio, 1.0); m_image->lock(); } return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result CSVLoader::buildAnimation(QIODevice *io, const QString &filename) { return decode(io, filename); } KisImageSP CSVLoader::image() { return m_image; } void CSVLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/tga/tga.h b/plugins/impex/tga/tga.h index 0c239071d6..ae52f0d0bb 100644 --- a/plugins/impex/tga/tga.h +++ b/plugins/impex/tga/tga.h @@ -1,114 +1,114 @@ /* This file is part of the KDE project Copyright (C) 2003 Dominik Seichter Copyright (C) 2004 Ignacio Castaño This program is free software; you can redistribute it and/or modify it under the terms of the Lesser 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 code supports: * reading: * uncompressed and run length encoded indexed, grey and color tga files. * image types 1, 2, 3, 9, 10 and 11. * only RGB color maps with no more than 256 colors. * pixel formats 8, 15, 24 and 32. * writing: * uncompressed true color tga files */ #ifndef TGA_H #define TGA_H #include #include #include // Header format of saved files. const uchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; enum TGAType { TGA_TYPE_INDEXED = 1, TGA_TYPE_RGB = 2, TGA_TYPE_GREY = 3, TGA_TYPE_RLE_INDEXED = 9, TGA_TYPE_RLE_RGB = 10, TGA_TYPE_RLE_GREY = 11 }; #define TGA_INTERLEAVE_MASK 0xc0 #define TGA_INTERLEAVE_NONE 0x00 #define TGA_INTERLEAVE_2WAY 0x40 #define TGA_INTERLEAVE_4WAY 0x80 #define TGA_ORIGIN_MASK 0x30 #define TGA_ORIGIN_LEFT 0x00 #define TGA_ORIGIN_RIGHT 0x10 #define TGA_ORIGIN_LOWER 0x00 #define TGA_ORIGIN_UPPER 0x20 /** Tga Header. */ struct TgaHeader { uchar id_length; uchar colormap_type; uchar image_type; ushort colormap_index; ushort colormap_length; uchar colormap_size; ushort x_origin; ushort y_origin; ushort width; ushort height; uchar pixel_size; uchar flags; enum { SIZE = 18 }; // const static int SIZE = 18; }; struct Color555 { ushort b : 5; ushort g : 5; ushort r : 5; }; struct TgaHeaderInfo { bool rle; bool pal; bool rgb; bool grey; TgaHeaderInfo(const TgaHeader & tga) : rle(false), pal(false), rgb(false), grey(false) { switch (tga.image_type) { case TGA_TYPE_RLE_INDEXED: rle = true; - /* Falls through */ + Q_FALLTHROUGH(); case TGA_TYPE_INDEXED: pal = true; break; case TGA_TYPE_RLE_RGB: rle = true; - /* Falls through */ + Q_FALLTHROUGH(); case TGA_TYPE_RGB: rgb = true; break; case TGA_TYPE_RLE_GREY: rle = true; - /* Falls through */ + Q_FALLTHROUGH(); case TGA_TYPE_GREY: grey = true; break; default: // Error, unknown image type. break; } } }; #endif // TGA_H diff --git a/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffect.cpp b/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffect.cpp index f573f50383..2f6a7f069a 100644 --- a/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffect.cpp +++ b/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffect.cpp @@ -1,363 +1,365 @@ /* This file is part of the KDE project * Copyright (c) 2010 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ConvolveMatrixEffect.h" #include "KoFilterEffectRenderContext.h" #include "KoFilterEffectLoadingContext.h" #include "KoViewConverter.h" #include "KoXmlWriter.h" #include "KoXmlReader.h" #include #include #include #include #include #include ConvolveMatrixEffect::ConvolveMatrixEffect() : KoFilterEffect(ConvolveMatrixEffectId, i18n("Convolve Matrix")) { setDefaults(); } void ConvolveMatrixEffect::setDefaults() { m_order = QPoint(3, 3); m_divisor = 0.0; m_bias = 0.0; m_target = QPoint(-1, -1); m_edgeMode = Duplicate; m_preserveAlpha = false; m_kernel.resize(m_order.x()*m_order.y()); for (int i = 0; i < m_kernel.size(); ++i) { m_kernel[i] = 0.0; } m_kernelUnitLength = QPointF(1, 1); } QPoint ConvolveMatrixEffect::order() const { return m_order; } void ConvolveMatrixEffect::setOrder(const QPoint &order) { m_order = QPoint(qMax(1, order.x()), qMax(1, order.y())); } QVector ConvolveMatrixEffect::kernel() const { return m_kernel; } void ConvolveMatrixEffect::setKernel(const QVector &kernel) { if (m_order.x()*m_order.y() != kernel.count()) { return; } m_kernel = kernel; } qreal ConvolveMatrixEffect::divisor() const { return m_divisor; } void ConvolveMatrixEffect::setDivisor(qreal divisor) { m_divisor = divisor; } qreal ConvolveMatrixEffect::bias() const { return m_bias; } void ConvolveMatrixEffect::setBias(qreal bias) { m_bias = bias; } QPoint ConvolveMatrixEffect::target() const { return m_target; } void ConvolveMatrixEffect::setTarget(const QPoint &target) { m_target = target; } ConvolveMatrixEffect::EdgeMode ConvolveMatrixEffect::edgeMode() const { return m_edgeMode; } void ConvolveMatrixEffect::setEdgeMode(EdgeMode edgeMode) { m_edgeMode = edgeMode; } bool ConvolveMatrixEffect::isPreserveAlphaEnabled() const { return m_preserveAlpha; } void ConvolveMatrixEffect::enablePreserveAlpha(bool on) { m_preserveAlpha = on; } QImage ConvolveMatrixEffect::processImage(const QImage &image, const KoFilterEffectRenderContext &context) const { QImage result = image; const int rx = m_order.x(); const int ry = m_order.y(); if (rx == 0 && ry == 0) { return result; } const int tx = m_target.x() >= 0 && m_target.x() <= rx ? m_target.x() : rx >> 1; const int ty = m_target.y() >= 0 && m_target.y() <= ry ? m_target.y() : ry >> 1; const int w = result.width(); const int h = result.height(); // setup mask const int maskSize = rx * ry; QVector offset(maskSize); int index = 0; for (int y = 0; y < ry; ++y) { for (int x = 0; x < rx; ++x) { offset[index] = QPoint(x - tx, y - ty); index++; } } qreal divisor = m_divisor; // if no divisor given, it is the sum of all kernel values // if sum of kernel values is zero, divisor is set to 1 if (divisor == 0.0) { Q_FOREACH (qreal k, m_kernel) { divisor += k; } if (divisor == 0.0) { divisor = 1.0; } } int dstPixel, srcPixel; qreal sumA, sumR, sumG, sumB; const QRgb *src = (const QRgb *)image.constBits(); QRgb *dst = (QRgb *)result.bits(); const QRect roi = context.filterRegion().toRect(); const int minX = roi.left(); const int maxX = roi.right(); const int minY = roi.top(); const int maxY = roi.bottom(); int srcRow, srcCol; for (int row = minY; row <= maxY; ++row) { for (int col = minX; col <= maxX; ++col) { dstPixel = row * w + col; sumA = sumR = sumG = sumB = 0; for (int i = 0; i < maskSize; ++i) { srcRow = row + offset[i].y(); srcCol = col + offset[i].x(); // handle top and bottom edge if (srcRow < 0 || srcRow >= h) { switch (m_edgeMode) { case Duplicate: srcRow = srcRow >= h ? h - 1 : 0; break; case Wrap: srcRow = (srcRow + h) % h; break; case None: // zero for all color channels continue; break; } } // handle left and right edge if (srcCol < 0 || srcCol >= w) { switch (m_edgeMode) { case Duplicate: srcCol = srcCol >= w ? w - 1 : 0; break; case Wrap: srcCol = (srcCol + w) % w; break; case None: // zero for all color channels continue; break; } } srcPixel = srcRow * w + srcCol; const QRgb &s = src[srcPixel]; const qreal &k = m_kernel[i]; if (!m_preserveAlpha) { sumA += qAlpha(s) * k; } sumR += qRed(s) * k; sumG += qGreen(s) * k; sumB += qBlue(s) * k; } if (m_preserveAlpha) { dst[dstPixel] = qRgba(qBound(0, static_cast(sumR / divisor + m_bias), 255), qBound(0, static_cast(sumG / divisor + m_bias), 255), qBound(0, static_cast(sumB / divisor + m_bias), 255), qAlpha(dst[dstPixel])); } else { dst[dstPixel] = qRgba(qBound(0, static_cast(sumR / divisor + m_bias), 255), qBound(0, static_cast(sumG / divisor + m_bias), 255), qBound(0, static_cast(sumB / divisor + m_bias), 255), qBound(0, static_cast(sumA / divisor + m_bias), 255)); } } } return result; } bool ConvolveMatrixEffect::load(const KoXmlElement &element, const KoFilterEffectLoadingContext &/*context*/) { if (element.tagName() != id()) { return false; } setDefaults(); if (element.hasAttribute("order")) { QString orderStr = element.attribute("order"); QStringList params = orderStr.replace(',', ' ').simplified().split(' '); switch (params.count()) { case 1: m_order.rx() = qMax(1, params[0].toInt()); m_order.ry() = m_order.x(); break; case 2: m_order.rx() = qMax(1, params[0].toInt()); m_order.ry() = qMax(1, params[1].toInt()); break; } } if (element.hasAttribute("kernelMatrix")) { QString matrixStr = element.attribute("kernelMatrix"); // values are separated by whitespace and/or comma QStringList values = matrixStr.replace(',', ' ').simplified().split(' '); if (values.count() == m_order.x()*m_order.y()) { m_kernel.resize(values.count()); for (int i = 0; i < values.count(); ++i) { m_kernel[i] = values[i].toDouble(); } } else { m_kernel.resize(m_order.x()*m_order.y()); for (int i = 0; i < m_kernel.size(); ++i) { m_kernel[i] = 0.0; } } } if (element.hasAttribute("divisor")) { m_divisor = element.attribute("divisor").toDouble(); } if (element.hasAttribute("bias")) { m_bias = element.attribute("bias").toDouble(); } if (element.hasAttribute("targetX")) { m_target.rx() = qBound(0, element.attribute("targetX").toInt(), m_order.x()); } if (element.hasAttribute("targetY")) { m_target.ry() = qBound(0, element.attribute("targetY").toInt(), m_order.y()); } if (element.hasAttribute("edgeMode")) { QString mode = element.attribute("edgeMode"); if (mode == "wrap") { m_edgeMode = Wrap; } else if (mode == "none") { m_edgeMode = None; } else { m_edgeMode = Duplicate; } } if (element.hasAttribute("kernelUnitLength")) { QString kernelUnitLengthStr = element.attribute("kernelUnitLength"); QStringList params = kernelUnitLengthStr.replace(',', ' ').simplified().split(' '); switch (params.count()) { case 1: m_kernelUnitLength.rx() = params[0].toDouble(); m_kernelUnitLength.ry() = m_kernelUnitLength.x(); break; case 2: m_kernelUnitLength.rx() = params[0].toDouble(); m_kernelUnitLength.ry() = params[1].toDouble(); break; } } if (element.hasAttribute("preserveAlpha")) { m_preserveAlpha = (element.attribute("preserveAlpha") == "true"); } return true; } void ConvolveMatrixEffect::save(KoXmlWriter &writer) { writer.startElement(ConvolveMatrixEffectId); saveCommonAttributes(writer); if (m_order.x() == m_order.y()) { writer.addAttribute("order", QString("%1").arg(m_order.x())); } else { writer.addAttribute("order", QString("%1 %2").arg(m_order.x()).arg(m_order.y())); } QString kernel; for (int i = 0; i < m_kernel.size(); ++i) { kernel += QString("%1 ").arg(m_kernel[i]); } writer.addAttribute("kernelMatrix", kernel); writer.addAttribute("divisor", QString("%1").arg(m_divisor)); if (m_bias != 0.0) { writer.addAttribute("bias", QString("%1").arg(m_bias)); } writer.addAttribute("targetX", QString("%1").arg(m_target.x())); writer.addAttribute("targetY", QString("%1").arg(m_target.y())); switch (m_edgeMode) { case Wrap: writer.addAttribute("edgeMode", "wrap"); break; case None: writer.addAttribute("edgeMode", "none"); break; case Duplicate: // fall through as it is the default - break; + Q_FALLTHROUGH(); + default: + ; } writer.addAttribute("kernelUnitLength", QString("%1 %2").arg(m_kernelUnitLength.x()).arg(m_kernelUnitLength.y())); if (m_preserveAlpha) { writer.addAttribute("preserveAlpha", "true"); } writer.endElement(); } diff --git a/plugins/tools/tool_crop/kis_constrained_rect.cpp b/plugins/tools/tool_crop/kis_constrained_rect.cpp index e83b825944..9358fbca6a 100644 --- a/plugins/tools/tool_crop/kis_constrained_rect.cpp +++ b/plugins/tools/tool_crop/kis_constrained_rect.cpp @@ -1,401 +1,401 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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 "kis_constrained_rect.h" #include #include "kis_debug.h" #include "kis_algebra_2d.h" KisConstrainedRect::KisConstrainedRect() : m_centered(false), m_canGrow(true), m_ratio(1.0), m_widthLocked(false), m_heightLocked(false), m_ratioLocked(false) { } KisConstrainedRect::~KisConstrainedRect() { } void KisConstrainedRect::setRectInitial(const QRect &rect) { m_rect = rect; if (!ratioLocked()) { storeRatioSafe(m_rect.size()); } emit sigValuesChanged(); } void KisConstrainedRect::setCropRect(const QRect &cropRect) { m_cropRect = cropRect; } bool KisConstrainedRect::centered() const { return m_centered; } void KisConstrainedRect::setCentered(bool value) { m_centered = value; } bool KisConstrainedRect::canGrow() const { return m_canGrow; } void KisConstrainedRect::setCanGrow(bool value) { m_canGrow = value; } QRect KisConstrainedRect::rect() const { return m_rect.normalized(); } qreal KisConstrainedRect::ratio() const { return qAbs(m_ratio); } void KisConstrainedRect::moveHandle(HandleType handle, const QPoint &offset, const QRect &oldRect) { const QSize oldSize = oldRect.size(); QSize newSize = oldSize; QPoint newOffset = oldRect.topLeft(); int xSizeCoeff = 1; int ySizeCoeff = 1; qreal xOffsetFromSizeChange = 1.0; qreal yOffsetFromSizeChange = 1.0; int baseSizeCoeff = 1; bool useMoveOnly = false; switch (handle) { case UpperLeft: xSizeCoeff = -1; ySizeCoeff = -1; xOffsetFromSizeChange = -1.0; yOffsetFromSizeChange = -1.0; break; case UpperRight: xSizeCoeff = 1; ySizeCoeff = -1; xOffsetFromSizeChange = 0.0; yOffsetFromSizeChange = -1.0; break; case Creation: baseSizeCoeff = 0; - /* Falls through */ + Q_FALLTHROUGH(); case LowerRight: xSizeCoeff = 1; ySizeCoeff = 1; xOffsetFromSizeChange = 0.0; yOffsetFromSizeChange = 0.0; break; case LowerLeft: xSizeCoeff = -1; ySizeCoeff = 1; xOffsetFromSizeChange = -1.0; yOffsetFromSizeChange = 0.0; break; case Upper: xSizeCoeff = 0; ySizeCoeff = -1; xOffsetFromSizeChange = -0.5; yOffsetFromSizeChange = -1.0; break; case Right: xSizeCoeff = 1; ySizeCoeff = 0; xOffsetFromSizeChange = 0.0; yOffsetFromSizeChange = -0.5; break; case Lower: xSizeCoeff = 0; ySizeCoeff = 1; xOffsetFromSizeChange = -0.5; yOffsetFromSizeChange = 0.0; break; case Left: xSizeCoeff = -1; ySizeCoeff = 0; xOffsetFromSizeChange = -1.0; yOffsetFromSizeChange = -0.5; break; case Inside: useMoveOnly = true; break; case None: // should never happen break; } if (!useMoveOnly) { const int centeringSizeCoeff = m_centered ? 2 : 1; if (m_centered) { xOffsetFromSizeChange = -0.5; yOffsetFromSizeChange = -0.5; } QSize sizeDiff(offset.x() * xSizeCoeff * centeringSizeCoeff, offset.y() * ySizeCoeff * centeringSizeCoeff); QSize tempSize = baseSizeCoeff * oldSize + sizeDiff; bool widthPreferrable = qAbs(tempSize.width()) > qAbs(tempSize.height() * m_ratio); if (ratioLocked() && ((widthPreferrable && xSizeCoeff != 0) || ySizeCoeff == 0)) { newSize.setWidth(tempSize.width()); newSize.setHeight(heightFromWidthUnsignedRatio(newSize.width(), m_ratio, tempSize.height())); } else if (ratioLocked() && ((!widthPreferrable && ySizeCoeff != 0) || xSizeCoeff == 0)) { newSize.setHeight(tempSize.height()); newSize.setWidth(widthFromHeightUnsignedRatio(newSize.height(), m_ratio, tempSize.width())); } else if (widthLocked() && heightLocked()) { newSize.setWidth(KisAlgebra2D::copysign(newSize.width(), tempSize.width())); newSize.setHeight(KisAlgebra2D::copysign(newSize.height(), tempSize.height())); } else if (widthLocked()) { newSize.setWidth(KisAlgebra2D::copysign(newSize.width(), tempSize.width())); newSize.setHeight(tempSize.height()); storeRatioSafe(newSize); } else if (heightLocked()) { newSize.setHeight(KisAlgebra2D::copysign(newSize.height(), tempSize.height())); newSize.setWidth(tempSize.width()); storeRatioSafe(newSize); } else { newSize = baseSizeCoeff * oldSize + sizeDiff; storeRatioSafe(newSize); } QSize realSizeDiff = newSize - baseSizeCoeff * oldSize; QPoint offsetDiff(realSizeDiff.width() * xOffsetFromSizeChange, realSizeDiff.height() * yOffsetFromSizeChange); newOffset = oldRect.topLeft() + offsetDiff; } else { newOffset = oldRect.topLeft() + offset; } m_rect = QRect(newOffset, newSize); if (!m_canGrow) { m_rect &= m_cropRect; } emit sigValuesChanged(); } QPointF KisConstrainedRect::handleSnapPoint(HandleType handle, const QPointF &cursorPos) { QPointF snapPoint = cursorPos; switch (handle) { case UpperLeft: snapPoint = m_rect.topLeft(); break; case UpperRight: snapPoint = m_rect.topRight() + QPointF(1, 0); break; case Creation: break; case LowerRight: snapPoint = m_rect.bottomRight() + QPointF(1, 1); break; case LowerLeft: snapPoint = m_rect.bottomLeft() + QPointF(0, 1); break; case Upper: snapPoint.ry() = m_rect.y(); break; case Right: snapPoint.rx() = m_rect.right() + 1; break; case Lower: snapPoint.ry() = m_rect.bottom() + 1; break; case Left: snapPoint.rx() = m_rect.x(); break; case Inside: break; case None: // should never happen break; } return snapPoint; } void KisConstrainedRect::normalize() { setRectInitial(m_rect.normalized()); } void KisConstrainedRect::setOffset(const QPoint &offset) { QRect newRect = m_rect; newRect.moveTo(offset); if (!m_canGrow) { newRect &= m_cropRect; } if (!newRect.isEmpty()) { m_rect = newRect; } emit sigValuesChanged(); } void KisConstrainedRect::setRatio(qreal value) { KIS_ASSERT_RECOVER_RETURN(value >= 0); const qreal eps = 1e-7; const qreal invEps = 1.0 / eps; if (value < eps || value > invEps) { emit sigValuesChanged(); return; } const QSize oldSize = m_rect.size(); QSize newSize = oldSize; if (widthLocked() && heightLocked()) { setHeightLocked(false); } m_ratio = value; if (!widthLocked() && !heightLocked()) { int area = oldSize.width() * oldSize.height(); newSize.setWidth(qRound(std::sqrt(area * m_ratio))); newSize.setHeight(qRound(newSize.width() / m_ratio)); } else if (widthLocked()) { newSize.setHeight(newSize.width() / m_ratio); } else if (heightLocked()) { newSize.setWidth(newSize.height() * m_ratio); } assignNewSize(newSize); } void KisConstrainedRect::setWidth(int value) { KIS_ASSERT_RECOVER_RETURN(value >= 0); const QSize oldSize = m_rect.size(); QSize newSize = oldSize; if (ratioLocked()) { newSize.setWidth(value); newSize.setHeight(newSize.width() / m_ratio); } else { newSize.setWidth(value); storeRatioSafe(newSize); } assignNewSize(newSize); } void KisConstrainedRect::setHeight(int value) { KIS_ASSERT_RECOVER_RETURN(value >= 0); const QSize oldSize = m_rect.size(); QSize newSize = oldSize; if (ratioLocked()) { newSize.setHeight(value); newSize.setWidth(newSize.height() * m_ratio); } else { newSize.setHeight(value); storeRatioSafe(newSize); } assignNewSize(newSize); } void KisConstrainedRect::assignNewSize(const QSize &newSize) { if (!m_centered) { m_rect.setSize(newSize); } else { QSize sizeDiff = newSize - m_rect.size(); m_rect.translate(-qRound(sizeDiff.width() / 2.0), -qRound(sizeDiff.height() / 2.0)); m_rect.setSize(newSize); } if (!m_canGrow) { m_rect &= m_cropRect; } emit sigValuesChanged(); } void KisConstrainedRect::storeRatioSafe(const QSize &newSize) { m_ratio = qAbs(qreal(newSize.width()) / newSize.height()); } int KisConstrainedRect::widthFromHeightUnsignedRatio(int height, qreal ratio, int oldWidth) const { int newWidth = qRound(height * ratio); return KisAlgebra2D::copysign(newWidth, oldWidth); } int KisConstrainedRect::heightFromWidthUnsignedRatio(int width, qreal ratio, int oldHeight) const { int newHeight = qRound(width / ratio); return KisAlgebra2D::copysign(newHeight, oldHeight); } bool KisConstrainedRect::widthLocked() const { return m_widthLocked; } bool KisConstrainedRect::heightLocked() const { return m_heightLocked; } bool KisConstrainedRect::ratioLocked() const { return m_ratioLocked; } void KisConstrainedRect::setWidthLocked(bool value) { m_widthLocked = value; m_ratioLocked &= !(m_widthLocked || m_heightLocked); emit sigLockValuesChanged(); } void KisConstrainedRect::setHeightLocked(bool value) { m_heightLocked = value; m_ratioLocked &= !(m_widthLocked || m_heightLocked); emit sigLockValuesChanged(); } void KisConstrainedRect::setRatioLocked(bool value) { m_ratioLocked = value; m_widthLocked &= !m_ratioLocked; m_heightLocked &= !m_ratioLocked; emit sigLockValuesChanged(); }