diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -38,6 +38,7 @@ set(kis_low_memory_benchmark_SRCS kis_low_memory_benchmark.cpp) set(kis_filter_selections_benchmark_SRCS kis_filter_selections_benchmark.cpp) set(kis_composition_benchmark_SRCS kis_composition_benchmark.cpp) +set(kis_thumbnail_benchmark_SRCS kis_thumbnail_benchmark.cpp) krita_add_benchmark(KisDatamanagerBenchmark TESTNAME krita-benchmarks-KisDataManager ${kis_datamanager_benchmark_SRCS}) krita_add_benchmark(KisHLineIteratorBenchmark TESTNAME krita-benchmarks-KisHLineIterator ${kis_hiterator_benchmark_SRCS}) @@ -56,6 +57,7 @@ krita_add_benchmark(KisLowMemoryBenchmark TESTNAME krita-benchmarks-KisLowMemory ${kis_low_memory_benchmark_SRCS}) krita_add_benchmark(KisFilterSelectionsBenchmark TESTNAME krita-image-KisFilterSelectionsBenchmark ${kis_filter_selections_benchmark_SRCS}) krita_add_benchmark(KisCompositionBenchmark TESTNAME krita-benchmarks-KisComposition ${kis_composition_benchmark_SRCS}) +krita_add_benchmark(KisThumbnailBenchmark TESTNAME krita-benchmarks-KisThumbnail ${kis_thumbnail_benchmark_SRCS}) target_link_libraries(KisDatamanagerBenchmark kritaimage Qt5::Test) target_link_libraries(KisHLineIteratorBenchmark kritaimage Qt5::Test) @@ -77,3 +79,5 @@ set_property(TARGET KisCompositionBenchmark APPEND PROPERTY COMPILE_OPTIONS "${Vc_ARCHITECTURE_FLAGS}") endif() target_link_libraries(KisMaskGeneratorBenchmark kritaimage Qt5::Test) +target_link_libraries(KisThumbnailBenchmark kritaimage Qt5::Test) + diff --git a/benchmarks/kis_thumbnail_benchmark.h b/benchmarks/kis_thumbnail_benchmark.h new file mode 100644 --- /dev/null +++ b/benchmarks/kis_thumbnail_benchmark.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Eugene Ingerman geneing at gmail dot com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KIS_THUMBNAIL_BENCHMARK_H +#define KIS_THUMBNAIL_BENCHMARK_H + +#include +#include "kis_paint_device.h" + +class KoColor; +class KoColorSpace; + +class KisThumbnailBenchmark : public QObject +{ + Q_OBJECT + +private: + const KoColorSpace * m_colorSpace; + KisPaintDeviceSP m_dev; + QVector m_thumbnails; + QSize m_thumbnailSizeLimit; + int m_oversampleRatio; + int m_skipCount; + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void benchmarkCreateThumbnail(); + void benchmarkCreateThumbnailCached(); + void benchmarkCreateThumbnailHiQ(); + + void benchmarkCreateThumbnailHiQcreateThumbOversample2x(); + void benchmarkCreateThumbnailHiQcreateThumbOversample3x(); + void benchmarkCreateThumbnailHiQcreateThumbOversample4x(); + +}; + + +#endif diff --git a/benchmarks/kis_thumbnail_benchmark.cpp b/benchmarks/kis_thumbnail_benchmark.cpp new file mode 100644 --- /dev/null +++ b/benchmarks/kis_thumbnail_benchmark.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2016 Eugene Ingerman geneing at gmail dot com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_thumbnail_benchmark.h" +#include "kis_benchmark_values.h" + +#include +#include +#include "kis_iterator_ng.h" + +#include "kis_paint_device.h" +#include "KoColorSpace.h" +#include "KoColorSpaceRegistry.h" +#include "KoCompositeOpRegistry.h" +#include "KoColor.h" + +#include "kis_image.h" +#include "kis_painter.h" +#include "kis_types.h" +#include "kis_sequential_iterator.h" +#include "kis_transform_worker.h" + + + +#include + +#define SAVE_OUTPUT + +const int THUMBNAIL_WIDTH = 64; +const int THUMBNAIL_HEIGHT = 64; +const int IMAGE_WIDTH = 8000; +const int IMAGE_HEIGHT = 6000; +const int OVERSAMPLE = 4; + +void KisThumbnailBenchmark::initTestCase() +{ + m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + + m_dev = new KisPaintDevice(m_colorSpace); + KoColor color(m_colorSpace); + color.fromQColor(Qt::white); + + m_dev->clear(); + m_dev->fill(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, color.data()); + + color.fromQColor(Qt::black); + + KisPainter painter(m_dev); + painter.setPaintColor(color); + float radius = std::min(IMAGE_WIDTH, IMAGE_HEIGHT); + const float angle = 2 * 3.1415926 / 360.; + const float endWidth = 30; + + for (float i = 0; i < 90; i += 5) { + painter.drawThickLine(QPointF(0, 0), QPointF(radius * std::sin(angle * i), radius * std::cos(angle * i)), 1, endWidth); + painter.drawThickLine(QPointF(IMAGE_WIDTH, IMAGE_HEIGHT), + QPointF(IMAGE_WIDTH - radius * std::sin(angle * i), IMAGE_HEIGHT - radius * std::cos(angle * i)), 1, endWidth); + } +#ifdef SAVE_OUTPUT + m_dev->convertToQImage(m_colorSpace->profile()).save("ThumbFullImage.png"); +#endif +} + +void KisThumbnailBenchmark::cleanupTestCase() +{ +} + +void KisThumbnailBenchmark::benchmarkCreateThumbnail() +{ + QImage image; + + QBENCHMARK{ + image = m_dev->createThumbnail(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, QRect() ); + //m_dev->setDirty(); + } + + image.save("createThumbnail.png"); +} + +void KisThumbnailBenchmark::benchmarkCreateThumbnailCached() +{ + QImage image; + + QBENCHMARK{ + image = m_dev->createThumbnail(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 2. ); + } +} + + +void KisThumbnailBenchmark::benchmarkCreateThumbnailHiQ() +{ + QImage image; + + QBENCHMARK{ + image = m_dev->createThumbnail(OVERSAMPLE * THUMBNAIL_WIDTH, OVERSAMPLE * THUMBNAIL_HEIGHT); + image = image.scaled(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_dev->setDirty(); + } + + image.save("createThumbnailHiQ.png"); +} + +void KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample2x() +{ + QImage image; + + QBENCHMARK{ + image = m_dev->createThumbnail(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, QRect(), 2, + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + m_dev->setDirty(); + } + + image.save("createThumbnailHiQcreateThumbOversample2x.png"); +} + +void KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample3x() +{ + QImage image; + + QBENCHMARK{ + image = m_dev->createThumbnail(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, QRect(), 3, + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + m_dev->setDirty(); + } + + image.save("createThumbnailHiQcreateThumbOversample3x.png"); +} + +void KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample4x() +{ + QImage image; + + QBENCHMARK{ + image = m_dev->createThumbnail(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, QRect(), 4, + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + m_dev->setDirty(); + } + + image.save("createThumbnailHiQcreateThumbOversample4x.png"); +} + + +QTEST_MAIN(KisThumbnailBenchmark) diff --git a/libs/image/kis_idle_watcher.h b/libs/image/kis_idle_watcher.h --- a/libs/image/kis_idle_watcher.h +++ b/libs/image/kis_idle_watcher.h @@ -32,14 +32,16 @@ { Q_OBJECT public: - KisIdleWatcher(int delay); + KisIdleWatcher(int delay, QObject* parent = 0); ~KisIdleWatcher(); bool isIdle() const; void setTrackedImages(const QVector &images); void setTrackedImage(KisImageSP image); + //Force to image modified state and start countdown to event + void startCountdown(void) { slotImageModified(); } Q_SIGNALS: void startedIdleMode(); diff --git a/libs/image/kis_idle_watcher.cpp b/libs/image/kis_idle_watcher.cpp --- a/libs/image/kis_idle_watcher.cpp +++ b/libs/image/kis_idle_watcher.cpp @@ -48,8 +48,8 @@ }; -KisIdleWatcher::KisIdleWatcher(int delay) - : m_d(new Private(delay)) +KisIdleWatcher::KisIdleWatcher(int delay, QObject *parent) + : QObject(parent), m_d(new Private(delay)) { connect(&m_d->imageModifiedCompressor, SIGNAL(timeout()), SLOT(startIdleCheck())); connect(&m_d->idleCheckTimer, SIGNAL(timeout()), SLOT(slotIdleCheckTick())); diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -788,7 +788,7 @@ KisPaintDeviceSP originalDevice = original(); return originalDevice ? - originalDevice->createThumbnail(w, h, + originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc --- a/libs/image/kis_mask.cc +++ b/libs/image/kis_mask.cc @@ -343,7 +343,7 @@ selection() ? selection()->projection() : 0; return originalDevice ? - originalDevice->createThumbnail(w, h, + originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -63,8 +63,8 @@ * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice - : public QObject - , public KisShared + : public QObject + , public KisShared { Q_OBJECT @@ -513,8 +513,8 @@ * like sRGB). */ QImage convertToQImage(const KoColorProfile * dstProfile, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining @@ -526,7 +526,8 @@ * @param rect: only this rect will be used for the thumbnail * */ - KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect()) const; + KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const; + KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. @@ -536,17 +537,18 @@ * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail + * @param oversample: ratio used for antialiasing */ - QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); + QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1, + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ - QImage createThumbnail(qint32 maxw, qint32 maxh, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); + QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1, + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Fill c and opacity with the values found at x and y. @@ -796,8 +798,7 @@ QRect calculateExactBounds(bool nonDefaultOnly) const; public: - struct MemoryReleaseObject : public QObject - { + struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject(); }; diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -37,7 +37,7 @@ #include #include #include - +#include #include "kis_image.h" #include "kis_random_sub_accessor.h" @@ -64,6 +64,9 @@ #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" +#include "kis_transform_worker.h" +#include "kis_filter_strategy.h" + struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { @@ -105,18 +108,40 @@ 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 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 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 KisPaintDeviceCache* cache() + { + return currentData()->cache(); + } - void cloneAllDataObjects(Private *rhs, bool copyFrames) { + void cloneAllDataObjects(Private *rhs, bool copyFrames) + { m_lodData.reset(); m_externalFrameData.reset(); @@ -167,19 +192,24 @@ return fastBitBltPossibleImpl(src->m_d->currentData()); } - int currentFrameId() const { - KIS_ASSERT_RECOVER(contentChannel) { return -1; } + int currentFrameId() const + { + KIS_ASSERT_RECOVER(contentChannel) { + return -1; + } return !defaultBounds->currentLevelOfDetail() ? - contentChannel->frameIdAt(defaultBounds->currentTime()) : - -1; + contentChannel->frameIdAt(defaultBounds->currentTime()) : + -1; } - KisDataManagerSP frameDataManager(int frameId) const { + KisDataManagerSP frameDataManager(int frameId) const + { DataSP data = m_frames[frameId]; return data->dataManager(); } - void invalidateFrameCache(int frameId) { + void invalidateFrameCache(int frameId) + { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } @@ -189,7 +219,8 @@ typedef QSharedPointer DataSP; typedef QHash FramesHash; - class FrameInsertionCommand : public KUndo2Command { + class FrameInsertionCommand : public KUndo2Command + { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) @@ -201,16 +232,19 @@ { } - void redo() { + void redo() + { doSwap(m_insert); } - void undo() { + void undo() + { doSwap(!m_insert); } private: - void doSwap(bool insert) { + void doSwap(bool insert) + { if (insert) { m_hash->insert(m_frameId, m_data); } else { @@ -229,7 +263,9 @@ int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { - KIS_ASSERT_RECOVER(parentCommand) { return -1; } + KIS_ASSERT_RECOVER(parentCommand) { + return -1; + } DataSP data; bool initialFrame = false; @@ -296,12 +332,14 @@ return extent; } - QPoint frameOffset(int frameId) const { + QPoint frameOffset(int frameId) const + { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } - void setFrameOffset(int frameId, const QPoint &offset) { + void setFrameOffset(int frameId, const QPoint &offset) + { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); @@ -312,25 +350,29 @@ return m_frames.keys(); } - bool readFrame(QIODevice *stream, int frameId) { + 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) { + bool writeFrame(KisPaintDeviceWriter &store, int frameId) + { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } - void setFrameDefaultPixel(const quint8 *defPixel, int frameId) { + void setFrameDefaultPixel(const quint8 *defPixel, int frameId) + { DataSP data = m_frames[frameId]; data->dataManager()->setDefaultPixel(defPixel); } - const quint8* frameDefaultPixel(int frameId) const { + const quint8* frameDefaultPixel(int frameId) const + { DataSP data = m_frames[frameId]; return data->dataManager()->defaultPixel(); } @@ -352,14 +394,17 @@ QRegion syncWholeDevice(Data *srcData); - inline DataSP currentFrameData() const { + inline DataSP currentFrameData() const + { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); - KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } + 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(); @@ -370,7 +415,8 @@ return data; } - inline Data* currentNonLodData() const { + inline Data* currentNonLodData() const + { Data *data = m_data.data(); if (contentChannel) { @@ -388,7 +434,8 @@ return data; } - inline void ensureLodDataPresent() const { + inline void ensureLodDataPresent() const + { if (!m_lodData) { Data *srcData = currentNonLodData(); @@ -399,7 +446,8 @@ } } - inline Data* currentData() const { + inline Data* currentData() const + { Data *data = m_data.data(); if (defaultBounds->currentLevelOfDetail()) { @@ -423,10 +471,11 @@ bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && - *colorSpace() == *srcData->colorSpace(); + *colorSpace() == *srcData->colorSpace(); } - QList allDataObjects() const { + QList allDataObjects() const + { QList dataObjects; if (m_frames.isEmpty()) { @@ -503,15 +552,18 @@ { } - KisHLineConstIteratorSP createConstIterator(const QRect &rect) { + 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) { + KisHLineIteratorSP createIterator(const QRect &rect) + { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } - int pixelSize() const { + int pixelSize() const + { return m_dataManager->pixelSize(); } @@ -522,8 +574,7 @@ int m_offsetY; }; -struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct -{ +struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; @@ -703,7 +754,8 @@ uploadFrameData(srcData, dstData); } -void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { +void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) +{ DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); @@ -713,7 +765,8 @@ uploadFrameData(srcData, dstData); } -void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { +void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) +{ if (srcData->colorSpace() != dstData->colorSpace() && !(*srcData->colorSpace() == *dstData->colorSpace())) { @@ -741,22 +794,26 @@ transferFromData(data, targetDevice); } -KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { +KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) +{ - class DeviceChangeColorSpaceCommand : public KUndo2Command { + class DeviceChangeColorSpaceCommand : public KUndo2Command + { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device) : m_firstRun(true), m_device(device) - { - } + { + } - void emitNotifications() { + void emitNotifications() + { m_device->emitColorSpaceChanged(); m_device->setDirty(); } - void redo() { + void redo() + { KUndo2Command::redo(); if (!m_firstRun) { @@ -767,7 +824,8 @@ emitNotifications(); } - void undo() { + void undo() + { KUndo2Command::undo(); emitNotifications(); } @@ -913,7 +971,7 @@ { prepareClone(src); - // we guarantee that *this is totally empty, so copy pixels that + // 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(); @@ -1053,32 +1111,33 @@ return m_d->cache()->exactBoundsAmortized(); } -namespace Impl { - -struct CheckFullyTransparent +namespace Impl { + +struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } - bool isPixelEmpty(const quint8 *pixelData) { + bool isPixelEmpty(const quint8 *pixelData) + { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; -struct CheckNonDefault -{ +struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } - bool isPixelEmpty(const quint8 *pixelData) { + bool isPixelEmpty(const quint8 *pixelData) + { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } @@ -1177,7 +1236,7 @@ { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { - for (qint32 y2 = y + h -1; y2 >= y || found; --y2) { + for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; @@ -1202,7 +1261,7 @@ QRect endRect; quint8 defaultOpacity = m_d->colorSpace()->opacityU8(defaultPixel()); - if(defaultOpacity != OPACITY_TRANSPARENT_U8) { + if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the @@ -1336,10 +1395,10 @@ 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()); + ->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; @@ -1405,57 +1464,119 @@ return image; } -KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect) const +inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { - KisPaintDeviceSP thumbnail = new KisPaintDevice(colorSpace()); - - int srcWidth, srcHeight; - int srcX0, srcY0; - QRect e = rect.isValid() ? rect : extent(); - e.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); - - if (w > srcWidth) { - w = srcWidth; - h = qint32(double(srcWidth) / w * h); + int pos = 0; + while (pos < numPixels) { + int step = std::min(iter.nConseqPixels(), numPixels - pos); + if (!iter.nextPixels(step)) + return false; + pos += step; } - if (h > srcHeight) { - h = srcHeight; - w = qint32(double(srcHeight) / h * w); - } - - if (srcWidth > srcHeight) - h = qint32(double(srcHeight) / srcWidth * w); - else if (srcHeight > srcWidth) - w = qint32(double(srcWidth) / srcHeight * h); + return true; +} - const qint32 pixelSize = this->pixelSize(); +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 iter = createRandomConstAccessorNG(0, 0); + KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); - for (qint32 y = 0; y < h; ++y) { + for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; - for (qint32 x = 0; x < w; ++x) { + for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; - iter->moveTo(iX, iY); + srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); - memcpy(dstIter->rawData(), iter->rawDataConst(), pixelSize); + memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; +} + +KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const +{ + QSize thumbnailSize(w, h); + + int srcWidth, srcHeight; + int srcX0, srcY0; + QRect imageRect = rect.isValid() ? rect : extent(); + + //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image + if (imageRect.isEmpty() || !imageRect.isValid()) { + return new KisPaintDevice(colorSpace()); + } + + imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); + + if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { + thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); + } + 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; } -QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) +KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { - KisPaintDeviceSP dev = createThumbnailDevice(w, h, rect); + QSize thumbnailSize(w, h); + qreal oversampleAdjusted = qMax(oversample, 1.); + QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; + + QRect outputRect; + QRect imageRect = (rect.isValid() && !rect.isNull()) ? rect : extent(); + + //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image + if (imageRect.isEmpty() || !imageRect.isValid()) { + return new KisPaintDevice(colorSpace()); + } + + qint32 hstart = thumbnailOversampledSize.height(); + + if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { + thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); + } + + 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) +{ + KisPaintDeviceSP dev = createThumbnailDeviceOversampled(w, h, 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, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) +QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { - return m_d->cache()->createThumbnail(w, h, renderingIntent, conversionFlags); + return m_d->cache()->createThumbnail(w, h, oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) @@ -1832,19 +1953,25 @@ const quint8* KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { - KIS_ASSERT_RECOVER(frameId >= 0) { return (quint8*)"deadbeef"; } + KIS_ASSERT_RECOVER(frameId >= 0) { + return (quint8*)"deadbeef"; + } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { - KIS_ASSERT_RECOVER(frameId >= 0) { return false; } + 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; } + KIS_ASSERT_RECOVER(frameId >= 0) { + return false; + } return q->m_d->readFrame(stream, frameId); } @@ -1855,7 +1982,9 @@ KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { - KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } + KIS_ASSERT_RECOVER(frameId >= 0) { + return q->m_d->dataManager(); + } return q->m_d->frameDataManager(frameId); } diff --git a/libs/image/kis_paint_device_cache.h b/libs/image/kis_paint_device_cache.h --- a/libs/image/kis_paint_device_cache.h +++ b/libs/image/kis_paint_device_cache.h @@ -82,37 +82,37 @@ return m_regionCache.getValue(); } - QImage createThumbnail(qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { + QImage createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QImage thumbnail; if(m_thumbnailsValid) { - thumbnail = findThumbnail(w, h); + thumbnail = findThumbnail(w, h, oversample); } else { m_thumbnails.clear(); m_thumbnailsValid = true; } if(thumbnail.isNull()) { - thumbnail = m_paintDevice->createThumbnail(w, h, QRect(), renderingIntent, conversionFlags); - cacheThumbnail(w, h, thumbnail); + thumbnail = m_paintDevice->createThumbnail(w, h, QRect(), oversample, renderingIntent, conversionFlags); + cacheThumbnail(w, h, oversample, thumbnail); } Q_ASSERT(!thumbnail.isNull() || m_paintDevice->extent().isEmpty()); return thumbnail; } private: - inline QImage findThumbnail(qint32 w, qint32 h) { + inline QImage findThumbnail(qint32 w, qint32 h, qreal oversample) { QImage resultImage; - if (m_thumbnails.contains(w) && m_thumbnails[w].contains(h)) { - resultImage = m_thumbnails[w][h]; + if (m_thumbnails.contains(w) && m_thumbnails[w].contains(h) && m_thumbnails[w][h].contains(oversample)) { + resultImage = m_thumbnails[w][h][oversample]; } return resultImage; } - inline void cacheThumbnail(qint32 w, qint32 h, QImage image) { - m_thumbnails[w][h] = image; + inline void cacheThumbnail(qint32 w, qint32 h, qreal oversample, QImage image) { + m_thumbnails[w][h][oversample] = image; } private: @@ -153,7 +153,7 @@ RegionCache m_regionCache; bool m_thumbnailsValid; - QMap > m_thumbnails; + QMap > > m_thumbnails; }; #endif /* __KIS_PAINT_DEVICE_CACHE_H */ diff --git a/libs/image/kis_selection_based_layer.cpp b/libs/image/kis_selection_based_layer.cpp --- a/libs/image/kis_selection_based_layer.cpp +++ b/libs/image/kis_selection_based_layer.cpp @@ -285,7 +285,7 @@ KisPaintDeviceSP originalDevice = original(); return originalDevice && originalSelection ? - originalDevice->createThumbnail(w, h, + originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); diff --git a/libs/image/kis_update_time_monitor.cpp b/libs/image/kis_update_time_monitor.cpp --- a/libs/image/kis_update_time_monitor.cpp +++ b/libs/image/kis_update_time_monitor.cpp @@ -224,7 +224,7 @@ QMutexLocker locker(&m_d->mutex); StrokeTicket *ticket = m_d->preliminaryTickets.take(key); - if (ticket) { + if( ticket ){ ticket->jobCompleted(); Q_FOREACH (const QRect &rect, rects) { diff --git a/libs/ui/kis_custom_pattern.cc b/libs/ui/kis_custom_pattern.cc --- a/libs/ui/kis_custom_pattern.cc +++ b/libs/ui/kis_custom_pattern.cc @@ -167,7 +167,7 @@ } QString dir = KoResourceServerProvider::instance()->patternServer()->saveLocation(); - m_pattern = new KoPattern(dev->createThumbnail(size.width(), size.height(), rc, + m_pattern = new KoPattern(dev->createThumbnail(size.width(), size.height(), rc, /*oversample*/ 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()), name, dir); } diff --git a/plugins/dockers/CMakeLists.txt b/plugins/dockers/CMakeLists.txt --- a/plugins/dockers/CMakeLists.txt +++ b/plugins/dockers/CMakeLists.txt @@ -21,4 +21,4 @@ add_subdirectory(animation) add_subdirectory(presethistory) add_subdirectory(shapedockers) -#add_subdirectory(histogram) +add_subdirectory(histogram) diff --git a/plugins/dockers/advancedcolorselector/kis_common_colors.cpp b/plugins/dockers/advancedcolorselector/kis_common_colors.cpp --- a/plugins/dockers/advancedcolorselector/kis_common_colors.cpp +++ b/plugins/dockers/advancedcolorselector/kis_common_colors.cpp @@ -131,7 +131,7 @@ KisImageWSP kisImage = m_canvas->image(); - QImage image = kisImage->projection()->createThumbnail(1024, 1024, kisImage->bounds(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); + QImage image = kisImage->projection()->createThumbnail(1024, 1024, kisImage->bounds(), 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KisCommonColorsRecalculationRunner* runner = new KisCommonColorsRecalculationRunner(image, patchCount(), this); QThreadPool::globalInstance()->start(runner); diff --git a/plugins/dockers/channeldocker/channeldocker_dock.h b/plugins/dockers/channeldocker/channeldocker_dock.h --- a/plugins/dockers/channeldocker/channeldocker_dock.h +++ b/plugins/dockers/channeldocker/channeldocker_dock.h @@ -24,15 +24,28 @@ class ChannelModel; class QTableView; class KisCanvas2; +class KisSignalCompressor; +class KisIdleWatcher; class ChannelDockerDock : public QDockWidget, public KoCanvasObserverBase { Q_OBJECT public: ChannelDockerDock(); + QString observerName() { return "ChannelDockerDock"; } - virtual void setCanvas(KoCanvasBase *canvas); - virtual void unsetCanvas() { m_canvas = 0; setEnabled(false);} + void setCanvas(KoCanvasBase *canvas); + void unsetCanvas(); + void showEvent(QShowEvent *event); + +public Q_SLOTS: + void startUpdateCanvasProjection(); + +private Q_SLOTS: + void updateChannelTable(void); + private: + KisIdleWatcher* m_imageIdleWatcher; + KisSignalCompressor *m_compressor; KisCanvas2 *m_canvas; QTableView *m_channelTable; ChannelModel *m_model; diff --git a/plugins/dockers/channeldocker/channeldocker_dock.cpp b/plugins/dockers/channeldocker/channeldocker_dock.cpp --- a/plugins/dockers/channeldocker/channeldocker_dock.cpp +++ b/plugins/dockers/channeldocker/channeldocker_dock.cpp @@ -32,37 +32,90 @@ #include #include #include +#include "kis_signal_compressor.h" #include +#include -ChannelDockerDock::ChannelDockerDock( ) : QDockWidget(i18n("Channels")), m_canvas(0) +ChannelDockerDock::ChannelDockerDock( ) : + QDockWidget(i18n("Channels")), + m_imageIdleWatcher(new KisIdleWatcher(250, this)), + m_canvas(0) { m_channelTable = new QTableView(this); m_model = new ChannelModel(this); m_channelTable->setModel(m_model); m_channelTable->setShowGrid(false); + m_channelTable->horizontalHeader()->setStretchLastSection(true); m_channelTable->verticalHeader()->setVisible(false); + m_channelTable->horizontalHeader()->setVisible(false); + m_channelTable->setSelectionBehavior( QAbstractItemView::SelectRows ); + setWidget(m_channelTable); + + connect(m_channelTable,&QTableView::activated, m_model, &ChannelModel::rowActivated); } void ChannelDockerDock::setCanvas(KoCanvasBase * canvas) { + if(m_canvas == canvas) + return; + setEnabled(canvas != 0); + if (m_canvas) { + m_canvas->disconnectCanvasObserver(this); + m_canvas->image()->disconnect(this); + } + if (!canvas) { m_canvas = 0; return; } + m_canvas = dynamic_cast(canvas); - if (m_canvas && m_canvas->imageView() && m_canvas->imageView()->image()) { - QPointer view = m_canvas->imageView(); - m_model->slotLayerActivated(view->image()->rootLayer()); - KisPaintDeviceSP dev = view->image()->projection(); + if ( m_canvas && m_canvas->image() ) { + m_model->slotSetCanvas(m_canvas); + + KisPaintDeviceSP dev = m_canvas->image()->projection(); + + m_imageIdleWatcher->setTrackedImage(m_canvas->image()); + connect(m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &ChannelDockerDock::updateChannelTable); + connect(dev, SIGNAL(colorSpaceChanged(const KoColorSpace*)), m_model, SLOT(slotColorSpaceChanged(const KoColorSpace*))); connect(dev, SIGNAL(colorSpaceChanged(const KoColorSpace*)), m_canvas, SLOT(channelSelectionChanged())); + connect(m_model, SIGNAL(channelFlagsChanged()), m_canvas, SLOT(channelSelectionChanged())); + m_imageIdleWatcher->startCountdown(); } - connect(m_model, SIGNAL(channelFlagsChanged()), m_canvas, SLOT(channelSelectionChanged())); } +void ChannelDockerDock::unsetCanvas() +{ + setEnabled(false); + m_canvas = 0; + m_model->unsetCanvas(); +} + +void ChannelDockerDock::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); + m_imageIdleWatcher->startCountdown(); +} + +void ChannelDockerDock::startUpdateCanvasProjection() +{ + m_imageIdleWatcher->startCountdown(); +} + +void ChannelDockerDock::updateChannelTable() +{ + if (isVisible() && m_canvas && m_canvas->image()){ + m_model->updateData(m_canvas); + m_channelTable->resizeRowsToContents(); + m_channelTable->resizeColumnsToContents(); + } +} + + diff --git a/plugins/dockers/channeldocker/channelmodel.h b/plugins/dockers/channeldocker/channelmodel.h --- a/plugins/dockers/channeldocker/channelmodel.h +++ b/plugins/dockers/channeldocker/channelmodel.h @@ -19,32 +19,49 @@ #define CHANNELMODEL_H #include +#include +#include class KoColorSpace; - -#include +class KisCanvas2; class ChannelModel : public QAbstractTableModel { Q_OBJECT public: ChannelModel(QObject* parent = 0); virtual ~ChannelModel(); - - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; - virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); - virtual Qt::ItemFlags flags(const QModelIndex& index) const; + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent = QModelIndex()) const; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + Qt::ItemFlags flags(const QModelIndex& index) const; + void unsetCanvas( void ); + + //set maximum size of the thumbnail image. This should be set based on screen resolution, etc. + void setThumbnailSizeLimit(QSize size); public Q_SLOTS: - void slotLayerActivated(KisLayerSP layer); + void slotSetCanvas(KisCanvas2* canvas); void slotColorSpaceChanged(const KoColorSpace *colorSpace); + void updateData(KisCanvas2 *canvas); + void rowActivated(const QModelIndex &index); + Q_SIGNALS: void channelFlagsChanged(); + private: - KisLayerWSP m_currentLayer; + void updateThumbnails(); + +private: + KisCanvas2* m_canvas; + QVector m_thumbnails; + QSize m_thumbnailSizeLimit; + int m_oversampleRatio; + int m_channelCount; }; + #endif // CHANNELMODEL_H diff --git a/plugins/dockers/channeldocker/channelmodel.cpp b/plugins/dockers/channeldocker/channelmodel.cpp --- a/plugins/dockers/channeldocker/channelmodel.cpp +++ b/plugins/dockers/channeldocker/channelmodel.cpp @@ -16,127 +16,239 @@ */ #include "channelmodel.h" - +#include #include #include -#include -#include +#include + +#include +#include +#include +#include + +#include -ChannelModel::ChannelModel(QObject* parent): QAbstractTableModel(parent), m_currentLayer(0) +ChannelModel::ChannelModel(QObject* parent): + QAbstractTableModel(parent), + m_canvas(nullptr), m_oversampleRatio(2), m_channelCount(0) { + setThumbnailSizeLimit(QSize(64, 64)); } ChannelModel::~ChannelModel() { } QVariant ChannelModel::data(const QModelIndex& index, int role) const { - if (m_currentLayer.isValid() && index.isValid()) - { - QList channels = m_currentLayer->colorSpace()->channels(); - int channelIndex = KoChannelInfo::displayPositionToChannelIndex(index.row(), channels); + if (m_canvas && index.isValid()) { + KisGroupLayerSP rootLayer = m_canvas->image()->rootLayer(); + const KoColorSpace* cs = rootLayer->colorSpace(); + QList channels = cs->channels(); + + int channelIndex = index.row(); switch (role) { - case Qt::DisplayRole: - { - return channels.at(channelIndex)->name(); - } + case Qt::DisplayRole: { + if (index.column() == 2) { + return channels.at(channelIndex)->name(); + } + return QVariant(); + } + case Qt::DecorationRole: { + if (index.column() == 1) { + Q_ASSERT(m_thumbnails.count() > index.row()); + return QVariant(m_thumbnails.at(index.row())); + } + return QVariant(); + } case Qt::CheckStateRole: { Q_ASSERT(index.row() < rowCount()); Q_ASSERT(index.column() < columnCount()); - + if (index.column() == 0) { - QBitArray flags = m_currentLayer->channelFlags(); + QBitArray flags = rootLayer->channelFlags(); return (flags.isEmpty() || flags.testBit(channelIndex)) ? Qt::Checked : Qt::Unchecked; } - - QBitArray flags = dynamic_cast(m_currentLayer.data())->channelLockFlags(); - return (flags.isEmpty() || flags.testBit(channelIndex)) ? Qt::Unchecked : Qt::Checked; + return QVariant(); } } } return QVariant(); } QVariant ChannelModel::headerData(int section, Qt::Orientation orientation, int role) const { - if(orientation == Qt::Horizontal && role == Qt::DisplayRole) { - if(section == 0) - return i18n("Enabled"); - - return i18n("Locked"); - } - - return QAbstractItemModel::headerData(section, orientation, role); + Q_UNUSED(section); Q_UNUSED(orientation); Q_UNUSED(role); + return QVariant(); } int ChannelModel::rowCount(const QModelIndex& /*parent*/) const { - if (!m_currentLayer) return 0; + if (!m_canvas) return 0; - return m_currentLayer.isValid() ? m_currentLayer->colorSpace()->channelCount() : 0; + return m_channelCount; } int ChannelModel::columnCount(const QModelIndex& /*parent*/) const { - if (!m_currentLayer) return 0; + if (!m_canvas) return 0; - return dynamic_cast(m_currentLayer.data()) ? 2 : 1; + //columns are: checkbox, thumbnail, channel name + return 3; } bool ChannelModel::setData(const QModelIndex& index, const QVariant& value, int role) { - if (m_currentLayer.isValid() && index.isValid()) - { - QList channels = m_currentLayer->colorSpace()->channels(); - int channelIndex = KoChannelInfo::displayPositionToChannelIndex(index.row(), channels); - + if (m_canvas && m_canvas->image()) { + KisGroupLayerSP rootLayer = m_canvas->image()->rootLayer(); + const KoColorSpace* cs = rootLayer->colorSpace(); + QList channels = cs->channels(); + Q_ASSERT(index.row() <= channels.count()); + + int channelIndex = index.row(); + if (role == Qt::CheckStateRole) { - Q_ASSERT(index.row() < rowCount()); - Q_ASSERT(index.column() < columnCount()); - - if (index.column() == 0) { - QBitArray flags = m_currentLayer->channelFlags(); + QBitArray flags = rootLayer->channelFlags(); + flags = flags.isEmpty() ? cs->channelFlags(true, true) : flags; + Q_ASSERT(!flags.isEmpty()); + + flags.setBit(channelIndex, value.toInt() == Qt::Checked); + rootLayer->setChannelFlags(flags); - flags = flags.isEmpty() ? m_currentLayer->colorSpace()->channelFlags(true, true) : flags; - flags.setBit(channelIndex, value.toInt() == Qt::Checked); - m_currentLayer->setChannelFlags(flags); - } - else { //if (index.column() == 1) - KisPaintLayer* paintLayer = dynamic_cast(m_currentLayer.data()); - QBitArray flags = paintLayer->channelLockFlags(); - flags = flags.isEmpty() ? m_currentLayer->colorSpace()->channelFlags(true, true) : flags; - flags.setBit(channelIndex, value.toInt() == Qt::Unchecked); - paintLayer->setChannelLockFlags(flags); - } - emit channelFlagsChanged(); + emit dataChanged(this->index(0, 0), this->index(channels.count(), 0)); return true; } } return false; } + +//User double clicked on a row (but on channel checkbox) +//we select this channel, and deselect all other channels (except alpha, which we don't touch) +//this makes it fast to select single color channel +void ChannelModel::rowActivated(const QModelIndex &index) +{ + if (m_canvas && m_canvas->image()) { + KisGroupLayerWSP rootLayer = m_canvas->image()->rootLayer(); + const KoColorSpace* cs = rootLayer->colorSpace(); + QList channels = cs->channels(); + Q_ASSERT(index.row() <= channels.count()); + + int channelIndex = index.row(); + + QBitArray flags = rootLayer->channelFlags(); + flags = flags.isEmpty() ? cs->channelFlags(true, true) : flags; + Q_ASSERT(!flags.isEmpty()); + + for (int i = 0; i < channels.count(); ++i) { + if (channels[i]->channelType() != KoChannelInfo::ALPHA) { + flags.setBit(i, (i == channelIndex)); + } + } + + rootLayer->setChannelFlags(flags); + + emit channelFlagsChanged(); + emit dataChanged(this->index(0, 0), this->index(channels.count(), 0)); + } +} + + Qt::ItemFlags ChannelModel::flags(const QModelIndex& /*index*/) const { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; return flags; } -void ChannelModel::slotLayerActivated(KisLayerSP layer) +void ChannelModel::unsetCanvas() { - beginResetModel(); - m_currentLayer = layer; - endResetModel(); + m_canvas = 0; +} + +void ChannelModel::setThumbnailSizeLimit(QSize size) +{ + m_thumbnailSizeLimit = size; + updateData(m_canvas); +} + +void ChannelModel::slotSetCanvas(KisCanvas2 *canvas) +{ + if (m_canvas != canvas) { + beginResetModel(); + m_canvas = canvas; + if (m_canvas && m_canvas->image()) { + m_channelCount = m_canvas->image()->colorSpace()->channelCount(); + updateThumbnails(); + } else { + m_channelCount = 0; + } + endResetModel(); + } } void ChannelModel::slotColorSpaceChanged(const KoColorSpace *colorSpace) { Q_UNUSED(colorSpace); - slotLayerActivated(m_currentLayer); + beginResetModel(); + updateThumbnails(); + endResetModel(); +} + +void ChannelModel::updateData(KisCanvas2 *canvas) +{ + beginResetModel(); + m_canvas = canvas; + m_channelCount = (m_canvas) ? m_canvas->image()->colorSpace()->channelCount() : 0; + updateThumbnails(); + endResetModel(); +} + + + +//Create thumbnails from full image. +//Assumptions: thumbnail size is small compared to the original image and thumbnail quality +//doesn't need to be high, so we use fast but not very accurate algorithm. +void ChannelModel::updateThumbnails(void) +{ + if (m_canvas && m_canvas->image()) { + KisImageSP canvas_image = m_canvas->image(); + const KoColorSpace* cs = canvas_image->colorSpace(); + m_channelCount = cs->channelCount(); + + KisPaintDeviceSP dev = canvas_image->projection(); + + //make sure thumbnail maintains aspect ratio of the original image + QSize thumbnailSize(canvas_image->bounds().size()); + thumbnailSize.scale(m_thumbnailSizeLimit, Qt::KeepAspectRatio); + + KisPaintDeviceSP thumbnailDev = dev->createThumbnailDeviceOversampled(thumbnailSize.width(), thumbnailSize.height(), + m_oversampleRatio, canvas_image->bounds()); + + m_thumbnails.resize(m_channelCount); + + for (quint32 i = 0; i < m_channelCount; ++i) { + m_thumbnails[i] = QImage(thumbnailSize, QImage::Format_Grayscale8); + } + + KisSequentialConstIterator it(thumbnailDev, QRect(0, 0, thumbnailSize.width(), thumbnailSize.height())); + + for (int y = 0; y < thumbnailSize.height(); y++) { + for (int x = 0; x < thumbnailSize.width(); x++) { + const quint8* pixel = it.rawDataConst(); + for (int chan = 0; chan < m_channelCount; ++chan) { + QImage &img = m_thumbnails[chan]; + *(img.scanLine(y) + x) = cs->scaleToU8(pixel, chan); + } + it.nextPixel(); + } + } + } else { + m_channelCount = 0; + } } #include "moc_channelmodel.cpp" diff --git a/plugins/dockers/histogram/CMakeLists.txt b/plugins/dockers/histogram/CMakeLists.txt --- a/plugins/dockers/histogram/CMakeLists.txt +++ b/plugins/dockers/histogram/CMakeLists.txt @@ -1,4 +1,4 @@ -set(KRITA_HISTOGRAMDOCKER_SOURCES histogramdocker.cpp histogramdocker_dock.cpp ) +set(KRITA_HISTOGRAMDOCKER_SOURCES histogramdocker.cpp histogramdocker_dock.cpp histogramdockerwidget.cpp) add_library(kritahistogramdocker MODULE ${KRITA_HISTOGRAMDOCKER_SOURCES}) target_link_libraries(kritahistogramdocker kritaui) -#install(TARGETS kritahistogramdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) +install(TARGETS kritahistogramdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/histogram/histogramdocker_dock.h b/plugins/dockers/histogram/histogramdocker_dock.h --- a/plugins/dockers/histogram/histogramdocker_dock.h +++ b/plugins/dockers/histogram/histogramdocker_dock.h @@ -25,8 +25,9 @@ class QVBoxLayout; class KisCanvas2; class KisHistogramView; -class KisSignalCompressor; +class KisIdleWatcher; class KoHistogramProducer; +class HistogramDockerWidget; class HistogramDockerDock : public QDockWidget, public KoCanvasObserverBase { Q_OBJECT @@ -38,16 +39,18 @@ virtual void unsetCanvas(); public Q_SLOTS: - virtual void startUpdateCanvasProjection(); - //virtual void sigProfileChanged(const KoColorProfile* cp); - virtual void sigColorSpaceChanged(const KoColorSpace* cs); + void startUpdateCanvasProjection(); + void sigColorSpaceChanged(const KoColorSpace* cs); + void updateHistogram(); + +protected: + void showEvent(QShowEvent *event); private: QVBoxLayout *m_layout; - KisSignalCompressor *m_compressor; - KisHistogramView *m_histogramWidget; + KisIdleWatcher *m_imageIdleWatcher; + HistogramDockerWidget *m_histogramWidget; KisCanvas2 *m_canvas; - KoHistogramProducer *m_producer; }; diff --git a/plugins/dockers/histogram/histogramdocker_dock.cpp b/plugins/dockers/histogram/histogramdocker_dock.cpp --- a/plugins/dockers/histogram/histogramdocker_dock.cpp +++ b/plugins/dockers/histogram/histogramdocker_dock.cpp @@ -26,69 +26,83 @@ #include #include "kis_image.h" #include "kis_paint_device.h" -#include "kis_signal_compressor.h" -#include "kis_histogram_view.h" +#include "kis_idle_watcher.h" +#include "histogramdockerwidget.h" -HistogramDockerDock::HistogramDockerDock( ) +HistogramDockerDock::HistogramDockerDock() : QDockWidget(i18n("Histogram")), - m_compressor(new KisSignalCompressor(200, KisSignalCompressor::POSTPONE, this)), - m_canvas(0), m_producer(nullptr) + m_imageIdleWatcher(new KisIdleWatcher(250, this)), + m_canvas(0) { QWidget *page = new QWidget(this); m_layout = new QVBoxLayout(page); - m_histogramWidget = new KisHistogramView(this); + m_histogramWidget = new HistogramDockerWidget(this); + m_histogramWidget->setMinimumHeight(50); - m_histogramWidget->setSmoothHistogram(true); + //m_histogramWidget->setSmoothHistogram(false); m_layout->addWidget(m_histogramWidget, 1); setWidget(page); - connect(m_compressor,SIGNAL(timeout()),SLOT(startUpdateCanvasProjection())); + connect(m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &HistogramDockerDock::updateHistogram); } void HistogramDockerDock::setCanvas(KoCanvasBase * canvas) { - if(m_canvas == canvas) + if (m_canvas == canvas) return; setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_canvas->image()->disconnect(this); } - m_canvas = dynamic_cast(canvas); - if (m_canvas && m_canvas->imageView() && m_canvas->imageView()->image() ) { - KisPaintDeviceSP dev = m_canvas->image()->projection(); - auto cs = m_canvas->image()->colorSpace(); + m_canvas = dynamic_cast(canvas); + if (m_canvas) { + m_histogramWidget->setPaintDevice(m_canvas); - QList producers = KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(cs,true); - m_producer = KoHistogramProducerFactoryRegistry::instance()->get(producers.at(0))->generate(); - m_histogramWidget->setPaintDevice( dev, m_producer, m_canvas->image()->bounds() ); + m_imageIdleWatcher->setTrackedImage(m_canvas->image()); - connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)), m_compressor, SLOT(start()), Qt::UniqueConnection); + connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)), this, SLOT(startUpdateCanvasProjection()), Qt::UniqueConnection); connect(m_canvas->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SLOT(sigColorSpaceChanged(const KoColorSpace*)), Qt::UniqueConnection); - - m_compressor->start(); + m_imageIdleWatcher->startCountdown(); } } void HistogramDockerDock::unsetCanvas() { setEnabled(false); m_canvas = 0; + m_histogramWidget->setPaintDevice(m_canvas); + m_imageIdleWatcher->startCountdown(); } void HistogramDockerDock::startUpdateCanvasProjection() { - m_histogramWidget->startUpdateCanvasProjection(); + if (isVisible()) { + m_imageIdleWatcher->startCountdown(); + } +} + +void HistogramDockerDock::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); + m_imageIdleWatcher->startCountdown(); } + void HistogramDockerDock::sigColorSpaceChanged(const KoColorSpace *cs) { - QList producers = KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(cs,true); - m_producer = KoHistogramProducerFactoryRegistry::instance()->get(producers.at(0))->generate(); - m_histogramWidget->setProducer(m_producer); + if (isVisible()) { + m_imageIdleWatcher->startCountdown(); + } } +void HistogramDockerDock::updateHistogram() +{ + if (isVisible()) { + m_histogramWidget->updateHistogram(); + } +} diff --git a/plugins/dockers/histogram/histogramdockerwidget.h b/plugins/dockers/histogram/histogramdockerwidget.h new file mode 100644 --- /dev/null +++ b/plugins/dockers/histogram/histogramdockerwidget.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016 Eugene Ingerman + * + * 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. + */ + + +#ifndef HISTOGRAMDOCKERWIDGET_H +#define HISTOGRAMDOCKERWIDGET_H + +#include +#include +#include +#include +#include "kis_types.h" +#include + +class KisCanvas2; + +typedef std::vector > HistVector; //Don't use QVector here - it's too slow for this purpose + + +class HistogramComputationThread : public QThread +{ + Q_OBJECT +public: + HistogramComputationThread(KisPaintDeviceSP _dev, const QRect& _bounds) : m_dev(_dev), m_bounds(_bounds) + {} + + void run() Q_DECL_OVERRIDE; + +Q_SIGNALS: + void resultReady(HistVector*); + +private: + KisPaintDeviceSP m_dev; + QRect m_bounds; + HistVector bins; +}; + + +class HistogramDockerWidget : public QLabel +{ + Q_OBJECT + +public: + HistogramDockerWidget(QWidget *parent = 0, const char *name = 0, Qt::WindowFlags f = 0); + ~HistogramDockerWidget(); + void setPaintDevice(KisCanvas2* canvas); + void paintEvent(QPaintEvent *event); + +public Q_SLOTS: + void updateHistogram(); + void receiveNewHistogram(HistVector*); + +private: + KisPaintDeviceSP m_paintDevice; + HistVector m_histogramData; + QRect m_bounds; + bool m_smoothHistogram; +}; + +#endif // HISTOGRAMDOCKERWIDGET_H diff --git a/plugins/dockers/histogram/histogramdockerwidget.cpp b/plugins/dockers/histogram/histogramdockerwidget.cpp new file mode 100644 --- /dev/null +++ b/plugins/dockers/histogram/histogramdockerwidget.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2016 Eugene Ingerman + * + * 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 "histogramdockerwidget.h" + +#include +#include +#include +#include +#include +#include + +#include "KoChannelInfo.h" +#include "kis_paint_device.h" +#include "KoColorSpace.h" +#include "kis_iterator_ng.h" +#include "kis_canvas2.h" + +HistogramDockerWidget::HistogramDockerWidget(QWidget *parent, const char *name, Qt::WindowFlags f) + : QLabel(parent, f), m_paintDevice(nullptr), m_smoothHistogram(true) +{ + setObjectName(name); +} + +HistogramDockerWidget::~HistogramDockerWidget() +{ + +} + +void HistogramDockerWidget::setPaintDevice(KisCanvas2* canvas) +{ + if (canvas) { + m_paintDevice = canvas->image()->projection(); + m_bounds = canvas->image()->bounds(); + } else { + m_paintDevice.clear(); + m_bounds = QRect(); + m_histogramData.clear(); + } +} + +void HistogramDockerWidget::updateHistogram() +{ + if (!m_paintDevice.isNull()) { + KisPaintDeviceSP m_devClone = new KisPaintDevice(m_paintDevice->colorSpace()); + + m_devClone->makeCloneFrom(m_paintDevice, m_bounds); + + HistogramComputationThread *workerThread = new HistogramComputationThread(m_devClone, m_bounds); + connect(workerThread, &HistogramComputationThread::resultReady, this, &HistogramDockerWidget::receiveNewHistogram); + connect(workerThread, &HistogramComputationThread::finished, workerThread, &QObject::deleteLater); + workerThread->start(); + } else { + m_histogramData.clear(); + update(); + } +} + +void HistogramDockerWidget::receiveNewHistogram(HistVector *histogramData) +{ + m_histogramData = *histogramData; + update(); +} + +void HistogramDockerWidget::paintEvent(QPaintEvent *event) +{ + if (!m_histogramData.empty()) { + int nBins = m_histogramData.at(0).size(); + const KoColorSpace* cs = m_paintDevice->colorSpace(); + + QLabel::paintEvent(event); + QPainter painter(this); + painter.setPen(this->palette().light().color()); + + const int NGRID = 4; + for (int i = 0; i <= NGRID; ++i) { + painter.drawLine(this->width()*i / NGRID, 0., this->width()*i / NGRID, this->height()); + painter.drawLine(0., this->height()*i / NGRID, this->width(), this->height()*i / NGRID); + } + + unsigned int nChannels = cs->channelCount(); + QList channels = cs->channels(); + unsigned int highest = 0; + //find the most populous bin in the histogram to scale it properly + for (int chan = 0; chan < channels.size(); chan++) { + if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) { + std::vector histogramTemp = m_histogramData.at(chan); + //use 98th percentile, rather than max for better visual appearance + int nthPercentile = 2 * histogramTemp.size() / 100; + //unsigned int max = *std::max_element(m_histogramData.at(chan).begin(),m_histogramData.at(chan).end()); + std::nth_element(histogramTemp.begin(), + histogramTemp.begin() + nthPercentile, histogramTemp.end(), std::greater()); + unsigned int max = *(histogramTemp.begin() + nthPercentile); + + highest = std::max(max, highest); + } + } + + painter.setWindow(QRect(-1, 0, nBins + 1, highest)); + painter.setCompositionMode(QPainter::CompositionMode_Plus); + + for (int chan = 0; chan < nChannels; chan++) { + if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) { + QColor color = channels.at(chan)->color(); + + //special handling of grayscale color spaces. can't use color returned above. + if(cs->colorChannelCount()==1){ + color = QColor(Qt::gray); + } + + QColor fill_color = color; + fill_color.setAlphaF(.25); + painter.setBrush(fill_color); + QPen pen = QPen(color); + pen.setWidth(0); + painter.setPen(pen); + + if (m_smoothHistogram) { + QPainterPath path; + path.moveTo(QPointF(-1, highest)); + for (qint32 i = 0; i < nBins; ++i) { + float v = std::max((float)highest - m_histogramData[chan][i], 0.f); + path.lineTo(QPointF(i, v)); + + } + path.lineTo(QPointF(nBins + 1, highest)); + path.closeSubpath(); + painter.drawPath(path); + } else { + pen.setWidth(1); + painter.setPen(pen); + for (qint32 i = 0; i < nBins; ++i) { + float v = std::max((float)highest - m_histogramData[chan][i], 0.f); + painter.drawLine(QPointF(i, highest), QPointF(i, v)); + } + } + } + } + } +} + +void HistogramComputationThread::run() +{ + const KoColorSpace *cs = m_dev->colorSpace(); + quint32 channelCount = m_dev->channelCount(); + quint32 pixelSize = m_dev->pixelSize(); + + quint32 imageSize = m_bounds.width() * m_bounds.height(); + quint32 nSkip = 1 + (imageSize >> 20); //for speed use about 1M pixels for computing histograms + + //allocate space for the histogram data + bins.resize((int)channelCount); + for (auto &bin : bins) { + bin.resize(std::numeric_limits::max() + 1); + } + + QRect bounds = m_dev->exactBounds(); + if (bounds.isEmpty()) + return; + + KisSequentialConstIterator it(m_dev, m_dev->exactBounds()); + int i; + quint32 toSkip = nSkip; + + do { + i = it.nConseqPixels(); + const quint8* pixel = it.rawDataConst(); + for (int k = 0; k < i; ++k) { + if (--toSkip == 0) { + for (int chan = 0; chan < (int)channelCount; ++chan) { + bins[chan][cs->scaleToU8(pixel, chan)]++; + } + toSkip = nSkip; + } + pixel += pixelSize; + } + } while (it.nextPixels(i)); + + emit resultReady(&bins); +} diff --git a/plugins/dockers/historydocker/KisUndoModel.cpp b/plugins/dockers/historydocker/KisUndoModel.cpp --- a/plugins/dockers/historydocker/KisUndoModel.cpp +++ b/plugins/dockers/historydocker/KisUndoModel.cpp @@ -254,7 +254,7 @@ if (m_stack->count() == idx && !m_imageMap.contains(currentCommand)) { KisImageWSP historyImage = m_canvas->viewManager()->image(); KisPaintDeviceSP paintDevice = historyImage->projection(); - QImage image = paintDevice->createThumbnail(32, 32, + QImage image = paintDevice->createThumbnail(32, 32, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); m_imageMap[currentCommand] = image; diff --git a/plugins/dockers/overview/overviewwidget.h b/plugins/dockers/overview/overviewwidget.h --- a/plugins/dockers/overview/overviewwidget.h +++ b/plugins/dockers/overview/overviewwidget.h @@ -19,14 +19,44 @@ #ifndef OVERVIEWWIDGET_H #define OVERVIEWWIDGET_H - +#include #include #include +#include +#include "kis_idle_watcher.h" +#include "kis_simple_stroke_strategy.h" class KisCanvas2; class KisSignalCompressor; class KoCanvasBase; +class OverviewThumbnailStrokeStrategy : public QObject, public KisSimpleStrokeStrategy +{ + Q_OBJECT +public: + OverviewThumbnailStrokeStrategy(KisImageWSP image); + ~OverviewThumbnailStrokeStrategy(); + + static QList createJobsData(KisPaintDeviceSP dev, const QRect& imageRect, KisPaintDeviceSP thumbDev, const QSize &thumbnailSize); + +private: + void initStrokeCallback(); + void doStrokeCallback(KisStrokeJobData *data); + void finishStrokeCallback(); + void cancelStrokeCallback(); + +Q_SIGNALS: + //Emitted when thumbnail is updated and overviewImage is fully generated. + void thumbnailUpdated(QImage pixmap); + + +private: + struct Private; + const QScopedPointer m_d; + QMutex m_thumbnailMergeMutex; + KisImageSP m_image; +}; + class OverviewWidget : public QWidget { Q_OBJECT @@ -37,36 +67,44 @@ virtual ~OverviewWidget(); virtual void setCanvas(KoCanvasBase *canvas); - virtual void unsetCanvas() { m_canvas = 0; } + virtual void unsetCanvas() + { + m_canvas = 0; + } public Q_SLOTS: void startUpdateCanvasProjection(); + void generateThumbnail(); + void updateThumbnail(QImage pixmap); protected: void resizeEvent(QResizeEvent *event); void showEvent(QShowEvent *event); void paintEvent(QPaintEvent *event); - + virtual void mousePressEvent(QMouseEvent* event); virtual void mouseMoveEvent(QMouseEvent* event); virtual void mouseReleaseEvent(QMouseEvent* event); virtual void wheelEvent(QWheelEvent* event); - + private: QSize calculatePreviewSize(); QPointF previewOrigin(); QTransform imageToPreviewTransform(); QPolygonF previewPolygon(); - - KisSignalCompressor *m_compressor; + QPixmap m_pixmap; KisCanvas2 *m_canvas; bool m_dragging; QPointF m_lastPos; QColor m_outlineColor; + KisIdleWatcher m_imageIdleWatcher; + KisStrokeId strokeId; + QMutex mutex; }; + #endif /* OVERVIEWWIDGET_H */ diff --git a/plugins/dockers/overview/overviewwidget.cc b/plugins/dockers/overview/overviewwidget.cc --- a/plugins/dockers/overview/overviewwidget.cc +++ b/plugins/dockers/overview/overviewwidget.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -32,15 +33,61 @@ #include #include #include +#include "kis_idle_watcher.h" +#include "krita_utils.h" +#include "kis_painter.h" +#include +#include "kis_transform_worker.h" +#include "kis_filter_strategy.h" +#include + +const qreal oversample = 2.; +const int thumbnailTileDim = 128; + +struct OverviewThumbnailStrokeStrategy::Private { + + class InitData : public KisStrokeJobData + { + public: + InitData(KisPaintDeviceSP _device) + : KisStrokeJobData(SEQUENTIAL), + device(_device) + {} + + KisPaintDeviceSP device; + }; + + class ProcessData : public KisStrokeJobData + { + public: + ProcessData(KisPaintDeviceSP _dev, KisPaintDeviceSP _thumbDev, const QSize& _thumbnailSize, const QRect &_rect) + : KisStrokeJobData(CONCURRENT), + dev(_dev), thumbDev(_thumbDev), thumbnailSize(_thumbnailSize), tileRect(_rect) + {} + + KisPaintDeviceSP dev; + KisPaintDeviceSP thumbDev; + QSize thumbnailSize; + QRect tileRect; + }; + class FinishProcessing : public KisStrokeJobData + { + public: + FinishProcessing(KisPaintDeviceSP _thumbDev) + : KisStrokeJobData(SEQUENTIAL), + thumbDev(_thumbDev) + {} + KisPaintDeviceSP thumbDev; + }; +}; OverviewWidget::OverviewWidget(QWidget * parent) : QWidget(parent) - , m_compressor(new KisSignalCompressor(500, KisSignalCompressor::POSTPONE, this)) , m_canvas(0) , m_dragging(false) + , m_imageIdleWatcher(250) { setMouseTracking(true); - connect(m_compressor, SIGNAL(timeout()), SLOT(startUpdateCanvasProjection())); KisConfig cfg; QRgb c = cfg.readEntry("OverviewWidgetColor", 0xFF454C); m_outlineColor = QColor(c); @@ -59,10 +106,15 @@ m_canvas = dynamic_cast(canvas); if (m_canvas) { - connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)), m_compressor, SLOT(start()), Qt::UniqueConnection); - connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)), m_compressor, SLOT(start()), Qt::UniqueConnection); + m_imageIdleWatcher.setTrackedImage(m_canvas->image()); + + connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &OverviewWidget::generateThumbnail); + + connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)),SLOT(startUpdateCanvasProjection())); + connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)),SLOT(startUpdateCanvasProjection())); + connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(update()), Qt::UniqueConnection); - m_compressor->start(); + generateThumbnail(); } } @@ -75,7 +127,7 @@ QPointF OverviewWidget::previewOrigin() { - return QPointF((width() - m_pixmap.width())/2.0f, (height() - m_pixmap.height())/2.0f); + return QPointF((width() - m_pixmap.width()) / 2.0f, (height() - m_pixmap.height()) / 2.0f); } QPolygonF OverviewWidget::previewPolygon() @@ -95,32 +147,20 @@ QTransform OverviewWidget::imageToPreviewTransform() { QTransform imageToPreview; - imageToPreview.scale(calculatePreviewSize().width()/(float)m_canvas->image()->width(), - calculatePreviewSize().height()/(float)m_canvas->image()->height()); + imageToPreview.scale(calculatePreviewSize().width() / (float)m_canvas->image()->width(), + calculatePreviewSize().height() / (float)m_canvas->image()->height()); return imageToPreview; } void OverviewWidget::startUpdateCanvasProjection() { - if (!m_canvas) return; - - KisImageSP image = m_canvas->image(); - QSize previewSize = calculatePreviewSize(); - - if (isVisible() && previewSize.isValid()) { - QImage img = - image->projection()-> - createThumbnail(previewSize.width(), previewSize.height(), image->bounds()); - - m_pixmap = QPixmap::fromImage(img); - } - update(); + m_imageIdleWatcher.startCountdown(); } void OverviewWidget::showEvent(QShowEvent *event) { Q_UNUSED(event); - m_compressor->start(); + m_imageIdleWatcher.startCountdown(); } void OverviewWidget::resizeEvent(QResizeEvent *event) @@ -131,7 +171,7 @@ QSize newSize = calculatePreviewSize(); m_pixmap = m_pixmap.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } - m_compressor->start(); + m_imageIdleWatcher.startCountdown(); } } @@ -189,6 +229,46 @@ } } +void OverviewWidget::generateThumbnail() +{ + if (isVisible()) { + QMutexLocker locker(&mutex); + if (m_canvas) { + QSize previewSize = calculatePreviewSize(); + if(previewSize.isValid()){ + KisImageSP image = m_canvas->image(); + + if (!strokeId.isNull()) { + image->cancelStroke(strokeId); + image->waitForDone(); + } + + OverviewThumbnailStrokeStrategy* stroke = new OverviewThumbnailStrokeStrategy(image); + connect(stroke, SIGNAL(thumbnailUpdated(QImage)), this, SLOT(updateThumbnail(QImage))); + + strokeId = image->startStroke(stroke); + KisPaintDeviceSP dev = image->projection(); + KisPaintDeviceSP thumbDev = new KisPaintDevice(dev->colorSpace()); + + //creating a special stroke that computes thumbnail image in small chunks that can be quickly interrupted + //if user starts painting + QList jobs = OverviewThumbnailStrokeStrategy::createJobsData(dev, image->bounds(), thumbDev, previewSize); + + Q_FOREACH (KisStrokeJobData *jd, jobs) { + image->addJob(strokeId, jd); + } + image->endStroke(strokeId); + } + } + } +} + +void OverviewWidget::updateThumbnail(QImage pixmap) +{ + m_pixmap = QPixmap::fromImage(pixmap); + update(); +} + void OverviewWidget::paintEvent(QPaintEvent* event) { @@ -217,3 +297,82 @@ } } +OverviewThumbnailStrokeStrategy::OverviewThumbnailStrokeStrategy(KisImageWSP image) + : KisSimpleStrokeStrategy("OverviewThumbnail"), m_image(image) +{ + enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); + enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); + //enableJob(KisSimpleStrokeStrategy::JOB_FINISH); + enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); + + setRequestsOtherStrokesToEnd(false); + setClearsRedoOnStart(false); + setCanForgetAboutMe(true); +} + +QList OverviewThumbnailStrokeStrategy::createJobsData(KisPaintDeviceSP dev, const QRect& imageRect, KisPaintDeviceSP thumbDev, const QSize& thumbnailSize) +{ + QSize thumbnailOversampledSize = oversample * thumbnailSize; + + if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { + thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); + } + + QVector tileRects = KritaUtils::splitRectIntoPatches(QRect(QPoint(0, 0), thumbnailOversampledSize), QSize(thumbnailTileDim, thumbnailTileDim)); + QList jobsData; + + Q_FOREACH (const QRect &tileRectangle, tileRects) { + jobsData << new OverviewThumbnailStrokeStrategy::Private::ProcessData(dev, thumbDev, thumbnailOversampledSize, tileRectangle); + } + jobsData << new OverviewThumbnailStrokeStrategy::Private::FinishProcessing(thumbDev); + + return jobsData; +} + +OverviewThumbnailStrokeStrategy::~OverviewThumbnailStrokeStrategy() +{ +} + + +void OverviewThumbnailStrokeStrategy::initStrokeCallback() +{ +} + +void OverviewThumbnailStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) +{ + Private::ProcessData *d_pd = dynamic_cast(data); + if (d_pd) { + //we aren't going to use oversample capability of createThumbnailDevice because it recomputes exact bounds for each small patch, which is + //slow. We'll handle scaling separately. + KisPaintDeviceSP thumbnailTile = d_pd->dev->createThumbnailDeviceOversampled(d_pd->thumbnailSize.width(), d_pd->thumbnailSize.height(), 1, m_image->bounds(), d_pd->tileRect); + { + QMutexLocker locker(&m_thumbnailMergeMutex); + KisPainter gc(d_pd->thumbDev); + gc.bitBlt(QPoint(d_pd->tileRect.x(), d_pd->tileRect.y()), thumbnailTile, d_pd->tileRect); + } + return; + } + + + Private::FinishProcessing *d_fp = dynamic_cast(data); + if (d_fp) { + QImage overviewImage; + + KoDummyUpdater updater; + KisTransformWorker worker(d_fp->thumbDev, 1 / oversample, 1 / oversample, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); + worker.run(); + + overviewImage = d_fp->thumbDev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile()); + emit thumbnailUpdated(overviewImage); + return; + } +} + +void OverviewThumbnailStrokeStrategy::finishStrokeCallback() +{ +} + +void OverviewThumbnailStrokeStrategy::cancelStrokeCallback() +{ +} diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -769,7 +769,7 @@ origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), - srcRect, + srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted();