diff --git a/libs/brush/kis_pipebrush_parasite.cpp b/libs/brush/kis_pipebrush_parasite.cpp index 24542d4999..fe18e189f2 100644 --- a/libs/brush/kis_pipebrush_parasite.cpp +++ b/libs/brush/kis_pipebrush_parasite.cpp @@ -1,179 +1,179 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Bart Coppens * * 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_pipebrush_parasite.h" KisPipeBrushParasite::KisPipeBrushParasite(const QString& source) { init(); needsMovement = false; QRegExp basicSplitter(" "); QRegExp parasiteSplitter(":"); QStringList parasites = source.split(basicSplitter, QString::SkipEmptyParts); for (int i = 0; i < parasites.count(); i++) { QStringList split = parasites.at(i).split(parasiteSplitter, QString::SkipEmptyParts); if (split.count() != 2) { warnImage << "Wrong count for this parasite key/value:" << parasites.at(i); continue; } QString index = split.at(0); if (index == "dim") { dim = (split.at(1)).toInt(); if (dim < 1 || dim > MaxDim) { dim = 1; } } else if (index.startsWith(QString("sel"))) { int selIndex = index.mid(strlen("sel")).toInt(); if (selIndex >= 0 && selIndex < dim) { selectionMode = split.at(1); if (selectionMode == "incremental") { selection[selIndex] = KisParasite::Incremental; } else if (selectionMode == "angular") { selection[selIndex] = KisParasite::Angular; needsMovement = true; } else if (selectionMode == "random") { selection[selIndex] = KisParasite::Random; } else if (selectionMode == "pressure") { selection[selIndex] = KisParasite::Pressure; } else if (selectionMode == "xtilt") { selection[selIndex] = KisParasite::TiltX; } else if (selectionMode == "ytilt") { selection[selIndex] = KisParasite::TiltY; } else { selection[selIndex] = KisParasite::Constant; } } else { warnImage << "Sel: wrong index: " << selIndex << "(dim = " << dim << ")"; } } else if (index.startsWith(QString("rank"))) { int rankIndex = index.mid(strlen("rank")).toInt(); if (rankIndex < 0 || rankIndex > dim) { warnImage << "Rankindex out of range: " << rankIndex; continue; } rank[rankIndex] = (split.at(1)).toInt(); } else if (index == "ncells") { ncells = (split.at(1)).toInt(); if (ncells < 1) { warnImage << "ncells out of range: " << ncells; ncells = 1; } } } for (int i = 0; i < dim; i++) { index[i] = 0; } setBrushesCount(); } void KisPipeBrushParasite::init() { for (int i = 0; i < MaxDim; i++) { rank[i] = index[i] = brushesCount[i] = 0; selection[i] = KisParasite::Constant; } } void KisPipeBrushParasite::sanitize() { for (int i = 0; i < dim; i++) { // In the 2 listed cases, we'd divide by 0! if (rank[i] == 0 && (selection[i] == KisParasite::Incremental || selection[i] == KisParasite::Angular)) { warnImage << "PIPE brush has a wrong rank for its selection mode!"; selection[i] = KisParasite::Constant; } } } void KisPipeBrushParasite::setBrushesCount() { // I assume ncells is correct. If it isn't, complain to the parasite header. if (rank[0] != 0) { brushesCount[0] = ncells / rank[0]; } else { brushesCount[0] = ncells; } for (int i = 1; i < dim; i++) { if (rank[i] == 0) { brushesCount[i] = brushesCount[i - 1]; } else { brushesCount[i] = brushesCount[i - 1] / rank[i]; } } } bool KisPipeBrushParasite::saveToDevice(QIODevice* dev) const { // write out something like // ncells: dim: rank0: sel0: <...> QTextStream stream(dev); stream.setCodec("UTF-8"); // XXX: FIXME things like step, placement and so are not added (nor loaded, as a matter of fact)" stream << ncells << " ncells:" << ncells << " dim:" << dim; for (int i = 0; i < dim; i++) { stream << " rank" << i << ":" << rank[i] << " sel" << i << ":"; switch (selection[i]) { case KisParasite::Constant: stream << "constant"; break; case KisParasite::Incremental: stream << "incremental"; break; case KisParasite::Angular: stream << "angular"; break; case KisParasite::Velocity: stream << "velocity"; break; case KisParasite::Random: stream << "random"; break; case KisParasite::Pressure: stream << "pressure"; break; case KisParasite::TiltX: stream << "xtilt"; break; case KisParasite::TiltY: stream << "ytilt"; break; } } return true; } bool loadFromDevice(QIODevice */*dev*/) { // XXX: implement... - return true;; + return true; } diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp index 6b9e51c14f..5407aca043 100644 --- a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp +++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp @@ -1,255 +1,255 @@ /* This file is part of the KDE project Copyright (c) 2017 L. E. Segovia 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 library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible(false)) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } QImage KoSvgSymbol::icon() { KoShapeGroup *group = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(group, QImage()); QRectF rc = group->boundingRect().normalized(); QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied); QPainter gc(&image); image.fill(Qt::gray); KoViewConverter vc; KoShapePaintingContext ctx; // qDebug() << "Going to render. Original bounding rect:" << group->boundingRect() // << "Normalized: " << rc // << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height(); gc.translate(-rc.x(), -rc.y()); paintGroup(group, gc, vc, ctx); gc.end(); image = image.scaled(128, 128, Qt::KeepAspectRatio); return image; } struct KoSvgSymbolCollectionResource::Private { QVector symbols; QString title; QString description; }; KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename) : KoResource(filename) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource() : KoResource(QString()) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->symbols = rhs.d->symbols; setValid(true); } KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource() { } bool KoSvgSymbolCollectionResource::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn; bool ok = doc.setContent(dev->readAll(), false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << filename() << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); return false; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values QSizeF fragmentSize; // We're not interested in the shapes themselves qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize)); d->symbols = parser.takeSymbols(); // qDebug() << "Loaded" << filename() << "\n\t" // << "Title" << parser.documentTitle() << "\n\t" // << "Description" << parser.documentDescription() // << "\n\tgot" << d->symbols.size() << "symbols" // << d->symbols[0]->shape->outlineRect() // << d->symbols[0]->shape->size(); d->title = parser.documentTitle(); setName(d->title); d->description = parser.documentDescription(); if (d->symbols.size() < 1) { setValid(false); return false; } - setValid(true);; + setValid(true); setImage(d->symbols[0]->icon()); return true; } bool KoSvgSymbolCollectionResource::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const { bool res = false; // XXX if (res) { KoResource::saveToDevice(dev); } return res; } QString KoSvgSymbolCollectionResource::defaultFileExtension() const { return QString(".svg"); } QString KoSvgSymbolCollectionResource::title() const { return d->title; } QString KoSvgSymbolCollectionResource::description() const { return d->description; } QString KoSvgSymbolCollectionResource::creator() const { return ""; } QString KoSvgSymbolCollectionResource::rights() const { return ""; } QString KoSvgSymbolCollectionResource::language() const { return ""; } QStringList KoSvgSymbolCollectionResource::subjects() const { return QStringList(); } QString KoSvgSymbolCollectionResource::license() const { return ""; } QStringList KoSvgSymbolCollectionResource::permits() const { return QStringList(); } QVector KoSvgSymbolCollectionResource::symbols() const { return d->symbols; } diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index 5522b6f3d4..c12dc9d33f 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2170 +1,2170 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 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_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QMutex m_wrappedStrategyMutex; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); bool assignProfile(const KoColorProfile * profile); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); Q_ASSERT(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); QRegion regionForLodSyncing() const; void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: qint64 estimateDataSize(Data *data) const { const QRect &rc = data->dataManager()->extent(); return rc.width() * rc.height() * data->colorSpace()->pixelSize(); } public: void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { imageData = 0; temporaryData = 0; lodData = 0; if (m_data) { imageData += estimateDataSize(m_data.data()); } if (m_lodData) { lodData += estimateDataSize(m_lodData.data()); } if (m_externalFrameData) { temporaryData += estimateDataSize(m_externalFrameData.data()); } Q_FOREACH (DataSP value, m_frames.values()) { imageData += estimateDataSize(value.data()); } } private: QRegion syncWholeDevice(Data *srcData); inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { currentData()->prepareClone(srcData); q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } const QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { QMutexLocker locker(&m_wrappedStrategyMutex); if (!wrappedStrategy) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } else if (wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy->setWrapRect(wrapRect); } } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; QRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { Data *srcData = currentNonLodData(); Data *lodData = new Data(srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } //QRegion dirtyRegion = syncWholeDevice(srcData); lodData->cache()->invalidate(); return lodStruct; } void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); Data *lodData = dst->lodData.data(); Data *srcData = currentNonLodData(); const int lod = lodData->levelOfDetail(); const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); const int pixelSize = srcData->dataManager()->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcData->dataManager().data(), srcData->x(), srcData->y()), srcRect); InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), lodData->dataManager().data(), lodData->x(), lodData->y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0 && srcIntIt.nextPixel()) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); int colsRemaining = dstRect.width(); while (colsRemaining > 0 && dstIntIt.nextPixel()) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; colsRemaining--; } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { class DeviceChangeColorSpaceCommand : public KUndo2Command { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device) : m_firstRun(true), m_device(device) { } void emitNotifications() { m_device->emitColorSpaceChanged(); m_device->setDirty(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } private: bool m_firstRun; KisPaintDeviceSP m_device; }; KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q); - QList dataObjects = allDataObjects();; + QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } if (!parentCommand->childCount()) { delete parentCommand; parentCommand = 0; } else { q->emitColorSpaceChanged(); } return parentCommand; } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; - QList dataObjects = allDataObjects();; + QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace); } q->emitProfileChanged(); // no undo information is provided here return true; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { - QList dataObjects = allDataObjects();; + QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames); if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(newParentNode); } } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { m_d->estimateMemoryStats(imageData, temporaryData, lodData); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // the passed extent might have weird invalid structure that // can overflow integer precision when calling startRect.right() if (!startRect.isValid()) return QRect(); // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } QRegion KisPaintDevice::regionExact() const { QRegion resultRegion; QVector rects = region().rects(); const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, rects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRegion += result; } } } return resultRegion; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags); return command; } bool KisPaintDevice::setProfile(const KoColorProfile * profile) { return m_d->assignProfile(profile); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds(); if (r.isValid()) { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { Q_ASSERT(m_d->contentChannel); return m_d->contentChannel.data(); } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } QRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/tests/kis_lazy_brush_test.cpp b/libs/image/tests/kis_lazy_brush_test.cpp index 226b1f53ef..f2aeab50f5 100644 --- a/libs/image/tests/kis_lazy_brush_test.cpp +++ b/libs/image/tests/kis_lazy_brush_test.cpp @@ -1,1405 +1,1405 @@ /* * Copyright (c) 2016 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_lazy_brush_test.h" #include #include "kis_debug.h" #include "kis_fill_painter.h" #include #include #include #include #include #include #include #include #include #include "lazybrush/kis_lazy_fill_graph.h" #if 0 int doSomething() { using namespace boost; typedef adjacency_list_traits < vecS, vecS, directedS > Traits; typedef adjacency_list < vecS, vecS, directedS, property < vertex_name_t, std::string, property < vertex_index_t, long, property < vertex_color_t, boost::default_color_type, property < vertex_distance_t, long, property < vertex_predecessor_t, Traits::edge_descriptor > > > > >, property < edge_capacity_t, long, property < edge_residual_capacity_t, long, property < edge_reverse_t, Traits::edge_descriptor > > > > Graph; Graph g; property_map < Graph, edge_capacity_t >::type capacity = get(edge_capacity, g); property_map < Graph, edge_residual_capacity_t >::type residual_capacity = get(edge_residual_capacity, g); property_map < Graph, edge_reverse_t >::type rev = get(edge_reverse, g); Traits::vertex_descriptor s, t; read_dimacs_max_flow(g, capacity, rev, s, t); std::vector color(num_vertices(g)); std::vector distance(num_vertices(g)); long flow = boykov_kolmogorov_max_flow(g ,s, t); std::cout << "c The total flow:" << std::endl; std::cout << "s " << flow << std::endl << std::endl; std::cout << "c flow values:" << std::endl; graph_traits < Graph >::vertex_iterator u_iter, u_end; graph_traits < Graph >::out_edge_iterator ei, e_end; for (boost::tie(u_iter, u_end) = vertices(g); u_iter != u_end; ++u_iter) { qDebug() << ppVar(get(vertex_color, g)[*u_iter]); for (boost::tie(ei, e_end) = out_edges(*u_iter, g); ei != e_end; ++ei) { if (capacity[*ei] > 0) { std::cout << "f " << *u_iter << " " << target(*ei, g) << " " << (capacity[*ei] - residual_capacity[*ei]) << std::endl; } } } return EXIT_SUCCESS; } #include #include #include template class ImageToCapacityMap { typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; typedef typename boost::graph_traits::edge_descriptor EdgeDescriptor; public: typedef EdgeDescriptor key_type; typedef float value_type; typedef const float& reference; typedef boost::readable_property_map_tag category; ImageToCapacityMap(Graph &graph, const QImage &image) : m_graph(graph), m_image(image) { m_bits = reinterpret_cast(m_image.constBits()); m_minIntensity = std::numeric_limits::max(); m_maxIntensity = std::numeric_limits::min(); int totalInt = 0; const int numPixels = m_image.width() * m_image.height(); m_intensities.resize(numPixels); for (int i = 0; i < numPixels; i++) { const int v = qGray(m_bits[i]); m_intensities[i] = v; qDebug() << ppVar(i) << ppVar(v); totalInt += v; if (m_minIntensity > v) m_minIntensity = v; if (m_maxIntensity < v) m_maxIntensity = v; } qDebug() << ppVar(totalInt); m_pixelSize = 4; m_rowStride = m_image.width() * m_pixelSize; qDebug() << ppVar(m_rowStride) << ppVar(m_image.size()) << ppVar(m_image.bytesPerLine()); } Graph &m_graph; QImage m_image; QVector m_intensities; const QRgb *m_bits; int m_rowStride; int m_pixelSize; int m_minIntensity; int m_maxIntensity; }; template typename ImageToCapacityMap::value_type get(const ImageToCapacityMap &map, const typename ImageToCapacityMap::key_type &key) { typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; VertexDescriptor src = source(key, map.m_graph); VertexDescriptor tgt = target(key, map.m_graph); const int x0 = src[0]; const int y0 = src[1]; const int x1 = tgt[0]; const int y1 = tgt[1]; const int k = 2 * (map.m_image.width() + map.m_image.height()); const int maxDimension = qMax(map.m_image.width(), map.m_image.height()); //if (x0 <= 5 && x1 <= 5 && y0 == 0 && y1 ==0) return 255; //if (x0 >= 5 && x1 >= 5 && y0 == 8 && y1 ==8) return 255; //const QRgb *p0 = map.m_bits + y0 * map.m_rowStride + x0 * map.m_pixelSize; //const QRgb *p1 = map.m_bits + y1 * map.m_rowStride + x1 * map.m_pixelSize; //float value = 255.0 - qAbs(qGray(*p0) - qGray(*p1)); if ((!x0 && !y0) || (!x1 && !y1) || (x0 == map.m_image.width() - 1 && y0 == map.m_image.height() - 1) || (x1 == map.m_image.width() - 1 && y1 == map.m_image.height() - 1)) { qreal value = maxDimension * k; qDebug() << x0 << y0 << "->" << x1 << y1 << value; return value; } const int i0 = map.m_intensities[x0 + y0 * map.m_image.width()]; const int i1 = map.m_intensities[x1 + y1 * map.m_image.width()]; const int diff = qAbs(i0 - i1); qreal normDiff = qreal(diff) / (map.m_maxIntensity - map.m_minIntensity); float value = 1.0 + k * (1.0 - normDiff) + qMin(y0, y1); qDebug() << x0 << y0 << "->" << x1 << y1 << value;// << ppVar(normDiff); return value; } template ImageToCapacityMap MakeImageToCapacityMap(Graph &graph, const QImage &image) { return ImageToCapacityMap(graph, image); } int doSomethingElse() { const unsigned int D = 2; typedef boost::grid_graph Graph; typedef boost::graph_traits::vertex_descriptor VertexDescriptor; typedef boost::graph_traits::edge_descriptor EdgeDescriptor;//ADDED typedef boost::graph_traits::vertices_size_type VertexIndex; typedef boost::graph_traits::edges_size_type EdgeIndex; QImage image(QSize(9,9), QImage::Format_ARGB32); QPainter gc(&image); gc.fillRect(image.rect(), Qt::white); gc.fillRect(QRect(0,4,2,1), Qt::blue); //gc.fillRect(QRect(0,5,2,1), Qt::blue); gc.fillRect(QRect(6,4,3,1), Qt::blue); gc.end(); image.save("graph_img.png"); boost::array lengths = { { image.width(), image.height() } }; Graph graph(lengths, false); std::vector groups(num_vertices(graph), 0); std::vector residual_capacity(num_edges(graph), 0); auto capacityMap = MakeImageToCapacityMap(graph, image); float capSum = 0; BGL_FORALL_EDGES(e,graph,Graph) { VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); //VertexIndex source_idx = get(boost::vertex_index,graph,src); //VertexIndex target_idx = get(boost::vertex_index,graph,tgt); //EdgeIndex edge_idx = get(boost::edge_index,graph,e); // qDebug() << "(" << src[0] << src[1] << ")" // << "->" // << "(" << tgt[0] << tgt[1] << ")" // << get(capacityMap, e); //capSum += get(capacityMap, e); } //qDebug() << ppVar(capSum); BGL_FORALL_VERTICES(v,graph,Graph) { //qDebug() << ppVar(v[0]) << ppVar(v[1]); /* VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); VertexIndex source_idx = get(boost::vertex_index,graph,src); VertexIndex target_idx = get(boost::vertex_index,graph,tgt); EdgeIndex edge_idx = get(boost::edge_index,graph,e); capacity[edge_idx] = 255.0f - fabs(pixel_intensity[source_idx]-pixel_intensity[target_idx]); //you should change this to your "gradiant or intensity or something" reverse_edges[edge_idx]=edge(tgt,src,graph).first;//ADDED */ } const int t_index = image.width() * image.height() - 1; VertexDescriptor s=vertex(0,graph), t=vertex(t_index,graph); float maxFlow = boykov_kolmogorov_max_flow(graph, capacityMap, make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), get(boost::edge_reverse, graph), make_iterator_property_map(&groups[0], get(boost::vertex_index, graph)), get(boost::vertex_index, graph), s, t ); qDebug() << ppVar(maxFlow); const int cell = 10; const int half = cell / 2; QImage result(QSize(cell * lengths[0], cell * lengths[1]), QImage::Format_ARGB32); QPainter resultPainter(&result); BGL_FORALL_VERTICES(v, graph, Graph) { const int x = v[0]; const int y = v[1]; VertexIndex vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; QColor color = label == 0 ? Qt::blue : label == 4 ? Qt::green : label == 1 ? Qt::gray : Qt::red; QRect rc(cell * x, cell * y, cell, cell); resultPainter.fillRect(rc, color); } BGL_FORALL_EDGES(e,graph,Graph) { EdgeIndex egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; QColor color(Qt::black); if (cap != 0) { const int fullCap = get(capacityMap, e); const int gray = qreal(cap) / fullCap * 50.0; color.setAlpha(gray); } VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); QPoint p0(half + cell * src[0], half + cell * src[1]); QPoint p1(half + cell * tgt[0], half + cell * tgt[1]); resultPainter.setPen(QPen(color, 2)); resultPainter.drawLine(p0, p1); // qDebug() << "(" << src[0] << src[1] << ")" // << "->" // << "(" << tgt[0] << tgt[1] << ")" // << residual_capacity[egdeIndex]; } result.save("result.png"); return 0; } void KisLazyBrushTest::test() { doSomethingElse(); } #endif /*0*/ bool verifyNormalVertex(const KisLazyFillGraph::vertex_descriptor &v, int x, int y) { bool result = v.type == KisLazyFillGraph::vertex_descriptor::NORMAL && v.x == x && v.y == y; if (!result) { qDebug() << ppVar(v) << ppVar(x) << ppVar(y); } return result; } bool verifyVertexIndex(KisLazyFillGraph &g, KisLazyFillGraph::vertex_descriptor v, KisLazyFillGraph::vertices_size_type index) { bool result = true; const KisLazyFillGraph::vertices_size_type actualIndex = g.index_of(v); const KisLazyFillGraph::vertex_descriptor actualVertex = g.vertex_at(index); if (index >= 0) { result &= v == actualVertex; } result &= index == actualIndex; if (!result) { qDebug() << "Vertex failed:"; qDebug() << v << "->" << actualIndex << "( expected:" << index << ")"; qDebug() << index << "->" << actualVertex << "( expected:" << v << ")"; } return result; } bool verifyEdgeIndex(KisLazyFillGraph &g, KisLazyFillGraph::edge_descriptor v, KisLazyFillGraph::edges_size_type index) { bool result = true; const KisLazyFillGraph::edges_size_type actualIndex = g.index_of(v); const KisLazyFillGraph::edge_descriptor actualEdge = g.edge_at(index); if (index >= 0) { result &= v == actualEdge; } result &= index == actualIndex; if (!result) { qDebug() << "Edge failed:"; qDebug() << v << "->" << actualIndex << "( expected:" << index << ")"; qDebug() << index << "->" << actualEdge << "( expected:" << v << ")"; } return result; } void KisLazyBrushTest::testGraph() { QRect mainRect(10, 10, 100, 100); QRect aLabelRect(30, 20, 20, 20); QRect bLabelRect(60, 60, 20, 20); KisLazyFillGraph g(mainRect, aLabelRect, bLabelRect); const int numVertices = g.num_vertices(); const int numEdges = g.num_edges(); qDebug() << ppVar(numVertices); qDebug() << ppVar(numEdges); QCOMPARE(numVertices, 10002); QCOMPARE(numEdges, 41200); for (int i = 0; i < numVertices; i++) { KisLazyFillGraph::vertex_descriptor vertex = g.vertex_at(i); int newVertexIndex = g.index_of(vertex); QCOMPARE(newVertexIndex, i); } for (int i = 0; i < numEdges; i++) { KisLazyFillGraph::edge_descriptor edge = g.edge_at(i); int newEdgeIndex = g.index_of(edge); QCOMPARE(newEdgeIndex, i); } KisLazyFillGraph::vertex_descriptor v1(10, 10); KisLazyFillGraph::vertex_descriptor v2(11, 10); KisLazyFillGraph::vertex_descriptor v3(10, 11); KisLazyFillGraph::vertex_descriptor v4(11, 11); KisLazyFillGraph::vertex_descriptor v5(12, 10); KisLazyFillGraph::vertex_descriptor v6(31, 21); KisLazyFillGraph::vertex_descriptor v7(61, 61); KisLazyFillGraph::vertex_descriptor v8(9, 10); KisLazyFillGraph::vertex_descriptor vA(0, 0, KisLazyFillGraph::vertex_descriptor::LABEL_A); KisLazyFillGraph::vertex_descriptor vB(0, 0, KisLazyFillGraph::vertex_descriptor::LABEL_B); // Verify vertex index mapping QVERIFY(verifyVertexIndex(g, v1, 0)); QVERIFY(verifyVertexIndex(g, v2, 1)); QVERIFY(verifyVertexIndex(g, v3, 100)); QVERIFY(verifyVertexIndex(g, v4, 101)); QVERIFY(verifyVertexIndex(g, v5, 2)); QVERIFY(verifyVertexIndex(g, v6, 1121)); QVERIFY(verifyVertexIndex(g, v7, 5151)); QVERIFY(verifyVertexIndex(g, v8, -1)); QVERIFY(verifyVertexIndex(g, vA, numVertices - 2)); QVERIFY(verifyVertexIndex(g, vB, numVertices - 1)); // Verify edge index mapping QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v2), 0)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v4), 99)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v3), 19800)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v4), 19801)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v1), 9900)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v3), 9999)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v1), 29700)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v2), 29701)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v8), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v4), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v1), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v5), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v6, vA), 39621)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, v6), 40021)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vB), 40421)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, v7), 40821)); QCOMPARE(g.out_degree(v1), long(2)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 0).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 1).second, 10, 11)); QCOMPARE(g.out_degree(v4), long(4)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 0).second, 10, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 1).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 2).second, 12, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 3).second, 11, 12)); QCOMPARE(g.out_degree(v6), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 0).second, 30, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 1).second, 31, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 2).second, 32, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 3).second, 31, 22)); QCOMPARE(g.out_edge_at(v6, 4).second, vA); QCOMPARE(g.out_degree(v7), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 0).second, 60, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 1).second, 61, 60)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 2).second, 62, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 3).second, 61, 62)); QCOMPARE(g.out_edge_at(v7, 4).second, vB); QCOMPARE(g.out_degree(vA), long(400)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 0).second, 30, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 1).second, 31, 20)); } void KisLazyBrushTest::testGraphMultilabel() { QRect mainRect(10, 10, 100, 100); QRect aLabelRect1(30, 20, 20, 20); QRect aLabelRect2(70, 30, 20, 20); QRect bLabelRect1(60, 60, 20, 20); QRect bLabelRect2(20, 80, 20, 20); QRect bLabelRect3(60, 40, 20, 30); QRegion aLabelRegion; aLabelRegion += aLabelRect1; aLabelRegion += aLabelRect2; QRegion bLabelRegion; bLabelRegion += bLabelRect1; bLabelRegion += bLabelRect2; bLabelRegion += bLabelRect3; KisLazyFillGraph g(mainRect, aLabelRegion, bLabelRegion); const int numVertices = g.num_vertices(); const int numEdges = g.num_edges(); qDebug() << ppVar(numVertices); qDebug() << ppVar(numEdges); QCOMPARE(numVertices, 10002); QCOMPARE(numEdges, 43600); for (int i = 0; i < numVertices; i++) { KisLazyFillGraph::vertex_descriptor vertex = g.vertex_at(i); int newVertexIndex = g.index_of(vertex); QCOMPARE(newVertexIndex, i); } for (int i = 0; i < numEdges; i++) { KisLazyFillGraph::edge_descriptor edge = g.edge_at(i); int newEdgeIndex = g.index_of(edge); if (i != newEdgeIndex) { qDebug() << ppVar(edge); } QCOMPARE(newEdgeIndex, i); } typedef KisLazyFillGraph::vertex_descriptor Vert; Vert v1(10, 10); Vert v2(11, 10); Vert v3(10, 11); Vert v4(11, 11); Vert v5(12, 10); Vert v6(31, 21); Vert v7(61, 61); Vert v8(9, 10); Vert vA(0, 0, Vert::LABEL_A); Vert vB(0, 0, Vert::LABEL_B); // Verify vertex index mapping QVERIFY(verifyVertexIndex(g, v1, 0)); QVERIFY(verifyVertexIndex(g, v2, 1)); QVERIFY(verifyVertexIndex(g, v3, 100)); QVERIFY(verifyVertexIndex(g, v4, 101)); QVERIFY(verifyVertexIndex(g, v5, 2)); QVERIFY(verifyVertexIndex(g, v6, 1121)); QVERIFY(verifyVertexIndex(g, v7, 5151)); QVERIFY(verifyVertexIndex(g, v8, -1)); QVERIFY(verifyVertexIndex(g, vA, numVertices - 2)); QVERIFY(verifyVertexIndex(g, vB, numVertices - 1)); // Verify edge index mapping QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v2), 0)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v4), 99)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v3), 19800)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v4), 19801)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v1), 9900)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v3), 9999)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v1), 29700)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v2), 29701)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v8), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v4), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v1), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v5), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v6, vA), 39621)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, v6), 40421)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vB), 41621)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, v7), 42821)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 30), vA), 40000)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, Vert(70, 30)), 40800)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 30), vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, Vert(70, 30)), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 49), vA), 40380)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, Vert(70, 49)), 41180)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 49), vB), 41390)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, Vert(70, 49)), 42590)); QCOMPARE(g.out_degree(v1), long(2)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 0).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 1).second, 10, 11)); QCOMPARE(g.out_degree(v4), long(4)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 0).second, 10, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 1).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 2).second, 12, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 3).second, 11, 12)); QCOMPARE(g.out_degree(v6), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 0).second, 30, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 1).second, 31, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 2).second, 32, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 3).second, 31, 22)); QCOMPARE(g.out_edge_at(v6, 4).second, vA); QCOMPARE(g.out_degree(v7), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 0).second, 60, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 1).second, 61, 60)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 2).second, 62, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 3).second, 61, 62)); QCOMPARE(g.out_edge_at(v7, 4).second, vB); QCOMPARE(g.out_degree(vA), long(800)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 0).second, 30, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 1).second, 31, 20)); // check second island QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 400).second, 70, 30)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 401).second, 71, 30)); } void KisLazyBrushTest::testGraphStandardIterators() { QRect mainRect(10, 10, 100, 100); QRect aLabelRect(30, 20, 20, 20); QRect bLabelRect(60, 60, 20, 20); KisLazyFillGraph g(mainRect, aLabelRect, bLabelRect); BGL_FORALL_VERTICES(v, g, KisLazyFillGraph) { int index = g.index_of(v); QVERIFY(index >= 0); KisLazyFillGraph::vertex_descriptor newVertex = g.vertex_at(index); QCOMPARE(newVertex, v); } BGL_FORALL_EDGES(e, g, KisLazyFillGraph) { int index = g.index_of(e); QVERIFY(index >= 0); KisLazyFillGraph::edge_descriptor newEdge = g.edge_at(index); QCOMPARE(newEdge, e); } } void KisLazyBrushTest::testGraphConcepts() { BOOST_CONCEPT_ASSERT(( VertexListGraphConcept )); //to have vertices(), num_vertices(), BOOST_CONCEPT_ASSERT(( EdgeListGraphConcept )); //to have edges() BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept )); //to have source(), target() and out_edges() BOOST_CONCEPT_ASSERT(( AdjacencyGraphConcept )); // to have adjacent_vertices(v, g) BOOST_CONCEPT_ASSERT(( AdjacencyMatrixConcept )); // to have edge(u, v, g) } template class ComplexCapacityMap { typedef ComplexCapacityMap type; typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; typedef typename boost::graph_traits::edge_descriptor EdgeDescriptor; public: typedef EdgeDescriptor key_type; typedef float value_type; typedef const float& reference; typedef boost::readable_property_map_tag category; ComplexCapacityMap(Graph &graph, const QImage &mainImage, const QImage &aLabelImage, const QImage &bLabelImage) : m_graph(graph), m_mainImage(mainImage), m_aLabelImage(aLabelImage), m_bLabelImage(bLabelImage) { const QRgb *bits = reinterpret_cast(m_mainImage.constBits()); m_minIntensity = std::numeric_limits::max(); m_maxIntensity = std::numeric_limits::min(); const int numPixels = m_mainImage.width() * m_mainImage.height(); m_intensities.resize(numPixels); for (int i = 0; i < numPixels; i++) { const int value = qGray(bits[i]); m_intensities[i] = value; if (m_minIntensity > value) m_minIntensity = value; if (m_maxIntensity < value) m_maxIntensity = value; } m_pixelSize = 4; m_rowStride = m_mainImage.width() * m_pixelSize; } friend value_type get(const type &map, const key_type &key) { VertexDescriptor src = source(key, map.m_graph); VertexDescriptor dst = target(key, map.m_graph); bool srcLabelA = src.type == VertexDescriptor::LABEL_A; bool srcLabelB = src.type == VertexDescriptor::LABEL_B; bool dstLabelA = dst.type == VertexDescriptor::LABEL_A; bool dstLabelB = dst.type == VertexDescriptor::LABEL_B; if (srcLabelA || srcLabelB) { std::swap(src, dst); std::swap(srcLabelA, dstLabelA); std::swap(srcLabelB, dstLabelB); } Q_ASSERT(!srcLabelA && !srcLabelB); const int k = 2 * (map.m_mainImage.width() + map.m_mainImage.height()); //const int maxDimension = qMax(map.m_mainImage.width(), map.m_mainImage.height()); float value = 0.0; if (dstLabelA) { const int xOffset = map.m_aLabelImage.offset().x(); const int yOffset = map.m_aLabelImage.offset().y(); const int x = src.x - xOffset; const int y = src.y - yOffset; //qDebug() << "Label A:" << ppVar(src.x) << ppVar(src.y) << ppVar(xOffset) << ppVar(yOffset); QRgb pixel = map.m_aLabelImage.pixel(x, y); const int i0 = qAlpha(pixel); value = i0 / 255.0 * k; } else if (dstLabelB) { const int xOffset = map.m_bLabelImage.offset().x(); const int yOffset = map.m_bLabelImage.offset().y(); const int x = src.x - xOffset; const int y = src.y - yOffset; //qDebug() << "Label B:" << ppVar(src.x) << ppVar(src.y) << ppVar(xOffset) << ppVar(yOffset); QRgb pixel = map.m_bLabelImage.pixel(x, y); const int i0 = qAlpha(pixel); value = i0 / 255.0 * k; } else { const int xOffset = map.m_mainImage.offset().x(); const int yOffset = map.m_mainImage.offset().y(); const int i0 = map.m_intensities[(src.x - xOffset) + (src.y - xOffset) * map.m_mainImage.width()]; const int i1 = map.m_intensities[(dst.x - xOffset) + (dst.y - yOffset) * map.m_mainImage.width()]; const int diff = qAbs(i0 - i1); qreal normDiff = qreal(diff) / (map.m_maxIntensity - map.m_minIntensity); value = 1.0 + k * (1.0 - normDiff); } return value; } private: Graph &m_graph; QImage m_mainImage; QImage m_aLabelImage; QImage m_bLabelImage; QVector m_intensities; int m_minIntensity; int m_maxIntensity; int m_rowStride; int m_pixelSize; }; template ComplexCapacityMap MakeComplexCapacityMap(Graph &graph, const QImage &mainImage, const QImage &aLabelImage, const QImage &bLabelImage) { return ComplexCapacityMap(graph, mainImage, aLabelImage, bLabelImage); } void KisLazyBrushTest::testCutOnGraph() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept, KisLazyFillGraph::edge_descriptor> )); QRect mainRect(0, 0, 27, 27); const int scribbleASize = 18; const int scribbleBSize = 19; const int holeSize = 7; const int obstacleVOffset = -5; const int asymVOffset = 1; const int obstacleSize = (mainRect.width() - holeSize) / 2; QRect o1Rect(0, mainRect.height() / 2 + obstacleVOffset + asymVOffset, obstacleSize, 1); QRect o2Rect(mainRect.width() - obstacleSize, mainRect.height() / 2 + obstacleVOffset - asymVOffset, obstacleSize, 1); QRect aLabelRect(0, 0, scribbleASize, 1); QRect bLabelRect(3, mainRect.bottom(), scribbleBSize, 1); KisLazyFillGraph graph(mainRect, aLabelRect, bLabelRect); QImage mainImage(mainRect.size(), QImage::Format_ARGB32); { QPainter gc(&mainImage); gc.fillRect(mainRect, Qt::white); gc.fillRect(o1Rect, Qt::blue); gc.fillRect(o2Rect, Qt::blue); } QImage aLabelImage(aLabelRect.size(), QImage::Format_ARGB32); { QPainter gc(&aLabelImage); gc.fillRect(QRect(QPoint(), aLabelRect.size()), Qt::red); aLabelImage.setOffset(aLabelRect.topLeft()); qDebug() << ppVar(aLabelImage.offset()); } - QImage bLabelImage(bLabelRect.size(), QImage::Format_ARGB32);; + QImage bLabelImage(bLabelRect.size(), QImage::Format_ARGB32); { QPainter gc(&bLabelImage); gc.fillRect(QRect(QPoint(), bLabelRect.size()), Qt::red); bLabelImage.setOffset(bLabelRect.topLeft()); } std::vector groups(num_vertices(graph), 0); std::vector residual_capacity(num_edges(graph), 0); auto capacityMap = MakeComplexCapacityMap(graph, mainImage, aLabelImage, bLabelImage); std::vector::vertices_size_type> distance_vec(num_vertices(graph), 0); std::vector::edge_descriptor> predecessor_vec(num_vertices(graph)); auto vertexIndexMap = get(boost::vertex_index, graph); typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; VertexDescriptor s(0, 0, VertexDescriptor::LABEL_A); VertexDescriptor t(0, 0, VertexDescriptor::LABEL_B); float maxFlow = boykov_kolmogorov_max_flow(graph, capacityMap, make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), get(boost::edge_reverse, graph), make_iterator_property_map(&predecessor_vec[0], vertexIndexMap), make_iterator_property_map(&groups[0], vertexIndexMap), make_iterator_property_map(&distance_vec[0], vertexIndexMap), vertexIndexMap, s, t); qDebug() << ppVar(maxFlow); const int cell = 10; const int half = cell / 2; QImage result(cell * mainRect.size(), QImage::Format_ARGB32); QPainter resultPainter(&result); BGL_FORALL_VERTICES(v, graph, KisLazyFillGraph) { long vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; QColor color = label == 0 ? Qt::blue : label == 4 ? Qt::green : label == 1 ? Qt::gray : Qt::red; QRect rc(cell * v.x, cell * v.y, cell, cell); resultPainter.fillRect(rc, color); } BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { long egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; QColor color(Qt::black); if (cap != 0) { const int fullCap = get(capacityMap, e); const int gray = qreal(cap) / fullCap * 50.0; color.setAlpha(gray); } VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); QPoint p0(half + cell * src.x, half + cell * src.y); QPoint p1(half + cell * tgt.x, half + cell * tgt.y); resultPainter.setPen(QPen(color, 2)); resultPainter.drawLine(p0, p1); // qDebug() << "(" << src[0] << src[1] << ")" // << "->" // << "(" << tgt[0] << tgt[1] << ")" // << residual_capacity[egdeIndex]; } resultPainter.save(); resultPainter.setTransform(QTransform::fromScale(cell, cell)); resultPainter.setBrush(Qt::transparent); resultPainter.setPen(QPen(Qt::yellow, 0)); resultPainter.drawRect(o1Rect); resultPainter.drawRect(o2Rect); resultPainter.setPen(QPen(Qt::red, 0)); resultPainter.drawRect(aLabelRect); resultPainter.drawRect(bLabelRect); resultPainter.restore(); result.save("result.png"); } #include "lazybrush/kis_lazy_fill_capacity_map.h" #include #include "testutil.h" #include void writeColors(KisLazyFillGraph &graph, const std::vector &groups, KisPaintDeviceSP dst) { KisSequentialIterator dstIt(dst, graph.rect()); KoColor blue(Qt::blue, dst->colorSpace()); KoColor green(Qt::red, dst->colorSpace()); KoColor red(Qt::red, dst->colorSpace()); KoColor gray(Qt::gray, dst->colorSpace()); const int pixelSize = dst->colorSpace()->pixelSize(); while (dstIt.nextPixel()) { KisLazyFillGraph::vertex_descriptor v(dstIt.x(), dstIt.y()); long vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; KoColor &color = label == 0 ? blue : label == 4 ? green : label == 1 ? gray : red; quint8 *dstPtr = dstIt.rawData(); memcpy(dstPtr, color.data(), pixelSize); } } void writeStat(KisLazyFillGraph &graph, const std::vector &groups, const std::vector &residual_capacity, KisLazyFillCapacityMap &capacityMap) { typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; const int cell = 10; const int half = cell / 2; QImage result(cell * graph.size(), QImage::Format_ARGB32); QPainter resultPainter(&result); BGL_FORALL_VERTICES(v, graph, KisLazyFillGraph) { if (v.type != VertexDescriptor::NORMAL) continue; long vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; QColor color = label == 0 ? Qt::blue : label == 4 ? Qt::green : label == 1 ? Qt::gray : Qt::red; QRect rc(cell * v.x, cell * v.y, cell, cell); resultPainter.fillRect(rc, color); } BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { long egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; const int fullCap = get(capacityMap, e); QColor color(Qt::red); if (cap > 0 || fullCap == 0) continue; /* if (fullCap != 0) { const int gray = fullCap / capacityMap.maxCapacity() * 50.0; color.setAlpha(gray); } else { continue; } */ VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); if (src.type != VertexDescriptor::NORMAL || tgt.type != VertexDescriptor::NORMAL) { /* VertexDescriptor &v = src.type != VertexDescriptor::NORMAL ? tgt : src; QPoint p0(half + cell * v.x, half + cell * v.y); resultPainter.setPen(QPen(color, 4)); resultPainter.drawEllipse(p0, 0.5 * half, 0.5 * half); */ } else { QPoint p0(half + cell * src.x, half + cell * src.y); QPoint p1(half + cell * tgt.x, half + cell * tgt.y); resultPainter.setPen(QPen(color, 4)); resultPainter.drawLine(p0, p1); } } BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { long egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; QColor color(Qt::black); if (cap != 0) { const int fullCap = get(capacityMap, e); const int gray = qreal(cap) / fullCap * 50.0; color.setAlpha(gray); } else { continue; } VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); if (src.type != VertexDescriptor::NORMAL || tgt.type != VertexDescriptor::NORMAL) { VertexDescriptor &v = src.type != VertexDescriptor::NORMAL ? tgt : src; QPoint p0(half + cell * v.x, half + cell * v.y); resultPainter.setPen(QPen(color, 2)); resultPainter.drawEllipse(QPointF(p0), 0.5 * half, 0.5 * half); } else { QPoint p0(half + cell * src.x, half + cell * src.y); QPoint p1(half + cell * tgt.x, half + cell * tgt.y); resultPainter.setPen(QPen(color, 2)); resultPainter.drawLine(p0, p1); } } resultPainter.save(); resultPainter.setTransform(QTransform::fromScale(cell, cell)); resultPainter.setBrush(Qt::transparent); //resultPainter.setPen(QPen(Qt::yellow, 0)); //resultPainter.drawRect(o1Rect); //resultPainter.drawRect(o2Rect); //resultPainter.setPen(QPen(Qt::red, 0)); //resultPainter.drawRect(aLabelRect); //resultPainter.drawRect(bLabelRect); resultPainter.restore(); result.save("result.png"); } #include "kis_paint_device_debug_utils.h" #include "kis_gaussian_kernel.h" #include "krita_utils.h" KisPaintDeviceSP loadTestImage(const QString &name, bool convertToAlpha) { QImage image(TestUtil::fetchDataFileLazy(name)); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->convertFromQImage(image, 0); if (convertToAlpha) { dev = KisPainter::convertToAlphaAsAlpha(dev); } return dev; } #include "lazybrush/kis_lazy_fill_tools.h" void KisLazyBrushTest::testCutOnGraphDevice() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); KisPaintDeviceSP mainDev = loadTestImage("fill2_main.png", false); KisPaintDeviceSP aLabelDev = loadTestImage("fill2_a.png", true); KisPaintDeviceSP bLabelDev = loadTestImage("fill2_b.png", true); KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); const QRect filterRect = filteredMainDev->exactBounds(); // KisGaussianKernel::applyLoG(filteredMainDev, // filterRect, // 5, // QBitArray(), 0); // KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, filterRect); KIS_DUMP_DEVICE_2(filteredMainDev, filterRect, "2filtered", "dd"); KoColor color(Qt::red, mainDev->colorSpace()); KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); KisPaintDeviceSP maskDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); maskDevice->fill(QRect(0,0,640,40), KoColor(Qt::gray, maskDevice->colorSpace())); KisLazyFillTools::cutOneWay(color, filteredMainDev, aLabelDev, bLabelDev, resultColoring, maskDevice, filterRect); KIS_DUMP_DEVICE_2(resultColoring, filterRect, "00result", "dd"); KIS_DUMP_DEVICE_2(maskDevice, filterRect, "01mask", "dd"); KIS_DUMP_DEVICE_2(mainDev, filterRect, "1main", "dd"); KIS_DUMP_DEVICE_2(aLabelDev, filterRect, "3aLabel", "dd"); KIS_DUMP_DEVICE_2(bLabelDev, filterRect, "4bLabel", "dd"); #if 0 KisLazyFillCapacityMap capacityMap(filteredMainDev, aLabelDev, bLabelDev); KisLazyFillGraph &graph = capacityMap.graph(); std::vector groups(num_vertices(graph), 0); std::vector residual_capacity(num_edges(graph), 0); std::vector::vertices_size_type> distance_vec(num_vertices(graph), 0); std::vector::edge_descriptor> predecessor_vec(num_vertices(graph)); auto vertexIndexMap = get(boost::vertex_index, graph); typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; VertexDescriptor s(VertexDescriptor::LABEL_A); VertexDescriptor t(VertexDescriptor::LABEL_B); float maxFlow = boykov_kolmogorov_max_flow(graph, capacityMap, make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), get(boost::edge_reverse, graph), make_iterator_property_map(&predecessor_vec[0], vertexIndexMap), make_iterator_property_map(&groups[0], vertexIndexMap), make_iterator_property_map(&distance_vec[0], vertexIndexMap), vertexIndexMap, s, t); qDebug() << ppVar(maxFlow); KisPaintDeviceSP resultColoring = new KisPaintDevice(*mainDev); writeColors(graph, groups, resultColoring); KIS_DUMP_DEVICE_2(resultColoring, graph.rect(), "0result", "dd"); KIS_DUMP_DEVICE_2(mainDev, graph.rect(), "1main", "dd"); KIS_DUMP_DEVICE_2(aLabelDev, graph.rect(), "3aLabel", "dd"); KIS_DUMP_DEVICE_2(bLabelDev, graph.rect(), "4bLabel", "dd"); writeStat(graph, groups, residual_capacity, capacityMap); #endif } #include "lazybrush/kis_multiway_cut.h" void KisLazyBrushTest::testCutOnGraphDeviceMulti() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); KisPaintDeviceSP mainDev = loadTestImage("fill4_main.png", false); KisPaintDeviceSP aLabelDev = loadTestImage("fill4_a.png", true); KisPaintDeviceSP bLabelDev = loadTestImage("fill4_b.png", true); KisPaintDeviceSP cLabelDev = loadTestImage("fill4_c.png", true); KisPaintDeviceSP dLabelDev = loadTestImage("fill4_d.png", true); KisPaintDeviceSP eLabelDev = loadTestImage("fill4_e.png", true); KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); const QRect filterRect = filteredMainDev->exactBounds(); KisGaussianKernel::applyLoG(filteredMainDev, filterRect, 2, 1.0, QBitArray(), 0); KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, filterRect); KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); KisMultiwayCut cut(filteredMainDev, resultColoring, filterRect); cut.addKeyStroke(aLabelDev, KoColor(Qt::red, mainDev->colorSpace())); cut.addKeyStroke(bLabelDev, KoColor(Qt::green, mainDev->colorSpace())); cut.addKeyStroke(cLabelDev, KoColor(Qt::blue, mainDev->colorSpace())); cut.addKeyStroke(dLabelDev, KoColor(Qt::yellow, mainDev->colorSpace())); cut.addKeyStroke(eLabelDev, KoColor(Qt::magenta, mainDev->colorSpace())); cut.run(); KIS_DUMP_DEVICE_2(resultColoring, filterRect, "00result", "dd"); KIS_DUMP_DEVICE_2(mainDev, filterRect, "1main", "dd"); KIS_DUMP_DEVICE_2(filteredMainDev, filterRect, "2filtered", "dd"); } void KisLazyBrushTest::testLoG() { QImage mainImage(TestUtil::fetchDataFileLazy("fill1_main.png")); QVERIFY(!mainImage.isNull()); KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); mainDev->convertFromQImage(mainImage, 0); QRect rect = mainDev->exactBounds(); // KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); // const QRect rect(0,0,10,10); // const QRect fillRect(0,0,5,10); // KoColor bg(Qt::white, mainDev->colorSpace()); // KoColor fg(Qt::black, mainDev->colorSpace()); // mainDev->fill(rect, bg); // mainDev->fill(fillRect, fg); KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); KisGaussianKernel::applyLoG(filteredMainDev, rect, 4.0, 1.0, QBitArray(), 0); KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, rect); KIS_DUMP_DEVICE_2(mainDev, rect, "1main", "dd"); KIS_DUMP_DEVICE_2(filteredMainDev, rect, "2filtered", "dd"); } void KisLazyBrushTest::testSplitIntoConnectedComponents() { const QRect rc1(10, 10, 10, 10); const QRect rc2(30, 10, 10, 10); const QRect boundingRect(0,0,100,100); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->fill(rc1, KoColor(Qt::red, dev->colorSpace())); dev->fill(rc2, KoColor(Qt::green, dev->colorSpace())); QCOMPARE(dev->exactBounds(), rc1 | rc2); QVector points = KisLazyFillTools::splitIntoConnectedComponents(dev, boundingRect); qDebug() << ppVar(points); QCOMPARE(points.size(), 2); QCOMPARE(points[0], QPoint(10,10)); QCOMPARE(points[1], QPoint(30,10)); } void KisLazyBrushTest::testEstimateTransparentPixels() { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect totalRect(0,0,50,100); qreal value = 0.0; value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QCOMPARE(value, 1.0); dev->fill(QRect(0,0,25,50), KoColor(Qt::red, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QVERIFY(qAbs(value - 0.75) < 0.05); dev->fill(QRect(25,0,25,50), KoColor(Qt::green, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QVERIFY(qAbs(value - 0.5) < 0.05); dev->fill(QRect(25,50,25,50), KoColor(Qt::blue, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QVERIFY(qAbs(value - 0.25) < 0.05); dev->fill(QRect(0,50,25,50), KoColor(Qt::blue, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QCOMPARE(value, 0.0); } void KisLazyBrushTest::multiwayCutBenchmark() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); const KoColor fillColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); QRect mainRect(0,0,512,512); QPainterPath path; path.moveTo(100, 100); path.lineTo(400, 100); path.lineTo(400, 400); path.lineTo(100, 400); path.lineTo(100, 120); KisFillPainter gc(mainDev); gc.setPaintColor(fillColor); gc.drawPainterPath(path, QPen(Qt::white, 10)); gc.fillRect(QRect(250, 100, 15, 120), fillColor); gc.fillRect(QRect(250, 280, 15, 120), fillColor); gc.fillRect(QRect(100, 250, 120, 15), fillColor); gc.fillRect(QRect(280, 250, 120, 15), fillColor); //KIS_DUMP_DEVICE_2(mainDev, mainRect, "1main", "dd"); KisPaintDeviceSP aLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); aLabelDev->fill(QRect(110, 110, 30,30), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP bLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); bLabelDev->fill(QRect(370, 110, 20,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP cLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); cLabelDev->fill(QRect(370, 370, 20,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP dLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); dLabelDev->fill(QRect(110, 370, 20,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP eLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); eLabelDev->fill(QRect(0, 0, 200,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsAlpha(mainDev); KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, mainRect); KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); KisMultiwayCut cut(filteredMainDev, resultColoring, mainRect); cut.addKeyStroke(aLabelDev, KoColor(Qt::red, mainDev->colorSpace())); cut.addKeyStroke(bLabelDev, KoColor(Qt::green, mainDev->colorSpace())); cut.addKeyStroke(cLabelDev, KoColor(Qt::blue, mainDev->colorSpace())); cut.addKeyStroke(dLabelDev, KoColor(Qt::yellow, mainDev->colorSpace())); cut.addKeyStroke(eLabelDev, KoColor(Qt::transparent, mainDev->colorSpace())); QBENCHMARK_ONCE { cut.run(); } // KIS_DUMP_DEVICE_2(resultColoring, mainRect, "00result", "dd"); // KIS_DUMP_DEVICE_2(mainDev, mainRect, "1main", "dd"); // KIS_DUMP_DEVICE_2(filteredMainDev, mainRect, "2filtered", "dd"); } QTEST_MAIN(KisLazyBrushTest) diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index f958b0acdc..f1d0c15203 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,416 +1,416 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program 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 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 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 "Krita.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 "View.h" #include "Document.h" #include "Window.h" #include "Extension.h" #include "DockWidgetFactoryBase.h" #include "Filter.h" #include "InfoObject.h" #include "Resource.h" Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList extensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); connect(KisPart::instance(), SIGNAL(sigWindowAdded(KisMainWindow*)), SLOT(mainWindowAdded(KisMainWindow*))); } Krita::~Krita() { qDeleteAll(d->extensions); delete d->notifier; delete d; } QList Krita::actions() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return QList(); } KActionCollection *actionCollection = mainWindow->actionCollection(); return actionCollection->actions(); } QAction *Krita::action(const QString &name) const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KActionCollection *actionCollection = mainWindow->actionCollection(); QAction *action = actionCollection->action(name); return action; } Document* Krita::activeDocument() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KisView *view = mainWindow->activeView(); if (!view) { return 0; } KisDocument *document = view->document(); return new Document(document); } void Krita::setActiveDocument(Document* value) { Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == value->document().data()) { view->activateWindow(); break; } } } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } QList Krita::documents() const { QList ret; foreach(QPointer doc, KisPart::instance()->documents()) { ret << new Document(doc); } return ret; } QStringList Krita::filters() const { QStringList ls = KisFilterRegistry::instance()->keys(); std::sort(ls.begin(), ls.end()); return ls; } Filter *Krita::filter(const QString &name) const { if (!filters().contains(name)) return 0; Filter *filter = new Filter(); filter->setName(name); KisFilterSP f = KisFilterRegistry::instance()->value(name); KisFilterConfigurationSP fc = f->defaultConfiguration(); InfoObject *info = new InfoObject(fc); filter->setConfiguration(info); return filter; } QStringList Krita::colorModels() const { QSet colorModelsIds; QList ids = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorModelsIds << id.id(); } - return colorModelsIds.toList();; + return colorModelsIds.toList(); } QStringList Krita::colorDepths(const QString &colorModel) const { QSet colorDepthsIds; QList ids = KoColorSpaceRegistry::instance()->colorDepthList(colorModel, KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorDepthsIds << id.id(); } - return colorDepthsIds.toList();; + return colorDepthsIds.toList(); } QStringList Krita::filterStrategies() const { return KisFilterStrategyRegistry::instance()->keys(); } QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const { QSet profileNames; QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth); QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id); Q_FOREACH(const KoColorProfile *profile, profiles) { profileNames << profile->name(); } return profileNames.toList(); } bool Krita::addProfile(const QString &profilePath) { KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); return iccEngine->addProfile(profilePath); } Notifier* Krita::notifier() const { return d->notifier; } QString Krita::version() const { return KritaVersionWrapper::versionString(true); } QList Krita::views() const { QList ret; foreach(QPointer view, KisPart::instance()->views()) { ret << new View(view); } return ret; } Window *Krita::activeWindow() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } return new Window(mainWindow); } QList Krita::windows() const { QList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { ret << new Window(mainWin); } return ret; } QMap Krita::resources(const QString &type) const { QMap resources = QMap (); if (type.toLower() == "pattern") { KoResourceServer* server = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type.toLower() == "gradient") { KoResourceServer* server = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type.toLower() == "brush") { KisBrushResourceServer* server = KisBrushServer::instance()->brushServer(); Q_FOREACH (KisBrushSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type.toLower() == "preset") { KisPaintOpPresetResourceServer* server = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (KisPaintOpPresetSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type.toLower() == "palette") { KoResourceServer* server = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type.toLower() == "workspace") { KoResourceServer< KisWorkspaceResource >* server = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } return resources; } QStringList Krita::recentDocuments() const { KConfigGroup grp = KSharedConfig::openConfig()->group(QString("RecentFiles")); QStringList keys = grp.keyList(); QStringList recentDocuments; for(int i = 0; i <= keys.filter("File").count(); i++) recentDocuments << grp.readEntry(QString("File%1").arg(i), QString("")); return recentDocuments; } Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile, double resolution) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); Q_ASSERT(cs); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); if (!document->newImage(name, width, height, cs, bgColor, true, 1, "", double(resolution / 72) )) { qDebug() << "Could not create a new image"; return 0; } Q_ASSERT(document->image()); qDebug() << document->image()->objectName(); return new Document(document); } Document* Krita::openDocument(const QString &filename) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); document->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); return new Document(document); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } void Krita::addExtension(Extension* extension) { d->extensions.append(extension); } QList< Extension* > Krita::extensions() { return d->extensions; } void Krita::writeSetting(const QString &group, const QString &name, const QString &value) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); grp.writeEntry(name, value); } QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); return grp.readEntry(name, defaultValue); } QIcon Krita::icon(QString &iconName) const { return KisIconUtils::loadIcon(iconName); } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } void Krita::mainWindowAdded(KisMainWindow *kisWindow) { Q_FOREACH(Extension *extension, d->extensions) { Window window(kisWindow); extension->createActions(&window); } } diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp index ebee7c2e19..f9aac97720 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,605 +1,605 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program 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 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 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 #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 "Krita.h" #include "Node.h" #include "Channel.h" #include "Filter.h" #include "Selection.h" #include "GroupLayer.h" #include "CloneLayer.h" #include "FilterLayer.h" #include "FillLayer.h" #include "FileLayer.h" #include "VectorLayer.h" #include "FilterMask.h" #include "SelectionMask.h" #include "LibKisUtils.h" struct Node::Private { Private() {} KisImageWSP image; KisNodeSP node; }; Node::Node(KisImageSP image, KisNodeSP node, QObject *parent) : QObject(parent) , d(new Private) { d->image = image; d->node = node; } Node::~Node() { delete d; } bool Node::operator==(const Node &other) const { return (d->node == other.d->node && d->image == other.d->image); } bool Node::operator!=(const Node &other) const { return !(operator==(other)); } Node *Node::clone() const { KisNodeSP clone = d->node->clone(); Node *node = new Node(0, clone); return node; } bool Node::alphaLocked() const { if (!d->node) return false; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { return paintLayer->alphaLocked(); } return false; } void Node::setAlphaLocked(bool value) { if (!d->node) return; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { paintLayer->setAlphaLocked(value); } } QString Node::blendingMode() const { if (!d->node) return QString(); return d->node->compositeOpId(); } void Node::setBlendingMode(QString value) { if (!d->node) return; d->node->setCompositeOpId(value); } QList Node::channels() const { QList channels; if (!d->node) return channels; if (!d->node->inherits("KisLayer")) return channels; Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) { Channel *channel = new Channel(d->node, info); channels << channel; } return channels; } QList Node::childNodes() const { QList nodes; if (d->node) { KisNodeList nodeList; int childCount = d->node->childCount(); for (int i = 0; i < childCount; ++i) { nodeList << d->node->at(i); } nodes = LibKisUtils::createNodeList(nodeList, d->image); } return nodes; } bool Node::addChildNode(Node *child, Node *above) { if (!d->node) return false; if (above) { return d->image->addNode(child->node(), d->node, above->node()); } else { return d->image->addNode(child->node(), d->node, d->node->childCount()); } } bool Node::removeChildNode(Node *child) { if (!d->node) return false; return d->image->removeNode(child->node()); } void Node::setChildNodes(QList nodes) { if (!d->node) return; KisNodeSP node = d->node->firstChild(); while (node) { d->image->removeNode(node); node = node->nextSibling(); } Q_FOREACH(Node *node, nodes) { d->image->addNode(node->node(), d->node); } } int Node::colorLabel() const { if (!d->node) return 0; return d->node->colorLabelIndex(); } void Node::setColorLabel(int index) { if (!d->node) return; d->node->setColorLabelIndex(index); } QString Node::colorDepth() const { if (!d->node) return ""; return d->node->colorSpace()->colorDepthId().id(); } QString Node::colorModel() const { if (!d->node) return ""; return d->node->colorSpace()->colorModelId().id(); } QString Node::colorProfile() const { if (!d->node) return ""; return d->node->colorSpace()->profile()->name(); } bool Node::setColorProfile(const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(srcCS->colorModelId().id(), srcCS->colorDepthId().id(), profile); KisChangeProfileVisitor v(srcCS, dstCs); return layer->accept(v); } bool Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); KisColorSpaceConvertVisitor v(d->image, srcCS, dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return layer->accept(v); } bool Node::animated() const { if (!d->node) return false; return d->node->isAnimated(); } void Node::enableAnimation() const { if (!d->node) return; d->node->enableAnimation(); } bool Node::collapsed() const { if (!d->node) return false; return d->node->collapsed(); } void Node::setCollapsed(bool collapsed) { if (!d->node) return; d->node->setCollapsed(collapsed); } bool Node::inheritAlpha() const { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; return qobject_cast(d->node)->alphaChannelDisabled(); } void Node::setInheritAlpha(bool value) { if (!d->node) return; if (!d->node->inherits("KisLayer")) return; const_cast(qobject_cast(d->node))->disableAlphaChannel(value); } bool Node::locked() const { if (!d->node) return false; return d->node->userLocked(); } void Node::setLocked(bool value) { if (!d->node) return; d->node->setUserLocked(value); } QString Node::name() const { if (!d->node) return QString(); return d->node->name(); } void Node::setName(QString name) { if (!d->node) return; d->node->setName(name); } int Node::opacity() const { if (!d->node) return 0; return d->node->opacity(); } void Node::setOpacity(int value) { if (!d->node) return; if (value < 0) value = 0; if (value > 255) value = 255; d->node->setOpacity(value); } Node* Node::parentNode() const { if (!d->node) return 0; return new Node(d->image, d->node->parent()); } QString Node::type() const { if (!d->node) return QString(); if (qobject_cast(d->node)) { return "paintlayer"; } else if (qobject_cast(d->node)) { return "grouplayer"; } if (qobject_cast(d->node)) { return "filelayer"; } if (qobject_cast(d->node)) { return "filterlayer"; } if (qobject_cast(d->node)) { return "filllayer"; } if (qobject_cast(d->node)) { return "clonelayer"; } if (qobject_cast(d->node)) { return "referenceimageslayer"; } if (qobject_cast(d->node)) { return "vectorlayer"; } if (qobject_cast(d->node)) { return "transparencymask"; } if (qobject_cast(d->node)) { return "filtermask"; } if (qobject_cast(d->node)) { return "transformmask"; } if (qobject_cast(d->node)) { return "selectionmask"; } if (qobject_cast(d->node)) { return "colorizemask"; } return QString(); } QIcon Node::icon() const { QIcon icon; if (d->node) { icon = d->node->icon(); } return icon; } bool Node::visible() const { if (!d->node) return false; - return d->node->visible();; + return d->node->visible(); } void Node::setVisible(bool visible) { if (!d->node) return; d->node->setVisible(visible); } QByteArray Node::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::pixelDataAtTime(int x, int y, int w, int h, int time) const { QByteArray ba; if (!d->node || !d->node->isAnimated()) return ba; // KisRasterKeyframeChannel *rkc = dynamic_cast(d->node->getKeyframeChannel(KisKeyframeChannel::Content.id())); if (!rkc) return ba; KisKeyframeSP frame = rkc->keyframeAt(time); if (!frame) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; rkc->fetchFrame(frame, dev); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::projectionPixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } void Node::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->node) return; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } QRect Node::bounds() const { if (!d->node) return QRect(); return d->node->exactBounds(); } void Node::move(int x, int y) { if (!d->node) return; d->node->setX(x); d->node->setY(y); } QPoint Node::position() const { if (!d->node) return QPoint(); return QPoint(d->node->x(), d->node->y()); } bool Node::remove() { if (!d->node) return false; if (!d->node->parent()) return false; return d->image->removeNode(d->node); } Node* Node::duplicate() { if (!d->node) return 0; return new Node(d->image, d->node->clone()); } bool Node::save(const QString &filename, double xRes, double yRes) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); QRect bounds = d->node->exactBounds(); QString mimeType = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.right(), bounds.bottom(), projection->compositionSourceColorSpace(), d->node->name()); dst->setResolution(xRes, yRes); doc->setFileBatchMode(Krita::instance()->batchmode()); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity()); paintLayer->paintDevice()->makeCloneFrom(projection, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->cropImage(bounds); dst->initialRefreshGraph(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(filename), mimeType.toLatin1()); if (!r) { qWarning() << doc->errorMessage(); } return r; } Node *Node::mergeDown() { if (!d->node) return 0; if (!qobject_cast(d->node.data())) return 0; if (!d->node->nextSibling()) return 0; if (!d->node->parent()) return 0; int index = d->node->parent()->index(d->node->prevSibling()); d->image->mergeDown(qobject_cast(d->node.data()), KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); d->image->waitForDone(); return new Node(d->image, d->node->parent()->at(index)); } void Node::scaleNode(int width, int height, QString strategy) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); d->image->scaleNode(d->node, width, height, actualStrategy); } void Node::rotateNode(double radians) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; d->image->rotateNode(d->node, radians); } void Node::cropNode(int x, int y, int w, int h) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; QRect rect = QRect(x, y, w, h); d->image->cropNode(d->node, rect); } void Node::shearNode(double angleX, double angleY) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; d->image->shearNode(d->node, angleX, angleY); } QImage Node::thumbnail(int w, int h) { if (!d->node) return QImage(); return d->node->createThumbnail(w, h); } KisPaintDeviceSP Node::paintDevice() const { return d->node->paintDevice(); } KisImageSP Node::image() const { return d->image; } KisNodeSP Node::node() const { return d->node; } diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 92ebb300fd..9fa531c60d 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1569 +1,1569 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia 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 library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColor.h" #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColorModelStandardIds.h" struct KoColorSet::Private { KoColorSet::PaletteType paletteType; QByteArray data; QString comment; qint32 columns; QVector colors; //ungrouped colors QStringList groupNames; //names of the groups, this is used to determine the order they are in. QMap> groups; //grouped colors. }; KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } else if (fi.suffix().toLower() == "kpl") { return KoColorSet::KPL; } else if (fi.suffix().toLower() == "sbz") { return KoColorSet::SBZ; } return KoColorSet::UNKNOWN; } KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private()) { // Implemented in KoResource class d->columns = 0; // Set the default value that the GIMP uses... } KoColorSet::KoColorSet() : KoResource(QString()) , d(new Private()) { d->columns = 0; // Set the default value that the GIMP uses... } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->comment = rhs.d->comment; d->columns = rhs.d->columns; d->colors = rhs.d->colors; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; setValid(true); } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return init(); } bool KoColorSet::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = saveGpl(dev); break; default: res = saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } bool KoColorSet::init() { d->colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog), d->groups.clear(); d->groupNames.clear(); if (filename().isNull()) { warnPigment << "Cannot load palette" << name() << "there is no filename set"; return false; } if (d->data.isNull()) { QFile file(filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); d->data = file.readAll(); file.close(); } bool res = false; d->paletteType = detectFormat(filename(), d->data); switch(d->paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; case KPL: res = loadKpl(); break; case SBZ: res = loadSbz(); break; default: res = false; } setValid(res); if (d->columns == 0) { d->columns = 10; } QImage img(d->columns * 4, (d->colors.size() / d->columns) * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); int counter = 0; for(int i = 0; i < d->columns; ++i) { for (int j = 0; j < (d->colors.size() / d->columns); ++j) { if (counter < d->colors.size()) { QColor c = d->colors.at(counter).color.toQColor(); gc.fillRect(i * 4, j * 4, 4, 4, c); counter++; } else { break; } } } setImage(img); // save some memory d->data.clear(); return res; } bool KoColorSet::saveGpl(QIODevice *dev) const { QTextStream stream(dev); stream << "GIMP Palette\nName: " << name() << "\nColumns: " << d->columns << "\n#\n"; for (int i = 0; i < d->colors.size(); i++) { const KoColorSetEntry& entry = d->colors.at(i); QColor c = entry.color.toQColor(); stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; if (entry.name.isEmpty()) stream << "Untitled\n"; else stream << entry.name << "\n"; } return true; } quint32 KoColorSet::nColors() { if (d->colors.isEmpty()) return 0; quint32 total = d->colors.size(); if (!d->groups.empty()) { Q_FOREACH (const QVector &group, d->groups.values()) { total += group.size(); } } return total; } quint32 KoColorSet::nColorsGroup(QString groupName) { if (d->groups.contains(groupName)) { return d->groups.value(groupName).size(); } else if (groupName.isEmpty() && !d->colors.isEmpty()){ return d->colors.size(); } else { return 0; } } quint32 KoColorSet::getIndexClosestColor(const KoColor color, bool useGivenColorSpace) { quint32 closestIndex = 0; quint8 highestPercentage = 0; quint8 testPercentage = 0; KoColor compare = color; for (quint32 i=0; idifference(compare.data(), entry.data())); if (testPercentage>highestPercentage) { closestIndex = i; highestPercentage = testPercentage; } } return closestIndex; } QString KoColorSet::closestColorName(const KoColor color, bool useGivenColorSpace) { int i = getIndexClosestColor(color, useGivenColorSpace); QString name = getColorGlobal(i).name; return name; } void KoColorSet::add(const KoColorSetEntry & c, QString groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { d->groups[groupName].push_back(c); } else { d->colors.push_back(c); } } quint32 KoColorSet::insertBefore(const KoColorSetEntry &c, qint32 index, const QString &groupName) { quint32 newIndex = index; if (d->groups.contains(groupName)) { d->groups[groupName].insert(index, c); } else if (groupName.isEmpty()){ - d->colors.insert(index, c);; + d->colors.insert(index, c); } else { warnPigment << "Couldn't find group to insert to"; } return newIndex; } void KoColorSet::removeAt(quint32 index, QString groupName) { if (d->groups.contains(groupName)){ if ((quint32)d->groups.value(groupName).size()>index) { d->groups[groupName].remove(index); } } else { if ((quint32)d->colors.size()>index) { d->colors.remove(index); } } } void KoColorSet::clear() { d->colors.clear(); d->groups.clear(); } KoColorSetEntry KoColorSet::getColorGlobal(quint32 index) { KoColorSetEntry e; quint32 groupIndex = index; QString groupName = findGroupByGlobalIndex(index, &groupIndex); e = getColorGroup(groupIndex, groupName); return e; } KoColorSetEntry KoColorSet::getColorGroup(quint32 index, QString groupName) { KoColorSetEntry e; if (d->groups.contains(groupName)) { if (nColorsGroup(groupName)>index) { e = d->groups.value(groupName).at(index); } else { warnPigment<index) { e = d->colors.at(index); } else { warnPigment<colors.size()<=*index) { *index -= (quint32)d->colors.size(); if (!d->groups.empty() || !d->groupNames.empty()) { QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { quint32 size = (quint32)d->groups.value(name).size(); if (size<=*index) { *index -= size; } else { groupName = name; return groupName; } } } } return groupName; } QString KoColorSet::findGroupByColorName(const QString &name, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { if(d->colors.at(i).name == name) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { if(d->groups[name].at(i).name == name) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QString KoColorSet::findGroupByID(const QString &id, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { if(d->colors.at(i).id == id) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { if(d->groups[name].at(i).id == id) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QStringList KoColorSet::getGroupNames() { if (d->groupNames.size()groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(QString oldGroupName, QString newGroupName) { if (d->groupNames.contains(oldGroupName)==false) { return false; } QVector dummyList = d->groups.value(oldGroupName); d->groups.remove(oldGroupName); d->groups[newGroupName] = dummyList; //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } bool KoColorSet::changeColorSetEntry(KoColorSetEntry entry, QString groupName, quint32 index) { if (index>=nColorsGroup(groupName) || (d->groupNames.contains(groupName)==false && groupName.size()>0)) { return false; } if (groupName==QString()) { d->colors[index] = entry; } else { d->groups[groupName][index] = entry; } return true; } void KoColorSet::setColumnCount(int columns) { d->columns = columns; } int KoColorSet::columnCount() { return d->columns; } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName] = QVector(); return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.size(); if (groupNameInsertBefore!=QString()) { index = d->groupNames.indexOf(groupNameInsertBefore); } d->groupNames.insert(index, groupName); return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (keepColors) { for (int i = 0; igroups.value(groupName).size(); i++) { d->colors.append(d->groups.value(groupName).at(i)); } } for(int n = 0; ngroupNames.size(); n++) { if (d->groupNames.at(n) == groupName) { d->groupNames.removeAt(n); } } d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } bool KoColorSet::loadGpl() { QString s = QString::fromUtf8(d->data.data(), d->data.count()); if (s.isEmpty() || s.isNull() || s.length() < 50) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } quint32 index = 0; QStringList lines = s.split('\n', QString::SkipEmptyParts); if (lines.size() < 3) { return false; } QString columns; qint32 r, g, b; KoColorSetEntry e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].startsWith("Name: ")) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1())); index = 2; // Read columns if (lines[index].startsWith("Columns: ")) { columns = lines[index].mid(strlen("Columns: ")).trimmed(); d->columns = columns.toInt(); index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { d->comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { break; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } } return true; } bool KoColorSet::loadAct() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; for (int i = 0; i < d->data.size(); i += 3) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } struct RiffHeader { quint32 riff; quint32 size; quint32 signature; quint32 data; quint32 datasize; quint16 version; quint16 colorcount; }; bool KoColorSet::loadRiff() { // http://worms2d.info/Palette_file QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; RiffHeader header; memcpy(&header, d->data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < d->data.size()); i += 4) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } bool KoColorSet::loadPsp() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; qint32 r, g, b; QString s = QString::fromUtf8(d->data.data(), d->data.count()); QStringList l = s.split('\n', QString::SkipEmptyParts); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } return true; } void scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KoColorSetEntry currentColor; //It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorValue; // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; currentColor.color.data()[0] = r; currentColor.color.data()[1] = g; currentColor.color.data()[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; currentColor.color.data()[0] = c; currentColor.color.data()[1] = m; currentColor.color.data()[2] = y; currentColor.color.data()[3] = k; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + currentColor.name); } } bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } bool KoColorSet::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(d->data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << filename() << ", Scribus format"; res = loadScribusXmlPalette(this, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << filename(); return true; } } bool KoColorSet::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; QSet profiles; QMap profileMap; { QDomDocument doc; QDomElement root = doc.createElement("Colorset"); root.setAttribute("version", "1.0"); root.setAttribute("name", name()); root.setAttribute("comment", d->comment); root.setAttribute("columns", d->columns); Q_FOREACH(const KoColorSetEntry &entry, d->colors) { // Only save non-builtin profiles.= const KoColorProfile *profile = entry.color.colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; profileMap[profile] = entry.color.colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); el.setAttribute("name", entry.name); el.setAttribute("id", entry.id); el.setAttribute("spot", entry.spotColor ? "true" : "false"); el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); entry.color.toXML(doc, el); root.appendChild(el); } Q_FOREACH(const QString &groupName, d->groupNames) { QDomElement gl = doc.createElement("Group"); gl.setAttribute("name", groupName); root.appendChild(gl); Q_FOREACH(const KoColorSetEntry &entry, d->groups.value(groupName)) { // Only save non-builtin profiles.= const KoColorProfile *profile = entry.color.colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; profileMap[profile] = entry.color.colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); el.setAttribute("name", entry.name); el.setAttribute("id", entry.id); el.setAttribute("spot", entry.spotColor ? "true" : "false"); el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); entry.color.toXML(doc, el); gl.appendChild(el); } } doc.appendChild(root); if (!store->open("colorset.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } } QDomDocument doc; QDomElement profileElement = doc.createElement("Profiles"); Q_FOREACH(const KoColorProfile *profile, profiles) { QString fn = QFileInfo(profile->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = profile->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement("Profile"); el.setAttribute("filename", fn); el.setAttribute("name", profile->name()); el.setAttribute("colorModelId", profileMap[profile]->colorModelId().id()); el.setAttribute("colorDepthId", profileMap[profile]->colorDepthId().id()); profileElement.appendChild(el); } doc.appendChild(profileElement); if (!store->open("profiles.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } return store->finalize(); } bool KoColorSet::loadKpl() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("profiles.xml")) { if (!store->open("profiles.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement("Profiles"); while (!c.isNull()) { QString name = c.attribute("name"); QString filename = c.attribute("filename"); QString colorModelId = c.attribute("colorModelId"); QString colorDepthId = c.attribute("colorDepthId"); if (!KoColorSpaceRegistry::instance()->profileByName(name)) { store->open(filename); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); } } c = c.nextSiblingElement(); } } { if (!store->open("colorset.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); setName(e.attribute("name")); d->comment = e.attribute("comment"); d->columns = e.attribute("columns").toInt(); QDomElement c = e.firstChildElement("ColorSetEntry"); while (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; entry.color = KoColor::fromXML(c.firstChildElement(), colorDepthId); entry.name = c.attribute("name"); entry.id = c.attribute("id"); entry.spotColor = c.attribute("spot", "false") == "true" ? true : false; d->colors << entry; c = c.nextSiblingElement("ColorSetEntry"); } QDomElement g = e.firstChildElement("Group"); while (!g.isNull()) { QString groupName = g.attribute("name"); addGroup(groupName); QDomElement cg = g.firstChildElement("ColorSetEntry"); while (!cg.isNull()) { QString colorDepthId = cg.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; entry.color = KoColor::fromXML(cg.firstChildElement(), colorDepthId); entry.name = cg.attribute("name"); entry.id = cg.attribute("id"); entry.spotColor = cg.attribute("spot", "false") == "true" ? true : false; add(entry, groupName); cg = cg.nextSiblingElement("ColorSetEntry"); } g = g.nextSiblingElement("Group"); } } buf.close(); return true; } quint16 readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::loadAco() { QFileInfo info(filename()); setName(info.baseName()); QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KoColorSetEntry e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 1) { // HSB e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16()); QColor c; c.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); e.color.fromQColor(c); e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 2) { // CMYK e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = quint16_MAX - ch1; reinterpret_cast(e.color.data())[1] = quint16_MAX - ch2; reinterpret_cast(e.color.data())[2] = quint16_MAX - ch3; reinterpret_cast(e.color.data())[3] = quint16_MAX - ch4; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 7) { // LAB e.color = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 8) { // GRAY e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = ch1 * (quint16_MAX / 10000); e.color.setOpacity(OPACITY_OPAQUE_U8); } else { warnPigment << "Unsupported colorspace in palette" << filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); e.name = Utf16Codec->toUnicode(ba); } else { warnPigment << "Version 2 name block is the wrong size" << filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { add(e); } } return true; } bool KoColorSet::loadSbz() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); // &buf is a subclass of QIODevice QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("swatchbook.xml")) { // Try opening... if (!store->open("swatchbook.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); dbgPigment << "XML palette: " << filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; return false; } QDomElement e = doc.documentElement(); // SwatchBook // Start reading properties... QDomElement metadata = e.firstChildElement("metadata"); if (e.isNull()) { warnPigment << "Palette metadata not found"; return false; } QDomElement title = metadata.firstChildElement("dc:title"); QString colorName = title.text(); colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; setName(colorName); dbgPigment << "Processed name of palette:" << name(); // End reading properties // Now read colors... QDomElement materials = e.firstChildElement("materials"); if (materials.isNull()) { warnPigment << "Materials (color definitions) not found"; return false; } // This one has lots of "color" elements QDomElement colorElement = materials.firstChildElement("color"); if (colorElement.isNull()) { warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; return false; } // Also read the swatch book... QDomElement book = e.firstChildElement("book"); if (book.isNull()) { warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; return false; } // Which has lots of "swatch"es (todo: support groups) QDomElement swatch = book.firstChildElement(); if (swatch.isNull()) { warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; return false; } // We'll store colors here, and as we process swatches // we'll add them to the palette QHash materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { KoColorSetEntry currentColor; // Set if color is spot currentColor.spotColor = colorElement.attribute("usage") == "spot"; // inside contains id and name // one or more define the color QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); // Get color name QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); // Is there an id? (we need that at the very least for identifying a color) if (colorId.text().isEmpty()) { warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } if (materialsBook.contains(colorId.text())) { warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } // Get a valid color name currentColor.id = colorId.text(); currentColor.name = colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text(); // Get a valid color definition if (currentColorValues.isEmpty()) { warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; return false; } bool firstDefinition = false; const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); // Priority: Lab, otherwise the first definition found for(int j = 0; j < currentColorValues.size(); j++) { QDomNode colorValue = currentColorValues.at(j); QDomElement colorValueE = colorValue.toElement(); QString model = colorValueE.attribute("model", QString()); // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 // Lab: L 0 -> 100 : ab -128 -> 127 // XYZ: 0 -> ~100 if (model == "Lab") { QStringList lab = colorValueE.text().split(" "); if (lab.length() != 3) { warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float l = lab.at(0).toFloat(&status); float a = lab.at(1).toFloat(&status); float b = lab.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(currentColor.color.data())[0] = l; reinterpret_cast(currentColor.color.data())[1] = a; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; break; // Immediately add this one } else if (model == "sRGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); reinterpret_cast(currentColor.color.data())[0] = r; reinterpret_cast(currentColor.color.data())[1] = g; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "XYZ" && !firstDefinition) { QStringList xyz = colorValueE.text().split(" "); if (xyz.length() != 3) { warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float x = xyz.at(0).toFloat(&status); float y = xyz.at(1).toFloat(&status); float z = xyz.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(currentColor.color.data())[0] = x; reinterpret_cast(currentColor.color.data())[1] = y; reinterpret_cast(currentColor.color.data())[2] = z; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } // The following color spaces admit an ICC profile (in SwatchBooker) else if (model == "CMYK" && !firstDefinition) { QStringList cmyk = colorValueE.text().split(" "); if (cmyk.length() != 4) { warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float c = cmyk.at(0).toFloat(&status); float m = cmyk.at(1).toFloat(&status); float y = cmyk.at(2).toFloat(&status); float k = cmyk.at(3).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = c; reinterpret_cast(currentColor.color.data())[1] = m; reinterpret_cast(currentColor.color.data())[2] = y; reinterpret_cast(currentColor.color.data())[3] = k; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "GRAY" && !firstDefinition) { QString gray = colorValueE.text(); float g = gray.toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = g; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "RGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = r; reinterpret_cast(currentColor.color.data())[1] = g; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { materialsBook.insert(currentColor.id, currentColor); } else { warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; return false; } } // End colors // Now decide which ones will go into the palette for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { QString type = swatch.tagName(); if (type.isEmpty() || type.isNull()) { warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } else if (type == "swatch") { QString id = swatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } } else if (type == "group") { QDomElement groupMetadata = swatch.firstChildElement("metadata"); if (groupMetadata.isNull()) { warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; return false; } QDomElement groupTitle = metadata.firstChildElement("dc:title"); if (groupTitle.isNull()) { warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; return false; } QString currentGroupName = groupTitle.text(); QDomElement groupSwatch = swatch.firstChildElement("swatch"); while(!groupSwatch.isNull()) { QString id = groupSwatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id), currentGroupName); } else { warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } groupSwatch = groupSwatch.nextSiblingElement("swatch"); } } } // End palette } buf.close(); return true; } diff --git a/libs/ui/dialogs/kis_dlg_layer_style.cpp b/libs/ui/dialogs/kis_dlg_layer_style.cpp index 854a92272f..3e94ed1088 100644 --- a/libs/ui/dialogs/kis_dlg_layer_style.cpp +++ b/libs/ui/dialogs/kis_dlg_layer_style.cpp @@ -1,1451 +1,1451 @@ /* * Copyright (c) 2014 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_dlg_layer_style.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cmb_contour.h" #include "kis_cmb_gradient.h" #include "KisResourceServerProvider.h" #include "kis_psd_layer_style_resource.h" #include "kis_psd_layer_style.h" #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_canvas_resource_provider.h" #include KoAbstractGradient* fetchGradientLazy(KoAbstractGradient *gradient, KisCanvasResourceProvider *resourceProvider) { if (!gradient) { gradient = resourceProvider->currentGradient(); } return gradient; } KisDlgLayerStyle::KisDlgLayerStyle(KisPSDLayerStyleSP layerStyle, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : KoDialog(parent) , m_layerStyle(layerStyle) , m_initialLayerStyle(layerStyle->clone()) , m_isSwitchingPredefinedStyle(false) , m_sanityLayerStyleDirty(false) { setCaption(i18n("Layer Styles")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_configChangedCompressor = new KisSignalCompressor(1000, KisSignalCompressor::POSTPONE, this); connect(m_configChangedCompressor, SIGNAL(timeout()), SIGNAL(configChanged())); QWidget *page = new QWidget(this); wdgLayerStyles.setupUi(page); setMainWidget(page); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(notifyGuiConfigChanged())); m_stylesSelector = new StylesSelector(this); connect(m_stylesSelector, SIGNAL(styleSelected(KisPSDLayerStyleSP)), SLOT(notifyPredefinedStyleSelected(KisPSDLayerStyleSP))); wdgLayerStyles.stylesStack->addWidget(m_stylesSelector); m_blendingOptions = new BlendingOptions(this); wdgLayerStyles.stylesStack->addWidget(m_blendingOptions); m_dropShadow = new DropShadow(DropShadow::DropShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_dropShadow); connect(m_dropShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerShadow = new DropShadow(DropShadow::InnerShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_innerShadow); connect(m_innerShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_outerGlow = new InnerGlow(InnerGlow::OuterGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_outerGlow); connect(m_outerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerGlow = new InnerGlow(InnerGlow::InnerGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_innerGlow); connect(m_innerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_contour = new Contour(this); m_texture = new Texture(this); m_bevelAndEmboss = new BevelAndEmboss(m_contour, m_texture, this); wdgLayerStyles.stylesStack->addWidget(m_bevelAndEmboss); wdgLayerStyles.stylesStack->addWidget(m_contour); wdgLayerStyles.stylesStack->addWidget(m_texture); connect(m_bevelAndEmboss, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_satin = new Satin(this); wdgLayerStyles.stylesStack->addWidget(m_satin); connect(m_satin, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_colorOverlay = new ColorOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_colorOverlay); connect(m_colorOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_gradientOverlay = new GradientOverlay(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_gradientOverlay); connect(m_gradientOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_patternOverlay = new PatternOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_patternOverlay); connect(m_patternOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_stroke = new Stroke(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_stroke); connect(m_stroke, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); KisConfig cfg; wdgLayerStyles.stylesStack->setCurrentIndex(cfg.readEntry("KisDlgLayerStyle::current", 1)); wdgLayerStyles.lstStyleSelector->setCurrentRow(cfg.readEntry("KisDlgLayerStyle::current", 1)); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); notifyPredefinedStyleSelected(layerStyle); connect(m_dropShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_innerShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_bevelAndEmboss, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(wdgLayerStyles.btnNewStyle, SIGNAL(clicked()), SLOT(slotNewStyle())); connect(wdgLayerStyles.btnLoadStyle, SIGNAL(clicked()), SLOT(slotLoadStyle())); connect(wdgLayerStyles.btnSaveStyle, SIGNAL(clicked()), SLOT(slotSaveStyle())); connect(wdgLayerStyles.chkMasterFxSwitch, SIGNAL(toggled(bool)), SLOT(slotMasterFxSwitchChanged(bool))); connect(this, SIGNAL(accepted()), SLOT(slotNotifyOnAccept())); connect(this, SIGNAL(rejected()), SLOT(slotNotifyOnReject())); } KisDlgLayerStyle::~KisDlgLayerStyle() { } void KisDlgLayerStyle::slotMasterFxSwitchChanged(bool value) { wdgLayerStyles.lstStyleSelector->setEnabled(value); wdgLayerStyles.stylesStack->setEnabled(value); wdgLayerStyles.btnNewStyle->setEnabled(value); wdgLayerStyles.btnLoadStyle->setEnabled(value); wdgLayerStyles.btnSaveStyle->setEnabled(value); notifyGuiConfigChanged(); } void KisDlgLayerStyle::notifyGuiConfigChanged() { if (m_isSwitchingPredefinedStyle) return; m_configChangedCompressor->start(); m_layerStyle->setUuid(QUuid::createUuid()); m_sanityLayerStyleDirty = true; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } void KisDlgLayerStyle::notifyPredefinedStyleSelected(KisPSDLayerStyleSP style) { m_isSwitchingPredefinedStyle = true; setStyle(style); m_isSwitchingPredefinedStyle = false; m_configChangedCompressor->start(); } void KisDlgLayerStyle::slotNotifyOnAccept() { if (m_configChangedCompressor->isActive()) { m_configChangedCompressor->stop(); emit configChanged(); } } void KisDlgLayerStyle::slotNotifyOnReject() { notifyPredefinedStyleSelected(m_initialLayerStyle); m_configChangedCompressor->stop(); emit configChanged(); } bool checkCustomNameAvailable(const QString &name) { const QString customName = "CustomStyles.asl"; KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); KoResource *resource = server->resourceByName(customName); if (!resource) return true; KisPSDLayerStyleCollectionResource *collection = dynamic_cast(resource); Q_FOREACH (KisPSDLayerStyleSP style, collection->layerStyles()) { if (style->name() == name) { return false; } } return true; } QString selectAvailableStyleName(const QString &name) { QString finalName = name; if (checkCustomNameAvailable(finalName)) { return finalName; } int i = 0; do { finalName = QString("%1%2").arg(name).arg(i++); } while (!checkCustomNameAvailable(finalName)); return finalName; } void KisDlgLayerStyle::slotNewStyle() { QString styleName = QInputDialog::getText(this, i18nc("@title:window", "Enter new style name"), i18nc("@label:textbox", "Name:"), QLineEdit::Normal, i18nc("Default name for a new style", "New Style")); KisPSDLayerStyleSP style = this->style(); style->setName(selectAvailableStyleName(styleName)); m_stylesSelector->addNewStyle(style->clone()); } void KisDlgLayerStyle::slotLoadStyle() { QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::OpenFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); m_stylesSelector->loadCollection(filename); wdgLayerStyles.lstStyleSelector->setCurrentRow(0); } void KisDlgLayerStyle::slotSaveStyle() { QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::SaveFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); QScopedPointer collection( new KisPSDLayerStyleCollectionResource(filename)); KisPSDLayerStyleSP newStyle = style()->clone(); newStyle->setName(QFileInfo(filename).baseName()); KisPSDLayerStyleCollectionResource::StylesVector vector = collection->layerStyles(); vector << newStyle; collection->setLayerStyles(vector); collection->save(); } void KisDlgLayerStyle::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) { current = previous; } wdgLayerStyles.stylesStack->setCurrentIndex(wdgLayerStyles.lstStyleSelector->row(current)); } void KisDlgLayerStyle::setStyle(KisPSDLayerStyleSP style) { // we may self-assign style is some cases if (style != m_layerStyle) { *m_layerStyle = *style; } m_sanityLayerStyleDirty = false; { KisSignalsBlocker b(m_stylesSelector); m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } QListWidgetItem *item; item = wdgLayerStyles.lstStyleSelector->item(2); item->setCheckState(m_layerStyle->dropShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(3); item->setCheckState(m_layerStyle->innerShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(4); item->setCheckState(m_layerStyle->outerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(5); item->setCheckState(m_layerStyle->innerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(6); item->setCheckState(m_layerStyle->bevelAndEmboss()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(7); item->setCheckState(m_layerStyle->bevelAndEmboss()->contourEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(8); item->setCheckState(m_layerStyle->bevelAndEmboss()->textureEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(9); item->setCheckState(m_layerStyle->satin()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(10); item->setCheckState(m_layerStyle->colorOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(11); item->setCheckState(m_layerStyle->gradientOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(12); item->setCheckState(m_layerStyle->patternOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(13); item->setCheckState(m_layerStyle->stroke()->effectEnabled() ? Qt::Checked : Qt::Unchecked); m_dropShadow->setShadow(m_layerStyle->dropShadow()); m_innerShadow->setShadow(m_layerStyle->innerShadow()); m_outerGlow->setConfig(m_layerStyle->outerGlow()); m_innerGlow->setConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->setBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->setSatin(m_layerStyle->satin()); m_colorOverlay->setColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->setGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->setPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->setStroke(m_layerStyle->stroke()); wdgLayerStyles.chkMasterFxSwitch->setChecked(m_layerStyle->isEnabled()); slotMasterFxSwitchChanged(m_layerStyle->isEnabled()); } KisPSDLayerStyleSP KisDlgLayerStyle::style() const { m_layerStyle->setEnabled(wdgLayerStyles.chkMasterFxSwitch->isChecked()); m_layerStyle->dropShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(2)->checkState() == Qt::Checked); m_layerStyle->innerShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(3)->checkState() == Qt::Checked); m_layerStyle->outerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(4)->checkState() == Qt::Checked); m_layerStyle->innerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(5)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(6)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setContourEnabled(wdgLayerStyles.lstStyleSelector->item(7)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setTextureEnabled(wdgLayerStyles.lstStyleSelector->item(8)->checkState() == Qt::Checked); m_layerStyle->satin()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(9)->checkState() == Qt::Checked); m_layerStyle->colorOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(10)->checkState() == Qt::Checked); m_layerStyle->gradientOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(11)->checkState() == Qt::Checked); m_layerStyle->patternOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(12)->checkState() == Qt::Checked); m_layerStyle->stroke()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(13)->checkState() == Qt::Checked); m_dropShadow->fetchShadow(m_layerStyle->dropShadow()); m_innerShadow->fetchShadow(m_layerStyle->innerShadow()); m_outerGlow->fetchConfig(m_layerStyle->outerGlow()); m_innerGlow->fetchConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->fetchBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->fetchSatin(m_layerStyle->satin()); m_colorOverlay->fetchColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->fetchGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->fetchPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->fetchStroke(m_layerStyle->stroke()); m_sanityLayerStyleDirty = false; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); return m_layerStyle; } void KisDlgLayerStyle::syncGlobalAngle(int angle) { KisPSDLayerStyleSP style = this->style(); if (style->dropShadow()->useGlobalLight()) { style->dropShadow()->setAngle(angle); } if (style->innerShadow()->useGlobalLight()) { style->innerShadow()->setAngle(angle); } if (style->bevelAndEmboss()->useGlobalLight()) { style->bevelAndEmboss()->setAngle(angle); } setStyle(style); } /********************************************************************/ /***** Styles Selector **********************************************/ /********************************************************************/ class StyleItem : public QListWidgetItem { public: StyleItem(KisPSDLayerStyleSP style) : QListWidgetItem(style->name()) , m_style(style) { } public: KisPSDLayerStyleSP m_style; }; StylesSelector::StylesSelector(QWidget *parent) : QWidget(parent) { ui.setupUi(this); connect(ui.cmbStyleCollections, SIGNAL(activated(QString)), this, SLOT(loadStyles(QString))); connect(ui.listStyles, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(selectStyle(QListWidgetItem*,QListWidgetItem*))); refillCollections(); if (ui.cmbStyleCollections->count()) { ui.cmbStyleCollections->setCurrentIndex(0); loadStyles(ui.cmbStyleCollections->currentText()); } } void StylesSelector::refillCollections() { QString previousCollection = ui.cmbStyleCollections->currentText(); ui.cmbStyleCollections->clear(); Q_FOREACH (KoResource *res, KisResourceServerProvider::instance()->layerStyleCollectionServer()->resources()) { ui.cmbStyleCollections->addItem(res->name()); } if (!previousCollection.isEmpty()) { KisSignalsBlocker blocker(this); int index = ui.cmbStyleCollections->findText(previousCollection); ui.cmbStyleCollections->setCurrentIndex(index); } } void StylesSelector::notifyExternalStyleChanged(const QString &name, const QUuid &uuid) { int currentIndex = -1; for (int i = 0; i < ui.listStyles->count(); i++ ) { StyleItem *item = dynamic_cast(ui.listStyles->item(i)); QString itemName = item->m_style->name(); if (itemName == name) { bool isDirty = item->m_style->uuid() != uuid; if (isDirty) { itemName += "*"; } currentIndex = i; } item->setText(itemName); } ui.listStyles->setCurrentRow(currentIndex); } void StylesSelector::loadStyles(const QString &name) { ui.listStyles->clear(); KoResource *res = KisResourceServerProvider::instance()->layerStyleCollectionServer()->resourceByName(name); KisPSDLayerStyleCollectionResource *collection = dynamic_cast(res); if (collection) { Q_FOREACH (KisPSDLayerStyleSP style, collection->layerStyles()) { // XXX: also use the preview image, when we have one ui.listStyles->addItem(new StyleItem(style)); } } } void StylesSelector::selectStyle(QListWidgetItem *current, QListWidgetItem* /*previous*/) { StyleItem *item = dynamic_cast(current); if (item) { emit styleSelected(item->m_style); } } void StylesSelector::loadCollection(const QString &fileName) { if (!QFileInfo(fileName).exists()) { warnKrita << "Loaded style collection doesn't exist!"; return; } KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource(fileName); collection->load(); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); collection->setFilename(server->saveLocation() + QDir::separator() + collection->name()); server->addResource(collection); refillCollections(); int index = ui.cmbStyleCollections->findText(collection->name()); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(collection->name()); } void StylesSelector::addNewStyle(KisPSDLayerStyleSP style) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); // NOTE: not translatable, since it is a key! const QString customName = "CustomStyles.asl"; const QString saveLocation = server->saveLocation(); const QString fullFilename = saveLocation + customName; KoResource *resource = server->resourceByName(customName); KisPSDLayerStyleCollectionResource *collection = 0; if (!resource) { collection = new KisPSDLayerStyleCollectionResource(""); collection->setName(customName); collection->setFilename(fullFilename); KisPSDLayerStyleCollectionResource::StylesVector vector; vector << style; collection->setLayerStyles(vector); server->addResource(collection); } else { collection = dynamic_cast(resource); KisPSDLayerStyleCollectionResource::StylesVector vector; vector = collection->layerStyles(); vector << style; collection->setLayerStyles(vector); collection->save(); } refillCollections(); // select in gui int index = ui.cmbStyleCollections->findText(customName); KIS_ASSERT_RECOVER_RETURN(index >= 0); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(customName); notifyExternalStyleChanged(style->name(), style->uuid()); } /********************************************************************/ /***** Bevel and Emboss *********************************************/ /********************************************************************/ BevelAndEmboss::BevelAndEmboss(Contour *contour, Texture *texture, QWidget *parent) : QWidget(parent) , m_contour(contour) , m_texture(texture) { ui.setupUi(this); // Structure ui.intDepth->setRange(0, 100); ui.intDepth->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSoften->setRange(0, 18); ui.intSoften->setSuffix(i18n(" px")); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbDirection, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSoften, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Shading ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intOpacity2->setRange(0, 100); ui.intOpacity2->setSuffix(i18n(" %")); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SLOT(slotDialAngleChanged(int))); connect(ui.intAngle, SIGNAL(valueChanged(int)), SLOT(slotIntAngleChanged(int))); connect(ui.chkUseGlobalLight, SIGNAL(toggled(bool)), SLOT(slotGlobalLightToggled())); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.chkUseGlobalLight, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intAltitude, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbHighlightMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnHighlightColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbShadowMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnShadowColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); - connect(ui.intOpacity2, SIGNAL(valueChanged(int)), SIGNAL(configChanged()));; + connect(ui.intOpacity2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Contour m_contour->ui.intRange->setRange(1, 100); m_contour->ui.intRange->setSuffix(i18n(" %")); connect(m_contour->ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(m_contour->ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_contour->ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Texture m_texture->ui.intScale->setRange(0, 100); m_texture->ui.intScale->setSuffix(i18n(" %")); m_texture->ui.intDepth->setRange(-1000, 1000); m_texture->ui.intDepth->setSuffix(i18n(" %")); connect(m_texture->ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(m_texture->ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_texture->ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void BevelAndEmboss::setBevelAndEmboss(const psd_layer_effects_bevel_emboss *bevelAndEmboss) { ui.cmbStyle->setCurrentIndex((int)bevelAndEmboss->style()); ui.cmbTechnique->setCurrentIndex((int)bevelAndEmboss->technique()); ui.intDepth->setValue(bevelAndEmboss->depth()); ui.cmbDirection->setCurrentIndex((int)bevelAndEmboss->direction()); ui.intSize->setValue(bevelAndEmboss->size()); ui.intSoften->setValue(bevelAndEmboss->soften()); ui.dialAngle->setValue(bevelAndEmboss->angle()); ui.intAngle->setValue(bevelAndEmboss->angle()); ui.chkUseGlobalLight->setChecked(bevelAndEmboss->useGlobalLight()); ui.intAltitude->setValue(bevelAndEmboss->altitude()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(bevelAndEmboss->glossAntiAliased()); ui.cmbHighlightMode->selectCompositeOp(KoID(bevelAndEmboss->highlightBlendMode())); KoColor highlightshadow(KoColorSpaceRegistry::instance()->rgb8()); highlightshadow.fromQColor(bevelAndEmboss->highlightColor()); ui.bnHighlightColor->setColor(highlightshadow); ui.intOpacity->setValue(bevelAndEmboss->highlightOpacity()); ui.cmbShadowMode->selectCompositeOp(KoID(bevelAndEmboss->shadowBlendMode())); highlightshadow.fromQColor(bevelAndEmboss->shadowColor()); ui.bnShadowColor->setColor(highlightshadow); ui.intOpacity2->setValue(bevelAndEmboss->shadowOpacity()); // FIXME: curve editing // m_contour->ui.cmbContour; m_contour->ui.chkAntiAliased->setChecked(bevelAndEmboss->antiAliased()); m_contour->ui.intRange->setValue(bevelAndEmboss->contourRange()); m_texture->ui.patternChooser->setCurrentPattern(bevelAndEmboss->texturePattern()); m_texture->ui.intScale->setValue(bevelAndEmboss->textureScale()); m_texture->ui.intDepth->setValue(bevelAndEmboss->textureDepth()); m_texture->ui.chkInvert->setChecked(bevelAndEmboss->textureInvert()); m_texture->ui.chkLinkWithLayer->setChecked(bevelAndEmboss->textureAlignWithLayer()); } void BevelAndEmboss::fetchBevelAndEmboss(psd_layer_effects_bevel_emboss *bevelAndEmboss) const { bevelAndEmboss->setStyle((psd_bevel_style)ui.cmbStyle->currentIndex()); bevelAndEmboss->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); bevelAndEmboss->setDepth(ui.intDepth->value()); bevelAndEmboss->setDirection((psd_direction)ui.cmbDirection->currentIndex()); bevelAndEmboss->setSize(ui.intSize->value()); bevelAndEmboss->setSoften(ui.intSoften->value()); bevelAndEmboss->setAngle(ui.dialAngle->value()); bevelAndEmboss->setUseGlobalLight(ui.chkUseGlobalLight->isChecked()); bevelAndEmboss->setAltitude(ui.intAltitude->value()); bevelAndEmboss->setGlossAntiAliased(ui.chkAntiAliased->isChecked()); bevelAndEmboss->setHighlightBlendMode(ui.cmbHighlightMode->selectedCompositeOp().id()); bevelAndEmboss->setHighlightColor(ui.bnHighlightColor->color().toQColor()); bevelAndEmboss->setHighlightOpacity(ui.intOpacity->value()); bevelAndEmboss->setShadowBlendMode(ui.cmbShadowMode->selectedCompositeOp().id()); bevelAndEmboss->setShadowColor(ui.bnShadowColor->color().toQColor()); bevelAndEmboss->setShadowOpacity(ui.intOpacity2->value()); // FIXME: curve editing bevelAndEmboss->setAntiAliased(m_contour->ui.chkAntiAliased->isChecked()); bevelAndEmboss->setContourRange(m_contour->ui.intRange->value()); bevelAndEmboss->setTexturePattern(static_cast(m_texture->ui.patternChooser->currentResource())); bevelAndEmboss->setTextureScale(m_texture->ui.intScale->value()); bevelAndEmboss->setTextureDepth(m_texture->ui.intDepth->value()); bevelAndEmboss->setTextureInvert(m_texture->ui.chkInvert->isChecked()); bevelAndEmboss->setTextureAlignWithLayer(m_texture->ui.chkLinkWithLayer->isChecked()); } void BevelAndEmboss::slotDialAngleChanged(int value) { KisSignalsBlocker b(ui.intAngle); ui.intAngle->setValue(value); if (ui.chkUseGlobalLight->isChecked()) { emit globalAngleChanged(value); } } void BevelAndEmboss::slotIntAngleChanged(int value) { KisSignalsBlocker b(ui.dialAngle); ui.dialAngle->setValue(value); if (ui.chkUseGlobalLight->isChecked()) { emit globalAngleChanged(value); } } void BevelAndEmboss::slotGlobalLightToggled() { if (ui.chkUseGlobalLight->isChecked()) { emit globalAngleChanged(ui.intAngle->value()); } } /********************************************************************/ /***** Texture *********************************************/ /********************************************************************/ Texture::Texture(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Contour *********************************************/ /********************************************************************/ Contour::Contour(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Blending Options *********************************************/ /********************************************************************/ BlendingOptions::BlendingOptions(QWidget *parent) : QWidget(parent) { ui.setupUi(this); // FIXME: Blend options are not implemented yet ui.grpBlendingOptions->setTitle(QString("%1 (%2)").arg(ui.grpBlendingOptions->title()).arg(i18n("Not Implemented Yet"))); ui.grpBlendingOptions->setEnabled(false); } /********************************************************************/ /***** Color Overlay *********************************************/ /********************************************************************/ ColorOverlay::ColorOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); } void ColorOverlay::setColorOverlay(const psd_layer_effects_color_overlay *colorOverlay) { ui.cmbCompositeOp->selectCompositeOp(KoID(colorOverlay->blendMode())); ui.intOpacity->setValue(colorOverlay->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(colorOverlay->color()); ui.bnColor->setColor(color); } void ColorOverlay::fetchColorOverlay(psd_layer_effects_color_overlay *colorOverlay) const { colorOverlay->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); colorOverlay->setOpacity(ui.intOpacity->value()); colorOverlay->setColor(ui.bnColor->color().toQColor()); } /********************************************************************/ /***** Drop Shadow **************************************************/ /********************************************************************/ DropShadow::DropShadow(Mode mode, QWidget *parent) : QWidget(parent), m_mode(mode) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 500); ui.intDistance->setSuffix(i18n(" px")); ui.intDistance->setExponentRatio(3.0); ui.intSpread->setRange(0, 100); ui.intSpread->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SLOT(slotDialAngleChanged(int))); connect(ui.intAngle, SIGNAL(valueChanged(int)), SLOT(slotIntAngleChanged(int))); connect(ui.chkUseGlobalLight, SIGNAL(toggled(bool)), SLOT(slotGlobalLightToggled())); // connect everything to configChanged() signal connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.chkUseGlobalLight, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSpread, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.chkLayerKnocksOutDropShadow, SIGNAL(toggled(bool)), SIGNAL(configChanged())); if (m_mode == InnerShadowMode) { ui.chkLayerKnocksOutDropShadow->setVisible(false); ui.grpMain->setTitle(i18n("Inner Shadow")); ui.lblSpread->setText(i18n("Choke:")); } } void DropShadow::slotDialAngleChanged(int value) { KisSignalsBlocker b(ui.intAngle); ui.intAngle->setValue(value); if (ui.chkUseGlobalLight->isChecked()) { emit globalAngleChanged(value); } } void DropShadow::slotIntAngleChanged(int value) { KisSignalsBlocker b(ui.dialAngle); ui.dialAngle->setValue(value); if (ui.chkUseGlobalLight->isChecked()) { emit globalAngleChanged(value); } } void DropShadow::slotGlobalLightToggled() { if (ui.chkUseGlobalLight->isChecked()) { emit globalAngleChanged(ui.intAngle->value()); } } void DropShadow::setShadow(const psd_layer_effects_shadow_common *shadow) { ui.cmbCompositeOp->selectCompositeOp(KoID(shadow->blendMode())); ui.intOpacity->setValue(shadow->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(shadow->color()); ui.bnColor->setColor(color); ui.dialAngle->setValue(shadow->angle()); ui.intAngle->setValue(shadow->angle()); ui.chkUseGlobalLight->setChecked(shadow->useGlobalLight()); ui.intDistance->setValue(shadow->distance()); ui.intSpread->setValue(shadow->spread()); ui.intSize->setValue(shadow->size()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(shadow->antiAliased()); ui.intNoise->setValue(shadow->noise()); if (m_mode == DropShadowMode) { const psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); ui.chkLayerKnocksOutDropShadow->setChecked(shadow->knocksOut()); } } void DropShadow::fetchShadow(psd_layer_effects_shadow_common *shadow) const { shadow->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); shadow->setOpacity(ui.intOpacity->value()); shadow->setColor(ui.bnColor->color().toQColor()); shadow->setAngle(ui.dialAngle->value()); shadow->setUseGlobalLight(ui.chkUseGlobalLight->isChecked()); shadow->setDistance(ui.intDistance->value()); shadow->setSpread(ui.intSpread->value()); shadow->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; shadow->setAntiAliased(ui.chkAntiAliased->isChecked()); shadow->setNoise(ui.intNoise->value()); if (m_mode == DropShadowMode) { psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); realDropShadow->setKnocksOut(ui.chkLayerKnocksOutDropShadow->isChecked()); } } class GradientPointerConverter { public: static KoAbstractGradientSP resourceToStyle(KoAbstractGradient *gradient) { return gradient ? KoAbstractGradientSP(gradient->clone()) : KoAbstractGradientSP(); } static KoAbstractGradient* styleToResource(KoAbstractGradientSP gradient) { if (!gradient) return 0; KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); KoAbstractGradient *resource = server->resourceByMD5(gradient->md5()); if (!resource) { KoAbstractGradient *clone = gradient->clone(); clone->setName(findAvailableName(gradient->name())); server->addResource(clone, false); resource = clone; } return resource; } private: static QString findAvailableName(const QString &name) { KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); QString newName = name; int i = 0; while (server->resourceByName(newName)) { newName = QString("%1%2").arg(name).arg(i++); } return newName; } }; /********************************************************************/ /***** Gradient Overlay *********************************************/ /********************************************************************/ GradientOverlay::GradientOverlay(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SLOT(slotDialAngleChanged(int))); connect(ui.intAngle, SIGNAL(valueChanged(int)), SLOT(slotIntAngleChanged(int))); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void GradientOverlay::setGradientOverlay(const psd_layer_effects_gradient_overlay *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); KoAbstractGradient *gradient = fetchGradientLazy( GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(config->reverse()); ui.cmbStyle->setCurrentIndex((int)config->style()); ui.chkAlignWithLayer->setCheckable(config->alignWithLayer()); ui.dialAngle->setValue(config->angle()); ui.intAngle->setValue(config->angle()); ui.intScale->setValue(config->scale()); } void GradientOverlay::fetchGradientOverlay(psd_layer_effects_gradient_overlay *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setReverse(ui.chkReverse->isChecked()); config->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); config->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); config->setAngle(ui.dialAngle->value()); config->setScale(ui.intScale->value()); } void GradientOverlay::slotDialAngleChanged(int value) { KisSignalsBlocker b(ui.intAngle); ui.intAngle->setValue(value); } void GradientOverlay::slotIntAngleChanged(int value) { KisSignalsBlocker b(ui.dialAngle); ui.dialAngle->setValue(value); } /********************************************************************/ /***** Innner Glow *********************************************/ /********************************************************************/ InnerGlow::InnerGlow(Mode mode, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_mode(mode), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); ui.intChoke->setRange(0, 100); ui.intChoke->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intRange->setRange(1, 100); ui.intRange->setSuffix(i18n(" %")); ui.intJitter->setRange(0, 100); ui.intJitter->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.radioColor, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.radioGradient, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbSource, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intChoke, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intJitter, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); if (m_mode == OuterGlowMode) { ui.cmbSource->hide(); ui.lblSource->hide(); ui.lblChoke->setText(i18nc("layer styles parameter", "Spread:")); } } void InnerGlow::setConfig(const psd_layer_effects_glow_common *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); ui.intNoise->setValue(config->noise()); ui.radioColor->setChecked(config->fillType() == psd_fill_solid_color); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(config->color()); ui.bnColor->setColor(color); ui.radioGradient->setChecked(config->fillType() == psd_fill_gradient); KoAbstractGradient *gradient = fetchGradientLazy( GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.cmbTechnique->setCurrentIndex((int)config->technique()); ui.intChoke->setValue(config->spread()); ui.intSize->setValue(config->size()); if (m_mode == InnerGlowMode) { const psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); ui.cmbSource->setCurrentIndex(iglow->source() == psd_glow_edge); } // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(config->antiAliased()); ui.intRange->setValue(config->range()); ui.intJitter->setValue(config->jitter()); } void InnerGlow::fetchConfig(psd_layer_effects_glow_common *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setNoise(ui.intNoise->value()); if (ui.radioColor->isChecked()) { config->setFillType(psd_fill_solid_color); } else { config->setFillType(psd_fill_gradient); } config->setColor(ui.bnColor->color().toQColor()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); config->setSpread(ui.intChoke->value()); config->setSize(ui.intSize->value()); if (m_mode == InnerGlowMode) { psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); iglow->setSource((psd_glow_source)ui.cmbSource->currentIndex()); } // FIXME: Curve editing //ui.cmbContour; config->setAntiAliased(ui.chkAntiAliased->isChecked()); config->setRange(ui.intRange->value()); config->setJitter(ui.intJitter->value()); } /********************************************************************/ /***** Pattern Overlay *********************************************/ /********************************************************************/ PatternOverlay::PatternOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void PatternOverlay::setPatternOverlay(const psd_layer_effects_pattern_overlay *pattern) { ui.cmbCompositeOp->selectCompositeOp(KoID(pattern->blendMode())); ui.intOpacity->setValue(pattern->opacity()); ui.patternChooser->setCurrentPattern(pattern->pattern()); ui.chkLinkWithLayer->setChecked(pattern->alignWithLayer()); ui.intScale->setValue(pattern->scale()); } void PatternOverlay::fetchPatternOverlay(psd_layer_effects_pattern_overlay *pattern) const { pattern->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); pattern->setOpacity(ui.intOpacity->value()); pattern->setPattern(static_cast(ui.patternChooser->currentResource())); pattern->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); pattern->setScale(ui.intScale->value()); } /********************************************************************/ /***** Satin *********************************************/ /********************************************************************/ Satin::Satin(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 250); ui.intDistance->setSuffix(i18n(" px")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SLOT(slotDialAngleChanged(int))); connect(ui.intAngle, SIGNAL(valueChanged(int)), SLOT(slotIntAngleChanged(int))); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intAngle, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void Satin::slotDialAngleChanged(int value) { KisSignalsBlocker b(ui.intAngle); ui.intAngle->setValue(value); } void Satin::slotIntAngleChanged(int value) { KisSignalsBlocker b(ui.dialAngle); ui.dialAngle->setValue(value); } void Satin::setSatin(const psd_layer_effects_satin *satin) { ui.cmbCompositeOp->selectCompositeOp(KoID(satin->blendMode())); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(satin->color()); ui.bnColor->setColor(color); ui.intOpacity->setValue(satin->opacity()); ui.dialAngle->setValue(satin->angle()); ui.intAngle->setValue(satin->angle()); ui.intDistance->setValue(satin->distance()); ui.intSize->setValue(satin->size()); // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(satin->antiAliased()); ui.chkInvert->setChecked(satin->invert()); } void Satin::fetchSatin(psd_layer_effects_satin *satin) const { satin->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); satin->setOpacity(ui.intOpacity->value()); satin->setColor(ui.bnColor->color().toQColor()); satin->setAngle(ui.dialAngle->value()); satin->setDistance(ui.intDistance->value()); satin->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; satin->setAntiAliased(ui.chkAntiAliased->isChecked()); satin->setInvert(ui.chkInvert->isChecked()); } /********************************************************************/ /***** Stroke *********************************************/ /********************************************************************/ Stroke::Stroke(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); ui.intScale_2->setRange(0, 100); ui.intScale_2->setSuffix(i18n(" %")); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), ui.fillStack, SLOT(setCurrentIndex(int))); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbPosition, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.dialAngle, SIGNAL(valueChanged(int)), SLOT(slotDialAngleChanged(int))); connect(ui.intAngle, SIGNAL(valueChanged(int)), SLOT(slotIntAngleChanged(int))); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale_2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // cold initialization ui.fillStack->setCurrentIndex(ui.cmbFillType->currentIndex()); } void Stroke::slotDialAngleChanged(int value) { KisSignalsBlocker b(ui.intAngle); ui.intAngle->setValue(value); } void Stroke::slotIntAngleChanged(int value) { KisSignalsBlocker b(ui.dialAngle); ui.dialAngle->setValue(value); } void Stroke::setStroke(const psd_layer_effects_stroke *stroke) { ui.intSize->setValue(stroke->size()); ui.cmbPosition->setCurrentIndex((int)stroke->position()); ui.cmbCompositeOp->selectCompositeOp(KoID(stroke->blendMode())); ui.intOpacity->setValue(stroke->opacity()); ui.cmbFillType->setCurrentIndex((int)stroke->fillType()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(stroke->color()); ui.bnColor->setColor(color); KoAbstractGradient *gradient = fetchGradientLazy(GradientPointerConverter::styleToResource(stroke->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(stroke->antiAliased()); ui.cmbStyle->setCurrentIndex((int)stroke->style()); ui.chkAlignWithLayer->setCheckable(stroke->alignWithLayer()); ui.dialAngle->setValue(stroke->angle()); ui.intAngle->setValue(stroke->angle()); ui.intScale->setValue(stroke->scale()); ui.patternChooser->setCurrentPattern(stroke->pattern()); ui.chkLinkWithLayer->setChecked(stroke->alignWithLayer()); ui.intScale_2->setValue(stroke->scale()); } void Stroke::fetchStroke(psd_layer_effects_stroke *stroke) const { stroke->setSize(ui.intSize->value()); stroke->setPosition((psd_stroke_position)ui.cmbPosition->currentIndex()); stroke->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); stroke->setOpacity(ui.intOpacity->value()); stroke->setFillType((psd_fill_type)ui.cmbFillType->currentIndex()); stroke->setColor(ui.bnColor->color().toQColor()); stroke->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); stroke->setReverse(ui.chkReverse->isChecked()); stroke->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); stroke->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); stroke->setAngle(ui.dialAngle->value()); stroke->setScale(ui.intScale->value()); stroke->setPattern(static_cast(ui.patternChooser->currentResource())); stroke->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); stroke->setScale(ui.intScale->value()); } diff --git a/libs/ui/input/KisInputActionGroup.h b/libs/ui/input/KisInputActionGroup.h index 86afc79858..e51691d2a3 100644 --- a/libs/ui/input/KisInputActionGroup.h +++ b/libs/ui/input/KisInputActionGroup.h @@ -1,79 +1,79 @@ /* * Copyright (c) 2018 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. */ #ifndef KISINPUTACTIONGROUP_H #define KISINPUTACTIONGROUP_H #include enum KisInputActionGroup { NoActionGroup = 0x0, ViewTransformActionGroup = 0x1, ModifyingActionGroup = 0x2, AllActionGroup = ViewTransformActionGroup | ModifyingActionGroup }; Q_DECLARE_FLAGS(KisInputActionGroupsMask, KisInputActionGroup) Q_DECLARE_OPERATORS_FOR_FLAGS(KisInputActionGroupsMask) /** * A special interface class for accessing masking properties using * KisInputActionGroupsMaskGuard */ struct KisInputActionGroupsMaskInterface { - virtual ~KisInputActionGroupsMaskInterface();; + virtual ~KisInputActionGroupsMaskInterface(); /** * Return the mask of currently available input action groups */ virtual KisInputActionGroupsMask inputActionGroupsMask() const = 0; /** * Set the mask of currently available action groups */ virtual void setInputActionGroupsMask(KisInputActionGroupsMask mask) = 0; }; /** * A RAII wrapper for setting the input actions mask on the masking interface * (which is usually a canvas). In constructor the guard saves the previous mask * value and resets it to the new one. In destructor the guard restores the old * mask value. */ class KisInputActionGroupsMaskGuard { public: /** * Create a guard and set a new mask \p mask onto \p object. The old mask value is * saved in the guard itself. */ KisInputActionGroupsMaskGuard(KisInputActionGroupsMaskInterface *object, KisInputActionGroupsMask mask); /** * Destroy the guard and reset the mask value to the old value */ ~KisInputActionGroupsMaskGuard(); private: KisInputActionGroupsMaskInterface *m_object; KisInputActionGroupsMask m_oldMask; }; #endif // KISINPUTACTIONGROUP_H diff --git a/libs/ui/input/wintab/qxcbconnection_xi2.cpp b/libs/ui/input/wintab/qxcbconnection_xi2.cpp index e0604be0dc..aa0e73745b 100644 --- a/libs/ui/input/wintab/qxcbconnection_xi2.cpp +++ b/libs/ui/input/wintab/qxcbconnection_xi2.cpp @@ -1,1045 +1,1045 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qxcbconnection_xi2.h" //#include "qxcbconnection.h" //#include "qxcbkeyboard.h" //#include "qxcbscreen.h" //#include "qxcbwindow.h" //#include "qtouchdevice.h" //#include #include #include #include #ifdef XCB_USE_XINPUT2 #include #include struct XInput2TouchDeviceData { XInput2TouchDeviceData() : xiDeviceInfo(0) , qtTouchDevice(0) { } XIDeviceInfo *xiDeviceInfo; QTouchDevice *qtTouchDevice; QHash touchPoints; // Stuff that is relevant only for touchpads QHash pointPressedPosition; // in screen coordinates where each point was pressed QPointF firstPressedPosition; // in screen coordinates where the first point was pressed QPointF firstPressedNormalPosition; // device coordinates (0 to 1, 0 to 1) where the first point was pressed QSizeF size; // device size in mm }; void QXcbConnection::initializeXInput2() { // TODO Qt 6 (or perhaps earlier): remove these redundant env variables if (qEnvironmentVariableIsSet("KIS_QT_XCB_DEBUG_XINPUT")) const_cast(lcQpaXInput()).setEnabled(QtDebugMsg, true); if (qEnvironmentVariableIsSet("KIS_QT_XCB_DEBUG_XINPUT_DEVICES")) const_cast(lcQpaXInputDevices()).setEnabled(QtDebugMsg, true); Display *xDisplay = static_cast(m_xlib_display); if (XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) { int xiMajor = 2; m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { m_xi2Minor = 1; // for smooth scrolling 2.1 is enough if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { m_xi2Minor = 0; // for tablet support 2.0 is enough m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest; } else m_xi2Enabled = true; } else m_xi2Enabled = true; if (m_xi2Enabled) { #ifdef XCB_USE_XINPUT22 qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.2 or greater", xiMajor, m_xi2Minor); #else qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.0", xiMajor, m_xi2Minor); #endif } xi2SetupDevices(); } } void QXcbConnection::xi2SetupDevices() { #ifndef QT_NO_TABLETEVENT m_tabletData.clear(); #endif m_scrollingDevices.clear(); if (!m_xi2Enabled) return; Display *xDisplay = static_cast(m_xlib_display); int deviceCount = 0; XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount); for (int i = 0; i < deviceCount; ++i) { // Only non-master pointing devices are relevant here. if (devices[i].use != XISlavePointer) continue; qCDebug(lcQpaXInputDevices) << "input device "<< devices[i].name; #ifndef QT_NO_TABLETEVENT TabletData tabletData; #endif ScrollingDevice scrollingDevice; for (int c = 0; c < devices[i].num_classes; ++c) { switch (devices[i].classes[c]->type) { case XIValuatorClass: { XIValuatorClassInfo *vci = reinterpret_cast(devices[i].classes[c]); const int valuatorAtom = qatom(vci->label); qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms); #ifndef QT_NO_TABLETEVENT if (valuatorAtom < QXcbAtom::NAtoms) { TabletData::ValuatorClassInfo info; info.minVal = vci->min; info.maxVal = vci->max; info.number = vci->number; tabletData.valuatorInfo[valuatorAtom] = info; } #endif // QT_NO_TABLETEVENT if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) scrollingDevice.lastScrollPosition.setX(vci->value); else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) scrollingDevice.lastScrollPosition.setY(vci->value); break; } #ifdef XCB_USE_XINPUT21 case XIScrollClass: { XIScrollClassInfo *sci = reinterpret_cast(devices[i].classes[c]); if (sci->scroll_type == XIScrollTypeVertical) { scrollingDevice.orientations |= Qt::Vertical; scrollingDevice.verticalIndex = sci->number; scrollingDevice.verticalIncrement = sci->increment; } else if (sci->scroll_type == XIScrollTypeHorizontal) { scrollingDevice.orientations |= Qt::Horizontal; scrollingDevice.horizontalIndex = sci->number; scrollingDevice.horizontalIncrement = sci->increment; } break; } case XIButtonClass: { XIButtonClassInfo *bci = reinterpret_cast(devices[i].classes[c]); if (bci->num_buttons >= 5) { Atom label4 = bci->labels[3]; Atom label5 = bci->labels[4]; // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons. if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) && (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown)) scrollingDevice.legacyOrientations |= Qt::Vertical; } if (bci->num_buttons >= 7) { Atom label6 = bci->labels[5]; Atom label7 = bci->labels[6]; if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight)) scrollingDevice.legacyOrientations |= Qt::Horizontal; } qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons); break; } #endif case XIKeyClass: qCDebug(lcQpaXInputDevices) << " it's a keyboard"; break; #ifdef XCB_USE_XINPUT22 case XITouchClass: // will be handled in deviceForId() break; #endif default: qCDebug(lcQpaXInputDevices) << " has class" << devices[i].classes[c]->type; break; } } bool isTablet = false; #ifndef QT_NO_TABLETEVENT // If we have found the valuators which we expect a tablet to have, it might be a tablet. if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) && tabletData.valuatorInfo.contains(QXcbAtom::AbsY) && tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure)) isTablet = true; // But we need to be careful not to take the touch and tablet-button devices as tablets. QByteArray name = QByteArray(devices[i].name).toLower(); QString dbgType = QLatin1String("UNKNOWN"); if (name.contains("eraser")) { isTablet = true; tabletData.pointerType = QTabletEvent::Eraser; dbgType = QLatin1String("eraser"); } else if (name.contains("cursor")) { isTablet = true; tabletData.pointerType = QTabletEvent::Cursor; dbgType = QLatin1String("cursor"); } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) { tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("wacom") && isTablet && !name.contains("touch")) { // combined device (evdev) rather than separate pen/eraser (wacom driver) tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) { // some "Genius" tablets isTablet = true; tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("waltop") && name.contains("tablet")) { // other "Genius" tablets // WALTOP International Corp. Slim Tablet isTablet = true; tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("uc-logic")) { isTablet = true; tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("ugee")) { isTablet = true; tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else { isTablet = false; } if (isTablet) { tabletData.deviceId = devices[i].deviceid; m_tabletData.append(tabletData); qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType; } #endif // QT_NO_TABLETEVENT #ifdef XCB_USE_XINPUT21 if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) { scrollingDevice.deviceId = devices[i].deviceid; // Only use legacy wheel button events when we don't have real scroll valuators. scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations; m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice); qCDebug(lcQpaXInputDevices) << " it's a scrolling device"; } #endif if (!isTablet) { // touchDeviceForId populates XInput2DeviceData the first time it is called // with a new deviceId. On subsequent calls it will return the cached object. XInput2TouchDeviceData *dev = touchDeviceForId(devices[i].deviceid); if (dev && lcQpaXInputDevices().isDebugEnabled()) { if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen) qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d", dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), dev->qtTouchDevice->maximumTouchPoints()); else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad) qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f", dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), dev->qtTouchDevice->maximumTouchPoints(), dev->size.width(), dev->size.height()); } } } XIFreeDeviceInfo(devices); } void QXcbConnection::finalizeXInput2() { foreach (XInput2TouchDeviceData *dev, m_touchDevices) { if (dev->xiDeviceInfo) XIFreeDeviceInfo(dev->xiDeviceInfo); delete dev->qtTouchDevice; delete dev; } } void QXcbConnection::xi2Select(xcb_window_t window) { if (!m_xi2Enabled) return; Display *xDisplay = static_cast(m_xlib_display); unsigned int bitMask = 0; unsigned char *xiBitMask = reinterpret_cast(&bitMask); #ifdef XCB_USE_XINPUT22 if (isAtLeastXI22()) { bitMask |= XI_TouchBeginMask; bitMask |= XI_TouchUpdateMask; bitMask |= XI_TouchEndMask; bitMask |= XI_PropertyEventMask; // for tablets if (xi2MouseEvents()) { // We want both mouse and touch through XI2 if touch is supported (>= 2.2). // The plain xcb press and motion events will not be delivered after this. bitMask |= XI_ButtonPressMask; bitMask |= XI_ButtonReleaseMask; bitMask |= XI_MotionMask; qCDebug(lcQpaXInput, "XInput 2.2: Selecting press/release/motion events in addition to touch"); } XIEventMask mask; mask.mask_len = sizeof(bitMask); mask.mask = xiBitMask; // When xi2MouseEvents() is true (the default), pointer emulation for touch and tablet // events will get disabled. This is preferable for touch, as Qt Quick handles touch events // directly while for others QtGui synthesizes mouse events, not so much for tablets. For // the latter we will synthesize the events ourselves. mask.deviceid = XIAllMasterDevices; Status result = XISelectEvents(xDisplay, window, &mask, 1); if (result != Success) qCDebug(lcQpaXInput, "XInput 2.2: failed to select pointer/touch events, window %x, result %d", window, result); } #endif // XCB_USE_XINPUT22 const bool pointerSelected = isAtLeastXI22() && xi2MouseEvents(); QSet tabletDevices; #ifndef QT_NO_TABLETEVENT if (!m_tabletData.isEmpty()) { unsigned int tabletBitMask; unsigned char *xiTabletBitMask = reinterpret_cast(&tabletBitMask); QVector xiEventMask(m_tabletData.count()); tabletBitMask = XI_PropertyEventMask; if (!pointerSelected) tabletBitMask |= XI_ButtonPressMask | XI_ButtonReleaseMask | XI_MotionMask; for (int i = 0; i < m_tabletData.count(); ++i) { int deviceId = m_tabletData.at(i).deviceId; tabletDevices.insert(deviceId); xiEventMask[i].deviceid = deviceId; xiEventMask[i].mask_len = sizeof(tabletBitMask); xiEventMask[i].mask = xiTabletBitMask; } XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count()); } #endif // QT_NO_TABLETEVENT #ifdef XCB_USE_XINPUT21 // Enable each scroll device if (!m_scrollingDevices.isEmpty() && !pointerSelected) { // Only when XI2 mouse events are not enabled, otherwise motion and release are selected already. QVector xiEventMask(m_scrollingDevices.size()); unsigned int scrollBitMask; unsigned char *xiScrollBitMask = reinterpret_cast(&scrollBitMask); scrollBitMask = XI_MotionMask; scrollBitMask |= XI_ButtonReleaseMask; int i=0; Q_FOREACH (const ScrollingDevice& scrollingDevice, m_scrollingDevices) { if (tabletDevices.contains(scrollingDevice.deviceId)) continue; // All necessary events are already captured. xiEventMask[i].deviceid = scrollingDevice.deviceId; xiEventMask[i].mask_len = sizeof(scrollBitMask); xiEventMask[i].mask = xiScrollBitMask; i++; } XISelectEvents(xDisplay, window, xiEventMask.data(), i); } #else Q_UNUSED(xiBitMask); #endif { // Listen for hotplug events XIEventMask xiEventMask; bitMask = XI_HierarchyChangedMask; bitMask |= XI_DeviceChangedMask; xiEventMask.deviceid = XIAllDevices; xiEventMask.mask_len = sizeof(bitMask); xiEventMask.mask = xiBitMask; XISelectEvents(xDisplay, window, &xiEventMask, 1); } } XInput2TouchDeviceData *QXcbConnection::touchDeviceForId(int id) { XInput2TouchDeviceData *dev = 0; QHash::const_iterator devIt = m_touchDevices.constFind(id); if (devIt != m_touchDevices.cend()) { dev = devIt.value(); } else { int nrDevices = 0; QTouchDevice::Capabilities caps = 0; dev = new XInput2TouchDeviceData; dev->xiDeviceInfo = XIQueryDevice(static_cast(m_xlib_display), id, &nrDevices); if (nrDevices <= 0) { delete dev; return 0; } int type = -1; int maxTouchPoints = 1; bool hasRelativeCoords = false; for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; switch (classinfo->type) { #ifdef XCB_USE_XINPUT22 case XITouchClass: { XITouchClassInfo *tci = reinterpret_cast(classinfo); maxTouchPoints = tci->num_touches; qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode); switch (tci->mode) { case XIDependentTouch: type = QTouchDevice::TouchPad; break; case XIDirectTouch: type = QTouchDevice::TouchScreen; break; } break; } #endif // XCB_USE_XINPUT22 case XIValuatorClass: { XIValuatorClassInfo *vci = reinterpret_cast(classinfo); if (vci->label == atom(QXcbAtom::AbsMTPositionX)) caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition; else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) caps |= QTouchDevice::Area; else if (vci->label == atom(QXcbAtom::AbsMTPressure) || vci->label == atom(QXcbAtom::AbsPressure)) caps |= QTouchDevice::Pressure; else if (vci->label == atom(QXcbAtom::RelX)) { hasRelativeCoords = true; dev->size.setWidth((vci->max - vci->min) * 1000.0 / vci->resolution); } else if (vci->label == atom(QXcbAtom::RelY)) { hasRelativeCoords = true; dev->size.setHeight((vci->max - vci->min) * 1000.0 / vci->resolution); } else if (vci->label == atom(QXcbAtom::AbsX)) { dev->size.setHeight((vci->max - vci->min) * 1000.0 / vci->resolution); } else if (vci->label == atom(QXcbAtom::AbsY)) { dev->size.setWidth((vci->max - vci->min) * 1000.0 / vci->resolution); } break; } default: break; } } if (type < 0 && caps && hasRelativeCoords) { type = QTouchDevice::TouchPad; if (dev->size.width() < 10 || dev->size.height() < 10 || dev->size.width() > 10000 || dev->size.height() > 10000) dev->size = QSizeF(130, 110); } // WARNING: Krita edit // if (!isAtLeastXI22() || type == QTouchDevice::TouchPad) // caps |= QTouchDevice::MouseEmulation; if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) { dev->qtTouchDevice = new QTouchDevice; dev->qtTouchDevice->setName(QString::fromUtf8(dev->xiDeviceInfo->name)); dev->qtTouchDevice->setType((QTouchDevice::DeviceType)type); dev->qtTouchDevice->setCapabilities(caps); dev->qtTouchDevice->setMaximumTouchPoints(maxTouchPoints); if (caps != 0) QWindowSystemInterface::registerTouchDevice(dev->qtTouchDevice); m_touchDevices[id] = dev; } else { XIFreeDeviceInfo(dev->xiDeviceInfo); delete dev; dev = 0; } } return dev; } #if defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT) static inline qreal fixed1616ToReal(FP1616 val) { return qreal(val) / 0x10000; } #endif // defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT) bool QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) { if (xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) { xXIGenericDeviceEvent *xiEvent = reinterpret_cast(event); int sourceDeviceId = xiEvent->deviceid; // may be the master id xXIDeviceEvent *xiDeviceEvent = 0; QWindow *window = 0; switch (xiEvent->evtype) { case XI_ButtonPress: case XI_ButtonRelease: case XI_Motion: #ifdef XCB_USE_XINPUT22 case XI_TouchBegin: case XI_TouchUpdate: case XI_TouchEnd: #endif { xiDeviceEvent = reinterpret_cast(event); window = windowFromId(xiDeviceEvent->event); sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master break; } case XI_HierarchyChanged: xi2HandleHierachyEvent(xiEvent); return false; case XI_DeviceChanged: xi2HandleDeviceChangedEvent(xiEvent); return false; default: break; } #ifndef QT_NO_TABLETEVENT for (int i = 0; i < m_tabletData.count(); ++i) { if (m_tabletData.at(i).deviceId == sourceDeviceId) { if (xi2HandleTabletEvent(xiEvent, &m_tabletData[i], window)) { return true; } } } #endif // QT_NO_TABLETEVENT #ifdef XCB_USE_XINPUT21 QHash::iterator device = m_scrollingDevices.find(sourceDeviceId); if (device != m_scrollingDevices.end()) xi2HandleScrollEvent(xiEvent, device.value()); // Removed from Qt... #endif // XCB_USE_XINPUT21 #ifdef XCB_USE_XINPUT22 // Removed from Qt... #endif // XCB_USE_XINPUT22 } return false; } #ifdef XCB_USE_XINPUT22 bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) { if (grab && !canGrab()) return false; int num_devices = 0; Display *xDisplay = static_cast(xlib_display()); XIDeviceInfo *info = XIQueryDevice(xDisplay, XIAllMasterDevices, &num_devices); if (!info) return false; XIEventMask evmask; unsigned char mask[XIMaskLen(XI_LASTEVENT)]; evmask.mask = mask; evmask.mask_len = sizeof(mask); memset(mask, 0, sizeof(mask)); evmask.deviceid = XIAllMasterDevices; XISetMask(mask, XI_ButtonPress); XISetMask(mask, XI_ButtonRelease); XISetMask(mask, XI_Motion); XISetMask(mask, XI_TouchBegin); XISetMask(mask, XI_TouchUpdate); XISetMask(mask, XI_TouchEnd); bool grabbed = true; for (int i = 0; i < num_devices; i++) { int id = info[i].deviceid, n = 0; XIDeviceInfo *deviceInfo = XIQueryDevice(xDisplay, id, &n); if (deviceInfo) { const bool grabbable = deviceInfo->use != XIMasterKeyboard; XIFreeDeviceInfo(deviceInfo); if (!grabbable) continue; } if (!grab) { Status result = XIUngrabDevice(xDisplay, id, CurrentTime); if (result != Success) { grabbed = false; qCDebug(lcQpaXInput, "XInput 2.2: failed to ungrab events for device %d (result %d)", id, result); } } else { Status result = XIGrabDevice(xDisplay, id, w, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &evmask); if (result != Success) { grabbed = false; qCDebug(lcQpaXInput, "XInput 2.2: failed to grab events for device %d on window %x (result %d)", id, w, result); } } } XIFreeDeviceInfo(info); m_xiGrab = grabbed; return grabbed; } #endif // XCB_USE_XINPUT22 void QXcbConnection::xi2HandleHierachyEvent(void *event) { xXIHierarchyEvent *xiEvent = reinterpret_cast(event); // We only care about hotplugged devices if (!(xiEvent->flags & (XISlaveRemoved | XISlaveAdded))) return; xi2SetupDevices(); // Reselect events for all event-listening windows. Q_FOREACH (xcb_window_t window, m_windowMapper.keys()) { xi2Select(window); } } void QXcbConnection::xi2HandleDeviceChangedEvent(void *event) { xXIDeviceChangedEvent *xiEvent = reinterpret_cast(event); // ### If a slave device changes (XIDeviceChange), we should probably run setup on it again. if (xiEvent->reason != XISlaveSwitch) return; #ifdef XCB_USE_XINPUT21 // This code handles broken scrolling device drivers that reset absolute positions // when they are made active. Whenever a new slave device is made active the // primary pointer sends a DeviceChanged event with XISlaveSwitch, and the new // active slave in sourceid. QHash::iterator device = m_scrollingDevices.find(xiEvent->sourceid); if (device == m_scrollingDevices.end()) return; int nrDevices = 0; XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast(m_xlib_display), xiEvent->sourceid, &nrDevices); if (nrDevices <= 0) { qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", xiEvent->sourceid); return; } updateScrollingDevice(*device, xiDeviceInfo->num_classes, xiDeviceInfo->classes); XIFreeDeviceInfo(xiDeviceInfo); #endif } void QXcbConnection::updateScrollingDevice(ScrollingDevice &scrollingDevice, int num_classes, void *classInfo) { #ifdef XCB_USE_XINPUT21 XIAnyClassInfo **classes = reinterpret_cast(classInfo); QPointF lastScrollPosition; if (lcQpaXInput().isDebugEnabled()) lastScrollPosition = scrollingDevice.lastScrollPosition; for (int c = 0; c < num_classes; ++c) { if (classes[c]->type == XIValuatorClass) { XIValuatorClassInfo *vci = reinterpret_cast(classes[c]); const int valuatorAtom = qatom(vci->label); if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) scrollingDevice.lastScrollPosition.setX(vci->value); else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) scrollingDevice.lastScrollPosition.setY(vci->value); } } if (lcQpaXInput().isDebugEnabled() && lastScrollPosition != scrollingDevice.lastScrollPosition) qCDebug(lcQpaXInput, "scrolling device %d moved from (%f, %f) to (%f, %f)", scrollingDevice.deviceId, lastScrollPosition.x(), lastScrollPosition.y(), scrollingDevice.lastScrollPosition.x(), scrollingDevice.lastScrollPosition.y()); #else Q_UNUSED(scrollingDevice); Q_UNUSED(num_classes); Q_UNUSED(classInfo); #endif } Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b) { switch (b) { case 1: return Qt::LeftButton; case 2: return Qt::MiddleButton; case 3: return Qt::RightButton; // 4-7 are for scrolling default: break; } if (b >= 8 && b <= Qt::MaxMouseButton) return static_cast(Qt::BackButton << (b - 8)); return Qt::NoButton; } Qt::MouseButtons QXcbConnection::xiToQtMouseButtons(xXIDeviceEvent *xiDeviceEvent) { /** * WARNING: we haven't tested this method on different tablets. For basic tablets * without any button remapping it seems to work. For more complex configurations * I just don't know. Right now it is only safe to check if buttons is * equal to Qt::NoButton! */ int len = xiDeviceEvent->buttons_len; - const uint32_t *buttons = reinterpret_cast(&xiDeviceEvent[1]);; + const uint32_t *buttons = reinterpret_cast(&xiDeviceEvent[1]); Qt::MouseButtons qtbuttons = Qt::NoButton; const int numBits = len * 32; for (int i = 1; i < numBits; i++) { const int index = (i) / 32; const int offset = (i) % 32; const bool isActive = buttons[index] & (1 << offset); if (isActive) { qtbuttons |= xiToQtMouseButton(i); } } return qtbuttons; } static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) { // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c switch (toolId) { case 0xd12: case 0x912: case 0x112: case 0x913: /* Intuos3 Airbrush */ case 0x91b: /* Intuos3 Airbrush Eraser */ case 0x902: /* Intuos4/5 13HD/24HD Airbrush */ case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */ case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */ case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */ return QTabletEvent::Airbrush; case 0x007: /* Mouse 4D and 2D */ case 0x09c: case 0x094: return QTabletEvent::FourDMouse; case 0x017: /* Intuos3 2D Mouse */ case 0x806: /* Intuos4 Mouse */ case 0x096: /* Lens cursor */ case 0x097: /* Intuos3 Lens cursor */ case 0x006: /* Intuos4 Lens cursor */ return QTabletEvent::Puck; case 0x885: /* Intuos3 Art Pen (Marker Pen) */ case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */ case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */ return QTabletEvent::RotationStylus; case 0: return QTabletEvent::NoDevice; } return QTabletEvent::Stylus; // Safe default assumption if nonzero } #ifndef QT_NO_TABLETEVENT bool QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData, QWindow *window) { bool handled = true; Display *xDisplay = static_cast(m_xlib_display); xXIGenericDeviceEvent *xiEvent = static_cast(event); xXIDeviceEvent *xiDeviceEvent = reinterpret_cast(xiEvent); /** * On some systems we can lose tablet button release event if the tablet * was "closing a popup window by clicking somewhere outside the app * window". It means that we get a tablet press event, but get absolutely * no tablet release event. That can cause quite a lot of troubles, so here we * check if reported button state is consistent and if not, just reset it. * * WARNING: We haven't tested xiToQtMouseButtons() functions on all the * tablet models and configurations, so at the moment we rely only * on its Qt::NoButton state. If people test it on custom tablet * button configurations, we can just stop tracking * tabletData->buttons and use this mapping instead. */ if (xiEvent->evtype == XI_Motion || xiEvent->evtype == XI_ButtonPress || xiEvent->evtype == XI_ButtonRelease) { Qt::MouseButtons expectedButtons = xiToQtMouseButtons(xiDeviceEvent); if (expectedButtons != tabletData->buttons) { if (expectedButtons == Qt::NoButton) { tabletData->buttons = expectedButtons; } else { qWarning() << "==="; qWarning() << "WARNING: Tracked tablet buttons are not consistent!"; qWarning() << " " << ppVar(tabletData->buttons); qWarning() << " " << ppVar(expectedButtons); } } } switch (xiEvent->evtype) { case XI_ButtonPress: { Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail); tabletData->buttons |= b; xi2ReportTabletEvent(*tabletData, xiEvent); break; } case XI_ButtonRelease: { Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail); tabletData->buttons ^= b; xi2ReportTabletEvent(*tabletData, xiEvent); break; } case XI_Motion: xi2ReportTabletEvent(*tabletData, xiEvent); break; case XI_PropertyEvent: { // This is the wacom driver's way of reporting tool proximity. // The evdev driver doesn't do it this way. xXIPropertyEvent *ev = reinterpret_cast(event); if (ev->what == XIPropertyModified) { if (ev->property == atom(QXcbAtom::WacomSerialIDs)) { enum WacomSerialIndex { _WACSER_USB_ID = 0, _WACSER_LAST_TOOL_SERIAL, _WACSER_LAST_TOOL_ID, _WACSER_TOOL_SERIAL, _WACSER_TOOL_ID, _WACSER_COUNT }; Atom propType; int propFormat; unsigned long numItems, bytesAfter; unsigned char *data; if (XIGetProperty(xDisplay, tabletData->deviceId, ev->property, 0, 100, 0, AnyPropertyType, &propType, &propFormat, &numItems, &bytesAfter, &data) == Success) { if (propType == atom(QXcbAtom::INTEGER) && propFormat == 32 && numItems == _WACSER_COUNT) { quint32 *ptr = reinterpret_cast(data); quint32 tool = ptr[_WACSER_TOOL_ID]; // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1 if (!tool && ptr[_WACSER_TOOL_SERIAL]) tool = ptr[_WACSER_TOOL_SERIAL]; // The property change event informs us which tool is in proximity or which one left proximity. if (tool) { tabletData->inProximity = true; tabletData->tool = toolIdToTabletDevice(tool); tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]); QWindowSystemInterface::handleTabletEnterProximityEvent(tabletData->tool, tabletData->pointerType, tabletData->serialId); } else { tabletData->inProximity = false; tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_ID]); // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1 if (!tabletData->tool) tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_SERIAL]); tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]); QWindowSystemInterface::handleTabletLeaveProximityEvent(tabletData->tool, tabletData->pointerType, tabletData->serialId); } // TODO maybe have a hash of tabletData->deviceId to device data so we can // look up the tablet name here, and distinguish multiple tablets qCDebug(lcQpaXInput, "XI2 proximity change on tablet %d (USB %x): last tool: %x id %x current tool: %x id %x TabletDevice %d", tabletData->deviceId, ptr[_WACSER_USB_ID], ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID], ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], tabletData->tool); } XFree(data); } } } break; } default: handled = false; break; } #ifdef XCB_USE_XINPUT22 // Synthesize mouse events since otherwise there are no mouse events from // the pen on the XI 2.2+ path. if (xi2MouseEvents() && window) { // DK: I have no idea why this line was introduced in Qt5.5! //eventListener->handleXIMouseEvent(reinterpret_cast(event)); } #else Q_UNUSED(eventListener); #endif return handled; } inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize) { return screenMin + normValue * screenSize; } void QXcbConnection::xi2ReportTabletEvent(TabletData &tabletData, void *event) { xXIDeviceEvent *ev = reinterpret_cast(event); QWindow *window = windowFromId(ev->event); if (!window) return; const double scale = 65536.0; QPointF local(ev->event_x / scale, ev->event_y / scale); QPointF global(ev->root_x / scale, ev->root_y / scale); double pressure = 0, rotation = 0, tangentialPressure = 0; int xTilt = 0, yTilt = 0; QRect screenArea = qApp->desktop()->rect(); for (QHash::iterator it = tabletData.valuatorInfo.begin(), ite = tabletData.valuatorInfo.end(); it != ite; ++it) { int valuator = it.key(); TabletData::ValuatorClassInfo &classInfo(it.value()); xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal); double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal); switch (valuator) { case QXcbAtom::AbsX: { const qreal value = scaleOneValuator(normalizedValue, screenArea.x(), screenArea.width()); const qreal offset = local.x() - global.x(); global.rx() = value; local.rx() = value + offset; break; } case QXcbAtom::AbsY: { qreal value = scaleOneValuator(normalizedValue, screenArea.y(), screenArea.height()); qreal offset = local.y() - global.y(); global.ry() = value; local.ry() = value + offset; break; } case QXcbAtom::AbsPressure: pressure = normalizedValue; break; case QXcbAtom::AbsTiltX: xTilt = classInfo.curVal; break; case QXcbAtom::AbsTiltY: yTilt = classInfo.curVal; break; case QXcbAtom::AbsWheel: switch (tabletData.tool) { case QTabletEvent::Airbrush: tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range break; case QTabletEvent::RotationStylus: rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees break; default: // Other types of styli do not use this valuator break; } break; default: break; } } if (Q_UNLIKELY(lcQpaXInput().isDebugEnabled())) qCDebug(lcQpaXInput, "XI2 event on tablet %d with tool %d type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", tabletData.deviceId, tabletData.tool, ev->evtype, ev->sequenceNumber, ev->detail, fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y), fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y), (int)tabletData.buttons, pressure, xTilt, yTilt, rotation); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); QWindowSystemInterface::handleTabletEvent(window, local, global, tabletData.tool, tabletData.pointerType, tabletData.buttons, pressure, xTilt, yTilt, tangentialPressure, rotation, 0, tabletData.serialId, modifiers); } void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice) { #ifdef XCB_USE_XINPUT21 xXIGenericDeviceEvent *xiEvent = reinterpret_cast(event); if (xiEvent->evtype == XI_Motion && scrollingDevice.orientations) { xXIDeviceEvent* xiDeviceEvent = reinterpret_cast(event); if (QWindow *platformWindow = windowFromId(xiDeviceEvent->event)) { QPoint rawDelta; QPoint angleDelta; double value; if (scrollingDevice.orientations & Qt::Vertical) { if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.verticalIndex, &value)) { double delta = scrollingDevice.lastScrollPosition.y() - value; scrollingDevice.lastScrollPosition.setY(value); angleDelta.setY((delta / scrollingDevice.verticalIncrement) * 120); // We do not set "pixel" delta if it is only measured in ticks. if (scrollingDevice.verticalIncrement > 1) rawDelta.setY(delta); } } if (scrollingDevice.orientations & Qt::Horizontal) { if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.horizontalIndex, &value)) { double delta = scrollingDevice.lastScrollPosition.x() - value; scrollingDevice.lastScrollPosition.setX(value); angleDelta.setX((delta / scrollingDevice.horizontalIncrement) * 120); // We do not set "pixel" delta if it is only measured in ticks. if (scrollingDevice.horizontalIncrement > 1) rawDelta.setX(delta); } } if (!angleDelta.isNull()) { const int dpr = int(platformWindow->devicePixelRatio()); QPoint local(fixed1616ToReal(xiDeviceEvent->event_x)/dpr, fixed1616ToReal(xiDeviceEvent->event_y)/dpr); QPoint global(fixed1616ToReal(xiDeviceEvent->root_x)/dpr, fixed1616ToReal(xiDeviceEvent->root_y)/dpr); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers & Qt::AltModifier) { std::swap(angleDelta.rx(), angleDelta.ry()); std::swap(rawDelta.rx(), rawDelta.ry()); } QWindowSystemInterface::handleWheelEvent(platformWindow, xiEvent->time, local, global, rawDelta, angleDelta, modifiers); } } } else if (xiEvent->evtype == XI_ButtonRelease && scrollingDevice.legacyOrientations) { xXIDeviceEvent* xiDeviceEvent = reinterpret_cast(event); if (QWindow *platformWindow = windowFromId(xiDeviceEvent->event)) { QPoint angleDelta; if (scrollingDevice.legacyOrientations & Qt::Vertical) { if (xiDeviceEvent->detail == 4) angleDelta.setY(120); else if (xiDeviceEvent->detail == 5) angleDelta.setY(-120); } if (scrollingDevice.legacyOrientations & Qt::Horizontal) { if (xiDeviceEvent->detail == 6) angleDelta.setX(120); else if (xiDeviceEvent->detail == 7) angleDelta.setX(-120); } if (!angleDelta.isNull()) { const int dpr = int(platformWindow->devicePixelRatio()); QPoint local(fixed1616ToReal(xiDeviceEvent->event_x)/dpr, fixed1616ToReal(xiDeviceEvent->event_y)/dpr); QPoint global(fixed1616ToReal(xiDeviceEvent->root_x)/dpr, fixed1616ToReal(xiDeviceEvent->root_y)/dpr); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers & Qt::AltModifier) std::swap(angleDelta.rx(), angleDelta.ry()); QWindowSystemInterface::handleWheelEvent(platformWindow, xiEvent->time, local, global, QPoint(), angleDelta, modifiers); } } } #else Q_UNUSED(event); Q_UNUSED(scrollingDevice); #endif // XCB_USE_XINPUT21 } #endif // QT_NO_TABLETEVENT #endif // XCB_USE_XINPUT2 diff --git a/libs/ui/kis_action_manager.cpp b/libs/ui/kis_action_manager.cpp index 6e49712ce5..8dfd5fbc4d 100644 --- a/libs/ui/kis_action_manager.cpp +++ b/libs/ui/kis_action_manager.cpp @@ -1,488 +1,488 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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_action_manager.h" #include #include #include #include "KisPart.h" #include "kis_action.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "operations/kis_operation_ui_factory.h" #include "operations/kis_operation_registry.h" #include "operations/kis_operation.h" #include "kis_layer.h" #include "KisDocument.h" #include "kis_clipboard.h" #include #include #include "QFile" #include #include #include "QFile" #include #include class Q_DECL_HIDDEN KisActionManager::Private { public: Private() : viewManager(0) {} ~Private() { qDeleteAll(uiRegistry.values()); } KisViewManager* viewManager; KActionCollection *actionCollection; QList> actions; KoGenericRegistry uiRegistry; KisOperationRegistry operationRegistry; }; KisActionManager::KisActionManager(KisViewManager* viewManager, KActionCollection *actionCollection) : d(new Private) { d->viewManager = viewManager; d->actionCollection = actionCollection; connect(d->actionCollection, SIGNAL(inserted(QAction*)), SLOT(slotActionAddedToCollection(QAction*))); } KisActionManager::~KisActionManager() { #if 0 if ((d->actions.size() > 0)) { QDomDocument doc; QDomElement e = doc.createElement("Actions"); e.setAttribute("version", "2"); doc.appendChild(e); Q_FOREACH (KisAction *action, d->actions) { QDomElement a = doc.createElement("Action"); a.setAttribute("name", action->objectName()); // But seriously, XML is the worst format ever designed auto addElement = [&](QString title, QString content) { QDomElement newNode = doc.createElement(title); QDomText newText = doc.createTextNode(content); newNode.appendChild(newText); a.appendChild(newNode); }; addElement("icon", action->icon().name()); addElement("text", action->text()); addElement("whatsThis" , action->whatsThis()); addElement("toolTip" , action->toolTip()); addElement("iconText" , action->iconText()); addElement("shortcut" , action->shortcut().toString()); - addElement("activationFlags" , QString::number(action->activationFlags(),2));; + addElement("activationFlags" , QString::number(action->activationFlags(),2)); addElement("activationConditions" , QString::number(action->activationConditions(),2)); addElement("defaultShortcut" , action->defaultShortcut().toString()); addElement("isCheckable" , QString((action->isChecked() ? "true" : "false"))); addElement("statusTip", action->statusTip()); e.appendChild(a); } QFile f("ActionManager.action"); f.open(QFile::WriteOnly); f.write(doc.toString().toUtf8()); f.close(); } #endif delete d; } void KisActionManager::setView(QPointer imageView) { Q_UNUSED(imageView); } void KisActionManager::slotActionAddedToCollection(QAction *action) { /** * Small hack alert: not all the actions are still created by the manager and * immediately added to the action collection. Some plugins add actions * directly to the action collection when a document is created. Here we * catch these cases */ KisActionRegistry::instance()->updateShortcut(action->objectName(), action); } void KisActionManager::addAction(const QString& name, KisAction* action) { Q_ASSERT(!name.isEmpty()); Q_ASSERT(action); Q_ASSERT(d->viewManager); Q_ASSERT(d->actionCollection); d->actionCollection->addAction(name, action); action->setParent(d->actionCollection); d->actions.append(action); action->setActionManager(this); } void KisActionManager::takeAction(KisAction* action) { d->actions.removeOne(action); if (!action->objectName().isEmpty()) { KIS_ASSERT_RECOVER_RETURN(d->actionCollection); d->actionCollection->takeAction(action); } } KisAction *KisActionManager::actionByName(const QString &name) const { Q_FOREACH (KisAction *action, d->actions) { if (action->objectName() == name) { return action; } } return 0; } KisAction *KisActionManager::createAction(const QString &name) { KisAction *a = actionByName(name); // Check if the action already exists if (a) { return a; } // There is some tension here. KisActionManager is supposed to be in control // of global actions, but these actions are supposed to be duplicated. We // will add them to the KisActionRegistry for the time being so we can get // properly categorized shortcuts. a = new KisAction(); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // Add extra properties actionRegistry->propertizeAction(name, a); bool ok; // We will skip this check int activationFlags = actionRegistry->getActionProperty(name, "activationFlags").toInt(&ok, 2); int activationConditions = actionRegistry->getActionProperty(name, "activationConditions").toInt(&ok, 2); a->setActivationFlags((KisAction::ActivationFlags) activationFlags); a->setActivationConditions((KisAction::ActivationConditions) activationConditions); addAction(name, a); return a; } void KisActionManager::updateGUI() { //TODO other flags KisAction::ActivationFlags flags; KisImageWSP image; KisNodeSP node; KisLayerSP layer; KisSelectionManager *selectionManager = 0; KisAction::ActivationConditions conditions = KisAction::NO_CONDITION; if (d->viewManager) { node = d->viewManager->activeNode(); selectionManager = d->viewManager->selectionManager(); // if there are no views, that means no document is open. // we cannot have nodes (selections), devices, or documents without a view if ( d->viewManager->viewCount() > 0 ) { image = d->viewManager->image(); flags |= KisAction::ACTIVE_IMAGE; if (image && image->animationInterface()->hasAnimation()) { flags |= KisAction::IMAGE_HAS_ANIMATION; } if (d->viewManager->viewCount() > 1) { flags |= KisAction::MULTIPLE_IMAGES; } if (d->viewManager->document() && d->viewManager->document()->isModified()) { flags |= KisAction::CURRENT_IMAGE_MODIFIED; } if (d->viewManager->activeDevice()) { flags |= KisAction::ACTIVE_DEVICE; } } if (d->viewManager->selectionEditable()) { conditions |= KisAction::SELECTION_EDITABLE; } } // is there a selection/mask? // you have to have at least one view (document) open for this to be true if (node) { // if a node exists, we know there is an active layer as well flags |= KisAction::ACTIVE_NODE; layer = qobject_cast(node.data()); if (layer) { flags |= KisAction::ACTIVE_LAYER; } if (node->inherits("KisTransparencyMask")) { flags |= KisAction::ACTIVE_TRANSPARENCY_MASK; } if (layer && layer->inherits("KisShapeLayer")) { flags |= KisAction::ACTIVE_SHAPE_LAYER; } if (KisClipboard::instance()->hasLayers()) { flags |= KisAction::LAYERS_IN_CLIPBOARD; } if (selectionManager) { if (selectionManager->havePixelsSelected()) { flags |= KisAction::PIXELS_SELECTED; } if (selectionManager->haveShapesSelected()) { flags |= KisAction::SHAPES_SELECTED; } if (selectionManager->havePixelSelectionWithPixels()) { flags |= KisAction::PIXEL_SELECTION_WITH_PIXELS; } if (selectionManager->havePixelsInClipboard()) { flags |= KisAction::PIXELS_IN_CLIPBOARD; } if (selectionManager->haveShapesInClipboard()) { flags |= KisAction::SHAPES_IN_CLIPBOARD; } } if (node->isEditable(false)) { conditions |= KisAction::ACTIVE_NODE_EDITABLE; } if (node->hasEditablePaintDevice()) { conditions |= KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE; } } // loop through all actions in action manager and determine what should be enabled Q_FOREACH (QPointer action, d->actions) { bool enable; if (action) { if (action->activationFlags() == KisAction::NONE) { enable = true; } else { enable = action->activationFlags() & flags; // combine action flags with updateGUI flags } enable = enable && (int)(action->activationConditions() & conditions) == (int)action->activationConditions(); if (node && enable) { Q_FOREACH (const QString &type, action->excludedNodeTypes()) { if (node->inherits(type.toLatin1())) { enable = false; break; } } } action->setActionEnabled(enable); } } } KisAction *KisActionManager::createStandardAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member) { QAction *standardAction = KStandardAction::create(actionType, receiver, member, 0); KisAction *action = new KisAction(standardAction->icon(), standardAction->text()); const QList defaultShortcuts = standardAction->property("defaultShortcuts").value >(); const QKeySequence defaultShortcut = defaultShortcuts.isEmpty() ? QKeySequence() : defaultShortcuts.at(0); action->setDefaultShortcut(standardAction->shortcut()); #ifdef Q_OS_WIN if (actionType == KStandardAction::SaveAs && defaultShortcuts.isEmpty()) { action->setShortcut(QKeySequence("CTRL+SHIFT+S")); } #endif action->setCheckable(standardAction->isCheckable()); if (action->isCheckable()) { action->setChecked(standardAction->isChecked()); } action->setMenuRole(standardAction->menuRole()); action->setText(standardAction->text()); action->setToolTip(standardAction->toolTip()); if (receiver && member) { if (actionType == KStandardAction::OpenRecent) { QObject::connect(action, SIGNAL(urlSelected(QUrl)), receiver, member); } else if (actionType == KStandardAction::ConfigureToolbars) { QObject::connect(action, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection); } else { QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); } } KisActionRegistry *actionRegistry = KisActionRegistry::instance(); actionRegistry->propertizeAction(standardAction->objectName(), action); addAction(standardAction->objectName(), action); delete standardAction; return action; } void KisActionManager::safePopulateMenu(QMenu *menu, const QString &actionId, KisActionManager *actionManager) { KIS_SAFE_ASSERT_RECOVER_RETURN(actionManager); KisAction *action = actionManager->actionByName(actionId); KIS_SAFE_ASSERT_RECOVER_RETURN(action); menu->addAction(action); } void KisActionManager::registerOperationUIFactory(KisOperationUIFactory* factory) { d->uiRegistry.add(factory); } void KisActionManager::registerOperation(KisOperation* operation) { d->operationRegistry.add(operation); } void KisActionManager::runOperation(const QString& id) { KisOperationConfigurationSP config = new KisOperationConfiguration(id); KisOperationUIFactory* uiFactory = d->uiRegistry.get(id); if (uiFactory) { bool gotConfig = uiFactory->fetchConfiguration(d->viewManager, config); if (!gotConfig) { return; } } runOperationFromConfiguration(config); } void KisActionManager::runOperationFromConfiguration(KisOperationConfigurationSP config) { KisOperation* operation = d->operationRegistry.get(config->id()); Q_ASSERT(operation); operation->runFromXML(d->viewManager, *config); } void KisActionManager::dumpActionFlags() { QFile data("actions.txt"); if (data.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream out(&data); out.setCodec("UTF-8"); Q_FOREACH (KisAction* action, d->actions) { KisAction::ActivationFlags flags = action->activationFlags(); out << "-------- " << action->text() << " --------\n"; out << "Action will activate on: \n"; if (flags & KisAction::ACTIVE_IMAGE) { out << " Active image\n"; } if (flags & KisAction::MULTIPLE_IMAGES) { out << " More than one image open\n"; } if (flags & KisAction::CURRENT_IMAGE_MODIFIED) { out << " Active image modified\n"; } if (flags & KisAction::ACTIVE_DEVICE) { out << " Active device\n"; } if (flags & KisAction::ACTIVE_LAYER) { out << " Active layer\n"; } if (flags & KisAction::ACTIVE_TRANSPARENCY_MASK) { out << " Active transparency mask\n"; } if (flags & KisAction::ACTIVE_NODE) { out << " Active node\n"; } if (flags & KisAction::ACTIVE_SHAPE_LAYER) { out << " Active shape layer\n"; } if (flags & KisAction::PIXELS_SELECTED) { out << " Pixels selected\n"; } if (flags & KisAction::SHAPES_SELECTED) { out << " Shapes selected\n"; } if (flags & KisAction::PIXEL_SELECTION_WITH_PIXELS) { out << " Pixel selection with pixels\n"; } if (flags & KisAction::PIXELS_IN_CLIPBOARD) { out << " Pixels in clipboard\n"; } if (flags & KisAction::SHAPES_IN_CLIPBOARD) { out << " Shape in clipboard\n"; } if (flags & KisAction::IMAGE_HAS_ANIMATION) { out << " Image has animation\n"; } out << "\n\n"; out << "Action will only activate if the following conditions are met: \n"; KisAction::ActivationConditions conditions = action->activationConditions(); if ((int)conditions == 0) { out << " -\n"; } if (conditions & KisAction::ACTIVE_NODE_EDITABLE) { out << " Active Node editable\n"; } if (conditions & KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE) { out << " Active Node has editable paint device\n"; } if (conditions & KisAction::SELECTION_EDITABLE) { out << " Selection is editable\n"; } out << "\n\n"; } } } diff --git a/libs/ui/kis_file_layer.cpp b/libs/ui/kis_file_layer.cpp index db414ccbe1..048c02cba1 100644 --- a/libs/ui/kis_file_layer.cpp +++ b/libs/ui/kis_file_layer.cpp @@ -1,252 +1,252 @@ /* * Copyright (c) 2013 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_file_layer.h" #include #include #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "kis_node_progress_proxy.h" #include "kis_node_visitor.h" #include "kis_image.h" #include "kis_types.h" #include "commands_new/kis_node_move_command2.h" #include "kis_default_bounds.h" #include "kis_layer_properties_icons.h" #include #include #include KisFileLayer::KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity) { /** * Set default paint device for a layer. It will be used in case * the file does not exist anymore. Or course, this can happen only * in the failing execution path. */ m_paintDevice = new KisPaintDevice(image->colorSpace()); connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int))); } KisFileLayer::KisFileLayer(KisImageWSP image, const QString &basePath, const QString &filename, ScalingMethod scaleToImageResolution, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity) , m_basePath(basePath) , m_filename(filename) , m_scalingMethod(scaleToImageResolution) { /** * Set default paint device for a layer. It will be used in case * the file does not exist anymore. Or course, this can happen only * in the failing execution path. */ m_paintDevice = new KisPaintDevice(image->colorSpace()); connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int))); QFileInfo fi(path()); if (fi.exists()) { m_loader.setPath(path()); m_loader.reloadImage(); } } KisFileLayer::~KisFileLayer() { } KisFileLayer::KisFileLayer(const KisFileLayer &rhs) : KisExternalLayer(rhs) { m_basePath = rhs.m_basePath; m_filename = rhs.m_filename; KIS_SAFE_ASSERT_RECOVER_NOOP(QFile::exists(path())); m_scalingMethod = rhs.m_scalingMethod; m_paintDevice = new KisPaintDevice(*rhs.m_paintDevice); connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int))); m_loader.setPath(path()); } QIcon KisFileLayer::icon() const { return KisIconUtils::loadIcon("fileLayer"); } void KisFileLayer::resetCache() { m_loader.reloadImage(); } const KoColorSpace *KisFileLayer::colorSpace() const { return m_paintDevice->colorSpace(); } KisPaintDeviceSP KisFileLayer::original() const { return m_paintDevice; } KisPaintDeviceSP KisFileLayer::paintDevice() const { return 0; } void KisFileLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id== KisLayerPropertiesIcons::openFileLayerFile.id()) { if (property.state.toBool() == false) { openFile(); } } } } KisBaseNode::PropertyList KisFileLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisLayer::sectionModelProperties(); l << KisBaseNode::Property(KoID("sourcefile", i18n("File")), m_filename); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::openFileLayerFile, true); return l; } void KisFileLayer::setFileName(const QString &basePath, const QString &filename) { m_basePath = basePath; m_filename = filename; QFileInfo fi(path()); if (fi.exists()) { m_loader.setPath(path()); m_loader.reloadImage(); } } QString KisFileLayer::fileName() const { return m_filename; } QString KisFileLayer::path() const { if (m_basePath.isEmpty()) { return m_filename; } else { - return QDir(m_basePath).filePath(QDir::cleanPath(m_filename));; + return QDir(m_basePath).filePath(QDir::cleanPath(m_filename)); } } void KisFileLayer::openFile() const { bool fileAlreadyOpen = false; Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { if (doc->url().toLocalFile()==path()){ fileAlreadyOpen = true; } } if (!fileAlreadyOpen) { KisPart::instance()->openExistingFile(QUrl::fromLocalFile(QFileInfo(path()).absoluteFilePath())); } } KisFileLayer::ScalingMethod KisFileLayer::scalingMethod() const { return m_scalingMethod; } void KisFileLayer::setScalingMethod(ScalingMethod method) { m_scalingMethod = method; } void KisFileLayer::slotLoadingFinished(KisPaintDeviceSP projection, int xRes, int yRes) { qint32 oldX = x(); qint32 oldY = y(); const QRect oldLayerExtent = m_paintDevice->extent(); m_paintDevice->makeCloneFrom(projection, projection->extent()); m_paintDevice->setDefaultBounds(new KisDefaultBounds(image())); QSize size = projection->exactBounds().size(); if (m_scalingMethod == ToImagePPI && (image()->xRes() != xRes || image()->yRes() != yRes)) { qreal xscale = image()->xRes() / xRes; qreal yscale = image()->yRes() / yRes; KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic")); worker.run(); } else if (m_scalingMethod == ToImageSize) { QSize sz = size; sz.scale(image()->size(), Qt::KeepAspectRatio); qreal xscale = (qreal)sz.width() / (qreal)size.width(); qreal yscale = (qreal)sz.height() / (qreal)size.height(); KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic")); worker.run(); } m_paintDevice->setX(oldX); m_paintDevice->setY(oldY); setDirty(m_paintDevice->extent() | oldLayerExtent); } KisNodeSP KisFileLayer::clone() const { return KisNodeSP(new KisFileLayer(*this)); } bool KisFileLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } bool KisFileLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisFileLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KUndo2Command* KisFileLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisFileLayer::transform(const QTransform &/*transform*/) { warnKrita << "WARNING: File Layer does not support transformations!" << name(); return 0; } diff --git a/libs/ui/kis_multinode_property.h b/libs/ui/kis_multinode_property.h index e30c56e32a..a8675d2df6 100644 --- a/libs/ui/kis_multinode_property.h +++ b/libs/ui/kis_multinode_property.h @@ -1,652 +1,652 @@ /* * Copyright (c) 2016 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. */ #ifndef __KIS_MULTINODE_PROPERTY_H #define __KIS_MULTINODE_PROPERTY_H #include #include #include #include #include #include #include #include #include "kis_node.h" #include "kis_layer.h" #include "kis_layer_utils.h" #include "kritaui_export.h" class KisMultinodePropertyInterface; class MultinodePropertyBaseConnector; template class MultinodePropertyBoolConnector; template class KisMultinodeProperty; /******************************************************************/ /* Adapters */ /******************************************************************/ struct BaseAdapter { static KisNodeList filterNodes(KisNodeList nodes) { return nodes; } void setNumNodes(int numNodes) { m_numNodes = numNodes; } int m_numNodes = 0; }; struct CompositeOpAdapter : public BaseAdapter { typedef QString ValueType; typedef MultinodePropertyBaseConnector ConnectorType; static const bool forceIgnoreByDefault = false; static ValueType propForNode(KisNodeSP node) { return node->compositeOpId(); } static void setPropForNode(KisNodeSP node, const ValueType &value, int index) { Q_UNUSED(index); node->setCompositeOpId(value); } }; struct NameAdapter : public BaseAdapter { typedef QString ValueType; typedef MultinodePropertyBaseConnector ConnectorType; static const bool forceIgnoreByDefault = true; ValueType propForNode(KisNodeSP node) { return m_numNodes == 1 ? node->name() : stripName(node->name()); } void setPropForNode(KisNodeSP node, const ValueType &value, int index) { QString name; if (index < 0 || m_numNodes == 1) { name = value; } else { name = QString("%1 %2").arg(stripName(value)).arg(index); } node->setName(name); } private: static QString stripName(QString name) { QRegExp rexp("^(.+) (\\d{1,3})$"); int pos = rexp.indexIn(name); if (pos > -1) { name = rexp.cap(1); } return name; } }; struct ColorLabelAdapter : public BaseAdapter { typedef int ValueType; typedef MultinodePropertyBaseConnector ConnectorType; static const bool forceIgnoreByDefault = false; static ValueType propForNode(KisNodeSP node) { return node->colorLabelIndex(); } static void setPropForNode(KisNodeSP node, const ValueType &value, int index) { Q_UNUSED(index); node->setColorLabelIndex(value); } }; struct OpacityAdapter : public BaseAdapter { typedef int ValueType; typedef MultinodePropertyBaseConnector ConnectorType; static const bool forceIgnoreByDefault = false; static ValueType propForNode(KisNodeSP node) { return qRound(node->opacity() / 255.0 * 100); } static void setPropForNode(KisNodeSP node, const ValueType &value, int index) { Q_UNUSED(index); node->setOpacity(qRound(value * 255.0 / 100)); } }; inline uint qHash(const KisBaseNode::Property &prop, uint seed = 0) { return qHash(prop.name, seed); } struct LayerPropertyAdapter : public BaseAdapter { typedef bool ValueType; typedef MultinodePropertyBoolConnector ConnectorType; static const bool forceIgnoreByDefault = false; LayerPropertyAdapter(const QString &propName) : m_propName(propName) {} ValueType propForNode(KisNodeSP node) { KisBaseNode::PropertyList props = node->sectionModelProperties(); Q_FOREACH (const KisBaseNode::Property &prop, props) { if (prop.name == m_propName) { return prop.state.toBool(); } } return false; } void setPropForNode(KisNodeSP node, const ValueType &value, int index) { Q_UNUSED(index); bool stateChanged = false; KisBaseNode::PropertyList props = node->sectionModelProperties(); KisBaseNode::PropertyList::iterator it = props.begin(); KisBaseNode::PropertyList::iterator end = props.end(); for (; it != end; ++it) { if (it->name == m_propName) { it->state = value; stateChanged = true; break; } } if (stateChanged) { node->setSectionModelProperties(props); } } QString name() const { return m_propName; } static KisBaseNode::PropertyList adaptersList(KisNodeList nodes) { QHash> adapters; Q_FOREACH (KisNodeSP node, nodes) { int sortingIndex = 0; KisBaseNode::PropertyList props = node->sectionModelProperties(); Q_FOREACH (const KisBaseNode::Property &prop, props) { if (prop.state.type() != QVariant::Bool) continue; if (!adapters.contains(prop.id)) { adapters.insert(prop.id, std::make_pair(prop, sortingIndex)); } else { adapters[prop.id].second = qMin(adapters[prop.id].second, sortingIndex); } sortingIndex++; } } QMultiMap sortedAdapters; auto it = adapters.constBegin(); auto end = adapters.constEnd(); for (; it != end; ++it) { KisBaseNode::Property prop; int sortingIndex = 0; std::tie(prop, sortingIndex) = it.value(); sortedAdapters.insert(sortingIndex, prop); } return sortedAdapters.values(); } private: QString m_propName; }; struct ChannelFlagAdapter : public BaseAdapter { typedef bool ValueType; typedef MultinodePropertyBoolConnector ConnectorType; static const bool forceIgnoreByDefault = false; struct Property { Property(QString _name, int _channelIndex) : name(_name), channelIndex(_channelIndex) {} QString name; int channelIndex; }; typedef QList PropertyList; ChannelFlagAdapter(const Property &prop) : m_prop(prop) {} ValueType propForNode(KisNodeSP node) { KisLayerSP layer = toLayer(node); Q_ASSERT(layer); QBitArray flags = layer->channelFlags(); if (flags.isEmpty()) return true; return flags.testBit(m_prop.channelIndex); } void setPropForNode(KisNodeSP node, const ValueType &value, int index) { Q_UNUSED(index); KisLayerSP layer = toLayer(node); Q_ASSERT(layer); QBitArray flags = layer->channelFlags(); if (flags.isEmpty()) { flags = QBitArray(layer->colorSpace()->channelCount(), true); } if (flags.testBit(m_prop.channelIndex) != value) { flags.setBit(m_prop.channelIndex, value); layer->setChannelFlags(flags); } } QString name() const { - return m_prop.name;; + return m_prop.name; } static PropertyList adaptersList(KisNodeList nodes) { PropertyList props; { bool nodesDiffer = KisLayerUtils::checkNodesDiffer(nodes, [](KisNodeSP node) { return node->colorSpace(); }); if (nodesDiffer) { return props; } } QList channels = nodes.first()->colorSpace()->channels(); int index = 0; Q_FOREACH (KoChannelInfo *info, channels) { props << Property(info->name(), index); index++; } return props; } static KisNodeList filterNodes(KisNodeList nodes) { KisNodeList filteredNodes; Q_FOREACH (KisNodeSP node, nodes) { if (toLayer(node)) { filteredNodes << node; } } return filteredNodes; } private: static KisLayerSP toLayer(KisNodeSP node) { return qobject_cast(node.data()); } private: Property m_prop; }; /******************************************************************/ /* MultinodePropertyConnectorInterface */ /******************************************************************/ class KRITAUI_EXPORT MultinodePropertyConnectorInterface : public QObject { Q_OBJECT public: ~MultinodePropertyConnectorInterface() override; /** * Public interface */ virtual void connectIgnoreCheckBox(QCheckBox *ignoreBox) = 0; void connectValueChangedSignal(const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection); /** * Clicking on this widget will automatically enable it, * setting "Ignored" property to false. * * Default implementation does nothing. */ virtual void connectAutoEnableWidget(QWidget *widget); Q_SIGNALS: void sigValueChanged(); protected Q_SLOTS: virtual void slotIgnoreCheckBoxChanged(int state) = 0; public: /** * Interface for KisMultinodeProperty's notifications */ virtual void notifyValueChanged(); virtual void notifyIgnoreChanged() = 0; }; /******************************************************************/ /* MultinodePropertyBaseConnector */ /******************************************************************/ class KRITAUI_EXPORT MultinodePropertyBaseConnector : public MultinodePropertyConnectorInterface { public: MultinodePropertyBaseConnector(KisMultinodePropertyInterface *parent); void connectIgnoreCheckBox(QCheckBox *ignoreBox) override; void notifyIgnoreChanged() override; void connectAutoEnableWidget(QWidget *widget) override; protected: void slotIgnoreCheckBoxChanged(int state) override; private: QPointer m_ignoreBox; KisMultinodePropertyInterface *m_parent; }; /******************************************************************/ /* MultinodePropertyBoolConnector */ /******************************************************************/ template class MultinodePropertyBoolConnector : public MultinodePropertyConnectorInterface { typedef KisMultinodeProperty PropertyType; public: MultinodePropertyBoolConnector(PropertyType *parent) : m_parent(parent) { } void connectIgnoreCheckBox(QCheckBox *ignoreBox) override { m_ignoreBox = ignoreBox; if ((!m_parent->isIgnored() && !m_parent->savedValuesDiffer()) || m_parent->haveTheOnlyNode()) { m_ignoreBox->setTristate(false); } else { m_ignoreBox->setTristate(true); } connect(m_ignoreBox, SIGNAL(stateChanged(int)), SLOT(slotIgnoreCheckBoxChanged(int))); } void notifyIgnoreChanged() override { // noop } void notifyValueChanged() override { if (m_ignoreBox) { Qt::CheckState newState = m_parent->isIgnored() ? Qt::PartiallyChecked : bool(m_parent->value()) ? Qt::Checked : Qt::Unchecked; if (m_ignoreBox->checkState() != newState) { m_ignoreBox->setCheckState(newState); } } MultinodePropertyConnectorInterface::notifyValueChanged(); } protected: void slotIgnoreCheckBoxChanged(int state) override { if (state == Qt::PartiallyChecked) { m_parent->setIgnored(true); } else { m_parent->setIgnored(false); m_parent->setValue(bool(state == Qt::Checked)); } } private: QPointer m_ignoreBox; PropertyType *m_parent; }; /******************************************************************/ /* MultinodePropertyUndoCommand */ /******************************************************************/ template class MultinodePropertyUndoCommand : public KUndo2Command { public: typedef typename PropertyAdapter::ValueType ValueType; public: MultinodePropertyUndoCommand(PropertyAdapter propAdapter, KisNodeList nodes, const QList &oldValues, ValueType newValue, KUndo2Command *parent = 0) : KUndo2Command(parent), m_propAdapter(propAdapter), m_nodes(nodes), m_oldValues(oldValues), m_newValue(newValue) { } void undo() override { int index = 0; Q_FOREACH (KisNodeSP node, m_nodes) { m_propAdapter.setPropForNode(node, m_oldValues[index], -1); index++; } } void redo() override { int index = 0; Q_FOREACH (KisNodeSP node, m_nodes) { m_propAdapter.setPropForNode(node, m_newValue, index); index++; } } private: PropertyAdapter m_propAdapter; KisNodeList m_nodes; QList m_oldValues; ValueType m_newValue; }; /******************************************************************/ /* KisMultinodePropertyInterface */ /******************************************************************/ class KRITAUI_EXPORT KisMultinodePropertyInterface { public: KisMultinodePropertyInterface(); virtual ~KisMultinodePropertyInterface(); virtual void rereadCurrentValue() = 0; virtual void setIgnored(bool value) = 0; virtual bool isIgnored() const = 0; virtual bool savedValuesDiffer() const = 0; virtual bool haveTheOnlyNode() const = 0; virtual void connectValueChangedSignal(const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) = 0; virtual void connectIgnoreCheckBox(QCheckBox *ignoreBox) = 0; virtual void connectAutoEnableWidget(QWidget *widget) = 0; virtual KUndo2Command* createPostExecutionUndoCommand() = 0; }; typedef QSharedPointer KisMultinodePropertyInterfaceSP; /******************************************************************/ /* KisMultinodeProperty */ /******************************************************************/ template class KisMultinodeProperty : public KisMultinodePropertyInterface { public: typedef typename PropertyAdapter::ValueType ValueType; typedef typename PropertyAdapter::ConnectorType ConnectorType; public: KisMultinodeProperty(KisNodeList nodes, PropertyAdapter adapter = PropertyAdapter()) : m_nodes(PropertyAdapter::filterNodes(nodes)), m_savedValuesDiffer(false), m_propAdapter(adapter), m_connector(new ConnectorType(this)) { Q_ASSERT(!m_nodes.isEmpty()); m_propAdapter.setNumNodes(m_nodes.size()); ValueType lastValue = m_propAdapter.propForNode(m_nodes.first()); Q_FOREACH (KisNodeSP node, m_nodes) { ValueType value = m_propAdapter.propForNode(node); m_savedValues.append(value); if (value != lastValue) { m_savedValuesDiffer = true; } lastValue = value; } m_isIgnored = m_nodes.size() > 1 && PropertyAdapter::forceIgnoreByDefault ? true : m_savedValuesDiffer; m_currentValue = defaultValue(); } ~KisMultinodeProperty() override {} void rereadCurrentValue() override { if (m_isIgnored) return; ValueType lastValue = m_propAdapter.propForNode(m_nodes.first()); Q_FOREACH (KisNodeSP node, m_nodes) { ValueType value = m_propAdapter.propForNode(node); if (value != lastValue) { qWarning() << "WARNING: mutiprops: values differ after reread!"; } lastValue = value; } if (lastValue != m_currentValue) { m_currentValue = lastValue; m_connector->notifyValueChanged(); } } void setValue(const ValueType &value) { Q_ASSERT(!m_isIgnored); if (value == m_currentValue) return; int index = 0; Q_FOREACH (KisNodeSP node, m_nodes) { m_propAdapter.setPropForNode(node, value, index); index++; } m_currentValue = value; m_connector->notifyValueChanged(); } ValueType value() const { return m_currentValue; } void setIgnored(bool value) override { if (value == m_isIgnored) return; m_isIgnored = value; if (m_isIgnored) { int index = 0; Q_FOREACH (KisNodeSP node, m_nodes) { m_propAdapter.setPropForNode(node, m_savedValues[index], -1); index++; } m_currentValue = defaultValue(); } else { int index = 0; Q_FOREACH (KisNodeSP node, m_nodes) { m_propAdapter.setPropForNode(node, m_currentValue, index); index++; } } m_connector->notifyValueChanged(); m_connector->notifyIgnoreChanged(); } bool isIgnored() const override { return m_isIgnored; } KUndo2Command* createPostExecutionUndoCommand() override { KIS_ASSERT_RECOVER(!m_isIgnored) { return new KUndo2Command(); } return new MultinodePropertyUndoCommand(m_propAdapter, m_nodes, m_savedValues, m_currentValue); } // TODO: disconnect methods... void connectIgnoreCheckBox(QCheckBox *ignoreBox) override { m_connector->connectIgnoreCheckBox(ignoreBox); } void connectValueChangedSignal(const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) override { m_connector->connectValueChangedSignal(receiver, method, type); } void connectAutoEnableWidget(QWidget *widget) override { m_connector->connectAutoEnableWidget(widget); } /** * Interface for the connector */ bool savedValuesDiffer() const override { return m_savedValuesDiffer; } bool haveTheOnlyNode() const override { return m_nodes.size() == 1; } private: ValueType defaultValue() const { return m_savedValues.first(); } private: bool m_isIgnored; ValueType m_currentValue; KisNodeList m_nodes; QList m_savedValues; bool m_savedValuesDiffer; PropertyAdapter m_propAdapter; QScopedPointer m_connector; }; typedef KisMultinodeProperty KisMultinodeCompositeOpProperty; typedef KisMultinodeProperty KisMultinodeOpacityProperty; typedef KisMultinodeProperty KisMultinodeNameProperty; typedef KisMultinodeProperty KisMultinodeColorLabelProperty; #endif /* __KIS_MULTINODE_PROPERTY_H */ diff --git a/libs/ui/widgets/KisVisualRectangleSelectorShape.cpp b/libs/ui/widgets/KisVisualRectangleSelectorShape.cpp index a61e547f32..a022e59114 100644 --- a/libs/ui/widgets/KisVisualRectangleSelectorShape.cpp +++ b/libs/ui/widgets/KisVisualRectangleSelectorShape.cpp @@ -1,373 +1,373 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 "KisVisualRectangleSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" KisVisualRectangleSelectorShape::KisVisualRectangleSelectorShape(QWidget *parent, Dimensions dimension, ColorModel model, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, int width, singelDTypes d) : KisVisualColorSelectorShape(parent, dimension, model, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualRectangleSelectorShape" << this; m_type = d; m_barWidth = width; } KisVisualRectangleSelectorShape::~KisVisualRectangleSelectorShape() { //qDebug() << "deleting KisVisualRectangleSelectorShape" << this; } void KisVisualRectangleSelectorShape::setBorderWidth(int width) { m_barWidth = width; } QRect KisVisualRectangleSelectorShape::getSpaceForSquare(QRect geom) { QPointF tl; QPointF br; if (m_type==KisVisualRectangleSelectorShape::vertical) { br = geom.bottomRight(); tl = QPoint(geom.topLeft().x()+m_barWidth, geom.topLeft().y()); } else if (m_type==KisVisualRectangleSelectorShape::horizontal) { br = geom.bottomRight(); tl = QPoint(geom.topLeft().x(), geom.topLeft().y()+m_barWidth); } else { tl = QPointF (geom.topLeft().x()+m_barWidth, geom.topLeft().y()+m_barWidth); br = QPointF (geom.bottomRight().x()-m_barWidth, geom.bottomRight().y()-m_barWidth); } QRect a(tl.toPoint(), br.toPoint()); QRect r(a.left(), a.top(), qMin(a.height(), a.width()), qMin(a.height(), a.width())); return r; } QRect KisVisualRectangleSelectorShape::getSpaceForCircle(QRect geom) { return getSpaceForSquare(geom); } QRect KisVisualRectangleSelectorShape::getSpaceForTriangle(QRect geom) { return getSpaceForSquare(geom); } QPointF KisVisualRectangleSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) { qreal x = 0.5 * m_barWidth; qreal y = 0.5 * m_barWidth; qreal offset = 5.0; KisVisualColorSelectorShape::Dimensions dimension = getDimensions(); if (dimension == KisVisualColorSelectorShape::onedimensional) { if ( m_type == KisVisualRectangleSelectorShape::vertical) { y = qMin(coordinate.x()*(height()-offset*2)+offset, (qreal)height()); } else if (m_type == KisVisualRectangleSelectorShape::horizontal) { x = qMin(coordinate.x()*(width()-offset*2)+offset, (qreal)width()); } else if (m_type == KisVisualRectangleSelectorShape::border) { QRectF innerRect(m_barWidth/2, m_barWidth/2, width()-m_barWidth, height()-m_barWidth); QPointF left (innerRect.left(),innerRect.center().y()); QList polygonLines; polygonLines.append(QLineF(left, innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), left)); qreal totalLength =0.0; Q_FOREACH(QLineF line, polygonLines) { totalLength += line.length(); } qreal length = coordinate.x()*totalLength; QPointF intersect(x,y); Q_FOREACH(QLineF line, polygonLines) { if (line.length()>length && length>0){ intersect = line.pointAt(length/line.length()); } length-=line.length(); } x = qRound(intersect.x()); y = qRound(intersect.y()); } else /*if (m_type == KisVisualRectangleSelectorShape::borderMirrored)*/ { QRectF innerRect(m_barWidth/2, m_barWidth/2, width()-m_barWidth, height()-m_barWidth); QPointF bottom (innerRect.center().x(), innerRect.bottom()); QList polygonLines; polygonLines.append(QLineF(bottom, innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), bottom)); qreal totalLength =0.0; Q_FOREACH(QLineF line, polygonLines) { totalLength += line.length(); } qreal length = coordinate.x()*(totalLength/2); QPointF intersect(x,y); if (coordinate.y()==1) { for (int i = polygonLines.size()-1; i==0; i--) { QLineF line = polygonLines.at(i); if (line.length()>length && length>0){ intersect = line.pointAt(length/line.length()); } length-=line.length(); } } else { Q_FOREACH(QLineF line, polygonLines) { if (line.length()>length && length>0){ intersect = line.pointAt(length/line.length()); } length-=line.length(); } } x = qRound(intersect.x()); y = qRound(intersect.y()); } } else { x = qMin(coordinate.x()*(height()-offset*2)+offset, (qreal)height()); y = qMin(coordinate.y()*(width()-offset*2)+offset, (qreal)width()); } return QPointF(x,y); } QPointF KisVisualRectangleSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) { //default implementation: qreal x = 0.5; qreal y = 0.5; qreal offset = 5.0; KisVisualColorSelectorShape::Dimensions dimension = getDimensions(); if (getMaskMap().contains(coordinate)) { if (dimension == KisVisualColorSelectorShape::onedimensional ) { if (m_type == KisVisualRectangleSelectorShape::vertical) { x = qMax(((qreal)coordinate.y()-offset)/((qreal)height()-offset*2), 0.0); } else if (m_type == KisVisualRectangleSelectorShape::horizontal) { x = qMax(((qreal)coordinate.x()-offset)/((qreal)width()-offset*2),0.0); } else if (m_type == KisVisualRectangleSelectorShape::border) { //border QRectF innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); QPointF left (innerRect.left(),innerRect.center().y()); QList polygonLines; polygonLines.append(QLineF(left, innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), left)); QLineF radius(coordinate, this->geometry().center()); QPointF intersect(0.5,0.5); qreal length = 0.0; qreal totalLength = 0.0; bool foundIntersect = false; Q_FOREACH(QLineF line, polygonLines) { if (line.intersect(radius,&intersect)==QLineF::BoundedIntersection && foundIntersect==false) { foundIntersect = true; length+=QLineF(line.p1(), intersect).length(); } if (foundIntersect==false) { length+=line.length(); } totalLength+=line.length(); } x = length/totalLength; } else /*if (m_type == KisVisualRectangleSelectorShape::borderMirrored)*/ { //border QRectF innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); QPointF bottom (innerRect.center().x(), innerRect.bottom()); QList polygonLines; polygonLines.append(QLineF(bottom, innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), bottom)); QLineF radius(coordinate, this->geometry().center()); QPointF intersect(0.5,0.5); qreal length = 0.0; qreal totalLength = 0.0; bool foundIntersect = false; Q_FOREACH(QLineF line, polygonLines) { if (line.intersect(radius,&intersect)==QLineF::BoundedIntersection && foundIntersect==false) { foundIntersect = true; length+=QLineF(line.p1(), intersect).length(); } if (foundIntersect==false) { length+=line.length(); } totalLength+=line.length(); } int halflength = totalLength/2; if (length>halflength) { x = (halflength - (length-halflength))/halflength; y = 1.0; } else { x = length/halflength; y = 0.0; } } } else { x = qMax(((qreal)coordinate.x()-offset)/((qreal)width()-offset*2), 0.0); - y = qMax(((qreal)coordinate.y()-offset)/((qreal)height()-offset*2), 0.0);; + y = qMax(((qreal)coordinate.y()-offset)/((qreal)height()-offset*2), 0.0); } } return QPointF(x, y); } QRegion KisVisualRectangleSelectorShape::getMaskMap() { QRegion mask = QRegion(0,0,width(),height()); if (m_type==KisVisualRectangleSelectorShape::border || m_type==KisVisualRectangleSelectorShape::borderMirrored) { mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2))); } return mask; } void KisVisualRectangleSelectorShape::resizeEvent(QResizeEvent *) { //qDebug() << this << "KisVisualRectangleSelectorShape::resizeEvent"; forceImageUpdate(); } void KisVisualRectangleSelectorShape::drawCursor() { //qDebug() << this << "KisVisualRectangleSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); //QPainterPath path; QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; QRect rect(cursorPoint.toPoint().x()-cursorwidth,cursorPoint.toPoint().y()-cursorwidth, cursorwidth*2,cursorwidth*2); if (m_type==KisVisualRectangleSelectorShape::vertical){ int x = ( cursorPoint.x()-(width()/2)+1 ); int y = ( cursorPoint.y()-cursorwidth ); rect.setCoords(x, y, x+width()-2, y+(cursorwidth*2)); painter.save(); painter.setCompositionMode(QPainter::CompositionMode_Clear); QPen pen; pen.setWidth(5); painter.setPen(pen); painter.drawLine(QLine(QPoint(0.0,0.0), QPoint(0.0,height()))); painter.drawLine(QLine(QPoint(width(),0.0), QPoint(width(),height()))); painter.restore(); } else { int x = cursorPoint.x()-cursorwidth; int y = cursorPoint.y()-(height()/2)+1; rect.setCoords(x, y, x+(cursorwidth*2), y+cursorwidth-2); } QRectF innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); if (getDimensions() == KisVisualColorSelectorShape::onedimensional && m_type!=KisVisualRectangleSelectorShape::border && m_type!=KisVisualRectangleSelectorShape::borderMirrored) { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawRect(rect); //set filter conversion! fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); rect.setCoords(rect.topLeft().x()+1, rect.topLeft().y()+1, rect.topLeft().x()+rect.width()-2, rect.topLeft().y()+rect.height()-2); painter.drawRect(rect); }else if(m_type==KisVisualRectangleSelectorShape::borderMirrored){ painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPoint mirror(innerRect.center().x()+(innerRect.center().x()-cursorPoint.x()),cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else { painter.save(); painter.setCompositionMode(QPainter::CompositionMode_Clear); QPen pen; pen.setWidth(5); painter.setPen(pen); painter.drawRect(QRect(0,0,width(),height())); painter.restore(); painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); } painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KoGlobal.cpp b/libs/widgets/KoGlobal.cpp index 3b936e6c48..02e676c840 100644 --- a/libs/widgets/KoGlobal.cpp +++ b/libs/widgets/KoGlobal.cpp @@ -1,158 +1,158 @@ /* This file is part of the KDE project Copyright (C) 2001 David Faure Copyright 2003 Nicolas GOUTTE 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 "KoGlobal.h" #include #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KoGlobal, s_instance) KoGlobal* KoGlobal::self() { return s_instance; } KoGlobal::KoGlobal() : m_pointSize(-1) , m_calligraConfig(0) { // Fixes a bug where values from some config files are not picked up // due to KSharedConfig::openConfig() being initialized before paths have been set up above. // NOTE: Values set without a sync() call before KoGlobal has been initialized will not stick KSharedConfig::openConfig()->reparseConfiguration(); } KoGlobal::~KoGlobal() { delete m_calligraConfig; } QStringList KoGlobal::_listOfLanguageTags() { if (m_langMap.isEmpty()) createListOfLanguages(); return m_langMap.values(); } QStringList KoGlobal::_listOfLanguages() { if (m_langMap.empty()) createListOfLanguages(); return m_langMap.keys(); } void KoGlobal::createListOfLanguages() { KConfig config("all_languages", KConfig::NoGlobals); // Note that we could also use KLocale::allLanguagesTwoAlpha QMap seenLanguages; const QStringList langlist = config.groupList(); for (QStringList::ConstIterator itall = langlist.begin(); itall != langlist.end(); ++itall) { const QString tag = *itall; const QString name = config.group(tag).readEntry("Name", tag); // e.g. name is "French" and tag is "fr" // The QMap does the sorting on the display-name, so that // comboboxes are sorted. m_langMap.insert(name, tag); seenLanguages.insert(tag, true); } // Also take a look at the installed translations. // Many of them are already in all_languages but all_languages doesn't // currently have en_GB or en_US etc. QStringList translationList = KoResourcePaths::findAllResources("locale", QString::fromLatin1("*/entry.desktop")) + KoResourcePaths::findAllResources("locale", - QString::fromLatin1("*/kf5_entry.desktop"));; + QString::fromLatin1("*/kf5_entry.desktop")); translationList.removeDuplicates(); for (QStringList::Iterator it = translationList.begin(); it != translationList.end(); ++it) { // Extract the language tag from the directory name QString tag = *it; int index = tag.lastIndexOf('/'); tag = tag.left(index); index = tag.lastIndexOf('/'); tag = tag.mid(index + 1); if (seenLanguages.find(tag) == seenLanguages.end()) { KConfig entry(*it, KConfig::SimpleConfig); const QString name = entry.group("KCM Locale").readEntry("Name", tag); // e.g. name is "US English" and tag is "en_US" m_langMap.insert(name, tag); // enable this if writing a third way of finding languages below //seenLanguages.insert( tag, true ); } } // #### We also might not have an entry for a language where spellchecking is supported, // but no KDE translation is available, like fr_CA. // How to add them? } QString KoGlobal::tagOfLanguage(const QString & _lang) { const LanguageMap& map = self()->m_langMap; QMap::ConstIterator it = map.find(_lang); if (it != map.end()) return *it; return QString(); } QString KoGlobal::languageFromTag(const QString &langTag) { const LanguageMap& map = self()->m_langMap; QMap::ConstIterator it = map.begin(); const QMap::ConstIterator end = map.end(); for (; it != end; ++it) if (it.value() == langTag) return it.key(); // Language code not found. Better return the code (tag) than nothing. return langTag; } KConfig* KoGlobal::_calligraConfig() { if (!m_calligraConfig) { m_calligraConfig = new KConfig("kritarc"); } return m_calligraConfig; } diff --git a/plugins/assistants/Assistants/kis_assistant_tool.cc b/plugins/assistants/Assistants/kis_assistant_tool.cc index ad1cd064f2..0c8e5dca20 100644 --- a/plugins/assistants/Assistants/kis_assistant_tool.cc +++ b/plugins/assistants/Assistants/kis_assistant_tool.cc @@ -1,917 +1,917 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2017 Scott Petrovic * * 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; version 2.1 of the License. * * 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 #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_global.h" #include "VanishingPointAssistant.h" #include KisAssistantTool::KisAssistantTool(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast(canvas)), m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0) { Q_ASSERT(m_canvas); setObjectName("tool_assistanttool"); } KisAssistantTool::~KisAssistantTool() { } void KisAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_canvas->paintingAssistantsDecoration()->activateAssistantsEditor(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_handleDrag = 0; m_internalMode = MODE_CREATION; m_assistantHelperYOffset = 10; m_canvas->paintingAssistantsDecoration()->setHandleSize(17); m_handleSize = 17; if (m_optionsWidget) { m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } m_canvas->updateCanvas(); } void KisAssistantTool::deactivate() { m_canvas->paintingAssistantsDecoration()->deactivateAssistantsEditor(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisAssistantTool::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); bool newAssistantAllowed = true; KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); if (m_newAssistant) { m_internalMode = MODE_CREATION; *m_newAssistant->handles().back() = canvasDecoration->snapToGuide(event, QPointF(), false); if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } m_canvas->updateCanvas(); return; } m_handleDrag = 0; double minDist = 81.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(canvasDecoration->snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); // syncs the assistant handles to the handles reference we store in this tool // they can get out of sync with the way the actions and paintevents occur // we probably need to stop storing a reference in m_handles and call the assistants directly m_handles = m_canvas->paintingAssistantsDecoration()->handles(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // find out which handle on all assistants is closest to the mouse position // vanishing points have "side handles", so make sure to include that { QList allAssistantHandles; allAssistantHandles.append(assistant->handles()); allAssistantHandles.append(assistant->sideHandles()); Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleDrag = handle; assistantSelected(assistant); // whatever handle is the closest contains the selected assistant } } } if(m_handleDrag && assistant->id() == "perspective") { // Look for the handle which was pressed if (m_handleDrag == assistant->topLeft()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_internalMode = MODE_DRAGGING_NODE; } else if (m_handleDrag == assistant->topRight()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); } else if (m_handleDrag == assistant->bottomLeft()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); } else if (m_handleDrag == assistant->bottomRight()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); } else if (m_handleDrag == assistant->leftMiddle()) { m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5); m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL ); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->rightMiddle()) { m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->topMiddle()) { m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5, (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->bottomMiddle()) { m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" || assistant->id() == "parallel ruler" || assistant->id() == "infinite ruler" || assistant->id() == "spline")){ if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if(assistant->handles().size()==4){ if (m_handleDrag == assistant->handles()[2]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[3]) { m_dragStart = *assistant->handles()[1]; } } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" || assistant->id() == "concentric ellipse" || assistant->id() == "fisheye-point")){ m_snapIsRadial = false; if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[2]) { m_dragStart = assistant->buttonPosition(); m_radius = QLineF(m_dragStart, *assistant->handles()[0]); m_snapIsRadial = true; } } else { m_dragStart = assistant->buttonPosition(); m_snapIsRadial = false; } } if (m_handleDrag) { // TODO: Shift-press should now be handled using the alternate actions // if (event->modifiers() & Qt::ShiftModifier) { // m_handleDrag->uncache(); // m_handleDrag = m_handleDrag->split()[0]; // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles(); // } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_assistantDrag.clear(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // This code contains the click event behavior. QTransform initialTransform = m_canvas->coordinatesConverter()->documentToWidgetTransform(); QPointF actionsPosition = initialTransform.map(assistant->buttonPosition()); // for UI editor widget controls with move, show, and delete -- disregard document transforms like rotating and mirroring. // otherwise the UI controls get awkward to use when they are at 45 degree angles or the order of controls gets flipped backwards QPointF uiMousePosition = initialTransform.map( canvasDecoration->snapToGuide(event, QPointF(), false)); AssistantEditorData editorShared; // shared position data between assistant tool and decoration QPointF iconMovePosition(actionsPosition + editorShared.moveIconPosition); QPointF iconSnapPosition(actionsPosition + editorShared.snapIconPosition); QPointF iconDeletePosition(actionsPosition + editorShared.deleteIconPosition); QRectF deleteRect(iconDeletePosition, QSizeF(editorShared.deleteIconSize, editorShared.deleteIconSize)); QRectF visibleRect(iconSnapPosition, QSizeF(editorShared.snapIconSize, editorShared.snapIconSize)); QRectF moveRect(iconMovePosition, QSizeF(editorShared.moveIconSize, editorShared.moveIconSize)); if (moveRect.contains(uiMousePosition)) { m_assistantDrag = assistant; m_cursorStart = event->point; m_currentAdjustment = QPointF(); m_internalMode = MODE_EDITING; assistantSelected(assistant); // whatever handle is the closest contains the selected assistant return; } if (deleteRect.contains(uiMousePosition)) { removeAssistant(assistant); if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) { m_internalMode = MODE_CREATION; } else m_internalMode = MODE_EDITING; m_canvas->updateCanvas(); return; } if (visibleRect.contains(uiMousePosition)) { newAssistantAllowed = false; assistant->setSnappingActive(!assistant->isSnappingActive()); // toggle assistant->uncache();//this updates the chache of the assistant, very important. assistantSelected(assistant); // whatever handle is the closest contains the selected assistant } } if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visiblity// QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant()); m_internalMode = MODE_CREATION; m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); if (m_newAssistant->numHandles() <= 1) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } } m_canvas->updateCanvas(); } void KisAssistantTool::continuePrimaryAction(KoPointerEvent *event) { KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); if (m_handleDrag) { *m_handleDrag = event->point; //ported from the gradient tool... we need to think about this more in the future. if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) { QLineF dragRadius = QLineF(m_dragStart, event->point); dragRadius.setLength(m_radius.length()); *m_handleDrag = dragRadius.p2(); } else if (event->modifiers() == Qt::ShiftModifier ) { QPointF move = snapToClosestAxis(event->point - m_dragStart); *m_handleDrag = m_dragStart + move; } else { *m_handleDrag = canvasDecoration->snapToGuide(event, QPointF(), false); } m_handleDrag->uncache(); m_handleCombine = 0; if (!(event->modifiers() & Qt::ShiftModifier)) { double minDist = 49.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { if (handle == m_handleDrag) continue; double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleCombine = handle; } } } m_canvas->updateCanvas(); } else if (m_assistantDrag) { QPointF newAdjustment = canvasDecoration->snapToGuide(event, QPointF(), false) - m_cursorStart; if (event->modifiers() == Qt::ShiftModifier ) { newAdjustment = snapToClosestAxis(newAdjustment); } Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) { *handle += (newAdjustment - m_currentAdjustment); } if (m_assistantDrag->id()== "vanishing point"){ Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) { *handle += (newAdjustment - m_currentAdjustment); } } m_currentAdjustment = newAdjustment; m_canvas->updateCanvas(); } else { event->ignore(); } bool wasHiglightedNode = m_higlightedNode != 0; QPointF mousep = m_canvas->viewConverter()->documentToView(event->point); QList pAssistant= m_canvas->paintingAssistantsDecoration()->assistants(); Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { if(assistant->id() == "perspective") { if ((m_higlightedNode = assistant->closestCornerHandleFromPoint(mousep))) { if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) { m_higlightedNode = 0; } else { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas break; } } } //this following bit sets the translations for the vanishing-point handles. if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) { //for inner handles, the outer handle gets translated. if (m_handleDrag == assistant->sideHandles()[0]) { QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); if (length < 2.0){ length = 2.0; } length += perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[1] = perspectiveline.p2(); } else if (m_handleDrag == assistant->sideHandles()[2]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){ length=2.0; } length += perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[3] = perspectiveline.p2(); } // for outer handles, only the vanishing point is translated, but only if there's an intersection. else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){ QPointF vanishingpoint(0,0); QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ *assistant->handles()[0] = vanishingpoint; } }// and for the vanishing point itself, only the outer handles get translated. else if (m_handleDrag == assistant->handles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length < 2.0) { length = 2.0; } if (length2 < 2.0) { length2=2.0; } length += perspectiveline.length(); length2 += perspectiveline2.length(); perspectiveline.setLength(length); perspectiveline2.setLength(length2); *assistant->sideHandles()[1] = perspectiveline.p2(); *assistant->sideHandles()[3] = perspectiveline2.p2(); } } } if (wasHiglightedNode && !m_higlightedNode) { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } } void KisAssistantTool::endPrimaryAction(KoPointerEvent *event) { setMode(KisTool::HOVER_MODE); if (m_handleDrag) { if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) { m_handleCombine->mergeWith(m_handleDrag); m_handleCombine->uncache(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } m_handleDrag = m_handleCombine = 0; } else if (m_assistantDrag) { m_assistantDrag.clear(); } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { addAssistant(); m_internalMode = MODE_CREATION; } else { event->ignore(); } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } void KisAssistantTool::addAssistant() { m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant); updateToolOptionsUI(); // vanishing point assistant will get an extra option KisAbstractPerspectiveGrid* grid = dynamic_cast(m_newAssistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } m_newAssistant.clear(); } void KisAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) { KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->removePerspectiveGrid(grid); } m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } void KisAssistantTool::assistantSelected(KisPaintingAssistantSP assistant) { m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(assistant); updateToolOptionsUI(); } void KisAssistantTool::updateToolOptionsUI() { KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point"; m_options.vanishingPointAngleSpinbox->setVisible(isVanishingPointAssistant); if (isVanishingPointAssistant) { QSharedPointer assis = qSharedPointerCast(m_selectedAssistant); m_options.vanishingPointAngleSpinbox->setValue(assis->referenceLineDensity()); } } else { // nothing selected, so hide UI controls m_options.vanishingPointAngleSpinbox->setVisible(false); } } void KisAssistantTool::slotChangeVanishingPointAngle(double value) { if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) { return; } // get the selected assistant and change the angle value KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point"; if (isVanishingPointAssistant) { QSharedPointer assis = qSharedPointerCast(m_selectedAssistant); assis->setReferenceLineDensity((float)value); } } m_canvas->canvasWidget()->update(); } void KisAssistantTool::mouseMoveEvent(KoPointerEvent *event) { if (m_newAssistant && m_internalMode == MODE_CREATION) { *m_newAssistant->handles().back() = event->point; } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { - QPointF translate = event->point - m_dragEnd;; + QPointF translate = event->point - m_dragEnd; m_dragEnd = event->point; m_selectedNode1.data()->operator = (QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate); } m_canvas->updateCanvas(); } void KisAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) { QRectF canvasSize = QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())); QColor assistantColor = m_canvas->paintingAssistantsDecoration()->assistantsColor(); QBrush fillAssistantColorBrush; fillAssistantColorBrush.setColor(m_canvas->paintingAssistantsDecoration()->assistantsColor()); // show special display while a new assistant is in the process of being created if (m_newAssistant) { m_newAssistant->setAssistantColor(assistantColor); m_newAssistant->drawAssistant(_gc, canvasSize, m_canvas->coordinatesConverter(), false, m_canvas, true, false); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) { QPainterPath path; path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize))); _gc.save(); _gc.setPen(Qt::NoPen); _gc.setBrush(assistantColor); _gc.drawPath(path); _gc.restore(); } } Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { QRectF ellipse(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); // render handles differently if it is the one being dragged. if (handle == m_handleDrag || handle == m_handleCombine) { QPen stroke(QColor(assistantColor.red(), assistantColor.green(), assistantColor.blue(), 80), 4); _gc.save(); _gc.setPen(stroke); _gc.setBrush(Qt::NoBrush); _gc.drawEllipse(ellipse); _gc.restore(); } } } } void KisAssistantTool::removeAllAssistants() { m_canvas->viewManager()->resourceProvider()->clearPerspectiveGrids(); m_canvas->paintingAssistantsDecoration()->removeAll(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } void KisAssistantTool::loadAssistants() { KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant"); dialog.setCaption(i18n("Select an Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); QXmlStreamReader xml(data); QMap handleMap; KisPaintingAssistantSP assistant; bool errors = false; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { if (assistant && !xml.attributes().value("ref").isEmpty()) { KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { assistant->addHandle(handle, HandleType::NORMAL); } else { errors = true; } } else { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(), y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } else { errors = true; } } else { errors = true; } } } else if (xml.name() == "assistant") { const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString()); if (factory) { if (assistant) { errors = true; assistant.clear(); } assistant = toQShared(factory->createPaintingAssistant()); } else { errors = true; } } else { if (assistant) { assistant->loadCustomXml(&xml); } } break; case QXmlStreamReader::EndElement: if (xml.name() == "assistant") { if (assistant) { if (assistant->handles().size() == assistant->numHandles()) { if (assistant->id() == "vanishing point"){ //ideally we'd save and load side-handles as well, but this is all I've got// QPointF pos = *assistant->handles()[0]; assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)), HandleType::SIDE); } m_canvas->paintingAssistantsDecoration()->addAssistant(assistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } } else { errors = true; } assistant.clear(); } } break; default: break; } } if (assistant) { errors = true; assistant.clear(); } if (xml.hasError()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString()); } if (errors) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded.")); } m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisAssistantTool::saveAssistants() { if (m_handles.isEmpty()) return; QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("paintingassistant"); xml.writeStartElement("handles"); QMap handleMap; Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { int id = handleMap.size(); handleMap.insert(handle, id); xml.writeStartElement("handle"); //xml.writeAttribute("type", handle->handleType()); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeStartElement("assistants"); Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { xml.writeStartElement("assistant"); xml.writeAttribute("type", assistant->id()); // custom assistant properties like angle density on vanishing point assistant->saveCustomXml(&xml); // handle information xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { xml.writeStartElement("handle"); xml.writeAttribute("ref", QString::number(handleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant"); dialog.setCaption(i18n("Save Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); file.open(QIODevice::WriteOnly); file.write(data); } QWidget *KisAssistantTool::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new QWidget; m_options.setupUi(m_optionsWidget); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_options.loadAssistantButton->setIcon(KisIconUtils::loadIcon("document-open")); m_options.saveAssistantButton->setIcon(KisIconUtils::loadIcon("document-save")); m_options.deleteAllAssistantsButton->setIcon(KisIconUtils::loadIcon("edit-delete")); QList assistants; Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) { QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name(); assistants << KoID(key, name); } std::sort(assistants.begin(), assistants.end(), KoID::compareNames); Q_FOREACH(const KoID &id, assistants) { m_options.availableAssistantsComboBox->addItem(id.name(), id.id()); } connect(m_options.saveAssistantButton, SIGNAL(clicked()), SLOT(saveAssistants())); connect(m_options.loadAssistantButton, SIGNAL(clicked()), SLOT(loadAssistants())); connect(m_options.deleteAllAssistantsButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); connect(m_options.assistantsColor, SIGNAL(changed(const QColor&)), SLOT(slotAssistantsColorChanged(const QColor&))); connect(m_options.assistantsOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotAssistantOpacityChanged())); connect(m_options.vanishingPointAngleSpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeVanishingPointAngle(double))); m_options.assistantsColor->setColor(QColor(176, 176, 176, 255)); // grey default for all assistants m_options.assistantsOpacitySlider->setValue(100); // 100% m_options.assistantsOpacitySlider->setPrefix(i18n("Opacity: ")); m_options.assistantsOpacitySlider->setSuffix(" %"); m_assistantsOpacity = m_options.assistantsOpacitySlider->value()*0.01; QColor newColor = m_options.assistantsColor->color(); newColor.setAlpha(m_assistantsOpacity*255); m_canvas->paintingAssistantsDecoration()->setAssistantsColor(newColor); m_options.vanishingPointAngleSpinbox->setPrefix(i18n("Density: ")); m_options.vanishingPointAngleSpinbox->setSuffix(QChar(Qt::Key_degree)); m_options.vanishingPointAngleSpinbox->setRange(1.0, 30.0); m_options.vanishingPointAngleSpinbox->setSingleStep(0.5); m_options.vanishingPointAngleSpinbox->setVisible(false); } return m_optionsWidget; } void KisAssistantTool::slotAssistantsColorChanged(const QColor& setColor) { // color and alpha are stored separately, so we need to merge the values before sending it on QColor newColor = setColor; newColor.setAlpha(m_assistantsOpacity * 255); m_canvas->paintingAssistantsDecoration()->setAssistantsColor(newColor); m_canvas->canvasWidget()->update(); } void KisAssistantTool::slotAssistantOpacityChanged() { QColor newColor = m_canvas->paintingAssistantsDecoration()->assistantsColor(); m_assistantsOpacity = m_options.assistantsOpacitySlider->value()*0.01; newColor.setAlpha(m_assistantsOpacity*255); m_canvas->paintingAssistantsDecoration()->setAssistantsColor(newColor); m_canvas->canvasWidget()->update(); } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutNoteArea.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutNoteArea.cpp index 53f0c166ce..eb2e3ca715 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutNoteArea.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutNoteArea.cpp @@ -1,196 +1,196 @@ /* This file is part of the KDE project * Copyright (C) 2011 Brijesh Patel * * 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 "KoTextLayoutNoteArea.h" #include "FrameIterator.h" #include "KoStyleManager.h" #include "KoParagraphStyle.h" #include "KoTextLayoutObstruction.h" #include "KoPointedAt.h" #include #include #include #include #define OVERLAPPREVENTION 1000 class Q_DECL_HIDDEN KoTextLayoutNoteArea::Private { public: Private() { } KoInlineNote *note; QTextLayout *textLayout; QTextLayout *postLayout; qreal labelIndent; bool isContinuedArea; qreal labelWidth; qreal labelHeight; qreal labelYOffset; }; KoTextLayoutNoteArea::KoTextLayoutNoteArea(KoInlineNote *note, KoTextLayoutArea *parent, KoTextDocumentLayout *documentLayout) : KoTextLayoutArea(parent, documentLayout) , d(new Private) { Q_ASSERT(note); Q_ASSERT(parent); d->note = note; d->isContinuedArea = false; d->postLayout = 0; } KoTextLayoutNoteArea::~KoTextLayoutNoteArea() { delete d; } void KoTextLayoutNoteArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { painter->save(); if (d->isContinuedArea) { painter->translate(0, -OVERLAPPREVENTION); } KoTextLayoutArea::paint(painter, context); if (d->postLayout) { d->postLayout->draw(painter, QPointF(left() + d->labelIndent, top() + d->labelYOffset)); } d->textLayout->draw(painter, QPointF(left() + d->labelIndent, top() + d->labelYOffset)); painter->restore(); } bool KoTextLayoutNoteArea::layout(FrameIterator *cursor) { KoOdfNotesConfiguration *notesConfig = 0; if (d->note->type() == KoInlineNote::Footnote) { notesConfig = KoTextDocument(d->note->textFrame()->document()).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); } else if (d->note->type() == KoInlineNote::Endnote) { notesConfig = KoTextDocument(d->note->textFrame()->document()).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Endnote); } QString label; if (d->isContinuedArea) { if (! notesConfig->footnoteContinuationBackward().isEmpty()) { label = notesConfig->footnoteContinuationBackward() + " " + d->note->label(); } setReferenceRect(left(), right(), top() + OVERLAPPREVENTION , maximumAllowedBottom() + OVERLAPPREVENTION); } else { label = d->note->label(); } label.prepend(notesConfig->numberFormat().prefix()); label.append(notesConfig->numberFormat().suffix()); QPaintDevice *pd = documentLayout()->paintDevice(); QTextBlock block = cursor->it.currentBlock(); QTextCharFormat format = block.charFormat(); KoCharacterStyle *style = static_cast(notesConfig->citationTextStyle()); if (style) { style->applyStyle(format); } QFont font(format.font(), pd); d->textLayout = new QTextLayout(label, font, pd); QList layouts; QTextLayout::FormatRange range; range.start = 0; range.length = label.length(); range.format = format; layouts.append(range); d->textLayout->setAdditionalFormats(layouts); QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute); d->textLayout->setTextOption(option); d->textLayout->beginLayout(); QTextLine line = d->textLayout->createLine(); d->textLayout->endLayout(); KoParagraphStyle pStyle(block.blockFormat(), QTextCharFormat()); d->labelIndent = textIndent(d->note->textFrame()->begin().currentBlock(), 0, pStyle); if (line.naturalTextWidth() > -d->labelIndent) { KoTextLayoutArea::setExtraTextIndent(line.naturalTextWidth()); } else { KoTextLayoutArea::setExtraTextIndent(-d->labelIndent); } d->labelIndent += pStyle.leftMargin(); d->labelWidth = line.naturalTextWidth(); d->labelHeight = line.naturalTextRect().bottom() - line.naturalTextRect().top(); d->labelYOffset = -line.ascent(); bool contNotNeeded = KoTextLayoutArea::layout(cursor); d->labelYOffset += block.layout()->lineAt(0).ascent(); if (!contNotNeeded) { QString contNote = notesConfig->footnoteContinuationForward(); font.setBold(true); d->postLayout = new QTextLayout(contNote, font, pd); QList contTextLayouts; QTextLayout::FormatRange contTextRange; contTextRange.start = 0; contTextRange.length = contNote.length(); - contTextRange.format = block.charFormat();; + contTextRange.format = block.charFormat(); contTextLayouts.append(contTextRange); d->postLayout->setAdditionalFormats(contTextLayouts); QTextOption contTextOption(Qt::AlignLeft | Qt::AlignAbsolute); //option.setTextDirection(); d->postLayout->setTextOption(contTextOption); d->postLayout->beginLayout(); QTextLine contTextLine = d->postLayout->createLine(); d->postLayout->endLayout(); contTextLine.setPosition(QPointF(right() - contTextLine.naturalTextWidth(), bottom() - contTextLine.height())); documentLayout()->setContinuationObstruction(new KoTextLayoutObstruction(contTextLine.naturalTextRect(), false)); } return contNotNeeded; } void KoTextLayoutNoteArea::setAsContinuedArea(bool isContinuedArea) { d->isContinuedArea = isContinuedArea; } KoPointedAt KoTextLayoutNoteArea::hitTest(const QPointF &p, Qt::HitTestAccuracy accuracy) const { KoPointedAt pointedAt; pointedAt.noteReference = -1; QPointF tmpP(p.x(), p.y() + (d->isContinuedArea ? OVERLAPPREVENTION : 0)); pointedAt = KoTextLayoutArea::hitTest(tmpP, accuracy); if (tmpP.x() > left() && tmpP.x() < d->labelWidth && tmpP.y() < top() + d->labelYOffset + d->labelHeight) { pointedAt.noteReference = d->note->getPosInDocument(); pointedAt.position = tmpP.x(); } return pointedAt; } QRectF KoTextLayoutNoteArea::selectionBoundingBox(QTextCursor &cursor) const { return KoTextLayoutArea::selectionBoundingBox(cursor).translated(0 , d->isContinuedArea ? -OVERLAPPREVENTION : 0); } diff --git a/plugins/impex/psd/psd_pixel_utils.cpp b/plugins/impex/psd/psd_pixel_utils.cpp index 47c0a59f16..98363e0025 100644 --- a/plugins/impex/psd/psd_pixel_utils.cpp +++ b/plugins/impex/psd/psd_pixel_utils.cpp @@ -1,717 +1,717 @@ /* * Copyright (c) 2015 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 "psd_pixel_utils.h" #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include #include #include "psd_layer_record.h" #include #include "kis_iterator_ng.h" #include "config_psd.h" #ifdef HAVE_ZLIB #include "zlib.h" #endif namespace PsdPixelUtils { template typename Traits::channels_type convertByteOrder(typename Traits::channels_type value); // default implementation is undefined for every color space should be added manually template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline quint32 convertByteOrder(quint32 value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline quint32 convertByteOrder(quint32 value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr); template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col]; } template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col] >> 8; } template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col] * 255; } template inline typename Traits::channels_type readChannelValue(const QMap &channelBytes, quint16 channelId, int col, typename Traits::channels_type defaultValue) { typedef typename Traits::channels_type channels_type; if (channelBytes.contains(channelId)) { const QByteArray &bytes = channelBytes[channelId]; if (col < bytes.size()) { return convertByteOrder(reinterpret_cast(bytes.constData())[col]); } dbgFile << "col index out of range channelId: "<< channelId << " col:" << col; } return defaultValue; } template void readGrayPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); - pixelPtr->gray = readChannelValue(channelBytes, 0, col, unitValue);; + pixelPtr->gray = readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readRgbPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->blue = readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->green = readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->red = readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readCmykPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->cyan = unitValue - readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->magenta = unitValue - readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->yellow = unitValue - readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->black = unitValue - readChannelValue(channelBytes, 3, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readLabPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->L = readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->a = readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->b = readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } void readRgbPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readRgbPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readRgbPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readRgbPixel(channelBytes, col, dstPtr); } } void readGrayPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readGrayPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readGrayPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readGrayPixel(channelBytes, col, dstPtr); } } void readCmykPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readCmykPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readCmykPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readCmykPixel(channelBytes, col, dstPtr); } } void readLabPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readLabPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readLabPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readLabPixel(channelBytes, col, dstPtr); } } void readAlphaMaskPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readAlphaMaskPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readAlphaMaskPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readAlphaMaskPixel(channelBytes, col, dstPtr); } } /**********************************************************************/ /* Two functions copied from the abandoned PSDParse library (GPL) */ /* See: http://www.telegraphics.com.au/svn/psdparse/trunk/psd_zip.c */ /* Created by Patrick in 2007.02.02, libpsd@graphest.com */ /* Modifications by Toby Thain */ /**********************************************************************/ typedef bool psd_status; typedef quint8 psd_uchar; typedef int psd_int; typedef quint8 Bytef; psd_status psd_unzip_without_prediction(psd_uchar *src_buf, psd_int src_len, psd_uchar *dst_buf, psd_int dst_len) { #ifdef HAVE_ZLIB z_stream stream; psd_int state; memset(&stream, 0, sizeof(z_stream)); stream.data_type = Z_BINARY; stream.next_in = (Bytef *)src_buf; stream.avail_in = src_len; stream.next_out = (Bytef *)dst_buf; stream.avail_out = dst_len; if(inflateInit(&stream) != Z_OK) return 0; do { state = inflate(&stream, Z_PARTIAL_FLUSH); if(state == Z_STREAM_END) break; if(state != Z_OK) break; } while (stream.avail_out > 0); if (state != Z_STREAM_END && state != Z_OK) return 0; return 1; #endif /* HAVE_ZLIB */ return 0; } psd_status psd_unzip_with_prediction(psd_uchar *src_buf, psd_int src_len, psd_uchar *dst_buf, psd_int dst_len, psd_int row_size, psd_int color_depth) { psd_status status; int len; psd_uchar * buf; status = psd_unzip_without_prediction(src_buf, src_len, dst_buf, dst_len); if(!status) return status; buf = dst_buf; do { len = row_size; if (color_depth == 16) { while(--len) { buf[2] += buf[0] + ((buf[1] + buf[3]) >> 8); buf[3] += buf[1]; buf += 2; } buf += 2; dst_len -= row_size * 2; } else { while(--len) { *(buf + 1) += *buf; buf ++; } buf ++; dst_len -= row_size; } } while(dst_len > 0); return 1; } /**********************************************************************/ /* End of third party block */ /**********************************************************************/ QMap fetchChannelsBytes(QIODevice *io, QVector channelInfoRecords, int row, int width, int channelSize, bool processMasks) { const int uncompressedLength = width * channelSize; QMap channelBytes; Q_FOREACH (ChannelInfo *channelInfo, channelInfoRecords) { // user supplied masks are ignored here if (!processMasks && channelInfo->channelId < -1) continue; io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); if (channelInfo->compressionType == Compression::Uncompressed) { channelBytes[channelInfo->channelId] = io->read(uncompressedLength); channelInfo->channelOffset += uncompressedLength; } else if (channelInfo->compressionType == Compression::RLE) { int rleLength = channelInfo->rleRowLengths[row]; QByteArray compressedBytes = io->read(rleLength); QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); channelBytes.insert(channelInfo->channelId, uncompressedBytes); channelInfo->channelOffset += rleLength; } else { QString error = QString("Unsupported Compression mode: %1").arg(channelInfo->compressionType); dbgFile << "ERROR: fetchChannelsBytes:" << error; throw KisAslReaderUtils::ASLParseException(error); } } return channelBytes; } typedef boost::function&, int, quint8*)> PixelFunc; void readCommon(KisPaintDeviceSP dev, QIODevice *io, const QRect &layerRect, QVector infoRecords, int channelSize, PixelFunc pixelFunc, bool processMasks) { KisOffsetKeeper keeper(io); if (layerRect.isEmpty()) { dbgFile << "Empty layer!"; return; } if (infoRecords.first()->compressionType == Compression::ZIP || infoRecords.first()->compressionType == Compression::ZIPWithPrediction) { const int numPixels = channelSize * layerRect.width() * layerRect.height(); QMap channelBytes; Q_FOREACH (ChannelInfo *info, infoRecords) { io->seek(info->channelDataStart); QByteArray compressedBytes = io->read(info->channelDataLength); QByteArray uncompressedBytes(numPixels, 0); bool status = false; if (infoRecords.first()->compressionType == Compression::ZIP) { status = psd_unzip_without_prediction((quint8*)compressedBytes.data(), compressedBytes.size(), (quint8*)uncompressedBytes.data(), uncompressedBytes.size()); } else { status = psd_unzip_with_prediction((quint8*)compressedBytes.data(), compressedBytes.size(), (quint8*)uncompressedBytes.data(), uncompressedBytes.size(), layerRect.width(), channelSize * 8); } if (!status) { QString error = QString("Failed to unzip channel data: id = %1, compression = %2").arg(info->channelId).arg(info->compressionType); dbgFile << "ERROR:" << error; dbgFile << " " << ppVar(info->channelId); dbgFile << " " << ppVar(info->channelDataStart); dbgFile << " " << ppVar(info->channelDataLength); dbgFile << " " << ppVar(info->compressionType); throw KisAslReaderUtils::ASLParseException(error); } channelBytes.insert(info->channelId, uncompressedBytes); } KisSequentialIterator it(dev, layerRect); int col = 0; while (it.nextPixel()) { pixelFunc(channelSize, channelBytes, col, it.rawData()); col++; } } else { KisHLineIteratorSP it = dev->createHLineIteratorNG(layerRect.left(), layerRect.top(), layerRect.width()); for (int i = 0 ; i < layerRect.height(); i++) { QMap channelBytes; channelBytes = fetchChannelsBytes(io, infoRecords, i, layerRect.width(), channelSize, processMasks); for (qint64 col = 0; col < layerRect.width(); col++){ pixelFunc(channelSize, channelBytes, col, it->rawData()); it->nextPixel(); } it->nextRow(); } } } void readChannels(QIODevice *io, KisPaintDeviceSP device, psd_color_mode colorMode, int channelSize, const QRect &layerRect, QVector infoRecords) { switch (colorMode) { case Grayscale: readCommon(device, io, layerRect, infoRecords, channelSize, &readGrayPixelCommon, false); break; case RGB: readCommon(device, io, layerRect, infoRecords, channelSize, &readRgbPixelCommon, false); break; case CMYK: readCommon(device, io, layerRect, infoRecords, channelSize, &readCmykPixelCommon, false); break; case Lab: readCommon(device, io, layerRect, infoRecords, channelSize, &readLabPixelCommon, false); break; case Bitmap: case Indexed: case MultiChannel: case DuoTone: case COLORMODE_UNKNOWN: default: QString error = QString("Unsupported color mode: %1").arg(colorMode); throw KisAslReaderUtils::ASLParseException(error); } } void readAlphaMaskChannels(QIODevice *io, KisPaintDeviceSP device, int channelSize, const QRect &layerRect, QVector infoRecords) { KIS_SAFE_ASSERT_RECOVER_RETURN(infoRecords.size() == 1); readCommon(device, io, layerRect, infoRecords, channelSize, &readAlphaMaskPixelCommon, true); } void writeChannelDataRLE(QIODevice *io, const quint8 *plane, const int channelSize, const QRect &rc, const qint64 sizeFieldOffset, const qint64 rleBlockOffset, const bool writeCompressionType) { typedef KisAslWriterUtils::OffsetStreamPusher Pusher; QScopedPointer channelBlockSizeExternalTag; if (sizeFieldOffset >= 0) { channelBlockSizeExternalTag.reset(new Pusher(io, 0, sizeFieldOffset)); } if (writeCompressionType) { SAFE_WRITE_EX(io, (quint16)Compression::RLE); } const bool externalRleBlock = rleBlockOffset >= 0; // the start of RLE sizes block const qint64 channelRLESizePos = externalRleBlock ? rleBlockOffset : io->pos(); { QScopedPointer rleOffsetKeeper; if (externalRleBlock) { rleOffsetKeeper.reset(new KisOffsetKeeper(io)); io->seek(rleBlockOffset); } // write zero's for the channel lengths block for(int i = 0; i < rc.height(); ++i) { // XXX: choose size for PSB! const quint16 fakeRLEBLockSize = 0; SAFE_WRITE_EX(io, fakeRLEBLockSize); } } quint32 stride = channelSize * rc.width(); for (qint32 row = 0; row < rc.height(); ++row) { QByteArray uncompressed = QByteArray::fromRawData((const char*)plane + row * stride, stride); QByteArray compressed = Compression::compress(uncompressed, Compression::RLE); KisAslWriterUtils::OffsetStreamPusher rleExternalTag(io, 0, channelRLESizePos + row * sizeof(quint16)); if (io->write(compressed) != compressed.size()) { throw KisAslWriterUtils::ASLWriteException("Failed to write image data"); } } } inline void preparePixelForWrite(quint8 *dataPlane, int numPixels, int channelSize, int channelId, psd_color_mode colorMode) { // if the bitdepth > 8, place the bytes in the right order // if cmyk, invert the pixel value if (channelSize == 1) { if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { for (int i = 0; i < numPixels; ++i) { dataPlane[i] = 255 - dataPlane[i]; } } } else if (channelSize == 2) { quint16 val; for (int i = 0; i < numPixels; ++i) { quint16 *pixelPtr = reinterpret_cast(dataPlane) + i; val = *pixelPtr; val = qFromBigEndian(val); if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { val = quint16_MAX - val; } *pixelPtr = val; } } else if (channelSize == 4) { quint32 val; for (int i = 0; i < numPixels; ++i) { quint32 *pixelPtr = reinterpret_cast(dataPlane) + i; val = *pixelPtr; val = qFromBigEndian(val); if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { val = quint16_MAX - val; } *pixelPtr = val; } } } void writePixelDataCommon(QIODevice *io, KisPaintDeviceSP dev, const QRect &rc, psd_color_mode colorMode, int channelSize, bool alphaFirst, const bool writeCompressionType, QVector &writingInfoList) { // Empty rects must be processed separately on a higher level! KIS_ASSERT_RECOVER_RETURN(!rc.isEmpty()); QVector tmp = dev->readPlanarBytes(rc.x() - dev->x(), rc.y() - dev->y(), rc.width(), rc.height()); const KoColorSpace *colorSpace = dev->colorSpace(); QVector planes; { // prepare 'planes' array quint8 *alphaPlanePtr = 0; QList origChannels = colorSpace->channels(); Q_FOREACH (KoChannelInfo *ch, KoChannelInfo::displayOrderSorted(origChannels)) { int channelIndex = KoChannelInfo::displayPositionToChannelIndex(ch->displayPosition(), origChannels); quint8 *holder = 0; std::swap(holder, tmp[channelIndex]); if (ch->channelType() == KoChannelInfo::ALPHA) { std::swap(holder, alphaPlanePtr); } else { planes.append(holder); } } if (alphaPlanePtr) { if (alphaFirst) { planes.insert(0, alphaPlanePtr); KIS_ASSERT_RECOVER_NOOP(writingInfoList.first().channelId == -1); } else { planes.append(alphaPlanePtr); KIS_ASSERT_RECOVER_NOOP( (writingInfoList.size() == planes.size() - 1) || (writingInfoList.last().channelId == -1)); } } // now planes are holding pointers to quint8 arrays tmp.clear(); } KIS_ASSERT_RECOVER_RETURN(planes.size() >= writingInfoList.size()); const int numPixels = rc.width() * rc.height(); // write down the planes try { for (int i = 0; i < writingInfoList.size(); i++) { const ChannelWritingInfo &info = writingInfoList[i]; dbgFile << "\tWriting channel" << i << "psd channel id" << info.channelId; preparePixelForWrite(planes[i], numPixels, channelSize, info.channelId, colorMode); dbgFile << "\t\tchannel start" << ppVar(io->pos()); writeChannelDataRLE(io, planes[i], channelSize, rc, info.sizeFieldOffset, info.rleBlockOffset, writeCompressionType); } } catch (KisAslWriterUtils::ASLWriteException &e) { qDeleteAll(planes); planes.clear(); throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what())); } qDeleteAll(planes); planes.clear(); } } diff --git a/plugins/impex/spriter/kis_spriter_export.h b/plugins/impex/spriter/kis_spriter_export.h index b1b48a619c..5d3c32c3b3 100644 --- a/plugins/impex/spriter/kis_spriter_export.h +++ b/plugins/impex/spriter/kis_spriter_export.h @@ -1,135 +1,135 @@ /* * Copyright (c) 2016 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. */ #ifndef _KIS_SPRITER_EXPORT_H_ #define _KIS_SPRITER_EXPORT_H_ #include #include #include #include #include struct SpriterFile { qreal id; QString name; QString pathName; QString baseName; QString layerName; qreal width; qreal height; qreal x; qreal y; }; struct Folder { qreal id; QString name; QString pathName; QString baseName; QString groupName; QList files; }; struct Bone { qreal id; const Bone *parentBone; QString name; qreal x; qreal y; qreal width; qreal height; qreal localX; qreal localY; qreal localAngle; qreal localScaleX; qreal localScaleY; qreal fixLocalX; qreal fixLocalY; qreal fixLocalAngle; qreal fixLocalScaleX; qreal fixLocalScaleY; QList bones; ~Bone() { qDeleteAll(bones); - bones.clear();; + bones.clear(); } }; struct SpriterSlot { QString name; bool defaultAttachmentFlag = false; }; struct SpriterObject { qreal id; qreal folderId; qreal fileId; Bone *bone; SpriterSlot *slot; qreal x; qreal y; qreal localX; qreal localY; qreal localAngle; qreal localScaleX; qreal localScaleY; qreal fixLocalX; qreal fixLocalY; qreal fixLocalAngle; qreal fixLocalScaleX; qreal fixLocalScaleY; ~SpriterObject() { delete slot; } }; class KisSpriterExport : public KisImportExportFilter { Q_OBJECT public: KisSpriterExport(QObject *parent, const QVariantList &); ~KisSpriterExport() override; bool supportsIO() const override { return false; } KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; private: bool savePaintDevice(KisPaintDeviceSP dev, const QString &fileName); void parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0); Bone *parseBone(const Bone *parent, KisGroupLayerSP groupLayer); void fixBone(Bone *bone); void fillScml(QDomDocument &scml, const QString &entityName); void writeBoneRef(const Bone *bone, QDomElement &mainline, QDomDocument &scml); void writeBone(const Bone *bone, QDomElement &timeline, QDomDocument &scml); KisImageSP m_image; qreal m_timelineid; QList m_folders; Bone *m_rootBone; QList m_objects; KisGroupLayerSP m_rootLayer; // Not the image's root later, but the one that is named "root" KisLayerSP m_boneLayer; }; #endif diff --git a/plugins/paintops/libpaintop/kis_compositeop_option.cpp b/plugins/paintops/libpaintop/kis_compositeop_option.cpp index d9563c0f8c..4fdf17527c 100644 --- a/plugins/paintops/libpaintop/kis_compositeop_option.cpp +++ b/plugins/paintops/libpaintop/kis_compositeop_option.cpp @@ -1,113 +1,113 @@ /* * 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_compositeop_option.h" #include #include #include #include #include #include #include #include #include "kis_signals_blocker.h" KisCompositeOpOption::KisCompositeOpOption(bool createConfigWidget): KisPaintOpOption(KisPaintOpOption::GENERAL, true), m_createConfigWidget(createConfigWidget), m_eraserMode(false) { m_checkable = false; m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); if (createConfigWidget) { QWidget* widget = new QWidget(); Ui_wdgCompositeOpOption ui; ui.setupUi(widget); ui.bnEraser->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_label = ui.lbChoosenMode; m_list = ui.list; m_bnEraser = ui.bnEraser; setConfigurationPage(widget); connect(ui.list , SIGNAL(clicked(const QModelIndex&)), this, SLOT(slotCompositeOpChanged(const QModelIndex&))); connect(ui.bnEraser, SIGNAL(toggled(bool)) , this, SLOT(slotEraserToggled(bool))); } setObjectName("KisCompositeOpOption"); } KisCompositeOpOption::~KisCompositeOpOption() { } void KisCompositeOpOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { setting->setProperty("CompositeOp", m_currCompositeOpID); setting->setProperty("EraserMode", m_eraserMode); } void KisCompositeOpOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { QString ompositeOpID = setting->getString("CompositeOp", KoCompositeOpRegistry::instance().getDefaultCompositeOp().id()); KoID compositeOp = KoCompositeOpRegistry::instance().getKoID(ompositeOpID); changeCompositeOp(compositeOp); - const bool eraserMode = setting->getBool("EraserMode", false);; + const bool eraserMode = setting->getBool("EraserMode", false); slotEraserToggled(eraserMode); } void KisCompositeOpOption::changeCompositeOp(const KoID& compositeOp) { if (compositeOp.id() == m_currCompositeOpID) return; m_currCompositeOpID = compositeOp.id(); if (m_createConfigWidget) { m_label->setText(compositeOp.name()); } emitSettingChanged(); } void KisCompositeOpOption::slotCompositeOpChanged(const QModelIndex& index) { Q_UNUSED(index); KoID compositeOp = m_list->selectedCompositeOp(); changeCompositeOp(compositeOp); } void KisCompositeOpOption::slotEraserToggled(bool toggled) { if (m_bnEraser->isChecked() != toggled) { KisSignalsBlocker b(m_bnEraser); m_bnEraser->setChecked(toggled); } m_eraserMode = toggled; emitSettingChanged(); } diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 5573144be1..8e307d5612 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1265 +1,1265 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 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_tool_transform.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 "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_changesTracker(&m_transaction) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; warpAction = new KisAction(i18n("Warp")); - liquifyAction = new KisAction(i18n("Liquify"));; - cageAction = new KisAction(i18n("Cage"));; - freeTransformAction = new KisAction(i18n("Free"));; + liquifyAction = new KisAction(i18n("Liquify")); + cageAction = new KisAction(i18n("Cage")); + freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); m_contextMenu.reset(new QMenu()); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(const QPointF&)), SLOT(cursorOutlineUpdateRequested(const QPointF&))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged()), this, SLOT(slotTrackerChangedConfig())); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeData.strokeId()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeData.strokeId()) { useCursor(KisCursor::pointingHandCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeData.strokeId()) { startStroke(m_currentArgs.mode(), false); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == ToolTransformArgs::FREE_TRANSFORM) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } } return m_contextMenu.data(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { m_currentArgs = adapter->transformArgs(); initGuiAfterTransformMode(); result = true; } } return result; } bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); KisNodeSP oldRootNode; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode) && args->mode() == mode && oldRootNode == currentNode) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); forceRepaintDelayedLayers(oldRootNode); result = true; } return result; } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { // NOTE: we are requesting an old value of m_currentArgs variable // here, which is global, don't forget about this on higher // levels. QString filterId = m_currentArgs.filterId(); m_currentArgs = ToolTransformArgs(); m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); if (mode == ToolTransformArgs::FREE_TRANSFORM) { m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { m_currentArgs.setMode(ToolTransformArgs::WARP); m_optionsWidget->setDefaultWarpPoints(); m_currentArgs.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { m_currentArgs.setMode(ToolTransformArgs::CAGE); m_currentArgs.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = m_transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::updateSelectionPath() { m_selectionPath = QPainterPath(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); QPainterPath selectionOutline; KisSelectionSP selection = resources->activeSelection(); if (selection && selection->outlineCacheValid()) { selectionOutline = selection->outlineCache(); } else { selectionOutline.addRect(m_selectedPortionCache->exactBounds()); } const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeData.strokeId()) return; m_changesTracker.requestUndo(); } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { if (m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeData.strokeId()); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) { return; } /** * FIXME: The transform tool is not completely asynchronous, it * needs the content of the layer for creation of the stroke * strategy. It means that we cannot start a new stroke until the * previous one is finished. Ideally, we should create the * m_selectedPortionCache and m_selectionPath somewhere in the * stroke and pass it to the tool somehow. But currently, we will * just disable starting a new stroke asynchronously */ if (image()->tryBarrierLock()) { image()->unlock(); } else { return; } /** * We must ensure that the currently selected subtree * has finished all its updates. */ forceRepaintDelayedLayers(currentNode); ToolTransformArgs fetchedArgs; bool fetchedFromCommand = false; if (!forceReset) { fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } QList nodesList = fetchNodesList(mode, currentNode, m_workRecursively); if (nodesList.isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selected layer cannot be transformed with active transformation mode "), koIcon("object-locked"), 4000, KisFloatingMessage::High); // force-reset transform mode to default initTransformMode(mode); return; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, resources->activeSelection(), image().data()); KisPaintDeviceSP previewDevice = strategy->previewDevice(); KisSelectionSP selection = strategy->realSelection(); QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds(); if (!selection && resources->activeSelection()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); } if (srcRect.isEmpty()) { delete strategy; KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); // force-reset transform mode to default initTransformMode(mode); return; } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList); initThumbnailImage(previewDevice); updateSelectionPath(); if (!forceReset && fetchedFromCommand) { m_currentArgs = fetchedArgs; initGuiAfterTransformMode(); } else if (forceReset || !tryInitTransformModeFromNode(currentNode)) { initTransformMode(mode); } m_strokeData = StrokeData(image()->startStroke(strategy)); bool haveInvisibleNodes = clearDevices(nodesList); if (haveInvisibleNodes) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } Q_ASSERT(m_changesTracker.isEmpty()); commitChanges(); } void KisToolTransform::endStroke() { if (!m_strokeData.strokeId()) return; if (!m_currentArgs.isIdentity()) { transformClearedDevices(); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); // root node is used for progress only image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::cancelStroke() { if (!m_strokeData.strokeId()) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { image()->cancelStroke(m_strokeData.strokeId()); m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; m_changesTracker.commitConfig(m_currentArgs); } void KisToolTransform::slotTrackerChangedConfig() { slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable() && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool KisToolTransform::clearDevices(const QList &nodes) { bool haveInvisibleNodes = false; Q_FOREACH (KisNodeSP node, nodes) { haveInvisibleNodes |= !node->visible(false); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::ClearSelectionData(node)); /** * It might happen that the editablity state of the node would * change during the stroke, so we need to save the set of * applicable nodes right in the beginning of the processing */ m_strokeData.addClearedNode(node); } return haveInvisibleNodes; } void KisToolTransform::transformClearedDevices() { Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(node); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, node)); } } QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedArgs.mode(), true); } void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root) { KIS_SAFE_ASSERT_RECOVER_RETURN(root); KisLayerUtils::forceAllDelayedNodesUpdate(root); image()->waitForDone(); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); }