diff --git a/libs/ui/canvas/kis_image_pyramid.cpp b/libs/ui/canvas/kis_image_pyramid.cpp index 75d2a4f786..8b82d5de45 100644 --- a/libs/ui/canvas/kis_image_pyramid.cpp +++ b/libs/ui/canvas/kis_image_pyramid.cpp @@ -1,537 +1,668 @@ /* * 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_image_pyramid.h" #include #include #include #include #include #include #include "kis_display_filter.h" #include "kis_painter.h" #include "kis_iterator_ng.h" #include "kis_datamanager.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include "kis_config.h" #include "kis_image_config.h" //#define DEBUG_PYRAMID #include #ifdef HAVE_OCIO #include #include #endif #define ORIGINAL_INDEX 0 #define FIRST_NOT_ORIGINAL_INDEX 1 #define SCALE_FROM_INDEX(idx) (1./qreal(1<<(idx))) /************* AUXILIARY FUNCTIONS **********************************/ #include #ifdef HAVE_OPENEXR #include #endif #define ceiledSize(sz) QSize(ceil((sz).width()), ceil((sz).height())) #define isOdd(x) ((x) & 0x01) /** * Aligns @p value to the lowest integer not smaller than @p value and * that is a divident of alignment */ inline void alignByPow2Hi(qint32 &value, qint32 alignment) { qint32 mask = alignment - 1; value |= mask; value++; } /** * Aligns @p value to the lowest integer not smaller than @p value and * that is, increased by one, a divident of alignment */ inline void alignByPow2ButOneHi(qint32 &value, qint32 alignment) { qint32 mask = alignment - 1; value |= mask; } /** * Aligns @p value to the highest integer not exceeding @p value and * that is a divident of @p alignment */ inline void alignByPow2Lo(qint32 &value, qint32 alignment) { qint32 mask = alignment - 1; value &= ~mask; } inline void alignRectBy2(qint32 &x, qint32 &y, qint32 &w, qint32 &h) { x -= isOdd(x); y -= isOdd(y); w += isOdd(x); w += isOdd(w); h += isOdd(y); h += isOdd(h); } /************* class KisImagePyramid ********************************/ KisImagePyramid::KisImagePyramid(qint32 pyramidHeight) : m_monitorProfile(0) , m_monitorColorSpace(0) , m_pyramidHeight(pyramidHeight) { configChanged(); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); } KisImagePyramid::~KisImagePyramid() { setImage(0); } void KisImagePyramid::setMonitorProfile(const KoColorProfile* monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { m_monitorProfile = monitorProfile; /** * If you change pixel size here, don't forget to change it * in optimized function downsamplePixels() */ m_monitorColorSpace = KoColorSpaceRegistry::instance()->rgb8(monitorProfile); m_renderingIntent = renderingIntent; m_conversionFlags = conversionFlags; rebuildPyramid(); } void KisImagePyramid::setChannelFlags(const QBitArray &channelFlags) { m_channelFlags = channelFlags; int selectedChannels = 0; const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace(); QList channelInfo = projectionCs->channels(); if (channelInfo.size() != m_channelFlags.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 KisImagePyramid::setDisplayFilter(QSharedPointer displayFilter) { m_displayFilter = displayFilter; } void KisImagePyramid::rebuildPyramid() { m_pyramid.clear(); for (qint32 i = 0; i < m_pyramidHeight; i++) { m_pyramid.append(new KisPaintDevice(m_monitorColorSpace)); } } void KisImagePyramid::clearPyramid() { for (qint32 i = 0; i < m_pyramidHeight; i++) { m_pyramid[i]->clear(); } } void KisImagePyramid::setImage(KisImageWSP newImage) { if (newImage) { m_originalImage = newImage; clearPyramid(); setImageSize(m_originalImage->width(), m_originalImage->height()); // Get the full image size QRect rc = m_originalImage->projection()->exactBounds(); KisImageConfig config(true); int patchWidth = config.updatePatchWidth(); int patchHeight = config.updatePatchHeight(); if (rc.width() * rc.height() <= patchWidth * patchHeight) { retrieveImageData(rc); } else { qint32 firstCol = rc.x() / patchWidth; qint32 firstRow = rc.y() / patchHeight; qint32 lastCol = (rc.x() + rc.width()) / patchWidth; qint32 lastRow = (rc.y() + rc.height()) / patchHeight; for(qint32 i = firstRow; i <= lastRow; i++) { for(qint32 j = firstCol; j <= lastCol; j++) { QRect maxPatchRect(j * patchWidth, i * patchHeight, patchWidth, patchHeight); QRect patchRect = rc & maxPatchRect; retrieveImageData(patchRect); } } } //TODO: check whether there is needed recalculateCache() } } void KisImagePyramid::setImageSize(qint32 w, qint32 h) { Q_UNUSED(w); Q_UNUSED(h); /* nothing interesting */ } void KisImagePyramid::updateCache(const QRect &dirtyImageRect) { retrieveImageData(dirtyImageRect); } void KisImagePyramid::retrieveImageData(const QRect &rect) { // XXX: use QThreadStorage to cache the two patches (512x512) of pixels. Note // that when we do that, we need to reset that cache when the projection's // colorspace changes. const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace(); KisPaintDeviceSP originalProjection = m_originalImage->projection(); quint32 numPixels = rect.width() * rect.height(); QScopedArrayPointer originalBytes( new quint8[originalProjection->colorSpace()->pixelSize() * numPixels]); originalProjection->readBytes(originalBytes.data(), rect); if (m_displayFilter && m_useOcio && projectionCs->colorModelId() == RGBAColorModelID) { #ifdef HAVE_OCIO const KoColorProfile *destinationProfile = m_displayFilter->useInternalColorManagement() ? m_monitorProfile : projectionCs->profile(); const KoColorSpace *floatCs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), destinationProfile); const KoColorSpace *modifiedMonitorCs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), destinationProfile); if (projectionCs->colorDepthId() == Float32BitsColorDepthID) { m_displayFilter->filter(originalBytes.data(), numPixels); } else { QScopedArrayPointer dst(new quint8[floatCs->pixelSize() * numPixels]); projectionCs->convertPixelsTo(originalBytes.data(), dst.data(), floatCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); m_displayFilter->filter(dst.data(), numPixels); originalBytes.swap(dst); } { QScopedArrayPointer dst(new quint8[modifiedMonitorCs->pixelSize() * numPixels]); floatCs->convertPixelsTo(originalBytes.data(), dst.data(), modifiedMonitorCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); originalBytes.swap(dst); } #endif } else { QList channelInfo = projectionCs->channels(); if (m_channelFlags.size() != channelInfo.size()) { setChannelFlags(QBitArray()); } if (!m_channelFlags.isEmpty() && !m_allChannelsSelected) { QScopedArrayPointer dst(new quint8[projectionCs->pixelSize() * numPixels]); int channelSize = channelInfo[m_selectedChannelIndex]->size(); int pixelSize = projectionCs->pixelSize(); KisConfig cfg(true); if (m_onlyOneChannelSelected && !cfg.showSingleChannelAsColor()) { int selectedChannelPos = channelInfo[m_selectedChannelIndex]->pos(); for (uint pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { for (uint channelIndex = 0; channelIndex < projectionCs->channelCount(); ++channelIndex) { - if (channelInfo[channelIndex]->channelType() == KoChannelInfo::COLOR) { - memcpy(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - originalBytes.data() + (pixelIndex * pixelSize) + selectedChannelPos, - channelSize); + if (projectionCs->colorModelId() == LABAColorModelID) { + if (channelIndex == 0) { + memcpy(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), originalBytes.data() + (pixelIndex * pixelSize) + selectedChannelPos, channelSize); + } else if (projectionCs->colorDepthId() == Integer8BitsColorDepthID) { + KoLabU8Traits::channels_type v; + switch (channelIndex) { + case KoLabU8Traits::L_pos: + v = KoLabU8Traits::math_trait::halfValueL; + break; + case KoLabU8Traits::a_pos: + case KoLabU8Traits::b_pos: + v = KoLabU8Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (projectionCs->colorDepthId() == Integer16BitsColorDepthID) { + KoLabU16Traits::channels_type v; + switch (channelIndex) { + case KoLabU16Traits::L_pos: + v = KoLabU16Traits::math_trait::halfValueL; + break; + case KoLabU16Traits::a_pos: + case KoLabU16Traits::b_pos: + v = KoLabU16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (projectionCs->colorDepthId() == Float16BitsColorDepthID) { + KoLabF16Traits::channels_type v; + switch (channelIndex) { + case KoLabF16Traits::L_pos: + v = KoLabF16Traits::math_trait::halfValueL; + break; + case KoLabF16Traits::a_pos: + case KoLabF16Traits::b_pos: + v = KoLabF16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (projectionCs->colorDepthId() == Float32BitsColorDepthID) { + KoLabF32Traits::channels_type v; + switch (channelIndex) { + case KoLabF32Traits::L_pos: + v = KoLabF32Traits::math_trait::halfValueL; + break; + case KoLabF32Traits::a_pos: + case KoLabF32Traits::b_pos: + v = KoLabF32Traits::math_trait::halfValueAB; + break; + default: + v = KoLabF32Traits::math_trait::min; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else { + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); + } + } else { + memcpy(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), originalBytes.data() + (pixelIndex * pixelSize) + selectedChannelPos, channelSize); + } } else if (channelInfo[channelIndex]->channelType() == KoChannelInfo::ALPHA) { memcpy(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), originalBytes.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), channelSize); } } } } else { for (uint pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { for (uint channelIndex = 0; channelIndex < projectionCs->channelCount(); ++channelIndex) { if (m_channelFlags.testBit(channelIndex)) { memcpy(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), originalBytes.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), channelSize); } else { - memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); + if (projectionCs->colorModelId() == LABAColorModelID) { + if (projectionCs->colorDepthId() == Integer8BitsColorDepthID) { + KoLabU8Traits::channels_type v; + switch (channelIndex) { + case KoLabU8Traits::L_pos: + v = KoLabU8Traits::math_trait::halfValueL; + break; + case KoLabU8Traits::a_pos: + case KoLabU8Traits::b_pos: + v = KoLabU8Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (projectionCs->colorDepthId() == Integer16BitsColorDepthID) { + KoLabU16Traits::channels_type v; + switch (channelIndex) { + case KoLabU16Traits::L_pos: + v = KoLabU16Traits::math_trait::halfValueL; + break; + case KoLabU16Traits::a_pos: + case KoLabU16Traits::b_pos: + v = KoLabU16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (projectionCs->colorDepthId() == Float16BitsColorDepthID) { + KoLabF16Traits::channels_type v; + switch (channelIndex) { + case KoLabF16Traits::L_pos: + v = KoLabF16Traits::math_trait::halfValueL; + break; + case KoLabF16Traits::a_pos: + case KoLabF16Traits::b_pos: + v = KoLabF16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (projectionCs->colorDepthId() == Float32BitsColorDepthID) { + KoLabF32Traits::channels_type v; + switch (channelIndex) { + case KoLabF32Traits::L_pos: + v = KoLabF32Traits::math_trait::halfValueL; + break; + case KoLabF32Traits::a_pos: + case KoLabF32Traits::b_pos: + v = KoLabF32Traits::math_trait::halfValueAB; + break; + default: + v = KoLabF32Traits::math_trait::min; + break; + } + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else { + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); + } + } else { + memset(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); + } } } } } originalBytes.swap(dst); } QScopedArrayPointer dst(new quint8[m_monitorColorSpace->pixelSize() * numPixels]); projectionCs->convertPixelsTo(originalBytes.data(), dst.data(), m_monitorColorSpace, numPixels, m_renderingIntent, m_conversionFlags); originalBytes.swap(dst); } m_pyramid[ORIGINAL_INDEX]->writeBytes(originalBytes.data(), rect); } void KisImagePyramid::recalculateCache(KisPPUpdateInfoSP info) { KisPaintDevice *src; KisPaintDevice *dst; QRect currentSrcRect = info->dirtyImageRectVar; for (int i = FIRST_NOT_ORIGINAL_INDEX; i < m_pyramidHeight; i++) { src = m_pyramid[i-1].data(); dst = m_pyramid[i].data(); if (!currentSrcRect.isEmpty()) { currentSrcRect = downsampleByFactor2(currentSrcRect, src, dst); } } #ifdef DEBUG_PYRAMID QImage image = m_pyramid[ORIGINAL_INDEX]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./PYRAMID_BASE.png"); image = m_pyramid[1]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./LEVEL1.png"); image = m_pyramid[2]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./LEVEL2.png"); image = m_pyramid[3]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./LEVEL3.png"); #endif } QRect KisImagePyramid::downsampleByFactor2(const QRect& srcRect, KisPaintDevice* src, KisPaintDevice* dst) { qint32 srcX, srcY, srcWidth, srcHeight; srcRect.getRect(&srcX, &srcY, &srcWidth, &srcHeight); alignRectBy2(srcX, srcY, srcWidth, srcHeight); // Nothing to do if (srcWidth < 1) return QRect(); if (srcHeight < 1) return QRect(); qint32 dstX = srcX / 2; qint32 dstY = srcY / 2; qint32 dstWidth = srcWidth / 2; qint32 dstHeight = srcHeight / 2; KisHLineConstIteratorSP srcIt0 = src->createHLineConstIteratorNG(srcX, srcY, srcWidth); KisHLineConstIteratorSP srcIt1 = src->createHLineConstIteratorNG(srcX, srcY + 1, srcWidth); KisHLineIteratorSP dstIt = dst->createHLineIteratorNG(dstX, dstY, dstWidth); int conseqPixels = 0; for (int row = 0; row < dstHeight; ++row) { do { int srcItConseq = srcIt0->nConseqPixels(); int dstItConseq = dstIt->nConseqPixels(); conseqPixels = qMin(srcItConseq, dstItConseq * 2); Q_ASSERT(!isOdd(conseqPixels)); downsamplePixels(srcIt0->oldRawData(), srcIt1->oldRawData(), dstIt->rawData(), conseqPixels); srcIt1->nextPixels(conseqPixels); dstIt->nextPixels(conseqPixels / 2); } while (srcIt0->nextPixels(conseqPixels)); srcIt0->nextRow(); srcIt0->nextRow(); srcIt1->nextRow(); srcIt1->nextRow(); dstIt->nextRow(); } return QRect(dstX, dstY, dstWidth, dstHeight); } void KisImagePyramid::downsamplePixels(const quint8 *srcRow0, const quint8 *srcRow1, quint8 *dstRow, qint32 numSrcPixels) { /** * FIXME (mandatory): Use SSE and friends here. */ qint16 b = 0; qint16 g = 0; qint16 r = 0; qint16 a = 0; static const qint32 pixelSize = 4; // This is preview argb8 mode for (qint32 i = 0; i < numSrcPixels / 2; i++) { b = srcRow0[0] + srcRow1[0] + srcRow0[4] + srcRow1[4]; g = srcRow0[1] + srcRow1[1] + srcRow0[5] + srcRow1[5]; r = srcRow0[2] + srcRow1[2] + srcRow0[6] + srcRow1[6]; a = srcRow0[3] + srcRow1[3] + srcRow0[7] + srcRow1[7]; dstRow[0] = b / 4; dstRow[1] = g / 4; dstRow[2] = r / 4; dstRow[3] = a / 4; dstRow += pixelSize; srcRow0 += 2 * pixelSize; srcRow1 += 2 * pixelSize; } } int KisImagePyramid::findFirstGoodPlaneIndex(qreal scale, QSize originalSize) { qint32 nearest = 0; for (qint32 i = 0; i < m_pyramidHeight; i++) { qreal planeScale = SCALE_FROM_INDEX(i); if (planeScale < scale) { if (originalSize*scale == originalSize*planeScale) nearest = i; break; } nearest = i; } // FOR DEBUGGING //nearest = 0; //nearest = qMin(1, nearest); dbgRender << "First good plane:" << nearest << "(sc:" << scale << ")"; return nearest; } void KisImagePyramid::alignSourceRect(QRect& rect, qreal scale) { qint32 index = findFirstGoodPlaneIndex(scale, rect.size()); qint32 alignment = 1 << index; dbgRender << "Before alignment:\t" << rect; /** * Assume that KisImage pixels are always positive * It allows us to use binary op-s for aligning */ Q_ASSERT(rect.left() >= 0 && rect.top() >= 0); qint32 x1, y1, x2, y2; rect.getCoords(&x1, &y1, &x2, &y2); alignByPow2Lo(x1, alignment); alignByPow2Lo(y1, alignment); /** * Here is a workaround of Qt's QRect::right()/bottom() * "historical reasons". It should be one pixel smaller * than actual right/bottom position */ alignByPow2ButOneHi(x2, alignment); alignByPow2ButOneHi(y2, alignment); rect.setCoords(x1, y1, x2, y2); dbgRender << "After alignment:\t" << rect; } KisImagePatch KisImagePyramid::getNearestPatch(KisPPUpdateInfoSP info) { qint32 index = findFirstGoodPlaneIndex(qMax(info->scaleX, info->scaleY), info->imageRect.size()); qreal planeScale = SCALE_FROM_INDEX(index); qint32 alignment = 1 << index; alignByPow2Hi(info->borderWidth, alignment); KisImagePatch patch(info->imageRect, info->borderWidth, planeScale, planeScale); patch.setImage(convertToQImageFast(m_pyramid[index], patch.patchRect())); return patch; } void KisImagePyramid::drawFromOriginalImage(QPainter& gc, KisPPUpdateInfoSP info) { KisImagePatch patch = getNearestPatch(info); patch.drawMe(gc, info->viewportRect, info->renderHints); } QImage KisImagePyramid::convertToQImageFast(KisPaintDeviceSP paintDevice, const QRect& unscaledRect) { qint32 x, y, w, h; unscaledRect.getRect(&x, &y, &w, &h); QImage image = QImage(w, h, QImage::Format_ARGB32); paintDevice->dataManager()->readBytes(image.bits(), x, y, w, h); return image; } void KisImagePyramid::configChanged() { KisConfig cfg(true); m_useOcio = cfg.useOcio(); } diff --git a/libs/ui/opengl/kis_texture_tile_update_info.h b/libs/ui/opengl/kis_texture_tile_update_info.h index 60900c9d58..28fd9b110b 100644 --- a/libs/ui/opengl/kis_texture_tile_update_info.h +++ b/libs/ui/opengl/kis_texture_tile_update_info.h @@ -1,375 +1,514 @@ /* * 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_config.h" #include "kis_image.h" #include "kis_paint_device.h" -#include "kis_config.h" -#include +#include "kis_texture_tile_info_pool.h" #include +#include +#include +#include #include -#include "kis_texture_tile_info_pool.h" - class KisTextureTileUpdateInfo; typedef QSharedPointer KisTextureTileUpdateInfoSP; typedef QVector KisTextureTileUpdateInfoSPList; /** * A buffer object for temporary data needed during the update process. * * - the buffer is allocated from the common pool to avoid memory * fragmentation * * - the buffer's lifetime defines the lifetime of the allocated chunk * of memory, so you don't have to thing about free'ing the memory */ class DataBuffer { public: DataBuffer(KisTextureTileInfoPoolSP pool) : m_data(0), m_pixelSize(0), m_pool(pool) { } DataBuffer(int pixelSize, KisTextureTileInfoPoolSP pool) : m_data(0), m_pixelSize(0), m_pool(pool) { allocate(pixelSize); } DataBuffer(DataBuffer &&rhs) : m_data(rhs.m_data), m_pixelSize(rhs.m_pixelSize), m_pool(rhs.m_pool) { rhs.m_data = 0; } DataBuffer& operator=(DataBuffer &&rhs) { swap(rhs); return *this; } ~DataBuffer() { if (m_data) { m_pool->free(m_data, m_pixelSize); } } void allocate(int pixelSize) { Q_ASSERT(!m_data); m_pixelSize = pixelSize; m_data = m_pool->malloc(m_pixelSize); } inline quint8* data() const { return m_data; } void swap(DataBuffer &other) { std::swap(other.m_pixelSize, m_pixelSize); std::swap(other.m_data, m_data); std::swap(other.m_pool, m_pool); } int size() const { return m_data ? m_pool->chunkSize(m_pixelSize) : 0; } KisTextureTileInfoPoolSP pool() const { return m_pool; } int pixelSize() const { return m_pixelSize; } private: Q_DISABLE_COPY(DataBuffer) quint8 *m_data; int m_pixelSize; KisTextureTileInfoPoolSP m_pool; }; class KisTextureTileUpdateInfo { public: 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, 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) { // TODO: check if isBottommost() works correctly when m_originalPatchRect gets aligned // and m_currentImageRect has non-aligned size 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() { } void retrieveData(KisPaintDeviceSP projectionDevice, const QBitArray &channelFlags, bool onlyOneChannelSelected, int selectedChannelIndex) { m_patchColorSpace = projectionDevice->colorSpace(); + //This seems like a dangerous thing to do at this place...? + bool isLab = m_patchColorSpace->colorModelId() == LABAColorModelID; m_patchPixels.allocate(m_patchColorSpace->pixelSize()); projectionDevice->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() && selectedChannelIndex >= 0 && selectedChannelIndex < m_patchColorSpace->channels().size()) { 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(true); 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(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - m_patchPixels.data() + (pixelIndex * pixelSize) + selectedChannelPos, - channelSize); + if (isLab) { + //if we're in lab, only copy the data into the first color channel(l), and the others filled with gray. + if (channelIndex==0) { + memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), + m_patchPixels.data() + (pixelIndex * pixelSize) + selectedChannelPos, + channelSize); + } else { + if (m_patchColorSpace->colorDepthId() == Integer8BitsColorDepthID) { + KoLabU8Traits::channels_type v; + switch (channelIndex) { + case KoLabU8Traits::L_pos: + v = KoLabU8Traits::math_trait::halfValueL; + break; + case KoLabU8Traits::a_pos: + case KoLabU8Traits::b_pos: + v = KoLabU8Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (m_patchColorSpace->colorDepthId() == Integer16BitsColorDepthID) { + KoLabU16Traits::channels_type v; + switch (channelIndex) { + case KoLabU16Traits::L_pos: + v = KoLabU16Traits::math_trait::halfValueL; + break; + case KoLabU16Traits::a_pos: + case KoLabU16Traits::b_pos: + v = KoLabU16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (m_patchColorSpace->colorDepthId() == Float16BitsColorDepthID) { + KoLabF16Traits::channels_type v; + switch (channelIndex) { + case KoLabF16Traits::L_pos: + v = KoLabF16Traits::math_trait::halfValueL; + break; + case KoLabF16Traits::a_pos: + case KoLabF16Traits::b_pos: + v = KoLabF16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (m_patchColorSpace->colorDepthId() == Float32BitsColorDepthID) { + KoLabF32Traits::channels_type v; + switch (channelIndex) { + case KoLabF32Traits::L_pos: + v = KoLabF32Traits::math_trait::halfValueL; + break; + case KoLabF32Traits::a_pos: + case KoLabF32Traits::b_pos: + v = KoLabF32Traits::math_trait::halfValueAB; + break; + default: + v = KoLabF32Traits::math_trait::min; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else { + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); + } + } + } else { + memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), + m_patchPixels.data() + (pixelIndex * pixelSize) + selectedChannelPos, + channelSize); + } } else if (channelInfo[channelIndex]->channelType() == KoChannelInfo::ALPHA) { 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(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), m_patchPixels.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), channelSize); + } else if (isLab) { + if (m_patchColorSpace->colorDepthId() == Integer8BitsColorDepthID) { + KoLabU8Traits::channels_type v; + switch (channelIndex) { + case KoLabU8Traits::L_pos: + v = KoLabU8Traits::math_trait::halfValueL; + break; + case KoLabU8Traits::a_pos: + case KoLabU8Traits::b_pos: + v = KoLabU8Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (m_patchColorSpace->colorDepthId() == Integer16BitsColorDepthID) { + KoLabU16Traits::channels_type v; + switch (channelIndex) { + case KoLabU16Traits::L_pos: + v = KoLabU16Traits::math_trait::halfValueL; + break; + case KoLabU16Traits::a_pos: + case KoLabU16Traits::b_pos: + v = KoLabU16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (m_patchColorSpace->colorDepthId() == Float16BitsColorDepthID) { + KoLabF16Traits::channels_type v; + switch (channelIndex) { + case KoLabF16Traits::L_pos: + v = KoLabF16Traits::math_trait::halfValueL; + break; + case KoLabF16Traits::a_pos: + case KoLabF16Traits::b_pos: + v = KoLabF16Traits::math_trait::halfValueAB; + break; + default: + v = 0; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else if (m_patchColorSpace->colorDepthId() == Float32BitsColorDepthID) { + KoLabF32Traits::channels_type v; + switch (channelIndex) { + case KoLabF32Traits::L_pos: + v = KoLabF32Traits::math_trait::halfValueL; + break; + case KoLabF32Traits::a_pos: + case KoLabF32Traits::b_pos: + v = KoLabF32Traits::math_trait::halfValueAB; + break; + default: + v = KoLabF32Traits::math_trait::min; + break; + } + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), v, channelSize); + } else { + memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); + } } else { memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); } } } } conversionCache.swap(m_patchPixels); } } void convertTo(const KoColorSpace* dstCS, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { // we use two-stage check of the color space equivalence: // first check pointers, and if not, check the spaces themselves if ((dstCS == m_patchColorSpace || *dstCS == *m_patchColorSpace) && conversionFlags == KoColorConversionTransformation::Empty) { return; } if (m_patchRect.isValid()) { const qint32 numPixels = m_patchRect.width() * m_patchRect.height(); DataBuffer conversionCache(dstCS->pixelSize(), m_pool); m_patchColorSpace->convertPixelsTo(m_patchPixels.data(), conversionCache.data(), dstCS, numPixels, renderingIntent, conversionFlags); m_patchColorSpace = dstCS; 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(); DataBuffer conversionCache(dstCS->pixelSize(), m_pool); m_patchColorSpace->proofPixelsTo(m_patchPixels.data(), conversionCache.data(), numPixels, proofingTransform); m_patchColorSpace = dstCS; conversionCache.swap(m_patchPixels); } } static KoColorConversionTransformation *generateProofingTransform(const KoColorSpace* srcCS, const KoColorSpace* dstCS, const KoColorSpace* proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KoColor gamutWarning, double adaptationState) { return srcCS->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 QRect realPatchRect() const { return m_patchRect; } 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 int pixelSize() const { return m_patchColorSpace->pixelSize(); } inline const KoColorSpace* patchColorSpace() const { return m_patchColorSpace; } inline quint32 patchPixelsLength() const { return m_patchPixels.size(); } inline bool valid() const { return m_patchRect.isValid(); } inline DataBuffer&& takePixelData() { return std::move(m_patchPixels); } inline void putPixelData(DataBuffer &&buffer, const KoColorSpace *colorSpace) { m_patchPixels = std::move(buffer); m_patchColorSpace = colorSpace; } 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; QRect m_realPatchRect; QRect m_realPatchOffset; QRect m_realTileSize; int m_patchLevelOfDetail; QRect m_originalPatchRect; QRect m_originalTileRect; DataBuffer m_patchPixels; KisTextureTileInfoPoolSP m_pool; }; #endif /* KIS_TEXTURE_TILE_UPDATE_INFO_H_ */