diff --git a/krita/pics/tool_transform/dark_transform_icons_mirror_x.svg b/krita/pics/tool_transform/dark_transform_icons_mirror_x.svg new file mode 100644 index 0000000000..5c856aef1b --- /dev/null +++ b/krita/pics/tool_transform/dark_transform_icons_mirror_x.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/tool_transform/dark_transform_icons_mirror_y.svg b/krita/pics/tool_transform/dark_transform_icons_mirror_y.svg new file mode 100644 index 0000000000..fc91d531f3 --- /dev/null +++ b/krita/pics/tool_transform/dark_transform_icons_mirror_y.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/tool_transform/dark_transform_icons_rotate_ccw.svg b/krita/pics/tool_transform/dark_transform_icons_rotate_ccw.svg new file mode 100644 index 0000000000..4404fe36bc --- /dev/null +++ b/krita/pics/tool_transform/dark_transform_icons_rotate_ccw.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/krita/pics/tool_transform/dark_transform_icons_rotate_cw.svg b/krita/pics/tool_transform/dark_transform_icons_rotate_cw.svg new file mode 100644 index 0000000000..951b763f2b --- /dev/null +++ b/krita/pics/tool_transform/dark_transform_icons_rotate_cw.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/krita/pics/tool_transform/light_transform_icons_mirror_x.svg b/krita/pics/tool_transform/light_transform_icons_mirror_x.svg new file mode 100644 index 0000000000..e82757f6dc --- /dev/null +++ b/krita/pics/tool_transform/light_transform_icons_mirror_x.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/tool_transform/light_transform_icons_mirror_y.svg b/krita/pics/tool_transform/light_transform_icons_mirror_y.svg new file mode 100644 index 0000000000..ff64b6ce80 --- /dev/null +++ b/krita/pics/tool_transform/light_transform_icons_mirror_y.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/krita/pics/tool_transform/light_transform_icons_rotate_ccw.svg b/krita/pics/tool_transform/light_transform_icons_rotate_ccw.svg new file mode 100644 index 0000000000..e024d99f07 --- /dev/null +++ b/krita/pics/tool_transform/light_transform_icons_rotate_ccw.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/krita/pics/tool_transform/light_transform_icons_rotate_cw.svg b/krita/pics/tool_transform/light_transform_icons_rotate_cw.svg new file mode 100644 index 0000000000..c28bc63983 --- /dev/null +++ b/krita/pics/tool_transform/light_transform_icons_rotate_cw.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/krita/pics/tool_transform/tool-transform-icons.qrc b/krita/pics/tool_transform/tool-transform-icons.qrc index 8e320d7164..ae594e1ee3 100644 --- a/krita/pics/tool_transform/tool-transform-icons.qrc +++ b/krita/pics/tool_transform/tool-transform-icons.qrc @@ -1,33 +1,40 @@ dark_transform_icons_cage.png dark_transform_icons_liquify_erase.png dark_transform_icons_liquify_main.png dark_transform_icons_liquify_move.png dark_transform_icons_liquify_offset.png dark_transform_icons_liquify_resize.png dark_transform_icons_liquify_rotate.png dark_transform_icons_liquify_rotateCCW.png dark_transform_icons_main.png dark_transform_icons_perspective.png dark_transform_icons_warp.png dark_transform_icons_penPressure.png dark_krita_tool_transform_recursive.png + dark_transform_icons_mirror_x.svg + dark_transform_icons_mirror_y.svg + dark_transform_icons_rotate_cw.svg + dark_transform_icons_rotate_ccw.svg light_transform_icons_cage.png light_transform_icons_liquify_erase.png light_transform_icons_liquify_main.png light_transform_icons_liquify_move.png light_transform_icons_liquify_offset.png light_transform_icons_liquify_resize.png light_transform_icons_liquify_rotate.png light_transform_icons_liquify_rotateCCW.png light_transform_icons_main.png light_transform_icons_perspective.png light_transform_icons_warp.png light_transform_icons_penPressure.png light_krita_tool_transform_recursive.png - + light_transform_icons_mirror_x.svg + light_transform_icons_mirror_y.svg + light_transform_icons_rotate_cw.svg + light_transform_icons_rotate_ccw.svg diff --git a/libs/image/tiles3/kis_tile_data.cc b/libs/image/tiles3/kis_tile_data.cc index 47c8f23e31..fe8f5a09e3 100644 --- a/libs/image/tiles3/kis_tile_data.cc +++ b/libs/image/tiles3/kis_tile_data.cc @@ -1,179 +1,242 @@ /* * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tile_data.h" #include "kis_tile_data_store.h" #include #include +#include "kis_tile_data_store_iterators.h" // BPP == bytes per pixel #define TILE_SIZE_4BPP (4 * __TILE_DATA_WIDTH * __TILE_DATA_HEIGHT) #define TILE_SIZE_8BPP (8 * __TILE_DATA_WIDTH * __TILE_DATA_HEIGHT) typedef boost::singleton_pool BoostPool4BPP; typedef boost::singleton_pool BoostPool8BPP; const qint32 KisTileData::WIDTH = __TILE_DATA_WIDTH; const qint32 KisTileData::HEIGHT = __TILE_DATA_HEIGHT; KisTileData::KisTileData(qint32 pixelSize, const quint8 *defPixel, KisTileDataStore *store) : m_state(NORMAL), m_mementoFlag(0), m_age(0), m_usersCount(0), m_refCount(0), m_pixelSize(pixelSize), m_store(store) { m_store->checkFreeMemory(); m_data = allocateData(m_pixelSize); fillWithPixel(defPixel); } /** * Duplicating tiledata * + new object loaded in memory * + it's unlocked and has refCount==0 * * NOTE: the memory allocated by the pooler for clones is not counted * by the store in memoryHardLimit. The pooler has it's own slice of * memory and keeps track of the its size itself. So we should be able * to disable the memory check with checkFreeMemory, otherwise, there * is a deadlock. */ KisTileData::KisTileData(const KisTileData& rhs, bool checkFreeMemory) : m_state(NORMAL), m_mementoFlag(0), m_age(0), m_usersCount(0), m_refCount(0), m_pixelSize(rhs.m_pixelSize), m_store(rhs.m_store) { if(checkFreeMemory) { m_store->checkFreeMemory(); } m_data = allocateData(m_pixelSize); memcpy(m_data, rhs.data(), m_pixelSize * WIDTH * HEIGHT); } KisTileData::~KisTileData() { releaseMemory(); } void KisTileData::fillWithPixel(const quint8 *defPixel) { quint8 *it = m_data; for (int i = 0; i < WIDTH*HEIGHT; i++, it += m_pixelSize) { memcpy(it, defPixel, m_pixelSize); } } void KisTileData::releaseMemory() { if (m_data) { freeData(m_data, m_pixelSize); m_data = 0; } KisTileData *clone = 0; while(m_clonesStack.pop(clone)) { delete clone; } Q_ASSERT(m_clonesStack.isEmpty()); } void KisTileData::allocateMemory() { Q_ASSERT(!m_data); m_data = allocateData(m_pixelSize); } quint8* KisTileData::allocateData(const qint32 pixelSize) { quint8 *ptr = 0; switch(pixelSize) { case 4: ptr = (quint8*)BoostPool4BPP::malloc(); break; case 8: ptr = (quint8*)BoostPool8BPP::malloc(); break; default: ptr = (quint8*) malloc(pixelSize * WIDTH * HEIGHT); } return ptr; } void KisTileData::freeData(quint8* ptr, const qint32 pixelSize) { switch(pixelSize) { case 4: BoostPool4BPP::free(ptr); break; case 8: BoostPool8BPP::free(ptr); break; default: free(ptr); } } //#define DEBUG_POOL_RELEASE #ifdef DEBUG_POOL_RELEASE #include #endif /* DEBUG_POOL_RELEASE */ void KisTileData::releaseInternalPools() { - if (!KisTileDataStore::instance()->numTiles() && - !KisTileDataStore::instance()->numTilesInMemory()) { + const int maxMigratedTiles = 100; - BoostPool4BPP::purge_memory(); - BoostPool8BPP::purge_memory(); + if (KisTileDataStore::instance()->numTilesInMemory() < maxMigratedTiles) { + + QVector dataObjects; + QVector memoryChunks; + bool failedToLock = false; + + KisTileDataStoreIterator *iter = KisTileDataStore::instance()->beginIteration(); + + while(iter->hasNext()) { + KisTileData *item = iter->next(); + + // first release all the clones + KisTileData *clone = 0; + while(item->m_clonesStack.pop(clone)) { + delete clone; + } + + // check if the tile data has actually been pooled + if (item->m_pixelSize != 4 && + item->m_pixelSize != 8) { + + continue; + } + + // check if the tile has been swapped out + if (item->m_data) { + const bool locked = item->m_swapLock.tryLockForWrite(); + if (!locked) { + failedToLock = true; + break; + } + + const int chunkSize = item->m_pixelSize * WIDTH * HEIGHT; + dataObjects << item; + memoryChunks << QByteArray((const char*)item->m_data, chunkSize); + } + + } + + if (!failedToLock) { + // purge the pools memory + BoostPool4BPP::purge_memory(); + BoostPool8BPP::purge_memory(); + + auto it = dataObjects.begin(); + auto chunkIt = memoryChunks.constBegin(); + + for (; it != dataObjects.end(); ++it, ++chunkIt) { + KisTileData *item = *it; + const int chunkSize = item->m_pixelSize * WIDTH * HEIGHT; + + item->m_data = allocateData(item->m_pixelSize); + memcpy(item->m_data, chunkIt->data(), chunkSize); + + item->m_swapLock.unlock(); + } + } else { + Q_FOREACH (KisTileData *item, dataObjects) { + item->m_swapLock.unlock(); + } + + warnKrita << "WARNING: Failed to lock the tiles while trying to release the pooled memory"; + } + + KisTileDataStore::instance()->endIteration(iter); #ifdef DEBUG_POOL_RELEASE dbgKrita << "After purging unused memory:"; char command[256]; sprintf(command, "cat /proc/%d/status | grep -i vm", (int)getpid()); printf("--- %s ---\n", command); (void)system(command); #endif /* DEBUG_POOL_RELEASE */ + } else { + dbgKrita << "DEBUG: releasing of the pooled memory has been cancelled:" + << "there are still" + << KisTileDataStore::instance()->numTilesInMemory() + << "tiles in memory"; } -// else { -// warnKrita << "WARNING: trying to purge pool memory while there are used tiles present!"; -// warnKrita << " The memory will *NOT* be returned to the system, though it will"; -// warnKrita << " be reused by Krita internally. Please report to developers!"; -// } } diff --git a/libs/image/tiles3/kis_tile_hash_table_p.h b/libs/image/tiles3/kis_tile_hash_table_p.h index 063673b73b..d37e5f33f1 100644 --- a/libs/image/tiles3/kis_tile_hash_table_p.h +++ b/libs/image/tiles3/kis_tile_hash_table_p.h @@ -1,398 +1,396 @@ /* * Copyright (c) 2004 C. Boemann * (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_debug.h" #include "kis_global.h" //#define SHARED_TILES_SANITY_CHECK template KisTileHashTableTraits::KisTileHashTableTraits(KisMementoManager *mm) : m_lock(QReadWriteLock::NonRecursive) { m_hashTable = new TileTypeSP [TABLE_SIZE]; Q_CHECK_PTR(m_hashTable); m_numTiles = 0; m_defaultTileData = 0; m_mementoManager = mm; } template KisTileHashTableTraits::KisTileHashTableTraits(const KisTileHashTableTraits &ht, KisMementoManager *mm) : m_lock(QReadWriteLock::NonRecursive) { QReadLocker locker(&ht.m_lock); m_mementoManager = mm; m_defaultTileData = 0; setDefaultTileDataImp(ht.m_defaultTileData); m_hashTable = new TileTypeSP [TABLE_SIZE]; Q_CHECK_PTR(m_hashTable); TileTypeSP foreignTile; TileType* nativeTile; TileType* nativeTileHead; for (qint32 i = 0; i < TABLE_SIZE; i++) { nativeTileHead = 0; foreignTile = ht.m_hashTable[i]; while (foreignTile) { nativeTile = new TileType(*foreignTile, m_mementoManager); nativeTile->setNext(nativeTileHead); nativeTileHead = nativeTile; foreignTile = foreignTile->next(); } m_hashTable[i] = nativeTileHead; } m_numTiles = ht.m_numTiles; } template KisTileHashTableTraits::~KisTileHashTableTraits() { clear(); delete[] m_hashTable; setDefaultTileDataImp(0); } template quint32 KisTileHashTableTraits::calculateHash(qint32 col, qint32 row) { return ((row << 5) + (col & 0x1F)) & 0x3FF; } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getTile(qint32 col, qint32 row) { qint32 idx = calculateHash(col, row); TileTypeSP tile = m_hashTable[idx]; for (; tile; tile = tile->next()) { if (tile->col() == col && tile->row() == row) { return tile; } } return 0; } template void KisTileHashTableTraits::linkTile(TileTypeSP tile) { qint32 idx = calculateHash(tile->col(), tile->row()); TileTypeSP firstTile = m_hashTable[idx]; #ifdef SHARED_TILES_SANITY_CHECK Q_ASSERT_X(!tile->next(), "KisTileHashTableTraits::linkTile", "A tile can't be shared by several hash tables, sorry."); #endif tile->setNext(firstTile); m_hashTable[idx] = tile; m_numTiles++; } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::unlinkTile(qint32 col, qint32 row) { qint32 idx = calculateHash(col, row); TileTypeSP tile = m_hashTable[idx]; TileTypeSP prevTile = 0; for (; tile; tile = tile->next()) { if (tile->col() == col && tile->row() == row) { if (prevTile) prevTile->setNext(tile->next()); else /* optimize here*/ m_hashTable[idx] = tile->next(); /** * The shared pointer may still be accessed by someone, so * we need to disconnects the tile from memento manager * explicitly */ tile->setNext(0); tile->notifyDead(); tile = 0; m_numTiles--; return tile; } prevTile = tile; } return 0; } template inline void KisTileHashTableTraits::setDefaultTileDataImp(KisTileData *defaultTileData) { if (m_defaultTileData) { - m_defaultTileData->unblockSwapping(); m_defaultTileData->release(); m_defaultTileData = 0; } if (defaultTileData) { defaultTileData->acquire(); - defaultTileData->blockSwapping(); m_defaultTileData = defaultTileData; } } template inline KisTileData* KisTileHashTableTraits::defaultTileDataImp() const { return m_defaultTileData; } template bool KisTileHashTableTraits::tileExists(qint32 col, qint32 row) { QReadLocker locker(&m_lock); return getTile(col, row); } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getExistedTile(qint32 col, qint32 row) { QReadLocker locker(&m_lock); return getTile(col, row); } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getTileLazy(qint32 col, qint32 row, bool& newTile) { /** * FIXME: Read access is better */ QWriteLocker locker(&m_lock); newTile = false; TileTypeSP tile = getTile(col, row); if (!tile) { tile = new TileType(col, row, m_defaultTileData, m_mementoManager); linkTile(tile); newTile = true; } return tile; } template typename KisTileHashTableTraits::TileTypeSP KisTileHashTableTraits::getReadOnlyTileLazy(qint32 col, qint32 row) { QReadLocker locker(&m_lock); TileTypeSP tile = getTile(col, row); if (!tile) tile = new TileType(col, row, m_defaultTileData, 0); return tile; } template void KisTileHashTableTraits::addTile(TileTypeSP tile) { QWriteLocker locker(&m_lock); linkTile(tile); } template void KisTileHashTableTraits::deleteTile(qint32 col, qint32 row) { QWriteLocker locker(&m_lock); TileTypeSP tile = unlinkTile(col, row); /* Done by KisSharedPtr */ //if(tile) // delete tile; } template void KisTileHashTableTraits::deleteTile(TileTypeSP tile) { deleteTile(tile->col(), tile->row()); } template void KisTileHashTableTraits::clear() { QWriteLocker locker(&m_lock); TileTypeSP tile = 0; qint32 i; for (i = 0; i < TABLE_SIZE; i++) { tile = m_hashTable[i]; while (tile) { TileTypeSP tmp = tile; tile = tile->next(); /** * About disconnection of tiles see a comment in unlinkTile() */ tmp->setNext(0); tmp->notifyDead(); tmp = 0; m_numTiles--; } m_hashTable[i] = 0; } Q_ASSERT(!m_numTiles); } template void KisTileHashTableTraits::setDefaultTileData(KisTileData *defaultTileData) { QWriteLocker locker(&m_lock); setDefaultTileDataImp(defaultTileData); } template KisTileData* KisTileHashTableTraits::defaultTileData() const { QWriteLocker locker(&m_lock); return defaultTileDataImp(); } /*************** Debugging stuff ***************/ template void KisTileHashTableTraits::debugPrintInfo() { dbgTiles << "==========================\n" << "TileHashTable:" << "\n def. data:\t\t" << m_defaultTileData << "\n numTiles:\t\t" << m_numTiles; debugListLengthDistibution(); dbgTiles << "==========================\n"; } template qint32 KisTileHashTableTraits::debugChainLen(qint32 idx) { qint32 len = 0; for (TileTypeSP it = m_hashTable[idx]; it; it = it->next(), len++) ; return len; } template void KisTileHashTableTraits::debugMaxListLength(qint32 &min, qint32 &max) { TileTypeSP tile; qint32 maxLen = 0; qint32 minLen = m_numTiles; qint32 tmp = 0; for (qint32 i = 0; i < TABLE_SIZE; i++) { tmp = debugChainLen(i); if (tmp > maxLen) maxLen = tmp; if (tmp < minLen) minLen = tmp; } min = minLen; max = maxLen; } template void KisTileHashTableTraits::debugListLengthDistibution() { qint32 min, max; qint32 arraySize; qint32 tmp; debugMaxListLength(min, max); arraySize = max - min + 1; qint32 *array = new qint32[arraySize]; memset(array, 0, sizeof(qint32)*arraySize); for (qint32 i = 0; i < TABLE_SIZE; i++) { tmp = debugChainLen(i); array[tmp-min]++; } dbgTiles << QString(" minChain:\t\t%d\n" " maxChain:\t\t%d").arg(min, max); dbgTiles << " Chain size distribution:"; for (qint32 i = 0; i < arraySize; i++) dbgTiles << QString(" %1:\t%2\n").arg(i + min, array[i]); delete[] array; } template void KisTileHashTableTraits::sanityChecksumCheck() { /** * We assume that the lock should have already been taken * by the code that was going to change the table */ Q_ASSERT(!m_lock.tryLockForWrite()); TileTypeSP tile = 0; qint32 exactNumTiles = 0; for (qint32 i = 0; i < TABLE_SIZE; i++) { tile = m_hashTable[i]; while (tile) { exactNumTiles++; tile = tile->next(); } } if (exactNumTiles != m_numTiles) { dbgKrita << "Sanity check failed!"; dbgKrita << ppVar(exactNumTiles); dbgKrita << ppVar(m_numTiles); dbgKrita << "Wrong tiles checksum!"; Q_ASSERT(0); // not fatalKrita for a backtrace support } } diff --git a/libs/image/tiles3/kis_tiled_data_manager.cc b/libs/image/tiles3/kis_tiled_data_manager.cc index cfb110d04c..5cf36da9f2 100644 --- a/libs/image/tiles3/kis_tiled_data_manager.cc +++ b/libs/image/tiles3/kis_tiled_data_manager.cc @@ -1,808 +1,812 @@ /* * Copyright (c) 2004 C. Boemann * (c) 2009 Dmitry Kazakov * (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "kis_tile.h" #include "kis_tiled_data_manager.h" #include "kis_tile_data_wrapper.h" #include "kis_tiled_data_manager_p.h" #include "kis_memento_manager.h" #include "swap/kis_legacy_tile_compressor.h" #include "swap/kis_tile_compressor_factory.h" #include "kis_paint_device_writer.h" #include "kis_global.h" /* The data area is divided into tiles each say 64x64 pixels (defined at compiletime) * The tiles are laid out in a matrix that can have negative indexes. * The matrix grows automatically if needed (a call for writeacces to a tile * outside the current extent) * Even though the matrix has grown it may still not contain tiles at specific positions. * They are created on demand */ KisTiledDataManager::KisTiledDataManager(quint32 pixelSize, const quint8 *defaultPixel) { /* See comment in destructor for details */ m_mementoManager = new KisMementoManager(); m_hashTable = new KisTileHashTable(m_mementoManager); m_pixelSize = pixelSize; m_defaultPixel = new quint8[m_pixelSize]; setDefaultPixel(defaultPixel); m_extentMinX = qint32_MAX; m_extentMinY = qint32_MAX; m_extentMaxX = qint32_MIN; m_extentMaxY = qint32_MIN; } KisTiledDataManager::KisTiledDataManager(const KisTiledDataManager &dm) : KisShared() { /* See comment in destructor for details */ /* We do not clone the history of the device, there is no usecase for it */ m_mementoManager = new KisMementoManager(); m_mementoManager->setDefaultTileData(dm.m_hashTable->defaultTileData()); m_hashTable = new KisTileHashTable(*dm.m_hashTable, m_mementoManager); m_pixelSize = dm.m_pixelSize; m_defaultPixel = new quint8[m_pixelSize]; /** * We won't call setDefaultTileData here, as defaultTileDatas * has already been made shared in m_hashTable(dm->m_hashTable) */ memcpy(m_defaultPixel, dm.m_defaultPixel, m_pixelSize); m_extentMinX = dm.m_extentMinX; m_extentMinY = dm.m_extentMinY; m_extentMaxX = dm.m_extentMaxX; m_extentMaxY = dm.m_extentMaxY; } KisTiledDataManager::~KisTiledDataManager() { /** * Here is an explanation why we use hash table and The Memento Manager * dynamically allocated We need to destroy them in that very order. The * reason is that when hash table destroying all her child tiles they all * cry about it to The Memento Manager using a pointer. So The Memento * Manager sould be alive during that destruction. We could use shared * pointers instead, but they create too much overhead. */ delete m_hashTable; delete m_mementoManager; delete[] m_defaultPixel; } void KisTiledDataManager::setDefaultPixel(const quint8 *defaultPixel) { QWriteLocker locker(&m_lock); setDefaultPixelImpl(defaultPixel); } void KisTiledDataManager::setDefaultPixelImpl(const quint8 *defaultPixel) { KisTileData *td = KisTileDataStore::instance()->createDefaultTileData(pixelSize(), defaultPixel); m_hashTable->setDefaultTileData(td); m_mementoManager->setDefaultTileData(td); memcpy(m_defaultPixel, defaultPixel, pixelSize()); } bool KisTiledDataManager::write(KisPaintDeviceWriter &store) { QReadLocker locker(&m_lock); bool retval = true; if(CURRENT_VERSION == LEGACY_VERSION) { char str[80]; sprintf(str, "%d\n", m_hashTable->numTiles()); retval = store.write(str, strlen(str)); } else { retval = writeTilesHeader(store, m_hashTable->numTiles()); } KisTileHashTableIterator iter(m_hashTable); KisTileSP tile; KisAbstractTileCompressorSP compressor = KisTileCompressorFactory::create(CURRENT_VERSION); while ((tile = iter.tile())) { retval = compressor->writeTile(tile, store); if (!retval) { warnFile << "Failed to write tile"; break; } ++iter; } return retval; } bool KisTiledDataManager::read(QIODevice *stream) { if (!stream) return false; clear(); QWriteLocker locker(&m_lock); KisMementoSP nothing = m_mementoManager->getMemento(); if (!stream) { m_mementoManager->commit(); return false; } const qint32 maxLineLength = 79; // Legacy magic QByteArray line = stream->readLine(maxLineLength); line = line.trimmed(); quint32 numTiles; qint32 tilesVersion = LEGACY_VERSION; if (line[0] == 'V') { QList lineItems = line.split(' '); QString keyword = lineItems.takeFirst(); Q_ASSERT(keyword == "VERSION"); tilesVersion = lineItems.takeFirst().toInt(); if(!processTilesHeader(stream, numTiles)) return false; } else { numTiles = line.toUInt(); } KisAbstractTileCompressorSP compressor = KisTileCompressorFactory::create(tilesVersion); bool readSuccess = true; for (quint32 i = 0; i < numTiles; i++) { if (!compressor->readTile(stream, this)) { readSuccess = false; } } m_mementoManager->commit(); return readSuccess; } bool KisTiledDataManager::writeTilesHeader(KisPaintDeviceWriter &store, quint32 numTiles) { QString buffer; buffer = QString("VERSION %1\n" "TILEWIDTH %2\n" "TILEHEIGHT %3\n" "PIXELSIZE %4\n" "DATA %5\n") .arg(CURRENT_VERSION) .arg(KisTileData::WIDTH) .arg(KisTileData::HEIGHT) .arg(pixelSize()) .arg(numTiles); return store.write(buffer.toLatin1()); } #define takeOneLine(stream, maxLine, keyword, value) \ do { \ QByteArray line = stream->readLine(maxLine); \ line = line.trimmed(); \ QList lineItems = line.split(' '); \ keyword = lineItems.takeFirst(); \ value = lineItems.takeFirst().toInt(); \ } while(0) \ bool KisTiledDataManager::processTilesHeader(QIODevice *stream, quint32 &numTiles) { /** * We assume that there is only one version of this header * possible. In case we invent something new, it'll be quite easy * to modify the behavior */ const qint32 maxLineLength = 25; const qint32 totalNumTests = 4; bool foundDataMark = false; qint32 testsPassed = 0; QString keyword; qint32 value; while(!foundDataMark && stream->canReadLine()) { takeOneLine(stream, maxLineLength, keyword, value); if (keyword == "TILEWIDTH") { if(value != KisTileData::WIDTH) goto wrongString; } else if (keyword == "TILEHEIGHT") { if(value != KisTileData::HEIGHT) goto wrongString; } else if (keyword == "PIXELSIZE") { if((quint32)value != pixelSize()) goto wrongString; } else if (keyword == "DATA") { numTiles = value; foundDataMark = true; } else { goto wrongString; } testsPassed++; } if(testsPassed != totalNumTests) { warnTiles << "Not enough fields of tiles header present" << testsPassed << "of" << totalNumTests; } return testsPassed == totalNumTests; wrongString: warnTiles << "Wrong string in tiles header:" << keyword << value; return false; } void KisTiledDataManager::purge(const QRect& area) { QWriteLocker locker(&m_lock); QList tilesToDelete; { const qint32 tileDataSize = KisTileData::HEIGHT * KisTileData::WIDTH * pixelSize(); - const quint8 *defaultData = m_hashTable->defaultTileData()->data(); + KisTileData *tileData = m_hashTable->defaultTileData(); + tileData->blockSwapping(); + const quint8 *defaultData = tileData->data(); KisTileHashTableIterator iter(m_hashTable); KisTileSP tile; while ((tile = iter.tile())) { if (tile->extent().intersects(area)) { tile->lockForRead(); if(memcmp(defaultData, tile->data(), tileDataSize) == 0) { tilesToDelete.push_back(tile); } tile->unlock(); } ++iter; } + + tileData->unblockSwapping(); } Q_FOREACH (KisTileSP tile, tilesToDelete) { m_hashTable->deleteTile(tile); } recalculateExtent(); } quint8* KisTiledDataManager::duplicatePixel(qint32 num, const quint8 *pixel) { const qint32 pixelSize = this->pixelSize(); /* FIXME: Make a fun filling here */ quint8 *dstBuf = new quint8[num * pixelSize]; quint8 *dstIt = dstBuf; for (qint32 i = 0; i < num; i++) { memcpy(dstIt, pixel, pixelSize); dstIt += pixelSize; } return dstBuf; } void KisTiledDataManager::clear(QRect clearRect, const quint8 *clearPixel) { QWriteLocker locker(&m_lock); if (clearPixel == 0) clearPixel = m_defaultPixel; if (clearRect.isEmpty()) return; const qint32 pixelSize = this->pixelSize(); bool pixelBytesAreDefault = !memcmp(clearPixel, m_defaultPixel, pixelSize); bool pixelBytesAreTheSame = true; for (qint32 i = 0; i < pixelSize; ++i) { if (clearPixel[i] != clearPixel[0]) { pixelBytesAreTheSame = false; break; } } if (pixelBytesAreDefault) { clearRect &= extentImpl(); } qint32 firstColumn = xToCol(clearRect.left()); qint32 lastColumn = xToCol(clearRect.right()); qint32 firstRow = yToRow(clearRect.top()); qint32 lastRow = yToRow(clearRect.bottom()); const quint32 rowStride = KisTileData::WIDTH * pixelSize; // Generate one row quint8 *clearPixelData = 0; quint32 maxRunLength = qMin(clearRect.width(), KisTileData::WIDTH); clearPixelData = duplicatePixel(maxRunLength, clearPixel); KisTileData *td = 0; if (!pixelBytesAreDefault && clearRect.width() >= KisTileData::WIDTH && clearRect.height() >= KisTileData::HEIGHT) { td = KisTileDataStore::instance()->createDefaultTileData(pixelSize, clearPixel); td->acquire(); } bool needsRecalculateExtent = false; for (qint32 row = firstRow; row <= lastRow; ++row) { for (qint32 column = firstColumn; column <= lastColumn; ++column) { QRect tileRect(column*KisTileData::WIDTH, row*KisTileData::HEIGHT, KisTileData::WIDTH, KisTileData::HEIGHT); QRect clearTileRect = clearRect & tileRect; if (clearTileRect == tileRect) { // Clear whole tile m_hashTable->deleteTile(column, row); needsRecalculateExtent = true; if (!pixelBytesAreDefault) { KisTileSP clearedTile = new KisTile(column, row, td, m_mementoManager); m_hashTable->addTile(clearedTile); updateExtent(column, row); } } else { const qint32 lineSize = clearTileRect.width() * pixelSize; qint32 rowsRemaining = clearTileRect.height(); KisTileDataWrapper tw(this, clearTileRect.left(), clearTileRect.top(), KisTileDataWrapper::WRITE); quint8* tileIt = tw.data(); if (pixelBytesAreTheSame) { while (rowsRemaining > 0) { memset(tileIt, *clearPixelData, lineSize); tileIt += rowStride; rowsRemaining--; } } else { while (rowsRemaining > 0) { memcpy(tileIt, clearPixelData, lineSize); tileIt += rowStride; rowsRemaining--; } } } } } if (needsRecalculateExtent) { recalculateExtent(); } if (td) td->release(); delete[] clearPixelData; } void KisTiledDataManager::clear(QRect clearRect, quint8 clearValue) { quint8 *buf = new quint8[pixelSize()]; memset(buf, clearValue, pixelSize()); clear(clearRect, buf); delete[] buf; } void KisTiledDataManager::clear(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *clearPixel) { clear(QRect(x, y, w, h), clearPixel); } void KisTiledDataManager::clear(qint32 x, qint32 y, qint32 w, qint32 h, quint8 clearValue) { clear(QRect(x, y, w, h), clearValue); } void KisTiledDataManager::clear() { QWriteLocker locker(&m_lock); m_hashTable->clear(); m_extentMinX = qint32_MAX; m_extentMinY = qint32_MAX; m_extentMaxX = qint32_MIN; m_extentMaxY = qint32_MIN; } template void KisTiledDataManager::bitBltImpl(KisTiledDataManager *srcDM, const QRect &rect) { QWriteLocker locker(&m_lock); if (rect.isEmpty()) return; const qint32 pixelSize = this->pixelSize(); const quint32 rowStride = KisTileData::WIDTH * pixelSize; qint32 firstColumn = xToCol(rect.left()); qint32 lastColumn = xToCol(rect.right()); qint32 firstRow = yToRow(rect.top()); qint32 lastRow = yToRow(rect.bottom()); for (qint32 row = firstRow; row <= lastRow; ++row) { for (qint32 column = firstColumn; column <= lastColumn; ++column) { // this is the only variation in the template KisTileSP srcTile = useOldSrcData ? srcDM->getOldTile(column, row) : srcDM->getTile(column, row, false); QRect tileRect(column*KisTileData::WIDTH, row*KisTileData::HEIGHT, KisTileData::WIDTH, KisTileData::HEIGHT); QRect cloneTileRect = rect & tileRect; if (cloneTileRect == tileRect) { // Clone whole tile m_hashTable->deleteTile(column, row); srcTile->lockForRead(); KisTileData *td = srcTile->tileData(); KisTileSP clonedTile = new KisTile(column, row, td, m_mementoManager); srcTile->unlock(); m_hashTable->addTile(clonedTile); updateExtent(column, row); } else { const qint32 lineSize = cloneTileRect.width() * pixelSize; qint32 rowsRemaining = cloneTileRect.height(); KisTileDataWrapper tw(this, cloneTileRect.left(), cloneTileRect.top(), KisTileDataWrapper::WRITE); srcTile->lockForRead(); // We suppose that the shift in both tiles is the same const quint8* srcTileIt = srcTile->data() + tw.offset(); quint8* dstTileIt = tw.data(); while (rowsRemaining > 0) { memcpy(dstTileIt, srcTileIt, lineSize); srcTileIt += rowStride; dstTileIt += rowStride; rowsRemaining--; } srcTile->unlock(); } } } } template void KisTiledDataManager::bitBltRoughImpl(KisTiledDataManager *srcDM, const QRect &rect) { QWriteLocker locker(&m_lock); if (rect.isEmpty()) return; qint32 firstColumn = xToCol(rect.left()); qint32 lastColumn = xToCol(rect.right()); qint32 firstRow = yToRow(rect.top()); qint32 lastRow = yToRow(rect.bottom()); for (qint32 row = firstRow; row <= lastRow; ++row) { for (qint32 column = firstColumn; column <= lastColumn; ++column) { /** * We are cloning whole tiles here so let's not be so boring * to check any borders :) */ // this is the only variation in the template KisTileSP srcTile = useOldSrcData ? srcDM->getOldTile(column, row) : srcDM->getTile(column, row, false); m_hashTable->deleteTile(column, row); srcTile->lockForRead(); KisTileData *td = srcTile->tileData(); KisTileSP clonedTile = new KisTile(column, row, td, m_mementoManager); srcTile->unlock(); m_hashTable->addTile(clonedTile); updateExtent(column, row); } } } void KisTiledDataManager::bitBlt(KisTiledDataManager *srcDM, const QRect &rect) { bitBltImpl(srcDM, rect); } void KisTiledDataManager::bitBltOldData(KisTiledDataManager *srcDM, const QRect &rect) { bitBltImpl(srcDM, rect); } void KisTiledDataManager::bitBltRough(KisTiledDataManager *srcDM, const QRect &rect) { bitBltRoughImpl(srcDM, rect); } void KisTiledDataManager::bitBltRoughOldData(KisTiledDataManager *srcDM, const QRect &rect) { bitBltRoughImpl(srcDM, rect); } void KisTiledDataManager::setExtent(qint32 x, qint32 y, qint32 w, qint32 h) { setExtent(QRect(x, y, w, h)); } void KisTiledDataManager::setExtent(QRect newRect) { QRect oldRect = extent(); newRect = newRect.normalized(); // Do nothing if the desired size is bigger than we currently are: // that is handled by the autoextending automatically if (newRect.contains(oldRect)) return; QWriteLocker locker(&m_lock); KisTileSP tile; QRect tileRect; { KisTileHashTableIterator iter(m_hashTable); while (!iter.isDone()) { tile = iter.tile(); tileRect = tile->extent(); if (newRect.contains(tileRect)) { //do nothing ++iter; } else if (newRect.intersects(tileRect)) { QRect intersection = newRect & tileRect; intersection.translate(- tileRect.topLeft()); const qint32 pixelSize = this->pixelSize(); tile->lockForWrite(); quint8* data = tile->data(); quint8* ptr; /* FIXME: make it faster */ for (int y = 0; y < KisTileData::HEIGHT; y++) { for (int x = 0; x < KisTileData::WIDTH; x++) { if (!intersection.contains(x, y)) { ptr = data + pixelSize * (y * KisTileData::WIDTH + x); memcpy(ptr, m_defaultPixel, pixelSize); } } } tile->unlock(); ++iter; } else { iter.deleteCurrent(); } } } recalculateExtent(); } void KisTiledDataManager::recalculateExtent() { m_extentMinX = qint32_MAX; m_extentMinY = qint32_MAX; m_extentMaxX = qint32_MIN; m_extentMaxY = qint32_MIN; KisTileHashTableIterator iter(m_hashTable); KisTileSP tile; while ((tile = iter.tile())) { updateExtent(tile->col(), tile->row()); ++iter; } } void KisTiledDataManager::updateExtent(qint32 col, qint32 row) { const qint32 tileMinX = col * KisTileData::WIDTH; const qint32 tileMinY = row * KisTileData::HEIGHT; const qint32 tileMaxX = tileMinX + KisTileData::WIDTH - 1; const qint32 tileMaxY = tileMinY + KisTileData::HEIGHT - 1; m_extentMinX = qMin(m_extentMinX, tileMinX); m_extentMaxX = qMax(m_extentMaxX, tileMaxX); m_extentMinY = qMin(m_extentMinY, tileMinY); m_extentMaxY = qMax(m_extentMaxY, tileMaxY); } QRect KisTiledDataManager::extentImpl() const { qint32 x = m_extentMinX; qint32 y = m_extentMinY; qint32 w = (m_extentMaxX >= m_extentMinX) ? m_extentMaxX - m_extentMinX + 1 : 0; qint32 h = (m_extentMaxY >= m_extentMinY) ? m_extentMaxY - m_extentMinY + 1 : 0; return QRect(x, y, w, h); } void KisTiledDataManager::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rect = extent(); rect.getRect(&x, &y, &w, &h); } QRect KisTiledDataManager::extent() const { QReadLocker locker(&m_lock); return extentImpl(); } QRegion KisTiledDataManager::region() const { QRegion region; KisTileHashTableIterator iter(m_hashTable); KisTileSP tile; while ((tile = iter.tile())) { region += tile->extent(); ++iter; } return region; } void KisTiledDataManager::setPixel(qint32 x, qint32 y, const quint8 * data) { QWriteLocker locker(&m_lock); KisTileDataWrapper tw(this, x, y, KisTileDataWrapper::WRITE); memcpy(tw.data(), data, pixelSize()); } void KisTiledDataManager::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 width, qint32 height, qint32 dataRowStride) { QWriteLocker locker(&m_lock); // Actial bytes reading/writing is done in private header writeBytesBody(data, x, y, width, height, dataRowStride); } void KisTiledDataManager::readBytes(quint8 *data, qint32 x, qint32 y, qint32 width, qint32 height, qint32 dataRowStride) const { QReadLocker locker(&m_lock); // Actual bytes reading/writing is done in private header readBytesBody(data, x, y, width, height, dataRowStride); } QVector KisTiledDataManager::readPlanarBytes(QVector channelSizes, qint32 x, qint32 y, qint32 width, qint32 height) const { QReadLocker locker(&m_lock); // Actial bytes reading/writing is done in private header return readPlanarBytesBody(channelSizes, x, y, width, height); } void KisTiledDataManager::writePlanarBytes(QVector planes, QVector channelSizes, qint32 x, qint32 y, qint32 width, qint32 height) { QWriteLocker locker(&m_lock); // Actial bytes reading/writing is done in private header bool allChannelsPresent = true; Q_FOREACH (const quint8* plane, planes) { if (!plane) { allChannelsPresent = false; break; } } if (allChannelsPresent) { writePlanarBytesBody(planes, channelSizes, x, y, width, height); } else { writePlanarBytesBody(planes, channelSizes, x, y, width, height); } } qint32 KisTiledDataManager::numContiguousColumns(qint32 x, qint32 minY, qint32 maxY) const { qint32 numColumns; Q_UNUSED(minY); Q_UNUSED(maxY); if (x >= 0) { numColumns = KisTileData::WIDTH - (x % KisTileData::WIDTH); } else { numColumns = ((-x - 1) % KisTileData::WIDTH) + 1; } return numColumns; } qint32 KisTiledDataManager::numContiguousRows(qint32 y, qint32 minX, qint32 maxX) const { qint32 numRows; Q_UNUSED(minX); Q_UNUSED(maxX); if (y >= 0) { numRows = KisTileData::HEIGHT - (y % KisTileData::HEIGHT); } else { numRows = ((-y - 1) % KisTileData::HEIGHT) + 1; } return numRows; } qint32 KisTiledDataManager::rowStride(qint32 x, qint32 y) const { Q_UNUSED(x); Q_UNUSED(y); return KisTileData::WIDTH * pixelSize(); } void KisTiledDataManager::releaseInternalPools() { KisTileData::releaseInternalPools(); } diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index ad74c97942..21dfc97bf0 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,533 +1,532 @@ # Disable -Wswitch because of the extra definitions we here: # kis_input_manager.cpp: In member function ‘virtual bool KisInputManager::eventFilter(QObject*, QEvent*)’: # warning: case value ‘1001’ not in enumerated type ‘QEvent::Type’ [-Wswitch] # warning: case value ‘1002’ not in enumerated type ‘QEvent::Type’ [-Wswitch] if (CMAKE_COMPILER_IS_GNUCXX) add_definitions(${KDE4_ENABLE_EXCEPTIONS} -Wno-switch) endif () include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_config_notifier.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp kis_painting_assistants_manager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp kis_resource_server_provider.cpp kis_selection_decoration.cc kis_selection_manager.cc kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp kis_view_plugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_async_action_feedback.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp kra/kis_kra_utils.cpp kra/kis_kra_load_visitor.cpp kra/kis_kra_loader.cpp kra/kis_kra_save_visitor.cpp kra/kis_kra_saver.cpp kra/kis_kra_savexml_visitor.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp - opengl/kis_texture_tile_update_info.cpp kis_fps_decoration.cpp ora/kis_open_raster_stack_load_visitor.cpp ora/kis_open_raster_stack_save_visitor.cpp ora/ora_load_context.cc ora/ora_save_context.cc recorder/kis_node_query_path_editor.cc recorder/kis_recorded_action_creator.cc recorder/kis_recorded_action_creator_factory.cc recorder/kis_recorded_action_creator_factory_registry.cc recorder/kis_recorded_action_editor_factory.cc recorder/kis_recorded_action_editor_factory_registry.cc recorder/kis_recorded_filter_action_editor.cc recorder/kis_recorded_filter_action_creator.cpp recorder/kis_recorded_paint_action_editor.cc tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_recording_adapter.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/strokes/freehand_stroke.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_gradient_slider_widget.cc widgets/kis_gradient_slider.cpp widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_pattern_chooser.cc widgets/kis_popup_button.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/squeezedcombobox.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisFilterChain.cpp KisFilterChainLink.cpp KisFilterChainLinkList.cpp KisImportExportFilter.cpp KisFilterEdge.cpp KisFilterEntry.cpp KisFilterGraph.cpp KisImportExportManager.cpp KisFilterVertex.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoStackAction.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp ) if(WIN32) if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS) message(FATAL_ERROR "Qt5Gui Private header are missing!") endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp ) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp canvas/kis_animation_player.cpp kis_animation_exporter.cpp kis_animation_importer.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_x11.cpp input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(WIN32) #ki18n_wrap_ui( # input/wintab/kis_screen_size_choice_dialog.ui #) endif() ki18n_wrap_ui(kritaui_LIB_SRCS forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgcustompalette.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgpaintactioneditor.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_link_libraries(kritaui LINK_INTERFACE_LIBRARIES kritaimage kritalibbrush kritapigment KF5::Completion KF5::I18n ${GL_INTERFACE_LIBRARIES}) target_include_directories(kritaui PUBLIC $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/opengl/kis_opengl_image_textures.cpp b/libs/ui/opengl/kis_opengl_image_textures.cpp index fa0c25fd71..3e30856fe3 100644 --- a/libs/ui/opengl/kis_opengl_image_textures.cpp +++ b/libs/ui/opengl/kis_opengl_image_textures.cpp @@ -1,635 +1,642 @@ /* * Copyright (c) 2005-2007 Adrian Page * * 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 "opengl/kis_opengl_image_textures.h" #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_config.h" #include "KisPart.h" #ifdef HAVE_OPENEXR #include #endif #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif KisOpenGLImageTextures::ImageTexturesMap KisOpenGLImageTextures::imageTexturesMap; KisOpenGLImageTextures::KisOpenGLImageTextures() : m_image(0) , m_monitorProfile(0) , m_proofingConfig(0) , m_proofingTransform(0) , m_createNewProofingTransform(true) , m_tilesDestinationColorSpace(0) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_allChannelsSelected(true) , m_useOcio(false) , m_initialized(false) { KisConfig cfg; m_renderingIntent = (KoColorConversionTransformation::Intent)cfg.monitorRenderIntent(); m_conversionFlags = KoColorConversionTransformation::HighQuality; if (cfg.useBlackPointCompensation()) m_conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; if (!cfg.allowLCMSOptimization()) m_conversionFlags |= KoColorConversionTransformation::NoOptimization; m_useOcio = cfg.useOcio(); } KisOpenGLImageTextures::KisOpenGLImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : m_image(image) , m_monitorProfile(monitorProfile) , m_renderingIntent(renderingIntent) , m_conversionFlags(conversionFlags) , m_proofingConfig(0) , m_proofingTransform(0) - , m_tilesDestinationColorSpace(0) , m_createNewProofingTransform(true) + , m_tilesDestinationColorSpace(0) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_allChannelsSelected(true) , m_useOcio(false) , m_initialized(false) { Q_ASSERT(renderingIntent < 4); } void KisOpenGLImageTextures::initGL(QOpenGLFunctions *f) { if (f) { m_glFuncs = f; } else { errUI << "Tried to create OpenGLImageTextures with uninitialized QOpenGLFunctions"; } getTextureSize(&m_texturesInfo); + + // we use local static object for creating pools shared among + // different images + static KisTextureTileInfoPoolRegistry s_poolRegistry; + m_infoChunksPool = s_poolRegistry.getPool(m_texturesInfo.width, m_texturesInfo.height); + m_glFuncs->glGenTextures(1, &m_checkerTexture); createImageTextureTiles(); KisOpenGLUpdateInfoSP info = updateCache(m_image->bounds()); recalculateCache(info); } KisOpenGLImageTextures::~KisOpenGLImageTextures() { ImageTexturesMap::iterator it = imageTexturesMap.find(m_image); if (it != imageTexturesMap.end()) { KisOpenGLImageTextures *textures = it.value(); if (textures == this) { dbgUI << "Removing shared image context from map"; imageTexturesMap.erase(it); } } destroyImageTextureTiles(); m_glFuncs->glDeleteTextures(1, &m_checkerTexture); } KisImageSP KisOpenGLImageTextures::image() const { return m_image; } bool KisOpenGLImageTextures::imageCanShareTextures() { KisConfig cfg; if (cfg.useOcio()) return false; if (KisPart::instance()->mainwindowCount() == 1) return true; if (qApp->desktop()->screenCount() == 1) return true; for (int i = 1; i < qApp->desktop()->screenCount(); i++) { if (cfg.displayProfile(i) != cfg.displayProfile(i - 1)) { return false; } } return true; } KisOpenGLImageTexturesSP KisOpenGLImageTextures::getImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { // Disabled until we figure out why we're deleting the shared textures on closing the second view on a single image if (false && imageCanShareTextures()) { ImageTexturesMap::iterator it = imageTexturesMap.find(image); if (it != imageTexturesMap.end()) { KisOpenGLImageTexturesSP textures = it.value(); textures->setMonitorProfile(monitorProfile, renderingIntent, conversionFlags); return textures; } else { KisOpenGLImageTextures *imageTextures = new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); imageTexturesMap[image] = imageTextures; dbgUI << "Added shareable textures to map"; return imageTextures; } } else { return new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); } } QRect KisOpenGLImageTextures::calculateTileRect(int col, int row) const { return m_image->bounds() & QRect(col * m_texturesInfo.effectiveWidth, row * m_texturesInfo.effectiveHeight, m_texturesInfo.effectiveWidth, m_texturesInfo.effectiveHeight); } void KisOpenGLImageTextures::createImageTextureTiles() { destroyImageTextureTiles(); updateTextureFormat(); if (!m_tilesDestinationColorSpace) { qDebug() << "No destination colorspace!!!!"; return; } m_storedImageBounds = m_image->bounds(); const int lastCol = xToCol(m_image->width()); const int lastRow = yToRow(m_image->height()); m_numCols = lastCol + 1; // Default color is transparent black const int pixelSize = m_tilesDestinationColorSpace->pixelSize(); QByteArray emptyTileData((m_texturesInfo.width) * (m_texturesInfo.height) * pixelSize, 0); KisConfig config; KisOpenGL::FilterMode mode = (KisOpenGL::FilterMode)config.openGLFilteringMode(); QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); m_initialized = true; dbgUI << "OpenGL: creating texture tiles of size" << m_texturesInfo.height << "x" << m_texturesInfo.width; m_textureTiles.reserve((lastRow+1)*m_numCols); for (int row = 0; row <= lastRow; row++) { for (int col = 0; col <= lastCol; col++) { QRect tileRect = calculateTileRect(col, row); KisTextureTile *tile = new KisTextureTile(tileRect, &m_texturesInfo, emptyTileData, mode, config.useOpenGLTextureBuffer(), config.numMipmapLevels(), f); m_textureTiles.append(tile); } } } else { dbgUI << "Tried to init texture tiles without a current OpenGL Context."; } } void KisOpenGLImageTextures::destroyImageTextureTiles() { if (m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { delete tile; } m_textureTiles.clear(); m_storedImageBounds = QRect(); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCache(const QRect& rect) { return updateCacheImpl(rect, true); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheNoConversion(const QRect& rect) { return updateCacheImpl(rect, false); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheImpl(const QRect& rect, bool convertColorSpace) { const KoColorSpace *dstCS = m_tilesDestinationColorSpace; ConversionOptions options; if (convertColorSpace) { options = ConversionOptions(dstCS, m_renderingIntent, m_conversionFlags); } KisOpenGLUpdateInfoSP info = new KisOpenGLUpdateInfo(options); QRect updateRect = rect & m_image->bounds(); if (updateRect.isEmpty() || !(m_initialized)) return info; /** * Why the rect is artificial? That's easy! * It does not represent any real piece of the image. It is * intentionally stretched to get through the overlappping * stripes of neutrality and poke neighbouring tiles. * Thanks to the rect we get the coordinates of all the tiles * involved into update process */ QRect artificialRect = stretchRect(updateRect, m_texturesInfo.border); artificialRect &= m_image->bounds(); int firstColumn = xToCol(artificialRect.left()); int lastColumn = xToCol(artificialRect.right()); int firstRow = yToRow(artificialRect.top()); int lastRow = yToRow(artificialRect.bottom()); QBitArray channelFlags; // empty by default if (m_channelFlags.size() != m_image->projection()->colorSpace()->channels().size()) { setChannelFlags(QBitArray()); } if (!m_useOcio) { // Ocio does its own channel flipping if (!m_allChannelsSelected) { // and we do it only if necessary channelFlags = m_channelFlags; } } qint32 numItems = (lastColumn - firstColumn + 1) * (lastRow - firstRow + 1); info->tileList.reserve(numItems); const QRect bounds = m_image->bounds(); const int levelOfDetail = m_image->currentLevelOfDetail(); QRect alignedUpdateRect = updateRect; QRect alignedBounds = bounds; if (levelOfDetail) { alignedUpdateRect = KisLodTransform::alignedRect(alignedUpdateRect, levelOfDetail); alignedBounds = KisLodTransform::alignedRect(alignedBounds, levelOfDetail); } for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { const QRect tileRect = calculateTileRect(col, row); const QRect tileTextureRect = stretchRect(tileRect, m_texturesInfo.border); QRect alignedTileTextureRect = levelOfDetail ? KisLodTransform::alignedRect(tileTextureRect, levelOfDetail) : tileTextureRect; KisTextureTileUpdateInfoSP tileInfo( new KisTextureTileUpdateInfo(col, row, alignedTileTextureRect, alignedUpdateRect, alignedBounds, - levelOfDetail)); + levelOfDetail, + m_infoChunksPool)); // Don't update empty tiles if (tileInfo->valid()) { tileInfo->retrieveData(m_image, channelFlags, m_onlyOneChannelSelected, m_selectedChannelIndex); //create transform if (m_createNewProofingTransform) { const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(m_proofingConfig->proofingModel,m_proofingConfig->proofingDepth,m_proofingConfig->proofingProfile); m_proofingTransform = tileInfo->generateProofingTransform(dstCS, proofingSpace, m_renderingIntent, m_proofingConfig->intent, m_proofingConfig->conversionFlags, m_proofingConfig->warningColor, m_proofingConfig->adaptationState); m_createNewProofingTransform = false; } if (convertColorSpace) { if (m_proofingConfig && m_proofingTransform && m_proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::SoftProofing)) { tileInfo->proofTo(dstCS, m_proofingConfig->conversionFlags, m_proofingTransform); } else { tileInfo->convertTo(dstCS, m_renderingIntent, m_conversionFlags); } } info->tileList.append(tileInfo); } else { dbgUI << "Trying to create an empty tileinfo record" << col << row << tileTextureRect << updateRect << m_image->bounds(); } } } info->assignDirtyImageRect(rect); info->assignLevelOfDetail(levelOfDetail); return info; } void KisOpenGLImageTextures::recalculateCache(KisUpdateInfoSP info) { if (!m_initialized) { dbgUI << "OpenGL: Tried to edit image texture cache before it was initialized."; return; } KisOpenGLUpdateInfoSP glInfo = dynamic_cast(info.data()); if(!glInfo) return; KisTextureTileUpdateInfoSP tileInfo; Q_FOREACH (tileInfo, glInfo->tileList) { KisTextureTile *tile = getTextureTileCR(tileInfo->tileCol(), tileInfo->tileRow()); KIS_ASSERT_RECOVER_RETURN(tile); tile->update(*tileInfo); } } void KisOpenGLImageTextures::generateCheckerTexture(const QImage &checkImage) { QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); dbgUI << "Attaching checker texture" << checkerTexture(); f->glBindTexture(GL_TEXTURE_2D, checkerTexture()); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); QImage img = checkImage; if (checkImage.width() != BACKGROUND_TEXTURE_SIZE || checkImage.height() != BACKGROUND_TEXTURE_SIZE) { img = checkImage.scaled(BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE); } f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE, 0, GL_BGRA, GL_UNSIGNED_BYTE, img.constBits()); } else { dbgUI << "OpenGL: Tried to generate checker texture before OpenGL was initialized."; } } GLuint KisOpenGLImageTextures::checkerTexture() { if (m_glFuncs) { if (m_checkerTexture == 0) { m_glFuncs->glGenTextures(1, &m_checkerTexture); } return m_checkerTexture; } else { dbgUI << "Tried to access checker texture before OpenGL was initialized"; return 0; } } void KisOpenGLImageTextures::updateConfig(bool useBuffer, int NumMipmapLevels) { if(m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { tile->setUseBuffer(useBuffer); tile->setNumMipmapLevels(NumMipmapLevels); } } void KisOpenGLImageTextures::slotImageSizeChanged(qint32 /*w*/, qint32 /*h*/) { createImageTextureTiles(); } void KisOpenGLImageTextures::setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { //dbgUI << "Setting monitor profile to" << monitorProfile->name() << renderingIntent << conversionFlags; m_monitorProfile = monitorProfile; m_renderingIntent = renderingIntent; m_conversionFlags = conversionFlags; createImageTextureTiles(); } void KisOpenGLImageTextures::setChannelFlags(const QBitArray &channelFlags) { m_channelFlags = channelFlags; int selectedChannels = 0; const KoColorSpace *projectionCs = m_image->projection()->colorSpace(); QList channelInfo = projectionCs->channels(); if (m_channelFlags.size() != channelInfo.size()) { m_channelFlags = QBitArray(); } for (int i = 0; i < m_channelFlags.size(); ++i) { if (m_channelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) { selectedChannels++; m_selectedChannelIndex = i; } } m_allChannelsSelected = (selectedChannels == m_channelFlags.size()); m_onlyOneChannelSelected = (selectedChannels == 1); } void KisOpenGLImageTextures::setProofingConfig(KisProofingConfiguration *proofingConfig) { m_proofingConfig = proofingConfig; m_createNewProofingTransform = true; } void KisOpenGLImageTextures::getTextureSize(KisGLTexturesInfo *texturesInfo) { KisConfig cfg; const GLint preferredTextureSize = cfg.openGLTextureSize(); GLint maxTextureSize; if (m_glFuncs) { m_glFuncs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } else { dbgUI << "OpenGL: Tried to read texture size before OpenGL was initialized."; maxTextureSize = GL_MAX_TEXTURE_SIZE; } texturesInfo->width = qMin(preferredTextureSize, maxTextureSize); texturesInfo->height = qMin(preferredTextureSize, maxTextureSize); texturesInfo->border = cfg.textureOverlapBorder(); texturesInfo->effectiveWidth = texturesInfo->width - 2 * texturesInfo->border; texturesInfo->effectiveHeight = texturesInfo->height - 2 * texturesInfo->border; } bool KisOpenGLImageTextures::internalColorManagementActive() const { return m_internalColorManagementActive; } bool KisOpenGLImageTextures::setInternalColorManagementActive(bool value) { bool needsFinalRegeneration = m_internalColorManagementActive != value; if (needsFinalRegeneration) { m_internalColorManagementActive = value; createImageTextureTiles(); // at this point the value of m_internalColorManagementActive might // have been forcely reverted to 'false' in case of some problems } return needsFinalRegeneration; } void KisOpenGLImageTextures::updateTextureFormat() { QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (!(m_image && ctx)) return; m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_BGRA; KoID colorModelId = m_image->colorSpace()->colorModelId(); KoID colorDepthId = m_image->colorSpace()->colorDepthId(); KoID destinationColorModelId = RGBAColorModelID; KoID destinationColorDepthId = Integer8BitsColorDepthID; dbgUI << "Choosing texture format:"; if (colorModelId == RGBAColorModelID) { if (colorDepthId == Float16BitsColorDepthID) { if (ctx->hasExtension("GL_ARB_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA16F_ARB; dbgUI << "Using ARB half"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA_FLOAT16_ATI; dbgUI << "Using ATI half"; } bool haveBuiltInOpenExr = false; #ifdef HAVE_OPENEXR haveBuiltInOpenExr = true; #endif if (haveBuiltInOpenExr && ctx->hasExtension("GL_ARB_half_float_pixel")) { m_texturesInfo.type = GL_HALF_FLOAT_ARB; destinationColorDepthId = Float16BitsColorDepthID; dbgUI << "Pixel type half"; } else { m_texturesInfo.type = GL_FLOAT; destinationColorDepthId = Float32BitsColorDepthID; dbgUI << "Pixel type float"; } m_texturesInfo.format = GL_RGBA; } else if (colorDepthId == Float32BitsColorDepthID) { if (ctx->hasExtension("GL_ARB_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA32F_ARB; dbgUI << "Using ARB float"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA_FLOAT32_ATI; dbgUI << "Using ATI float"; } m_texturesInfo.type = GL_FLOAT; m_texturesInfo.format = GL_RGBA; destinationColorDepthId = Float32BitsColorDepthID; } else if (colorDepthId == Integer16BitsColorDepthID) { m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using 16 bits rgba"; } } else { // We will convert the colorspace to 16 bits rgba, instead of 8 bits if (colorDepthId == Integer16BitsColorDepthID) { m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using conversion to 16 bits rgba"; } } if (!m_internalColorManagementActive && colorModelId != destinationColorModelId) { KisConfig cfg; KisConfig::OcioColorManagementMode cm = cfg.ocioColorManagementMode(); if (cm != KisConfig::INTERNAL) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You enabled OpenColorIO based color management, but your image is not an RGB image.\n" "OpenColorIO-based color management only works with RGB images.\n" "Please check the settings in the LUT docker.\n" "OpenColorIO will now be deactivated.")); } warnUI << "WARNING: Internal color management was forcely enabled"; warnUI << "Color Management Mode: " << cm; warnUI << ppVar(m_image->colorSpace()); warnUI << ppVar(destinationColorModelId); warnUI << ppVar(destinationColorDepthId); cfg.setOcioColorManagementMode(KisConfig::INTERNAL); m_internalColorManagementActive = true; } const KoColorProfile *profile = m_internalColorManagementActive || colorModelId != destinationColorModelId ? m_monitorProfile : m_image->colorSpace()->profile(); /** * TODO: add an optimization so that the tile->convertTo() method * would not be called when not needed (DK) */ m_tilesDestinationColorSpace = KoColorSpaceRegistry::instance()->colorSpace(destinationColorModelId.id(), destinationColorDepthId.id(), profile); } diff --git a/libs/ui/opengl/kis_opengl_image_textures.h b/libs/ui/opengl/kis_opengl_image_textures.h index b23e9ab714..33fd2c71ac 100644 --- a/libs/ui/opengl/kis_opengl_image_textures.h +++ b/libs/ui/opengl/kis_opengl_image_textures.h @@ -1,206 +1,210 @@ /* * Copyright (c) 2005-2007 Adrian Page * * 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_OPENGL_IMAGE_TEXTURES_H_ #define KIS_OPENGL_IMAGE_TEXTURES_H_ #include #include #include "kritaui_export.h" #include "kis_shared.h" #include "canvas/kis_update_info.h" #include "opengl/kis_texture_tile.h" #include "KisProofingConfiguration.h" #include class KisOpenGLImageTextures; class QOpenGLFunctions; typedef KisSharedPtr KisOpenGLImageTexturesSP; class KoColorProfile; +class KisTextureTileUpdateInfoPoolCollection; +typedef QSharedPointer KisTextureTileInfoPoolSP; /** * A set of OpenGL textures that contains the projection of a KisImage. */ class KRITAUI_EXPORT KisOpenGLImageTextures : public KisShared { public: /** * Obtain a KisOpenGLImageTextures object for the given image. * @param image The image * @param monitorProfile The profile of the display device */ static KisOpenGLImageTexturesSP getImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Default constructor. */ KisOpenGLImageTextures(); /** * Destructor. */ virtual ~KisOpenGLImageTextures(); /** * \return the image associated with the textures */ KisImageSP image() const; /** * Set the color profile of the display device. * @param profile The color profile of the display device */ void setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Complete initialization can only happen once an OpenGL context has been created. * @param f Pointer to OpenGL functions. They must already be ininitialized. */ void initGL(QOpenGLFunctions *f); void setChannelFlags(const QBitArray &channelFlags); void setProofingConfig(KisProofingConfiguration*); bool internalColorManagementActive() const; bool setInternalColorManagementActive(bool value); /** * The background checkers texture. */ static const int BACKGROUND_TEXTURE_CHECK_SIZE = 32; static const int BACKGROUND_TEXTURE_SIZE = BACKGROUND_TEXTURE_CHECK_SIZE * 2; /** * Generate a background texture from the given QImage. This is used for the checker * pattern on which the image is rendered. */ void generateCheckerTexture(const QImage & checkImage); GLuint checkerTexture(); void updateConfig(bool useBuffer, int NumMipmapLevels); public: inline QRect storedImageBounds() { return m_storedImageBounds; } inline int xToCol(int x) { return x / m_texturesInfo.effectiveWidth; } inline int yToRow(int y) { return y / m_texturesInfo.effectiveHeight; } inline KisTextureTile* getTextureTileCR(int col, int row) { if (m_initialized) { int tile = row * m_numCols + col; KIS_ASSERT_RECOVER_RETURN_VALUE(m_textureTiles.size() > tile, 0); return m_textureTiles[tile]; } return 0; } inline qreal texelSize() const { Q_ASSERT(m_texturesInfo.width == m_texturesInfo.height); return 1.0 / m_texturesInfo.width; } KisOpenGLUpdateInfoSP updateCache(const QRect& rect); KisOpenGLUpdateInfoSP updateCacheNoConversion(const QRect& rect); void recalculateCache(KisUpdateInfoSP info); void slotImageSizeChanged(qint32 w, qint32 h); protected: KisOpenGLImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); void createImageTextureTiles(); void destroyImageTextureTiles(); static bool imageCanShareTextures(); private: QRect calculateTileRect(int col, int row) const; void getTextureSize(KisGLTexturesInfo *texturesInfo); void updateTextureFormat(); KisOpenGLUpdateInfoSP updateCacheImpl(const QRect& rect, bool convertColorSpace); private: KisImageWSP m_image; QRect m_storedImageBounds; const KoColorProfile *m_monitorProfile; KoColorConversionTransformation::Intent m_renderingIntent; KoColorConversionTransformation::ConversionFlags m_conversionFlags; KisProofingConfiguration *m_proofingConfig; KoColorConversionTransformation *m_proofingTransform; bool m_createNewProofingTransform; /** * If the destination color space coincides with the one of the image, * then effectively, there is no conversion happens. That is used * for working with OCIO. */ const KoColorSpace *m_tilesDestinationColorSpace; /** * Shows whether the internal color management should be enabled or not. * Please note that if you disable color management, *but* your image color * space will not be supported (non-RGB), then it will be enabled anyway. * And this valiable will hold the real state of affairs! */ bool m_internalColorManagementActive; GLuint m_checkerTexture; KisGLTexturesInfo m_texturesInfo; int m_numCols; QVector m_textureTiles; QOpenGLFunctions *m_glFuncs; QBitArray m_channelFlags; bool m_allChannelsSelected; bool m_onlyOneChannelSelected; int m_selectedChannelIndex; bool m_useOcio; bool m_initialized; + KisTextureTileInfoPoolSP m_infoChunksPool; + private: typedef QMap ImageTexturesMap; static ImageTexturesMap imageTexturesMap; }; #endif // KIS_OPENGL_IMAGE_TEXTURES_H_ diff --git a/libs/ui/opengl/kis_texture_tile_info_pool.h b/libs/ui/opengl/kis_texture_tile_info_pool.h new file mode 100644 index 0000000000..a9cf463990 --- /dev/null +++ b/libs/ui/opengl/kis_texture_tile_info_pool.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2016 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __KIS_TEXTURE_TILE_INFO_POOL_H +#define __KIS_TEXTURE_TILE_INFO_POOL_H + +#include +#include +#include + +#include +#include +#include + +#include "kis_assert.h" +#include "kis_debug.h" +#include "kis_global.h" + +const int minPoolChunk = 32; // 8 MiB (default, with tilesize 256) +const int maxPoolChunk = 128; // 32 MiB (default, with tilesize 256) +const int freeThreshold = 64; // 16 MiB (default, with tilesize 256) + +class KisTextureTileInfoPoolSingleSize +{ +public: + KisTextureTileInfoPoolSingleSize(int tileWidth, int tileHeight, int pixelSize) + : m_chunkSize(tileWidth * tileHeight * pixelSize), + m_pool(m_chunkSize, minPoolChunk, maxPoolChunk), + m_numAllocations(0), + m_maxAllocations(0) + { + } + + quint8* malloc() { + m_numAllocations++; + m_maxAllocations = qMax(m_maxAllocations, m_numAllocations); + + return (quint8*)m_pool.malloc(); + } + + void free(quint8 *ptr) { + m_numAllocations--; + m_pool.free(ptr); + + KIS_ASSERT_RECOVER_NOOP(m_numAllocations >= 0); + + if (!m_numAllocations && m_maxAllocations > freeThreshold) { + // qDebug() << "Purging memory" << ppVar(m_maxAllocations); + m_pool.purge_memory(); + m_maxAllocations = 0; + } + } + + int chunkSize() const { + return m_chunkSize; + } + +private: + const int m_chunkSize; + boost::pool m_pool; + int m_numAllocations; + int m_maxAllocations; +}; + +class KisTextureTileInfoPool +{ +public: + KisTextureTileInfoPool(int tileWidth, int tileHeight) + : m_tileWidth(tileWidth), + m_tileHeight(tileHeight) + { + } + + ~KisTextureTileInfoPool() { + qDeleteAll(m_pools); + } + + quint8* malloc(int pixelSize) { + QMutexLocker l(&m_mutex); + + if (m_pools.size() <= pixelSize) { + m_pools.resize(pixelSize + 1); + } + + if (!m_pools[pixelSize]) { + m_pools[pixelSize] = + new KisTextureTileInfoPoolSingleSize(m_tileWidth, m_tileHeight, pixelSize); + } + + return m_pools[pixelSize]->malloc(); + } + + void free(quint8 *ptr, int pixelSize) { + QMutexLocker l(&m_mutex); + m_pools[pixelSize]->free(ptr); + } + + int chunkSize(int pixelSize) const { + QMutexLocker l(&m_mutex); + return m_pools[pixelSize]->chunkSize(); + } + +private: + mutable QMutex m_mutex; + const int m_tileWidth; + const int m_tileHeight; + QVector m_pools; +}; + +typedef QSharedPointer KisTextureTileInfoPoolSP; + +class KisTextureTileInfoPoolRegistry +{ + typedef QWeakPointer KisTextureTileInfoPoolWSP; + typedef QPair PoolId; + +public: + KisTextureTileInfoPoolSP getPool(int tileWidth, int tileHeight) { + QMutexLocker l(&m_mutex); + + PoolId id(tileWidth, tileHeight); + + KisTextureTileInfoPoolSP pool = + m_storage[id].toStrongRef(); + + if (!pool) { + pool = toQShared( + new KisTextureTileInfoPool(tileWidth, tileHeight)); + m_storage[id] = pool; + } + + return pool; + } + +private: + QMutex m_mutex; + QHash m_storage; +}; + + +#endif /* __KIS_TEXTURE_TILE_INFO_POOL_H */ diff --git a/libs/ui/opengl/kis_texture_tile_update_info.cpp b/libs/ui/opengl/kis_texture_tile_update_info.cpp deleted file mode 100644 index 938a4d37e1..0000000000 --- a/libs/ui/opengl/kis_texture_tile_update_info.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010, Dmitry Kazakov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "kis_texture_tile_update_info.h" - -ConversionCache KisTextureTileUpdateInfo::m_patchPixelsCache; -ConversionCache KisTextureTileUpdateInfo::m_conversionCache; diff --git a/libs/ui/opengl/kis_texture_tile_update_info.h b/libs/ui/opengl/kis_texture_tile_update_info.h index 8a0a174874..8fafa6a885 100644 --- a/libs/ui/opengl/kis_texture_tile_update_info.h +++ b/libs/ui/opengl/kis_texture_tile_update_info.h @@ -1,329 +1,315 @@ /* * Copyright (c) 2010, Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TEXTURE_TILE_UPDATE_INFO_H_ #define KIS_TEXTURE_TILE_UPDATE_INFO_H_ #include #include #include #include #include "kis_image.h" #include "kis_paint_device.h" #include "kis_config.h" #include #include #include +#include "kis_texture_tile_info_pool.h" class KisTextureTileUpdateInfo; typedef QSharedPointer KisTextureTileUpdateInfoSP; typedef QVector KisTextureTileUpdateInfoSPList; - -class ConversionCache { +class DataBuffer +{ public: - class Buffer { - public: - Buffer () : m_size(0) {} + DataBuffer(KisTextureTileInfoPoolSP pool) + : m_data(0), + m_pixelSize(0), + m_pool(pool) + { + } - inline void swap(Buffer &rhs) { - m_data.swap(rhs.m_data); - qSwap(m_size, rhs.m_size); - } + DataBuffer(int pixelSize, KisTextureTileInfoPoolSP pool) + : m_data(0), + m_pixelSize(0), + m_pool(pool) + { + allocate(pixelSize); + } - inline quint8* data() const { - return m_data.data(); - } - - inline void ensureNotSmaller(int size) { - if (size > m_size) { - try { - m_data.reset(new quint8[size]); - m_size = size; - } - catch (std::bad_alloc) { - QMessageBox::critical(0, - i18nc("@title:window", "Fatal Error"), - i18n("Krita has run out of memory and has to close.")); - qFatal("KisTextureTileUpdateInfo: Could not allocate enough memory"); - } - } + ~DataBuffer() { + if (m_data) { + m_pool->free(m_data, m_pixelSize); } + } - private: - QScopedArrayPointer m_data; - int m_size; - }; + void allocate(int pixelSize) { + Q_ASSERT(!m_data); -public: - inline void swap(Buffer &rhs) { - m_cache.localData()->swap(rhs); + m_pixelSize = pixelSize; + m_data = m_pool->malloc(m_pixelSize); } inline quint8* data() const { - return m_cache.localData()->data(); + return m_data; } - inline void ensureNotSmaller(int size) { - if (!m_cache.hasLocalData()) { - m_cache.setLocalData(new Buffer()); - } - m_cache.localData()->ensureNotSmaller(size); + void swap(DataBuffer &other) { + std::swap(other.m_pixelSize, m_pixelSize); + std::swap(other.m_data, m_data); + } + + int size() const { + return m_data ? m_pool->chunkSize(m_pixelSize) : 0; } private: - QThreadStorage m_cache; + quint8 *m_data; + int m_pixelSize; + KisTextureTileInfoPoolSP m_pool; }; class KisTextureTileUpdateInfo { public: - KisTextureTileUpdateInfo() - : m_patchPixelsLength(0) + KisTextureTileUpdateInfo(KisTextureTileInfoPoolSP pool) + : m_patchPixels(pool), + m_pool(pool) { } KisTextureTileUpdateInfo(qint32 col, qint32 row, const QRect &tileRect, const QRect &updateRect, const QRect ¤tImageRect, - int levelOfDetail) - : m_patchPixelsLength(0) + int levelOfDetail, + KisTextureTileInfoPoolSP pool) + : m_patchPixels(pool), + m_pool(pool) { m_tileCol = col; m_tileRow = row; m_tileRect = tileRect; m_originalTileRect = m_tileRect; m_patchRect = m_tileRect & updateRect; m_originalPatchRect = m_patchRect; m_currentImageRect = currentImageRect; m_patchLevelOfDetail = levelOfDetail; if (m_patchLevelOfDetail) { m_originalPatchRect = KisLodTransform::alignedRect(m_originalPatchRect, m_patchLevelOfDetail); m_patchRect = KisLodTransform::scaledRect(m_originalPatchRect, m_patchLevelOfDetail); m_tileRect = KisLodTransform::scaledRect(m_originalTileRect, m_patchLevelOfDetail); } } ~KisTextureTileUpdateInfo() { - if (m_patchPixels.data()) { - m_patchPixelsCache.swap(m_patchPixels); - } } void retrieveData(KisImageWSP image, const QBitArray &channelFlags, bool onlyOneChannelSelected, int selectedChannelIndex) { m_patchColorSpace = image->projection()->colorSpace(); - - m_patchPixelsLength = m_patchColorSpace->pixelSize() * m_patchRect.width() * m_patchRect.height(); - m_patchPixelsCache.ensureNotSmaller(m_patchPixelsLength); - m_patchPixelsCache.swap(m_patchPixels); + m_patchPixels.allocate(m_patchColorSpace->pixelSize()); image->projection()->readBytes(m_patchPixels.data(), m_patchRect.x(), m_patchRect.y(), m_patchRect.width(), m_patchRect.height()); // XXX: if the paint colorspace is rgb, we should do the channel swizzling in // the display shader if (!channelFlags.isEmpty()) { - m_conversionCache.ensureNotSmaller(m_patchPixelsLength); + DataBuffer conversionCache(m_patchColorSpace->pixelSize(), m_pool); QList channelInfo = m_patchColorSpace->channels(); int channelSize = channelInfo[selectedChannelIndex]->size(); int pixelSize = m_patchColorSpace->pixelSize(); quint32 numPixels = m_patchRect.width() * m_patchRect.height(); KisConfig cfg; if (onlyOneChannelSelected && !cfg.showSingleChannelAsColor()) { int selectedChannelPos = channelInfo[selectedChannelIndex]->pos(); for (uint pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { for (uint channelIndex = 0; channelIndex < m_patchColorSpace->channelCount(); ++channelIndex) { if (channelInfo[channelIndex]->channelType() == KoChannelInfo::COLOR) { - memcpy(m_conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), + memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), m_patchPixels.data() + (pixelIndex * pixelSize) + selectedChannelPos, channelSize); } else if (channelInfo[channelIndex]->channelType() == KoChannelInfo::ALPHA) { - memcpy(m_conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), + memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), m_patchPixels.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), channelSize); } } } } else { for (uint pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { for (uint channelIndex = 0; channelIndex < m_patchColorSpace->channelCount(); ++channelIndex) { if (channelFlags.testBit(channelIndex)) { - memcpy(m_conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), + memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), m_patchPixels.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), channelSize); } else { - memset(m_conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); } } } } - m_conversionCache.swap(m_patchPixels); + conversionCache.swap(m_patchPixels); } } void convertTo(const KoColorSpace* dstCS, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (dstCS == m_patchColorSpace && conversionFlags == KoColorConversionTransformation::Empty) return; if (m_patchRect.isValid()) { const qint32 numPixels = m_patchRect.width() * m_patchRect.height(); - const quint32 conversionCacheLength = numPixels * dstCS->pixelSize(); + DataBuffer conversionCache(dstCS->pixelSize(), m_pool); - m_conversionCache.ensureNotSmaller(conversionCacheLength); - m_patchColorSpace->convertPixelsTo(m_patchPixels.data(), m_conversionCache.data(), dstCS, numPixels, renderingIntent, conversionFlags); + m_patchColorSpace->convertPixelsTo(m_patchPixels.data(), conversionCache.data(), dstCS, numPixels, renderingIntent, conversionFlags); m_patchColorSpace = dstCS; - m_conversionCache.swap(m_patchPixels); - m_patchPixelsLength = conversionCacheLength; + conversionCache.swap(m_patchPixels); } } void proofTo(const KoColorSpace* dstCS, KoColorConversionTransformation::ConversionFlags conversionFlags, KoColorConversionTransformation *proofingTransform) { if (dstCS == m_patchColorSpace && conversionFlags == KoColorConversionTransformation::Empty) return; if (m_patchRect.isValid()) { const qint32 numPixels = m_patchRect.width() * m_patchRect.height(); - const quint32 conversionCacheLength = numPixels * dstCS->pixelSize(); + DataBuffer conversionCache(dstCS->pixelSize(), m_pool); - m_conversionCache.ensureNotSmaller(conversionCacheLength); - m_patchColorSpace->proofPixelsTo(m_patchPixels.data(), m_conversionCache.data(), numPixels, proofingTransform); + m_patchColorSpace->proofPixelsTo(m_patchPixels.data(), conversionCache.data(), numPixels, proofingTransform); m_patchColorSpace = dstCS; - m_conversionCache.swap(m_patchPixels); - m_patchPixelsLength = conversionCacheLength; + conversionCache.swap(m_patchPixels); } } KoColorConversionTransformation *generateProofingTransform(const KoColorSpace* dstCS, const KoColorSpace* proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KoColor gamutWarning, double adaptationState) { return m_patchColorSpace->createProofingTransform(dstCS, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning.data(), adaptationState); } inline quint8* data() const { return m_patchPixels.data(); } inline int patchLevelOfDetail() const { return m_patchLevelOfDetail; } inline QPoint realPatchOffset() const { return QPoint(m_patchRect.x() - m_tileRect.x(), m_patchRect.y() - m_tileRect.y()); } inline QSize realPatchSize() const { return m_patchRect.size(); } inline QSize realTileSize() const { return m_tileRect.size(); } inline bool isTopmost() const { return m_originalPatchRect.top() == m_currentImageRect.top(); } inline bool isLeftmost() const { return m_originalPatchRect.left() == m_currentImageRect.left(); } inline bool isRightmost() const { return m_originalPatchRect.right() == m_currentImageRect.right(); } inline bool isBottommost() const { return m_originalPatchRect.bottom() == m_currentImageRect.bottom(); } inline bool isEntireTileUpdated() const { return m_patchRect == m_tileRect; } inline qint32 tileCol() const { return m_tileCol; } inline qint32 tileRow() const { return m_tileRow; } inline quint32 pixelSize() const { return m_patchColorSpace->pixelSize(); } inline quint32 patchPixelsLength() const { - return m_patchPixelsLength; + return m_patchPixels.size(); } inline bool valid() const { return m_patchRect.isValid(); } private: Q_DISABLE_COPY(KisTextureTileUpdateInfo) private: qint32 m_tileCol; qint32 m_tileRow; QRect m_currentImageRect; QRect m_tileRect; QRect m_patchRect; const KoColorSpace* m_patchColorSpace; - quint32 m_patchPixelsLength; QRect m_realPatchRect; QRect m_realPatchOffset; QRect m_realTileSize; int m_patchLevelOfDetail; QRect m_originalPatchRect; QRect m_originalTileRect; - ConversionCache::Buffer m_patchPixels; - static ConversionCache m_patchPixelsCache; - static ConversionCache m_conversionCache; + DataBuffer m_patchPixels; + KisTextureTileInfoPoolSP m_pool; }; #endif /* KIS_TEXTURE_TILE_UPDATE_INFO_H_ */ diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp index 8d07c21f2c..63af5e6213 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp +++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp @@ -1,1159 +1,1201 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform_config_widget.h" #include #include "rotation_icons.h" #include "kis_canvas2.h" #include #include "kis_liquify_properties.h" #include "KisMainWindow.h" #include "KisViewManager.h" template inline T sign(T x) { return x > 0 ? 1 : x == (T)0 ? 0 : -1; } const int KisToolTransformConfigWidget::DEFAULT_POINTS_PER_LINE = 3; KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionProperties *transaction, KisCanvas2 *canvas, bool workRecursively, QWidget *parent) : QWidget(parent), m_transaction(transaction), m_notificationsBlocked(0), m_uiSlotsBlocked(0), m_configChanged(false) { setupUi(this); showDecorationsBox->setIcon(KisIconUtils::loadIcon("krita_tool_transform")); chkWorkRecursively->setIcon(KisIconUtils::loadIcon("krita_tool_transform_recursive")); + flipXButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_x")); + flipYButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_y")); + rotateCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_cw")); + rotateCCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_ccw")); chkWorkRecursively->setChecked(workRecursively); connect(chkWorkRecursively, SIGNAL(toggled(bool)), this, SIGNAL(sigRestartTransform())); // Init Filter combo cmbFilter->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); cmbFilter->setCurrent("Bicubic"); cmbFilter->setToolTip(i18nc("@info:tooltip", "Select filtering mode:\n" "" "Bilinear for areas with uniform color to avoid artifacts" "Bicubic for smoother results" "Lanczos3 for sharp results. May produce aerials." "")); connect(cmbFilter, SIGNAL(activated(const KoID &)), this, SLOT(slotFilterChanged(const KoID &))); // Init Warp Type combo cmbWarpType->insertItem(KisWarpTransformWorker::AFFINE_TRANSFORM,i18n("Default (Affine)")); cmbWarpType->insertItem(KisWarpTransformWorker::RIGID_TRANSFORM,i18n("Strong (Rigid)")); cmbWarpType->insertItem(KisWarpTransformWorker::SIMILITUDE_TRANSFORM,i18n("Strongest (Similitude)")); cmbWarpType->setCurrentIndex(KisWarpTransformWorker::AFFINE_TRANSFORM); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWarpTypeChanged(int))); // Init Rotation Center buttons m_handleDir[0] = QPointF(1, 0); m_handleDir[1] = QPointF(1, -1); m_handleDir[2] = QPointF(0, -1); m_handleDir[3] = QPointF(-1, -1); m_handleDir[4] = QPointF(-1, 0); m_handleDir[5] = QPointF(-1, 1); m_handleDir[6] = QPointF(0, 1); m_handleDir[7] = QPointF(1, 1); m_handleDir[8] = QPointF(0, 0); // also add the center m_rotationCenterButtons = new QButtonGroup(0); // we set the ids to match m_handleDir m_rotationCenterButtons->addButton(middleRightButton, 0); m_rotationCenterButtons->addButton(topRightButton, 1); m_rotationCenterButtons->addButton(middleTopButton, 2); m_rotationCenterButtons->addButton(topLeftButton, 3); m_rotationCenterButtons->addButton(middleLeftButton, 4); m_rotationCenterButtons->addButton(bottomLeftButton, 5); m_rotationCenterButtons->addButton(middleBottomButton, 6); m_rotationCenterButtons->addButton(bottomRightButton, 7); m_rotationCenterButtons->addButton(centerButton, 8); QToolButton *nothingSelected = new QToolButton(0); nothingSelected->setCheckable(true); nothingSelected->setAutoExclusive(true); nothingSelected->hide(); // a convenient button for when no button is checked in the group m_rotationCenterButtons->addButton(nothingSelected, 9); // initialize values for free transform sliders shearXBox->setSuffix(i18n(" px")); shearYBox->setSuffix(i18n(" px")); shearXBox->setRange(-5.0, 5.0, 2); shearYBox->setRange(-5.0, 5.0, 2); shearXBox->setSingleStep(0.01); shearYBox->setSingleStep(0.01); shearXBox->setValue(0.0); shearYBox->setValue(0.0); translateXBox->setSuffix(i18n(" px")); translateYBox->setSuffix(i18n(" px")); translateXBox->setRange(-10000, 10000); translateYBox->setRange(-10000, 10000); scaleXBox->setSuffix("%"); scaleYBox->setSuffix("%"); scaleXBox->setRange(-10000, 10000); scaleYBox->setRange(-10000, 10000); scaleXBox->setValue(100.0); scaleYBox->setValue(100.0); m_scaleRatio = 1.0; aXBox->setSuffix(QChar(Qt::Key_degree)); aYBox->setSuffix(QChar(Qt::Key_degree)); aZBox->setSuffix(QChar(Qt::Key_degree)); aXBox->setRange(0.0, 360.0, 2); aYBox->setRange(0.0, 360.0, 2); aZBox->setRange(0.0, 360.0, 2); aXBox->setValue(0.0); aYBox->setValue(0.0); aZBox->setValue(0.0); aXBox->setSingleStep(1.0); aYBox->setSingleStep(1.0); aZBox->setSingleStep(1.0); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(slotRotationCenterChanged(int))); // Init Free Transform Values connect(scaleXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleX(int))); connect(scaleYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleY(int))); connect(shearXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearX(qreal))); connect(shearYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearY(qreal))); connect(translateXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateX(int))); connect(translateYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateY(int))); connect(aXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAX(qreal))); connect(aYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAY(qreal))); connect(aZBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAZ(qreal))); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotSetKeepAspectRatio(bool))); + connect(flipXButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipX())); + connect(flipYButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipY())); + connect(rotateCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCW())); + connect(rotateCCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCCW())); // toggle visibility of different free buttons connect(freeMoveRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeRotationRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeScaleRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeShearRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); // only first group for free transform rotationGroup->hide(); moveGroup->show(); scaleGroup->hide(); shearGroup->hide(); // Init Warp Transform Values alphaBox->setSingleStep(0.1); alphaBox->setRange(0, 10, 1); connect(alphaBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetWarpAlpha(qreal))); connect(densityBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetWarpDensity(int))); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpDefaultPointsButtonClicked(bool))); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpCustomPointsButtonClicked(bool))); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpLockPointsButtonClicked())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpResetPointsButtonClicked())); // Init Cage Transform Values cageTransformButtonGroup->setId(cageAddEditRadio, 0); // we need to set manually since Qt Designer generates negative by default cageTransformButtonGroup->setId(cageDeformRadio, 1); connect(cageTransformButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCageOptionsChanged(int))); // Init Liquify Transform Values liquifySizeSlider->setRange(KisLiquifyProperties::minSize(), KisLiquifyProperties::maxSize(), 2); liquifySizeSlider->setExponentRatio(4); liquifySizeSlider->setValue(50.0); connect(liquifySizeSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySizeChanged(qreal))); liquifySizeSlider->setToolTip(i18nc("@info:tooltip", "Size of the deformation brush")); liquifyAmountSlider->setRange(0.0, 1.0, 2); liquifyAmountSlider->setValue(1.0); connect(liquifyAmountSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyAmountChanged(qreal))); liquifyAmountSlider->setToolTip(i18nc("@info:tooltip", "Amount of the deformation you get")); liquifyFlowSlider->setRange(0.0, 1.0, 2); liquifyFlowSlider->setValue(1.0); connect(liquifyFlowSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyFlowChanged(qreal))); liquifyFlowSlider->setToolTip(i18nc("@info:tooltip", "When in non-buildup mode, shows how fast the deformation limit is reached.")); buidupModeComboBox->setCurrentIndex(0); // set to build-up mode by default connect(buidupModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(liquifyBuildUpChanged(int))); buidupModeComboBox->setToolTip(i18nc("@info:tooltip", "Switch between Build Up and Wash mode of painting. Build Up mode adds deformations one on top of the other without any limits. Wash mode gradually deforms the piece to the selected deformation level.")); liquifySpacingSlider->setRange(0.0, 3.0, 2); liquifySizeSlider->setExponentRatio(3); liquifySpacingSlider->setSingleStep(0.01); liquifySpacingSlider->setValue(0.2); connect(liquifySpacingSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySpacingChanged(qreal))); liquifySpacingSlider->setToolTip(i18nc("@info:tooltip", "Space between two sequential applications of the deformation")); liquifySizePressureBox->setChecked(true); connect(liquifySizePressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifySizePressureChanged(bool))); liquifySizePressureBox->setToolTip(i18nc("@info:tooltip", "Scale Size value according to current stylus pressure")); liquifyAmountPressureBox->setChecked(true); connect(liquifyAmountPressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifyAmountPressureChanged(bool))); liquifyAmountPressureBox->setToolTip(i18nc("@info:tooltip", "Scale Amount value according to current stylus pressure")); liquifyReverseDirectionChk->setChecked(false); connect(liquifyReverseDirectionChk, SIGNAL(toggled(bool)), this, SLOT(liquifyReverseDirectionChanged(bool))); liquifyReverseDirectionChk->setToolTip(i18nc("@info:tooltip", "Reverse direction of the current deformation tool")); QSignalMapper *liquifyModeMapper = new QSignalMapper(this); connect(liquifyMove, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyScale, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyRotate, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyOffset, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyUndo, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); liquifyModeMapper->setMapping(liquifyMove, (int)KisLiquifyProperties::MOVE); liquifyModeMapper->setMapping(liquifyScale, (int)KisLiquifyProperties::SCALE); liquifyModeMapper->setMapping(liquifyRotate, (int)KisLiquifyProperties::ROTATE); liquifyModeMapper->setMapping(liquifyOffset, (int)KisLiquifyProperties::OFFSET); liquifyModeMapper->setMapping(liquifyUndo, (int)KisLiquifyProperties::UNDO); connect(liquifyModeMapper, SIGNAL(mapped(int)), SLOT(slotLiquifyModeChanged(int))); liquifyMove->setToolTip(i18nc("@info:tooltip", "Move: drag the image along the brush stroke")); liquifyScale->setToolTip(i18nc("@info:tooltip", "Scale: grow/shrink image under cursor")); liquifyRotate->setToolTip(i18nc("@info:tooltip", "Rotate: twirl image under cursor")); liquifyOffset->setToolTip(i18nc("@info:tooltip", "Offset: shift the image to the right of the stroke direction")); liquifyUndo->setToolTip(i18nc("@info:tooltip", "Undo: erase actions of other tools")); // Connect all edit boxes to the Editing Finished signal connect(densityBox, SIGNAL(editingFinished()), this, SLOT(notifyEditingFinished())); // Connect other widget (not having editingFinished signal) to // the same slot. From Qt 4.6 onwards the sequence of the signal // delivery is definite. connect(cmbFilter, SIGNAL(activated(const KoID &)), this, SLOT(notifyEditingFinished())); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(notifyEditingFinished())); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(notifyEditingFinished())); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(notifyEditingFinished())); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); // Liquify // // liquify brush options do not affect the image directly and are not // saved to undo, so we don't emit notifyEditingFinished() for them // Connect Apply/Reset buttons connect(buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(slotButtonBoxClicked(QAbstractButton *))); // Mode switch buttons connect(freeTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetFreeTransformModeButtonClicked(bool))); connect(warpButton, SIGNAL(clicked(bool)), this, SLOT(slotSetWarpModeButtonClicked(bool))); connect(cageButton, SIGNAL(clicked(bool)), this, SLOT(slotSetCageModeButtonClicked(bool))); connect(perspectiveTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetPerspectiveModeButtonClicked(bool))); connect(liquifyButton, SIGNAL(clicked(bool)), this, SLOT(slotSetLiquifyModeButtonClicked(bool))); // Connect Decorations switcher connect(showDecorationsBox, SIGNAL(toggled(bool)), canvas, SLOT(updateCanvas())); tooBigLabelWidget->hide(); connect(canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); slotUpdateIcons(); } void KisToolTransformConfigWidget::slotUpdateIcons() { freeTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_main")); warpButton->setIcon(KisIconUtils::loadIcon("transform_icons_warp")); cageButton->setIcon(KisIconUtils::loadIcon("transform_icons_cage")); perspectiveTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_perspective")); liquifyButton->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_main")); liquifyMove->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_move")); liquifyScale->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_resize")); liquifyRotate->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_rotate")); liquifyOffset->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_offset")); liquifyUndo->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_erase")); middleRightButton->setIcon(KisIconUtils::loadIcon("arrow-right")); topRightButton->setIcon(KisIconUtils::loadIcon("arrow-topright")); middleTopButton->setIcon(KisIconUtils::loadIcon("arrow-up")); topLeftButton->setIcon(KisIconUtils::loadIcon("arrow-topleft")); middleLeftButton->setIcon(KisIconUtils::loadIcon("arrow-left")); bottomLeftButton->setIcon(KisIconUtils::loadIcon("arrow-downleft")); middleBottomButton->setIcon(KisIconUtils::loadIcon("arrow-down")); bottomRightButton->setIcon(KisIconUtils::loadIcon("arrow-downright")); // pressure icons liquifySizePressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); liquifyAmountPressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } double KisToolTransformConfigWidget::radianToDegree(double rad) { double piX2 = 2 * M_PI; if (rad < 0 || rad >= piX2) { rad = fmod(rad, piX2); if (rad < 0) { rad += piX2; } } return (rad * 360. / piX2); } double KisToolTransformConfigWidget::degreeToRadian(double degree) { if (degree < 0. || degree >= 360.) { degree = fmod(degree, 360.); if (degree < 0) degree += 360.; } return (degree * M_PI / 180.); } void KisToolTransformConfigWidget::updateLiquifyControls() { blockUiSlots(); ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); const bool useWashMode = props->useWashMode(); liquifySizeSlider->setValue(props->size()); liquifyAmountSlider->setValue(props->amount()); liquifyFlowSlider->setValue(props->flow()); buidupModeComboBox->setCurrentIndex(useWashMode); liquifySpacingSlider->setValue(props->spacing()); liquifySizePressureBox->setChecked(props->sizeHasPressure()); liquifyAmountPressureBox->setChecked(props->amountHasPressure()); liquifyReverseDirectionChk->setChecked(props->reverseDirection()); KisLiquifyProperties::LiquifyMode mode = static_cast(props->mode()); bool canInverseDirection = mode != KisLiquifyProperties::UNDO; bool canUseWashMode = mode != KisLiquifyProperties::UNDO; liquifyReverseDirectionChk->setEnabled(canInverseDirection); liquifyFlowSlider->setEnabled(canUseWashMode && useWashMode); buidupModeComboBox->setEnabled(canUseWashMode); const qreal maxAmount = canUseWashMode ? 5.0 : 1.0; liquifyAmountSlider->setRange(0.0, maxAmount, 2); unblockUiSlots(); } void KisToolTransformConfigWidget::slotLiquifyModeChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); KisLiquifyProperties::LiquifyMode mode = static_cast(value); if (mode == props->mode()) return; props->setMode(mode); props->loadMode(); updateLiquifyControls(); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizeChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSize(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmount(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyFlowChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setFlow(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyBuildUpChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setUseWashMode(value); // 0 == build up mode / 1 == wash mode notifyConfigChanged(); // we need to enable/disable flow slider updateLiquifyControls(); } void KisToolTransformConfigWidget::liquifySpacingChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSpacing(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizePressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSizeHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountPressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmountHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyReverseDirectionChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setReverseDirection(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config) { blockUiSlots(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM || config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { + quickTransformGroup->setEnabled(config.mode() == ToolTransformArgs::FREE_TRANSFORM); + stackedWidget->setCurrentIndex(0); bool freeTransformIsActive = config.mode() == ToolTransformArgs::FREE_TRANSFORM; if (freeTransformIsActive) { freeTransformButton->setChecked(true); } else { perspectiveTransformButton->setChecked(true); } aXBox->setEnabled(freeTransformIsActive); aYBox->setEnabled(freeTransformIsActive); aZBox->setEnabled(freeTransformIsActive); Q_FOREACH (QAbstractButton *button, m_rotationCenterButtons->buttons()) { button->setEnabled(freeTransformIsActive); freeRotationRadioButton->setEnabled(freeTransformIsActive); } scaleXBox->setValue(config.scaleX() * 100.); scaleYBox->setValue(config.scaleY() * 100.); shearXBox->setValue(config.shearX()); shearYBox->setValue(config.shearY()); translateXBox->setValue(config.transformedCenter().x()); translateYBox->setValue(config.transformedCenter().y()); aXBox->setValue(radianToDegree(config.aX())); aYBox->setValue(radianToDegree(config.aY())); aZBox->setValue(radianToDegree(config.aZ())); aspectButton->setKeepAspectRatio(config.keepAspectRatio()); cmbFilter->setCurrent(config.filterId()); QPointF pt = m_transaction->currentConfig()->rotationCenterOffset(); pt.rx() /= m_transaction->originalHalfWidth(); pt.ry() /= m_transaction->originalHalfHeight(); for (int i = 0; i < 9; i++) { if (qFuzzyCompare(m_handleDir[i].x(), pt.x()) && qFuzzyCompare(m_handleDir[i].y(), pt.y())) { m_rotationCenterButtons->button(i)->setChecked(true); break; } } } else if (config.mode() == ToolTransformArgs::WARP) { stackedWidget->setCurrentIndex(1); warpButton->setChecked(true); if (config.defaultPoints()) { densityBox->setValue(std::sqrt(config.numPoints())); } cmbWarpType->setCurrentIndex((int)config.warpType()); defaultRadioButton->setChecked(config.defaultPoints()); customRadioButton->setChecked(!config.defaultPoints()); densityBox->setEnabled(config.defaultPoints()); customWarpWidget->setEnabled(!config.defaultPoints()); updateLockPointsButtonCaption(); } else if (config.mode() == ToolTransformArgs::CAGE) { // default UI options resetUIOptions(); // we need at least 3 point before we can start actively deforming if (config.origPoints().size() >= 3) { cageTransformDirections->setText(i18n("Switch between editing and deforming cage")); cageAddEditRadio->setVisible(true); cageDeformRadio->setVisible(true); // update to correct radio button if (config.isEditingTransformPoints()) cageAddEditRadio->setChecked(true); else cageDeformRadio->setChecked(true); } } else if (config.mode() == ToolTransformArgs::LIQUIFY) { stackedWidget->setCurrentIndex(3); liquifyButton->setChecked(true); const KisLiquifyProperties *props = config.liquifyProperties(); switch (props->mode()) { case KisLiquifyProperties::MOVE: liquifyMove->setChecked(true); break; case KisLiquifyProperties::SCALE: liquifyScale->setChecked(true); break; case KisLiquifyProperties::ROTATE: liquifyRotate->setChecked(true); break; case KisLiquifyProperties::OFFSET: liquifyOffset->setChecked(true); break; case KisLiquifyProperties::UNDO: liquifyUndo->setChecked(true); break; case KisLiquifyProperties::N_MODES: qFatal("Unsupported mode"); } updateLiquifyControls(); } unblockUiSlots(); } void KisToolTransformConfigWidget::updateLockPointsButtonCaption() { ToolTransformArgs *config = m_transaction->currentConfig(); if (config->isEditingTransformPoints()) { lockUnlockPointsButton->setText(i18n("Lock Points")); } else { lockUnlockPointsButton->setText(i18n("Unlock Points")); } } void KisToolTransformConfigWidget::setApplyResetDisabled(bool disabled) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); Q_ASSERT(applyButton); Q_ASSERT(resetButton); applyButton->setDisabled(disabled); resetButton->setDisabled(disabled); } void KisToolTransformConfigWidget::resetRotationCenterButtons() { int checkedId = m_rotationCenterButtons->checkedId(); if (checkedId >= 0 && checkedId <= 8) { // uncheck the current checked button m_rotationCenterButtons->button(9)->setChecked(true); } } bool KisToolTransformConfigWidget::workRecursively() const { return chkWorkRecursively->isChecked(); } void KisToolTransformConfigWidget::setTooBigLabelVisible(bool value) { tooBigLabelWidget->setVisible(value); } bool KisToolTransformConfigWidget::showDecorations() const { return showDecorationsBox->isChecked(); } void KisToolTransformConfigWidget::blockNotifications() { m_notificationsBlocked++; } void KisToolTransformConfigWidget::unblockNotifications() { m_notificationsBlocked--; } void KisToolTransformConfigWidget::notifyConfigChanged() { if (!m_notificationsBlocked) { emit sigConfigChanged(); } m_configChanged = true; } void KisToolTransformConfigWidget::blockUiSlots() { m_uiSlotsBlocked++; } void KisToolTransformConfigWidget::unblockUiSlots() { m_uiSlotsBlocked--; } void KisToolTransformConfigWidget::notifyEditingFinished() { if (m_uiSlotsBlocked || m_notificationsBlocked || !m_configChanged) return; emit sigEditingFinished(); m_configChanged = false; } void KisToolTransformConfigWidget::resetUIOptions() { // reset tool states since we are done (probably can encapsulate this later) ToolTransformArgs *config = m_transaction->currentConfig(); if (config->mode() == ToolTransformArgs::CAGE) { cageAddEditRadio->setVisible(false); cageAddEditRadio->setChecked(true); cageDeformRadio->setVisible(false); cageTransformDirections->setText(i18n("Create 3 points on the canvas to begin")); // ensure we are on the right options view stackedWidget->setCurrentIndex(2); } } void KisToolTransformConfigWidget::slotButtonBoxClicked(QAbstractButton *button) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); resetUIOptions(); if (button == applyButton) { emit sigApplyTransform(); } else if (button == resetButton) { emit sigResetTransform(); } } void KisToolTransformConfigWidget::slotSetFreeTransformModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(freeTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::FREE_TRANSFORM); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetWarpModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(warpButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::WARP); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetCageModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(cageButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::CAGE); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetLiquifyModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(liquifyButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::LIQUIFY); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetPerspectiveModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(perspectiveTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::PERSPECTIVE_4POINT); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotFilterChanged(const KoID &filterId) { ToolTransformArgs *config = m_transaction->currentConfig(); config->setFilterId(filterId.id()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotRotationCenterChanged(int index) { if (m_uiSlotsBlocked) return; if (index >= 0 && index <= 8) { ToolTransformArgs *config = m_transaction->currentConfig(); double i = m_handleDir[index].x(); double j = m_handleDir[index].y(); config->setRotationCenterOffset(QPointF(i * m_transaction->originalHalfWidth(), j * m_transaction->originalHalfHeight())); notifyConfigChanged(); } } void KisToolTransformConfigWidget::slotSetScaleX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setScaleX(value / 100.); if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int( value/ m_scaleRatio ); scaleYBox->blockSignals(true); scaleYBox->setValue(calculatedValue); config->setScaleY(calculatedValue / 100.); scaleYBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setScaleY(value / 100.); if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int(m_scaleRatio * value); scaleXBox->blockSignals(true); scaleXBox->setValue(calculatedValue); config->setScaleX(calculatedValue / 100.); scaleXBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setShearX((double)value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setShearY((double)value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetTranslateX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setTransformedCenter(QPointF(value, config->transformedCenter().y())); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetTranslateY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setTransformedCenter(QPointF(config->transformedCenter().x(), value)); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetAX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAX(degreeToRadian((double)value)); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAY(degreeToRadian((double)value)); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAZ(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAZ(degreeToRadian((double)value)); notifyConfigChanged(); notifyEditingFinished(); } +void KisToolTransformConfigWidget::slotFlipX() +{ + ToolTransformArgs *config = m_transaction->currentConfig(); + config->setScaleX(config->scaleX() * -1); + notifyConfigChanged(); + notifyEditingFinished(); +} + +void KisToolTransformConfigWidget::slotFlipY() +{ + ToolTransformArgs *config = m_transaction->currentConfig(); + config->setScaleY(config->scaleY() * -1); + notifyConfigChanged(); + notifyEditingFinished(); +} + +void KisToolTransformConfigWidget::slotRotateCW() +{ + ToolTransformArgs *config = m_transaction->currentConfig(); + config->setAZ(normalizeAngle(config->aZ() + M_PI_2)); + notifyConfigChanged(); + notifyEditingFinished(); +} + +void KisToolTransformConfigWidget::slotRotateCCW() +{ + ToolTransformArgs *config = m_transaction->currentConfig(); + config->setAZ(normalizeAngle(config->aZ() - M_PI_2)); + notifyConfigChanged(); + notifyEditingFinished(); +} + // change free transform setting we want to alter (all radio buttons call this) void KisToolTransformConfigWidget::slotTransformAreaVisible(bool value) { Q_UNUSED(value); //QCheckBox sender = (QCheckBox)(*)(QObject::sender()); QString senderName = QObject::sender()->objectName(); // only show setting with what we have selected rotationGroup->hide(); shearGroup->hide(); scaleGroup->hide(); moveGroup->hide(); if ("freeMoveRadioButton" == senderName) { moveGroup->show(); } else if ("freeShearRadioButton" == senderName) { shearGroup->show(); } else if ("freeScaleRadioButton" == senderName) { scaleGroup->show(); } else { rotationGroup->show(); } } void KisToolTransformConfigWidget::slotSetKeepAspectRatio(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setKeepAspectRatio(value); if (value) { blockNotifications(); int tmpXScaleBox = scaleXBox->value(); int tmpYScaleBox = scaleYBox->value(); m_scaleRatio = (tmpXScaleBox / (double) tmpYScaleBox); unblockNotifications(); } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetWarpAlpha(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAlpha((double)value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetWarpDensity(int value) { if (m_uiSlotsBlocked) return; setDefaultWarpPoints(value); } void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine) { if (pointsPerLine < 0) { pointsPerLine = DEFAULT_POINTS_PER_LINE; } int nbPoints = pointsPerLine * pointsPerLine; QVector origPoints(nbPoints); QVector transfPoints(nbPoints); qreal gridSpaceX, gridSpaceY; if (nbPoints == 1) { //there is actually no grid origPoints[0] = m_transaction->originalCenterGeometric(); transfPoints[0] = m_transaction->originalCenterGeometric(); } else if (nbPoints > 1) { gridSpaceX = m_transaction->originalRect().width() / (pointsPerLine - 1); gridSpaceY = m_transaction->originalRect().height() / (pointsPerLine - 1); double y = m_transaction->originalRect().top(); for (int i = 0; i < pointsPerLine; ++i) { double x = m_transaction->originalRect().left(); for (int j = 0 ; j < pointsPerLine; ++j) { origPoints[i * pointsPerLine + j] = QPointF(x, y); transfPoints[i * pointsPerLine + j] = QPointF(x, y); x += gridSpaceX; } y += gridSpaceY; } } ToolTransformArgs *config = m_transaction->currentConfig(); config->setDefaultPoints(nbPoints > 0); config->setPoints(origPoints, transfPoints); notifyConfigChanged(); } void KisToolTransformConfigWidget::activateCustomWarpPoints(bool enabled) { ToolTransformArgs *config = m_transaction->currentConfig(); densityBox->setEnabled(!enabled); customWarpWidget->setEnabled(enabled); if (!enabled) { config->setEditingTransformPoints(false); setDefaultWarpPoints(densityBox->value()); } else { config->setEditingTransformPoints(true); setDefaultWarpPoints(0); } updateLockPointsButtonCaption(); } void KisToolTransformConfigWidget::slotWarpDefaultPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(!value); } void KisToolTransformConfigWidget::slotWarpCustomPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(value); } void KisToolTransformConfigWidget::slotWarpResetPointsButtonClicked() { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(true); } void KisToolTransformConfigWidget::slotWarpLockPointsButtonClicked() { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setEditingTransformPoints(!config->isEditingTransformPoints()); if (config->isEditingTransformPoints()) { // reinit the transf points to their original value ToolTransformArgs *config = m_transaction->currentConfig(); int nbPoints = config->origPoints().size(); for (int i = 0; i < nbPoints; ++i) { config->transfPoint(i) = config->origPoint(i); } } updateLockPointsButtonCaption(); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotWarpTypeChanged(int index) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); switch (index) { case KisWarpTransformWorker::AFFINE_TRANSFORM: case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: case KisWarpTransformWorker::RIGID_TRANSFORM: config->setWarpType((KisWarpTransformWorker::WarpType)index); break; default: config->setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotCageOptionsChanged(int value) { if ( value == 0) { slotEditCagePoints(true); } else { slotEditCagePoints(false); } notifyEditingFinished(); } void KisToolTransformConfigWidget::slotEditCagePoints(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->refTransformedPoints() = config->origPoints(); config->setEditingTransformPoints(value); notifyConfigChanged(); } diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h index ba0de48799..a23fc280a5 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h +++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h @@ -1,143 +1,148 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_TRANSFORM_CONFIG_WIDGET_H #define __KIS_TOOL_TRANSFORM_CONFIG_WIDGET_H #include "transform_transaction_properties.h" #include "tool_transform_args.h" #include "ui_wdg_tool_transform.h" class KisCanvas2; class KisToolTransformConfigWidget : public QWidget, private Ui::WdgToolTransform { Q_OBJECT public: KisToolTransformConfigWidget(TransformTransactionProperties *transaction, KisCanvas2 *canvas, bool workRecursively, QWidget *parent); void setApplyResetDisabled(bool disabled); void resetRotationCenterButtons(); void setDefaultWarpPoints(int pointsPerLine = -1); void setTooBigLabelVisible(bool value); bool showDecorations() const; bool workRecursively() const; public Q_SLOTS: void updateConfig(const ToolTransformArgs &config); void slotUpdateIcons(); Q_SIGNALS: void sigConfigChanged(); void sigApplyTransform(); void sigResetTransform(); void sigRestartTransform(); void sigEditingFinished(); public Q_SLOTS: void slotFilterChanged(const KoID &filter); void slotWarpTypeChanged(int index); void slotRotationCenterChanged(int index); void slotSetScaleX(int value); void slotSetScaleY(int value); void slotSetShearX(qreal value); void slotSetShearY(qreal value); void slotSetTranslateX(int value); void slotSetTranslateY(int value); void slotSetAX(qreal value); void slotSetAY(qreal value); void slotSetAZ(qreal value); + void slotFlipX(); + void slotFlipY(); + void slotRotateCW(); + void slotRotateCCW(); + void slotSetWarpAlpha(qreal value); void slotSetWarpDensity(int value); void slotSetKeepAspectRatio(bool value); void slotTransformAreaVisible(bool value); void slotWarpDefaultPointsButtonClicked(bool value); void slotWarpCustomPointsButtonClicked(bool value); void slotWarpLockPointsButtonClicked(); void slotWarpResetPointsButtonClicked(); void slotSetFreeTransformModeButtonClicked(bool); void slotSetWarpModeButtonClicked(bool); void slotSetCageModeButtonClicked(bool); void slotCageOptionsChanged(int); void slotSetPerspectiveModeButtonClicked(bool); void slotSetLiquifyModeButtonClicked(bool); void slotButtonBoxClicked(QAbstractButton *button); void slotEditCagePoints(bool value); void liquifySizeChanged(qreal value); void liquifyAmountChanged(qreal value); void liquifyFlowChanged(qreal value); void liquifyBuildUpChanged(int value); void liquifySpacingChanged(qreal value); void liquifySizePressureChanged(bool value); void liquifyAmountPressureChanged(bool value); void liquifyReverseDirectionChanged(bool value); void slotLiquifyModeChanged(int value); void notifyEditingFinished(); private: // rad being in |R, the returned value is in [0; 360] double radianToDegree(double rad); // degree being in |R, the returned value is in [0; 2*M_PI] double degreeToRadian(double degree); void blockNotifications(); void unblockNotifications(); void notifyConfigChanged(); void blockUiSlots(); void unblockUiSlots(); void activateCustomWarpPoints(bool enabled); void updateLockPointsButtonCaption(); void updateLiquifyControls(); void resetUIOptions(); private: static const int DEFAULT_POINTS_PER_LINE; private: TransformTransactionProperties *m_transaction; QPointF m_handleDir[9]; QButtonGroup *m_rotationCenterButtons; int m_notificationsBlocked; int m_uiSlotsBlocked; double m_scaleRatio; bool m_configChanged; }; #endif /* __KIS_TOOL_TRANSFORM_CONFIG_WIDGET_H */ diff --git a/plugins/tools/tool_transform2/wdg_tool_transform.ui b/plugins/tools/tool_transform2/wdg_tool_transform.ui index 4273b1759b..ab0b96f5aa 100755 --- a/plugins/tools/tool_transform2/wdg_tool_transform.ui +++ b/plugins/tools/tool_transform2/wdg_tool_transform.ui @@ -1,2045 +1,2184 @@ WdgToolTransform 0 0 424 558 0 0 0 0 16777215 16777215 0 0 Qt::LeftToRight 0 0 0 0 0 4 QFrame::NoFrame QFrame::Plain 0 1 1 1 1 0 0 0 Free Free Transform true true true true 0 0 Perspective Perspective true true true 0 0 Warp Warp true false true true 0 0 Cage Cage true true true 0 0 Liquify Liquify true true true 0 0 Free Transform Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::StyledPanel QFrame::Raised 0 0 0 0 0 0 0 12 QLayout::SetFixedSize 10 10 20 10 15 0 0 Qt::RightToLeft Filter: Qt::AlignCenter cmbFilter QLayout::SetDefaultConstraint 0 true 0 0 true true 0 0 true true 0 0 true true 0 0 true true true 0 0 true true 0 0 true true 0 0 true true 0 0 true true 0 0 true true 0 0 Position true freeTransformRadioGroup Rotate freeTransformRadioGroup Scale freeTransformRadioGroup Shear freeTransformRadioGroup 0 0 0 0 75 true Qt::LeftToRight false off canvas Qt::RichText false true Qt::NoTextInteraction Qt::Horizontal 40 20 true 0 0 240 100 Rotation false false false 2 5 0 7 0 QFormLayout::AllNonFixedFieldsGrow 5 5 0 0 0 0 Rotate around X-Axis Qt::LeftToRight x: aXBox Rotate around X-Axis 0 0 0 0 Rotate around Y-Axis Qt::LeftToRight y: aYBox Rotate around Y-Axis 0 0 Rotate around Z-Axis 0 0 0 0 Rotate around Z-Axis Qt::LeftToRight z: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter aZBox 0 0 240 70 Scale false false false 2 5 0 0 5 0 0 0 0 Horizontal Scale width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter scaleXBox 0 0 Horizontal Scale % 0 0 Vertical Scale % 0 0 0 0 Vertical Scale height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter scaleYBox 0 0 20 25 0 0 240 70 Position false false false 2 5 0 0 QFormLayout::AllNonFixedFieldsGrow 0 0 Horizontal Translation Qt::LeftToRight x: translateXBox 0 0 Horizontal Translation Qt::LeftToRight 0 0 Vertical Translation 0 0 Vertical Translation y: translateYBox 0 0 240 80 false Shear Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false false false 2 5 0 0 QFormLayout::AllNonFixedFieldsGrow 8 20 0 0 Horizontal Shear x: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter shearXBox 0 0 Vertical Shear 0 0 0 0 Vertical Shear y: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter shearYBox 0 0 Horizontal Shear Qt::Vertical QSizePolicy::Preferred 20 20 + + + + 6 + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Flip selection horizontally + + + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Flip selection vertically + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Rotate selection counter-clockwise 90 degrees + + + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Rotate the selection clockwise 90 degrees + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 20 + + + + 10 0 251 51 10 0 0 0 0 0 2 0 Flexibility: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter alphaBox 0 0 0 0 Anchor Strength: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter cmbWarpType 10 70 260 111 false Anchor Points false false 10 30 241 26 0 0 0 0 0 0 0 Subdivide true true buttonGroup Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 3 10 60 241 31 0 0 0 0 0 0 0 Draw true buttonGroup false 0 0 0 0 0 Clear Points Lock Points Create 3 points on the canvas to begin Add/Edit Anchor Points true cageTransformButtonGroup true Deform Layer cageTransformButtonGroup Qt::Vertical 20 40 0 0 0 <html><head/><body><p><br/></p></body></html> QFrame::NoFrame QFrame::Plain 0 2 2 15 0 0 Move true true true true 0 0 Scale true true true 0 0 Rotate true true true 0 0 Offset true true true 0 0 Undo true true true 3 5 0 0 51 0 Reverse: 0 0 49 0 Spacing: 0 0 32 0 Flow: 0 0 27 0 Size: 0 0 51 0 Amount: 0 0 38 0 Mode: 0 0 0 Build Up Wash true 0 0 true Pressure true true true Pressure true true true Qt::Vertical 20 40 6 6 0 0 32 16777215 Show Decorations true false 0 0 32 16777215 Work Recursively true true Qt::Horizontal 13 13 Qt::LeftToRight Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Reset true KoAspectButton QWidget
KoAspectButton.h
1
KisCmbIDList QComboBox
widgets/kis_cmb_idlist.h
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1