Commit 2a1987a8 authored by Eugene Ingerman's avatar Eugene Ingerman

Add antialiasing to createThumbnailDevice

Summary:
1. Added oversampling/antialising to createThumbnailDevice. Interpolation is done using KisTransformWorker with bilinear interpolation. Added parameter that controls oversampling ratio.
2. Added benchmark for new thumbnail code. Results for 6Kx8K image turned into 640px thumbnail see below. About 2x hit in time for 2x oversampling. Quality with 2x oversampling is much better than no oversampling. 4x oversampling slightly better, but not dramatically. See below.
3. Changed oversampling for overview widget to 2x.
4. Fixed caching of oversampled thumbnails.
5. Fixed up functions calls to createThumbnail.

{F156583}
No Oversampling

{F156585}
2x Oversampling

{F156586}
4x Oversampling

PASS   : KisThumbnailBenchmark::benchmarkCreateThumbnail()
RESULT : KisThumbnailBenchmark::benchmarkCreateThumbnail():
     161 msecs per iteration (total: 161, iterations: 1)
PASS   : KisThumbnailBenchmark::benchmarkCreateThumbnailCached()
RESULT : KisThumbnailBenchmark::benchmarkCreateThumbnailCached():
     0.000059 msecs per iteration (total: 62, iterations: 1048576)
PASS   : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQ()
RESULT : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQ():
     3,962 msecs per iteration (total: 3,962, iterations: 1)
PASS   : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample2x()
RESULT : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample2x():
     269 msecs per iteration (total: 269, iterations: 1)
PASS   : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample3x()
RESULT : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample3x():
     489 msecs per iteration (total: 489, iterations: 1)
PASS   : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample4x()
RESULT : KisThumbnailBenchmark::benchmarkCreateThumbnailHiQcreateThumbOversample4x():
     701 msecs per iteration (total: 701, iterations: 1)

Test Plan: Run KisThumbnailBenchmark.

Reviewers: rempt, dkazakov, woltherav

Reviewed By: rempt, woltherav

Subscribers: woltherav

Differential Revision: https://phabricator.kde.org/D1979
parent 2b0c5372
......@@ -38,6 +38,7 @@ set(kis_mask_generator_benchmark_SRCS kis_mask_generator_benchmark.cpp)
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(KisMaskGeneratorBenchmark TESTNAME krita-benchmarks-KisMaskG
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 @@ if(HAVE_VC)
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)
/*
* 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 <QTest>
#include <QImage>
#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 <cmath>
#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)
/*
* 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 <QtTest>
#include "kis_paint_device.h"
class KoColor;
class KoColorSpace;
class KisThumbnailBenchmark : public QObject
{
Q_OBJECT
private:
const KoColorSpace * m_colorSpace;
KisPaintDeviceSP m_dev;
QVector<QImage> 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
......@@ -48,8 +48,8 @@ struct KisIdleWatcher::Private
};
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()));
......
......@@ -32,7 +32,7 @@ class KRITAIMAGE_EXPORT KisIdleWatcher : public QObject
{
Q_OBJECT
public:
KisIdleWatcher(int delay);
KisIdleWatcher(int delay, QObject* parent = 0);
~KisIdleWatcher();
bool isIdle() const;
......@@ -40,6 +40,8 @@ public:
void setTrackedImages(const QVector<KisImageSP> &images);
void setTrackedImage(KisImageSP image);
//Force to image modified state and start countdown to event
void startCountdown(void) { slotImageModified(); }
Q_SIGNALS:
void startedIdleMode();
......
......@@ -788,7 +788,7 @@ QImage KisLayer::createThumbnail(qint32 w, qint32 h)
KisPaintDeviceSP originalDevice = original();
return originalDevice ?
originalDevice->createThumbnail(w, h,
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) : QImage();
}
......
......@@ -343,7 +343,7 @@ QImage KisMask::createThumbnail(qint32 w, qint32 h)
selection() ? selection()->projection() : 0;
return originalDevice ?
originalDevice->createThumbnail(w, h,
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) : QImage();
}
......
......@@ -37,7 +37,7 @@
#include <KoColorModelStandardIds.h>
#include <KoIntegerMaths.h>
#include <KoMixColorsOp.h>
#include <KoUpdater.h>
#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 @@ public:
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 @@ public:
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 @@ private:
typedef QSharedPointer<Data> DataSP;
typedef QHash<int, DataSP> 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 @@ private:
{
}
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 @@ public:
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 @@ public:
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,7 +350,8 @@ public:
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);
......@@ -320,17 +359,20 @@ public:
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 @@ private:
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 @@ private:
return data;
}
inline Data* currentNonLodData() const {
inline Data* currentNonLodData() const
{
Data *data = m_data.data();
if (contentChannel) {
......@@ -388,7 +434,8 @@ private:
return data;
}
inline void ensureLodDataPresent() const {
inline void ensureLodDataPresent() const
{
if (!m_lodData) {
Data *srcData = currentNonLodData();
......@@ -399,7 +446,8 @@ private:
}
}
inline Data* currentData() const {
inline Data* currentData() const
{
Data *data = m_data.data();
if (defaultBounds->currentLevelOfDetail()) {
......@@ -423,10 +471,11 @@ private:
bool fastBitBltPossibleImpl(Data *srcData)
{
return x() == srcData->x() && y() == srcData->y() &&
*colorSpace() == *srcData->colorSpace();
*colorSpace() == *srcData->colorSpace();
}
QList<Data*> allDataObjects() const {
QList<Data*> allDataObjects() const
{
QList<Data*> dataObjects;
if (m_frames.isEmpty()) {
......@@ -503,15 +552,18 @@ struct KisPaintDevice::Private::StrategyPolicy {
{
}
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 @@ struct KisPaintDevice::Private::StrategyPolicy {
int m_offsetY;
};
struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct
{
struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct {
LodDataStructImpl(Data *_lodData) : lodData(_lodData) {}
QScopedPointer<Data> lodData;
};
......@@ -703,7 +754,8 @@ void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPai
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 @@ void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDe
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 @@ void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice
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 @@ KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * d
emitNotifications();
}
void undo() {
void undo()
{
KUndo2Command::undo();
emitNotifications();
}
......@@ -913,7 +971,7 @@ void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect)
{
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,16 +1111,17 @@ QRect KisPaintDevice::exactBoundsAmortized() const
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;
}
......@@ -1070,15 +1129,15 @@ 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 @@ QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startR
{
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 KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const
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 @@ void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfil
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 @@ QImage KisPaintDevice::convertToQImage(const KoColorProfile * dstProfile, qint3
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();