diff --git a/libs/image/tiles3/kis_tiled_data_manager.cc b/libs/image/tiles3/kis_tiled_data_manager.cc index 2191de50af..2d28bb6afb 100644 --- a/libs/image/tiles3/kis_tiled_data_manager.cc +++ b/libs/image/tiles3/kis_tiled_data_manager.cc @@ -1,812 +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 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(); 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 = KisTileSP(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 = KisTileSP(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 = KisTileSP(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/libkis/Document.cpp b/libs/libkis/Document.cpp index c97b8a68bd..b07bd901d5 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,449 +1,449 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct Document::Private { Private() {} QPointer document; bool ownsDocument {false}; }; Document::Document(KisDocument *document, bool ownsDocument, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; d->ownsDocument = ownsDocument; } Document::~Document() { qDebug() << "Document" << this << "deleted"; delete d; } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { return new Node(d->document->image(), activeNodes.first()); } return new Node(d->document->image(), d->document->image()->root()->firstChild()); } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; d->document->image()->lock(); bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->unlock(); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->lock(); d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->unlock(); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { KoXmlDocument doc = KoXmlDocument(true); QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString::null; return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setHeight(value); image->resizeImage(rc); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return new Node(image, image->root()); } Selection *Document::selection() const { return 0; } void Document::setSelection(Selection* value) { } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(value); image->resizeImage(rc); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes(); } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes(); } void Document::setyRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes); } -QByteArray Document::pixelData() const +QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); - quint8 *data = new quint8[image->width() * image->height() * dev->pixelSize()]; - dev->readBytes(data, 0, 0, image->width(), image->height()); - ba = QByteArray((const char*)data, (int)(image->width() * image->height() * dev->pixelSize())); + quint8 *data = new quint8[w * h * dev->pixelSize()]; + dev->readBytes(data, x, y, w, h); + ba = QByteArray((const char*)data, (int)(w * h * dev->pixelSize())); delete[] data; return ba; } bool Document::close() { if (d->ownsDocument) { KisPart::instance()->removeDocument(d->document); } bool retval = d->document->closeUrl(false); if (d->ownsDocument) { delete d->document; } return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; return d->document->exportDocument(QUrl::fromLocalFile(filename)); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->lock(); d->document->image()->flatten(); d->document->image()->unlock(); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); } void Document::resizeImage(int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } bool Document::save() { if (!d->document) return false; return d->document->save(); } bool Document::saveAs(const QString &filename) { if (!d->document) return false; return d->document->saveAs(QUrl::fromLocalFile(filename)); } void Document::openView() { } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType == "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType == "localselectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } QPointer Document::document() const { return d->document; } diff --git a/libs/libkis/Document.h b/libs/libkis/Document.h index 8d49e528ca..d928cd32f2 100644 --- a/libs/libkis/Document.h +++ b/libs/libkis/Document.h @@ -1,233 +1,264 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_DOCUMENT_H #define LIBKIS_DOCUMENT_H #include #include "kritalibkis_export.h" #include "libkis.h" class KisDocument; /** * Document */ class KRITALIBKIS_EXPORT Document : public QObject { Q_OBJECT Q_DISABLE_COPY(Document) public: explicit Document(KisDocument *document, bool ownsDocument = false, QObject *parent = 0); virtual ~Document(); public Q_SLOTS: bool batchmode() const; void setBatchmode(bool value); Node* activeNode() const; void setActiveNode(Node* value); /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return false if the colorProfile name does not correspond to to a registered profile or if assigning * the profile failed. */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the nodes and the image to the given colorspace. The conversion is * done with Perceptual as intent, High Quality and No LCMS Optimizations as flags and no blackpoint * compensation. * * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. * @return false the combination of these arguments does not correspond to a colorspace. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief documentInfo creates and XML document representing document and author information. * @return a string containing a valid XML document */ QString documentInfo() const; void setDocumentInfo(const QString &document); QString fileName() const; void setFileName(QString value); int height() const; void setHeight(int value); QString name() const; void setName(QString value); /** * @return the resolution in pixels per inch */ int resolution() const; /** * @brief setResolution set the resolution of the image; this does not scale the image * @param value the resolution in pixels per inch */ void setResolution(int value); Node* rootNode() const; Selection* selection() const; void setSelection(Selection* value); int width() const; void setWidth(int value); /** * @return xRes the horizontal resolution of the image in pixels per pt (there are 72 pts to an inch) */ double xRes() const; void setXRes(double xRes) const; /** * @return yRes the vertical resolution of the image in pixels per pt (there are 72 pts to an inch) */ double yRes() const; void setyRes(double yRes) const; - QByteArray pixelData() const; - + /** + * @brief pixelData reads the given rectangle from the image projection and returns it as a byte + * array. The pixel data starts top-left, and is ordered row-first. + * + * The byte array can be interpreted as follows: 8 bits images have one byte per channel, + * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, + * representing an unsigned short. 16 bits float images have two bytes per channel, representing + * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a + * float. + * + * You can read outside the image boundaries; those pixels will be transparent black. + * + * The order of channels is: + * + *
    + *
  • Integer RGBA: Blue, Green, Red, Alpha + *
  • Float RGBA: Red, Green, Blue, Alpha + *
  • LabA: L, a, b, Alpha + *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha + *
  • XYZA: X, Y, Z, A + *
  • YCbCrA: Y, Cb, Cr, Alpha + *
+ * + * The byte array is a copy of the original image data. In Python, you can use bytes, bytearray + * and the struct module to interpret the data and construct, for instance, a Pillow Image object. + * + * @param x x position from where to start reading + * @param y y position from where to start reading + * @param w row length to read + * @param h number of rows to read + * @return a QByteArray with the pixel data. The byte array may be empty. + */ + QByteArray pixelData(int x, int y, int w, int h) const; bool close(); void crop(int x, int y, int w, int h); bool exportImage(const QString &filename, const InfoObject &exportConfiguration); void flatten(); void resizeImage(int w, int h); bool save(); bool saveAs(const QString &filename); void openView(); /** * @brief createNode create a new node of the given type. The node is not added * to the node hierarchy; you need to do that by finding the right parent node, * getting its list of child nodes and adding the node in the right place, then * calling Node::SetChildNodes * * @param name The name of the node * * @param nodeType The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • localselectionmask *
* * When relevant, the new Node will have the colorspace of the image by default; * that can be changed with Node::setColorSpace. * * The settings and selections for relevant layer and mask types can also be set * after the Node has been created. * * @return the new Node. */ Node* createNode(const QString &name, const QString &nodeType); Q_SIGNALS: void nodeCreated(Node *node); private: friend class Krita; friend class Window; QPointer document() const; private: struct Private; Private *const d; }; #endif // LIBKIS_DOCUMENT_H diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp index 8570185e87..ae26a872b0 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,407 +1,424 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "Krita.h" #include "Node.h" #include "Channel.h" #include "Generator.h" #include "Filter.h" #include "Transformation.h" #include "Selection.h" struct Node::Private { Private() {} KisImageSP image; KisNodeSP node; }; Node::Node(KisImageSP image, KisNodeSP node, QObject *parent) : QObject(parent) , d(new Private) { d->image = image; d->node = node; } Node::~Node() { delete d; } bool Node::alphaLocked() const { if (!d->node) return false; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { return paintLayer->alphaLocked(); } return false; } void Node::setAlphaLocked(bool value) { if (!d->node) return; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { paintLayer->setAlphaLocked(value); } } QString Node::blendingMode() const { if (!d->node) return QString(); return d->node->compositeOpId(); } void Node::setBlendingMode(QString value) { if (!d->node) return; d->node->setCompositeOpId(value); } QList Node::channels() const { QList channels; if (!d->node) return channels; if (!d->node->inherits("KisLayer")) return channels; Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) { Channel *channel = new Channel(d->node, info); channels << channel; } return channels; } QList Node::childNodes() const { QList nodes; if (d->node) { int childCount = d->node->childCount(); for (int i = 0; i < childCount; ++i) { nodes << new Node(d->image, d->node->at(i)); } } return nodes; } void Node::setChildNodes(QList value) { if (!d->node) return; } QString Node::colorLabel() const { if (!d->node) return QString(); return QString(); } void Node::setColorLabel(QString value) { if (!d->node) return; } QString Node::colorDepth() const { if (!d->node) return ""; return d->node->colorSpace()->colorDepthId().id(); } QString Node::colorModel() const { if (!d->node) return ""; return d->node->colorSpace()->colorModelId().id(); } QString Node::colorProfile() const { if (!d->node) return ""; return d->node->colorSpace()->profile()->name(); } +void Node::setColorProfile(const QString &colorProfile) +{ + +} + void Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { } bool Node::inheritAlpha() const { if (!d->node) return false; return false; } void Node::setInheritAlpha(bool value) { if (!d->node) return; } bool Node::locked() const { if (!d->node) return false; return false; } void Node::setLocked(bool value) { if (!d->node) return; } QString Node::name() const { if (!d->node) return QString(); return d->node->name(); } void Node::setName(QString value) { if (!d->node) return; } int Node::opacity() const { if (!d->node) return 0; return d->node->opacity(); } void Node::setOpacity(int value) { if (!d->node) return; d->node->setOpacity(value); } Node* Node::parentNode() const { if (!d->node) return 0; return 0; } void Node::setParentNode(Node* value) { if (!d->node) return; } QString Node::type() const { if (!d->node) return QString(); return QString(); } void Node::setType(QString value) { if (!d->node) return; } bool Node::visible() const { if (!d->node) return false; return false; } void Node::setVisible(bool value) { if (!d->node) return; } InfoObject* Node::metaDataInfo() const { if (!d->node) return 0; return 0; } void Node::setMetaDataInfo(InfoObject* value) { if (!d->node) return; } Generator* Node::generator() const { if (!d->node) return 0; return 0; } void Node::setGenerator(Generator* value) { if (!d->node) return; } Filter* Node::filter() const { if (!d->node) return 0; return 0; } void Node::setFilter(Filter* value) { if (!d->node) return; } Transformation* Node::transformation() const { if (!d->node) return 0; return 0; } void Node::setTransformation(Transformation* value) { if (!d->node) return; } Selection* Node::selection() const { if (!d->node) return 0; return 0; } void Node::setSelection(Selection* value) { if (!d->node) return; } QString Node::fileName() const { if (!d->node) return QString(); return QString(); } void Node::setFileName(QString value) { if (!d->node) return; } -QByteArray Node::pixelData() const +QByteArray Node::pixelData(int x, int y, int w, int h) const { - if (!d->node) return QByteArray(); - return QByteArray(); + QByteArray ba; + + if (!d->node) return ba; + + KisPaintDeviceSP dev = d->node->projection(); + quint8 *data = new quint8[w * h * dev->pixelSize()]; + dev->readBytes(data, x, y, w, h); + ba = QByteArray((const char*)data, (int)(w * h * dev->pixelSize())); + delete[] data; + + return ba; } -void Node::setPixelData(QByteArray value) +void Node::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->node) return; + KisPaintDeviceSP dev = d->node->paintDevice(); + if (!dev) return; + dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } QRect Node::bounds() const { if (!d->node) return QRect(); return d->node->exactBounds(); } void Node::move(int x, int y) { if (!d->node) return; } void Node::moveToParent(Node *parent) { if (!d->node) return; } void Node::remove() { if (!d->node) return; } Node* Node::duplicate() { if (!d->node) return 0; return 0; } bool Node::save(const QString &filename, double xRes, double yRes) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); QRect bounds = d->node->exactBounds(); QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);; QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), projection->compositionSourceColorSpace(), d->node->name()); dst->setResolution(xRes, yRes); doc->setFileBatchMode(Krita::instance()->batchmode()); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity()); paintLayer->paintDevice()->makeCloneFrom(projection, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); doc->setOutputMimeType(mimefilter.toLatin1()); bool r = doc->exportDocument(QUrl::fromLocalFile(filename)); if (!r) { qWarning() << doc->errorMessage(); } return r; } KisPaintDeviceSP Node::paintDevice() const { return d->node->paintDevice(); } KisImageSP Node::image() const { return d->image; } KisNodeSP Node::node() const { return d->node; } diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h index 2221a535ee..9bbddc6d1d 100644 --- a/libs/libkis/Node.h +++ b/libs/libkis/Node.h @@ -1,233 +1,301 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_NODE_H #define LIBKIS_NODE_H #include #include #include "kritalibkis_export.h" #include "libkis.h" /** * Node */ class KRITALIBKIS_EXPORT Node : public QObject { Q_OBJECT Q_DISABLE_COPY(Node) public: explicit Node(KisImageSP image, KisNodeSP node, QObject *parent = 0); virtual ~Node(); - +public Q_SLOTS: /** * @brief alphaLocked checks whether the node is a paint layer and returns whether it is alpha locked * @return whether the paint layer is alpha locked, or false if the node is not a paint layer */ bool alphaLocked() const; /** * @brief setAlphaLocked set the layer to value if the the node is paint layer. */ void setAlphaLocked(bool value); /** * @return the blending mode of the layer. The values of the blending modes are defined in @see KoCompositeOpRegistry.h */ QString blendingMode() const; /** * @brief setBlendingMode set the blending mode of the node to the given value * @param value one of the string values from @see KoCompositeOpRegistry.h */ void setBlendingMode(QString value); /** * @brief channels creates a list of Channel objects that can be used individually to * show or hide certain channels, and to retrieve the contents of each channel in a * node separately. * * Only layers have channels, masks do not, and calling channels on a Node that is a mask * will return an empty list. * * @return the list of channels ordered in by position of the channels in pixel position */ QList channels() const; /** * Return a list of child nodes of the current node. The nodes are ordered from the bottommost up. * The function is not recursive. */ QList childNodes() const; /** * @brief setChildNodes this replaces the existing set of child nodes with the new set. * @param value */ void setChildNodes(QList value); /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile */ void setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the node to the given colorspace * @param colorModel A string describing the color model of the node: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combinatiojn. */ void setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); QString colorLabel() const; void setColorLabel(QString value); bool inheritAlpha() const; void setInheritAlpha(bool value); bool locked() const; void setLocked(bool value); QString name() const; void setName(QString value); int opacity() const; void setOpacity(int value); Node* parentNode() const; void setParentNode(Node* value); QString type() const; void setType(QString value); bool visible() const; void setVisible(bool value); InfoObject* metaDataInfo() const; void setMetaDataInfo(InfoObject* value); Generator* generator() const; void setGenerator(Generator* value); Filter* filter() const; void setFilter(Filter* value); Transformation* transformation() const; void setTransformation(Transformation* value); + /** + * Get the embedded selection object. This does not return the local selection mask, + * which is a child node, but the embedded selection for filter and generator layers + * and for masks. + */ Selection* selection() const; void setSelection(Selection* value); QString fileName() const; void setFileName(QString value); - QByteArray pixelData() const; - void setPixelData(QByteArray value); - - QRect bounds() const; + /** + * @brief pixelData reads the given rectangle from the Node's projection (that is, what the node + * looks like after all sub-Nodes (like layers in a group or masks on a layer) have been applied, + * and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. + * + * The byte array can be interpreted as follows: 8 bits images have one byte per channel, + * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, + * representing an unsigned short. 16 bits float images have two bytes per channel, representing + * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a + * float. + * + * You can read outside the node boundaries; those pixels will be transparent black. + * + * The order of channels is: + * + *
    + *
  • Integer RGBA: Blue, Green, Red, Alpha + *
  • Float RGBA: Red, Green, Blue, Alpha + *
  • GrayA: Gray, Alpha + *
  • Selection: selectedness + *
  • LabA: L, a, b, Alpha + *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha + *
  • XYZA: X, Y, Z, A + *
  • YCbCrA: Y, Cb, Cr, Alpha + *
+ * + * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray + * and the struct module to interpret the data and construct, for instance, a Pillow Image object. + * + * If you read the projection of a mask, you get the selection bytes, which is one channel with + * values in the range from 0..255. To read the selection mask of a filter layer or generator layer + * first get the Node's Selection object and read its pixel data. + * + * If you want to change the pixels of a node you can write the pixels back after manipulation + * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups + * or file layers. + * + * @param x x position from where to start reading + * @param y y position from where to start reading + * @param w row length to read + * @param h number of rows to read + * @return a QByteArray with the pixel data. The byte array may be empty. + */ + QByteArray pixelData(int x, int y, int w, int h) const; + /** + * @brief setPixelData writes the given bytes, of which there must be enough, into the + * Node, if the Node has writable pixel data: + * + *
    + *
  • paint layer: the layer's original pixels are overwritten + *
  • filter layer, generator layer, any mask: the embedded selection's pixels are overwritten. + * Note: for these + *
+ * + * File layers, Group layers, Clone layers cannot be written to. Calling setPixelData on + * those layer types will silently do nothing. + * + * @param value the byte array representing the pixels. There must be enough bytes available. + * Krita will take the raw pointer from the QByteArray and start reading, not stopping before + * (number of channels * size of channel * w * h) bytes are read. + * + * @param x the x position to start writing from + * @param y the y position to start writing from + * @param w the width of each row + * @param h the number of rows to write + */ + void setPixelData(QByteArray value, int x, int y, int w, int h); -public Q_SLOTS: + QRect bounds() const; void move(int x, int y); void moveToParent(Node *parent); void remove(); Node* duplicate(); /** * @brief save exports the given node with this filename. The extension of the filename determins the filetype. * @param filename the filename including extension * @param xRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @param yRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @return true if saving succeeded, false if it failed. */ bool save(const QString &filename, double xRes, double yRes); private: friend class Filter; friend class Document; /** * @brief paintDevice gives access to the internal paint device of this Node * @return the paintdevice or 0 if the node does not have an editable paint device. */ KisPaintDeviceSP paintDevice() const; KisImageSP image() const; KisNodeSP node() const; struct Private; - Private *d; + Private *const d; }; #endif // LIBKIS_NODE_H diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp index a7a681f1e2..16fddd34d2 100644 --- a/libs/libkis/Selection.cpp +++ b/libs/libkis/Selection.cpp @@ -1,142 +1,154 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Selection.h" +#include +#include +#include + +#include + struct Selection::Private { Private() {} + KisSelectionSP selection; }; +Selection::Selection(KisSelectionSP selection, QObject *parent) + : QObject(parent) + , d(new Private) +{ + d->selection = selection; +} + + Selection::Selection(QObject *parent) : QObject(parent) , d(new Private) { + d->selection = new KisSelection(); } Selection::~Selection() { delete d; } int Selection::width() const { return 0; } -void Selection::setWidth(int value) -{ -} - - int Selection::height() const { return 0; } -void Selection::setHeight(int value) -{ -} - - int Selection::x() const { return 0; } -void Selection::setX(int value) -{ -} - - int Selection::y() const { return 0; } -void Selection::setY(int value) +void Selection::move(int x, int y) { -} - -QString Selection::type() const -{ - return QString(); } -void Selection::setType(QString value) -{ -} void Selection::clear() { } void Selection::contract(int value) { } -Selection* Selection::copy(int x, int y, int w, int h) +void Selection::cut(Node* node) { - return 0; } -void Selection::cut(Node* node) +void Selection::paste(Node *source, Node*destination) { + } void Selection::deselect() { } void Selection::expand(int value) { } void Selection::feather(int value) { } void Selection::fill(Node* node) { } void Selection::grow(int value) { } void Selection::invert() { } void Selection::resize(int w, int h) { } void Selection::rotate(int degrees) { } void Selection::select(int x, int y, int w, int h, int value) { } void Selection::selectAll(Node *node) { } +QByteArray Selection::pixelData(int x, int y, int w, int h) const +{ + QByteArray ba; + if (!d->selection) return ba; + KisPaintDeviceSP dev = d->selection->projection(); + quint8 *data = new quint8[w * h]; + dev->readBytes(data, x, y, w, h); + ba = QByteArray((const char*)data, (int)(w * h)); + delete[] data; + return ba; +} + +void Selection::setPixelData(QByteArray value, int x, int y, int w, int h) +{ + if (!d->selection) return; + KisPixelSelectionSP dev = d->selection->pixelSelection(); + if (!dev) return; + dev->writeBytes((const quint8*)value.constData(), x, y, w, h); +} diff --git a/libs/libkis/Selection.h b/libs/libkis/Selection.h index 279ec6701e..817c3b2cb5 100644 --- a/libs/libkis/Selection.h +++ b/libs/libkis/Selection.h @@ -1,103 +1,118 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_SELECTION_H #define LIBKIS_SELECTION_H #include #include "kritalibkis_export.h" #include "libkis.h" +#include /** * Selection */ class KRITALIBKIS_EXPORT Selection : public QObject { Q_OBJECT - Q_DISABLE_COPY(Selection) - - Q_PROPERTY(int Width READ width WRITE setWidth) - Q_PROPERTY(int Height READ height WRITE setHeight) - Q_PROPERTY(int X READ x WRITE setX) - Q_PROPERTY(int Y READ y WRITE setY) - Q_PROPERTY(QString Type READ type WRITE setType) public: + Selection(KisSelectionSP selection, QObject *parent = 0); explicit Selection(QObject *parent = 0); virtual ~Selection(); +public Q_SLOTS: + int width() const; - void setWidth(int value); int height() const; - void setHeight(int value); int x() const; - void setX(int value); int y() const; - void setY(int value); - - QString type() const; - void setType(QString value); + void move(int x, int y); - -public Q_SLOTS: - void clear(); void contract(int value); - Selection* copy(int x, int y, int w, int h); - void cut(Node* node); + void paste(Node *source, Node*destination); + void deselect(); void expand(int value); void feather(int value); void fill(Node* node); void grow(int value); void invert(); void resize(int w, int h); void rotate(int degrees); void select(int x, int y, int w, int h, int value); void selectAll(Node *node); - - -Q_SIGNALS: - - + /** + * @brief pixelData reads the given rectangle from the Selection's mask and returns it as a + * byte array. The pixel data starts top-left, and is ordered row-first. + * + * The byte array will contain one byte for every pixel, representing the selectedness. 0 + * is totally unselected, 255 is fully selected. + * + * You can read outside the Selection's boundaries; those pixels will be unselected. + * + * The byte array is a copy of the original selection data. + * @param x x position from where to start reading + * @param y y position from where to start reading + * @param w row length to read + * @param h number of rows to read + * @return a QByteArray with the pixel data. The byte array may be empty. + */ + QByteArray pixelData(int x, int y, int w, int h) const; + + /** + * @brief setPixelData writes the given bytes, of which there must be enough, into the + * Selection. + * + * @param value the byte array representing the pixels. There must be enough bytes available. + * Krita will take the raw pointer from the QByteArray and start reading, not stopping before + * (w * h) bytes are read. + * + * @param x the x position to start writing from + * @param y the y position to start writing from + * @param w the width of each row + * @param h the number of rows to write + */ + void setPixelData(QByteArray value, int x, int y, int w, int h); private: struct Private; - const Private *const d; + Private *const d; }; #endif // LIBKIS_SELECTION_H diff --git a/plugins/extensions/pykrita/sip/krita/Document.sip b/plugins/extensions/pykrita/sip/krita/Document.sip index aa91f398c9..73c4b92742 100644 --- a/plugins/extensions/pykrita/sip/krita/Document.sip +++ b/plugins/extensions/pykrita/sip/krita/Document.sip @@ -1,51 +1,51 @@ class Document : QObject /NoDefaultCtors/ { %TypeHeaderCode #include "Document.h" %End Document(const Document & __0); public Q_SLOTS: Node * activeNode() const; void setActiveNode(Node* value); bool batchmode() const; void setBatchmode(bool value); QString colorDepth() const; QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); void setColorSpace(const QString &value, const QString &colorDepth, const QString &colorProfile); QString documentInfo() const; void setDocumentInfo(const QString &document); QString fileName() const; void setFileName(QString value); int height() const; void setHeight(int value); QString name() const; void setName(QString value); int resolution() const; void setResolution(int value); Node * rootNode() const; Selection * selection() const; void setSelection(Selection* value); int width() const; void setWidth(int value); double xRes() const; void setXRes(double xRes) const; double yRes() const; void setyRes(double yRes) const; - QByteArray pixelData() const; + QByteArray pixelData(int x, int y, int w, int h) const; bool close(); void crop(int x, int y, int w, int h); bool exportImage(const QString &filename, const InfoObject & exportConfiguration); void flatten(); void resizeImage(int w, int h); bool save(); bool saveAs(const QString & filename); void openView(); Node * createNode(const QString & name, const QString & nodeType); Q_SIGNALS: void nodeCreated(Node *node); private: }; diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip index 246226519d..71e0e6769a 100644 --- a/plugins/extensions/pykrita/sip/krita/Node.sip +++ b/plugins/extensions/pykrita/sip/krita/Node.sip @@ -1,59 +1,59 @@ class Node : QObject { %TypeHeaderCode #include "Node.h" %End Node(const Node & __0); public: virtual ~Node(); +public Q_SLOTS: bool alphaLocked() const; void setAlphaLocked(bool value); QString blendingMode() const; void setBlendingMode(QString value); QList channels() const; QList childNodes() const; void setChildNodes(QList value); QString colorDepth() const; QString colorLabel() const; void setColorLabel(QString value); QString colorModel() const; QString colorProfile() const; void setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); bool inheritAlpha() const; void setInheritAlpha(bool value); bool locked() const; void setLocked(bool value); QString name() const; void setName(QString value); int opacity() const; void setOpacity(int value); Node * parentNode() const; void setParentNode(Node* value); QString type() const; void setType(QString value); bool visible() const; void setVisible(bool value); InfoObject * metaDataInfo() const; void setMetaDataInfo(InfoObject* value); Generator * generator() const; void setGenerator(Generator* value); Filter * filter() const; void setFilter(Filter* value); Transformation * transformation() const; void setTransformation(Transformation* value); Selection * selection() const; void setSelection(Selection* value); QString fileName() const; void setFileName(QString value); - QByteArray pixelData() const; - void setPixelData(QByteArray value); + QByteArray pixelData(int x, int y, int w, int h) const; + void setPixelData(QByteArray value, int x, int y, int w, int h); QRect bounds() const; -public Q_SLOTS: void move(int x, int y); void moveToParent(Node* parent); void remove(); Node * duplicate(); void save(const QString &filename, double xRes, double yRes); Q_SIGNALS: private: }; diff --git a/plugins/extensions/pykrita/sip/krita/Selection.sip b/plugins/extensions/pykrita/sip/krita/Selection.sip index 69bef57131..8927a8370d 100644 --- a/plugins/extensions/pykrita/sip/krita/Selection.sip +++ b/plugins/extensions/pykrita/sip/krita/Selection.sip @@ -1,37 +1,37 @@ class Selection : QObject { %TypeHeaderCode #include "Selection.h" %End Selection(const Selection & __0); public: Selection(QObject* parent /TransferThis/ = 0); virtual ~Selection(); +public Q_SLOTS: + int width() const; - void setWidth(int value); int height() const; - void setHeight(int value); int x() const; - void setX(int value); int y() const; - void setY(int value); - QString type() const; - void setType(QString value); -public Q_SLOTS: + void move(int x, int y); + void clear(); void contract(int value); - Selection * copy(int x, int y, int w, int h); - void cut(Node* node); + void cut(Node *node); + void paste(Node *source, Node*destination); void deselect(); void expand(int value); void feather(int value); void fill(Node* node); void grow(int value); void invert(); void resize(int w, int h); void rotate(int degrees); void select(int x, int y, int w, int h, int value); void selectAll(Node* node); + QByteArray pixelData(int x, int y, int w, int h) const; + void setPixelData(QByteArray value, int x, int y, int w, int h); + Q_SIGNALS: private: };