diff --git a/krita/image/kis_cross_device_color_picker.h b/krita/image/kis_cross_device_color_picker.h index 6179751e077..fc8174242a0 100644 --- a/krita/image/kis_cross_device_color_picker.h +++ b/krita/image/kis_cross_device_color_picker.h @@ -1,139 +1,139 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_CROSS_DEVICE_COLOR_PICKER_H #define __KIS_CROSS_DEVICE_COLOR_PICKER_H #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_random_sub_accessor.h" struct PickerTraitReal { typedef qreal coord_type; typedef KisRandomSubAccessorSP accessor_type; static inline accessor_type createAccessor(KisPaintDeviceSP dev) { return dev->createRandomSubAccessor(); } template static inline void sampleData(accessor_type accessor, quint8 *data, const KoColorSpace*) { if (useOldData) { accessor->sampledOldRawData(data); } else { accessor->sampledRawData(data); } } }; struct PickerTraitInt { typedef int coord_type; typedef KisRandomConstAccessorSP accessor_type; static inline accessor_type createAccessor(KisPaintDeviceSP dev) { return dev->createRandomConstAccessorNG(0,0); } template static inline void sampleData(accessor_type accessor, quint8 *data, const KoColorSpace *cs) { if (useOldData) { memcpy(data, accessor->oldRawData(), cs->pixelSize()); } else { memcpy(data, accessor->rawDataConst(), cs->pixelSize()); } } }; /** * The picker class is supposed to help to pick color from one device * and automatically convert it to the color space of another device * * WARNING: Please note, that if you want to access correct rawData(), * you shouldn't store the picker class (as well as any * random accessor class) across different calls to * paintAt. This is related to the fact that * KisRandomAccessor has an internal cache of the tiles, but * any tile may become 'old' with the time, so you'll end up * reading from the old tile instead of current one. */ template class KisCrossDeviceColorPickerImpl { public: KisCrossDeviceColorPickerImpl(KisPaintDeviceSP src, KisPaintDeviceSP dst) { init(src, dst); } KisCrossDeviceColorPickerImpl(KisPaintDeviceSP src, KisFixedPaintDeviceSP dst) { init(src, dst); } KisCrossDeviceColorPickerImpl(KisPaintDeviceSP src, const KoColor &dst) { init(src, &dst); } ~KisCrossDeviceColorPickerImpl() { delete[] m_data; } inline void pickColor(typename Traits::coord_type x, typename Traits::coord_type y, quint8 *dst) { pickColorImpl(x, y, dst); } inline void pickOldColor(typename Traits::coord_type x, typename Traits::coord_type y, quint8 *dst) { pickColorImpl(x, y, dst); } private: template inline void init(KisPaintDeviceSP src, T dst) { m_srcCS = src->colorSpace(); m_dstCS = dst->colorSpace(); m_data = new quint8[m_srcCS->pixelSize()]; m_accessor = Traits::createAccessor(src); } template inline void pickColorImpl(typename Traits::coord_type x, typename Traits::coord_type y, quint8 *dst) { m_accessor->moveTo(x, y); Traits::template sampleData(m_accessor, m_data, m_srcCS); m_srcCS->convertPixelsTo(m_data, dst, m_dstCS, 1, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } private: const KoColorSpace *m_srcCS; const KoColorSpace *m_dstCS; typename Traits::accessor_type m_accessor; quint8 *m_data; }; typedef KisCrossDeviceColorPickerImpl KisCrossDeviceColorPicker; typedef KisCrossDeviceColorPickerImpl KisCrossDeviceColorPickerInt; #endif /* __KIS_CROSS_DEVICE_COLOR_PICKER_H */ diff --git a/krita/image/kis_fixed_paint_device.cpp b/krita/image/kis_fixed_paint_device.cpp index d82794bad5a..e97226997ad 100644 --- a/krita/image/kis_fixed_paint_device.cpp +++ b/krita/image/kis_fixed_paint_device.cpp @@ -1,310 +1,310 @@ /* * Copyright (c) 2009 Boudewijn Rempt * Copyright (c) 2010 Lukáš Tvrdý * * 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_fixed_paint_device.h" #include #include #include #include "kis_debug.h" KisFixedPaintDevice::KisFixedPaintDevice(const KoColorSpace* colorSpace) : m_colorSpace(colorSpace) { } KisFixedPaintDevice::~KisFixedPaintDevice() { } KisFixedPaintDevice::KisFixedPaintDevice(const KisFixedPaintDevice& rhs) : KisShared() { m_bounds = rhs.m_bounds; m_colorSpace = rhs.m_colorSpace; m_data = rhs.m_data; } KisFixedPaintDevice& KisFixedPaintDevice::operator=(const KisFixedPaintDevice& rhs) { m_bounds = rhs.m_bounds; m_colorSpace = rhs.m_colorSpace; m_data = rhs.m_data; return *this; } void KisFixedPaintDevice::setRect(const QRect& rc) { m_bounds = rc; } QRect KisFixedPaintDevice::bounds() const { return m_bounds; } int KisFixedPaintDevice::allocatedPixels() const { return m_data.size() / m_colorSpace->pixelSize(); } quint32 KisFixedPaintDevice::pixelSize() const { return m_colorSpace->pixelSize(); } bool KisFixedPaintDevice::initialize(quint8 defaultValue) { m_data.fill(defaultValue, m_bounds.height() * m_bounds.width() * pixelSize()); return true; } quint8* KisFixedPaintDevice::data() { return m_data.data(); } quint8* KisFixedPaintDevice::data() const { return const_cast(m_data.data()); } void KisFixedPaintDevice::convertTo(const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (*m_colorSpace == *dstColorSpace) { return; } quint32 size = m_bounds.width() * m_bounds.height(); QVector dstData(size * dstColorSpace->pixelSize()); m_colorSpace->convertPixelsTo(data(), dstData.data(), dstColorSpace, size, renderingIntent, conversionFlags); m_colorSpace = dstColorSpace; m_data = dstData; } void KisFixedPaintDevice::convertFromQImage(const QImage& _image, const QString &srcProfileName) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } setRect(image.rect()); initialize(); // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (srcProfileName.isEmpty() && colorSpace()->id() == "RGBA") { memcpy(data(), image.constBits(), image.byteCount()); } else { KoColorSpaceRegistry::instance() ->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), srcProfileName) ->convertPixelsTo(image.constBits(), data(), colorSpace(), image.width() * image.height(), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } } QImage KisFixedPaintDevice::convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent intent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; x1 = m_bounds.x(); y1 = m_bounds.y(); w = m_bounds.width(); h = m_bounds.height(); return convertToQImage(dstProfile, x1, y1, w, h, intent, conversionFlags); } QImage KisFixedPaintDevice::convertToQImage(const KoColorProfile * dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent intent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT( m_bounds.contains(QRect(x1,y1,w,h)) ); if (w < 0) return QImage(); if (h < 0) return QImage(); if (QRect(x1, y1, w, h) == m_bounds) { return colorSpace()->convertToQImage(data(), w, h, dstProfile, intent, conversionFlags); } else { try { // XXX: fill the image row by row! int pSize = pixelSize(); int deviceWidth = m_bounds.width(); quint8* newData = new quint8[w * h * pSize]; quint8* srcPtr = data() + x1 * pSize + y1 * deviceWidth * pSize; quint8* dstPtr = newData; // copy the right area out of the paint device into data for (int row = 0; row < h; row++) { memcpy(dstPtr, srcPtr, w * pSize); srcPtr += deviceWidth * pSize; dstPtr += w * pSize; } QImage image = colorSpace()->convertToQImage(newData, w, h, dstProfile, intent, conversionFlags); return image; } catch(std::bad_alloc) { return QImage(); } } } void KisFixedPaintDevice::clear(const QRect & rc) { KoColor c(Qt::black, m_colorSpace); quint8* black = new quint8[pixelSize()]; memcpy(black, c.data(), m_colorSpace->pixelSize()); m_colorSpace->setOpacity(black, OPACITY_TRANSPARENT_U8, 1); fill(rc.x(), rc.y(), rc.width(), rc.height(), black); delete[] black; } void KisFixedPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { if (m_data.isEmpty() || m_bounds.isEmpty()) { setRect(QRect(x, y, w, h)); initialize(); } QRect rc(x, y, w, h); if (!m_bounds.contains(rc)) { rc = m_bounds; } quint8 pixelSize = m_colorSpace->pixelSize(); quint8* dabPointer = data(); if (rc.contains(m_bounds)) { for (int i = 0; i < w * h ; ++i) { memcpy(dabPointer, fillPixel, pixelSize); dabPointer += pixelSize; } } else { int deviceWidth = bounds().width(); quint8* rowPointer = dabPointer + ((y - bounds().y()) * deviceWidth + (x - bounds().x())) * pixelSize; for (int row = 0; row < h; row++) { for (int col = 0; col < w; col++) { memcpy(rowPointer + col * pixelSize , fillPixel, pixelSize); } rowPointer += deviceWidth * pixelSize; } } } void KisFixedPaintDevice::readBytes(quint8* dstData, qint32 x, qint32 y, qint32 w, qint32 h) const { if (m_data.isEmpty() || m_bounds.isEmpty()) { return; } QRect rc(x, y, w, h); if (!m_bounds.contains(rc)){ return; } quint8 pixelSize = m_colorSpace->pixelSize(); quint8* dabPointer = data(); if (rc == m_bounds) { memcpy(dstData, dabPointer, pixelSize * w * h); } else { int deviceWidth = bounds().width(); quint8* rowPointer = dabPointer + ((y - bounds().y()) * deviceWidth + (x - bounds().x())) * pixelSize; for (int row = 0; row < h; row++) { memcpy(dstData,rowPointer, w * pixelSize); rowPointer += deviceWidth * pixelSize; dstData += w * pixelSize; } } } void KisFixedPaintDevice::mirror(bool horizontal, bool vertical) { if (!horizontal && !vertical){ return; } int pixelSize = m_colorSpace->pixelSize(); int w = m_bounds.width(); int h = m_bounds.height(); if (horizontal){ int rowSize = pixelSize * w; quint8 * dabPointer = data(); quint8 * row = new quint8[ rowSize ]; quint8 * mirror = 0; for (int y = 0; y < h ; y++){ memcpy(row, dabPointer, rowSize); mirror = row; mirror += (w-1) * pixelSize; for (int x = 0; x < w; x++){ memcpy(dabPointer,mirror,pixelSize); dabPointer += pixelSize; mirror -= pixelSize; } } delete [] row; } if (vertical){ int rowsToMove = h / 2; int rowSize = pixelSize * w; quint8 * startRow = data(); quint8 * endRow = data() + (h-1) * w * pixelSize; quint8 * row = new quint8[ rowSize ]; for (int y = 0; y < rowsToMove; y++){ memcpy(row, startRow, rowSize); memcpy(startRow, endRow, rowSize); memcpy(endRow, row, rowSize); startRow += rowSize; endRow -= rowSize; } delete [] row; } } diff --git a/krita/image/kis_fixed_paint_device.h b/krita/image/kis_fixed_paint_device.h index 5fdbf07734a..a8caa2e66b8 100644 --- a/krita/image/kis_fixed_paint_device.h +++ b/krita/image/kis_fixed_paint_device.h @@ -1,183 +1,183 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * 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_FIXED_PAINT_DEVICE_H #define KIS_FIXED_PAINT_DEVICE_H #include #include #include "kis_global.h" #include "kis_shared.h" #include #include #include #include /** * A fixed paint device is a simple paint device that consists of an array * of bytes and a rectangle. It cannot grow, it cannot shrink, all you can * do is fill the paint device with the right bytes and use it as an argument * to KisPainter or use the bytes as an argument to KoColorSpace functions. */ class KRITAIMAGE_EXPORT KisFixedPaintDevice : public KisShared { public: KisFixedPaintDevice(const KoColorSpace* colorSpace); virtual ~KisFixedPaintDevice(); /** * Deep copy the fixed paint device, including the data. */ KisFixedPaintDevice(const KisFixedPaintDevice& rhs); /** * Deep copy the fixed paint device, including the data. */ KisFixedPaintDevice& operator=(const KisFixedPaintDevice& rhs); /** * setRect sets the rect of the fixed paint device to rect. * This will _not_ create the associated data area. * * @rect the bounds in pixels. The x,y of the rect represent the origin * of the fixed paint device. */ void setRect(const QRect& rc); /** * @return the rect that the data represents */ QRect bounds() const; /** * @return the amount of allocated pixels (you can fake the size with setRect/bounds) * It is useful to know the accumulated memory size in pixels (not in bytes) for optimizations to avoid re-allocation. */ int allocatedPixels() const; /** * @return the pixelSize associated with this fixed paint device. */ quint32 pixelSize() const; const KoColorSpace* colorSpace() const { return m_colorSpace; } /** * initializes the paint device. * * @param defaultValue the default byte with which all pixels will be filled. * @return false if the allocation failed. */ bool initialize(quint8 defaultValue = 0); /** * @return a pointer to the beginning of the data associated with this fixed paint device. */ quint8* data(); quint8* data() const; /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * The reading is done only if the rectangular area x,y,w,h is inside the bounds of the device * and the device is not empty */ void readBytes(quint8 * dstData, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Converts the paint device to a different colorspace */ void convertTo(const KoColorSpace * dstColorSpace = 0, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Fill this paint device with the data from image * * @param srcProfileName name of the RGB profile to interpret the image as. 0 is interpreted as sRGB */ virtual void convertFromQImage(const QImage& image, const QString &srcProfileName); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ virtual QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags) const; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ virtual QImage convertToQImage(const KoColorProfile *dstProfile, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags) const; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Clear the given rectangle to transparent black. * * XXX: this will not (yet) expand the paint device to contain the specified rect * but if the paintdevice has not been initialized, it will be. */ void clear(const QRect & rc); /** * Fill the given rectangle with the given pixel. This does not take the * selection into account. * * XXX: this will not (yet) expand the paint device to contain the specified rect * but if the paintdevice has not been initialized, it will be. */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); /** * Mirrors the device. */ void mirror(bool horizontal, bool vertical); private: const KoColorSpace* m_colorSpace; QRect m_bounds; QVector m_data; }; #endif diff --git a/krita/image/kis_group_layer.cc b/krita/image/kis_group_layer.cc index 3d06f65b9d5..f4f0de958cb 100644 --- a/krita/image/kis_group_layer.cc +++ b/krita/image/kis_group_layer.cc @@ -1,384 +1,384 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * 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_group_layer.h" #include #include #include #include #include #include "kis_types.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_default_bounds.h" #include "kis_clone_layer.h" #include "kis_selection_mask.h" #include "kis_psd_layer_style.h" struct Q_DECL_HIDDEN KisGroupLayer::Private { public: Private() : paintDevice(0) , x(0) , y(0) , passThroughMode(false) { } KisPaintDeviceSP paintDevice; qint32 x; qint32 y; bool passThroughMode; }; KisGroupLayer::KisGroupLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisLayer(image, name, opacity), m_d(new Private()) { resetCache(); } KisGroupLayer::KisGroupLayer(const KisGroupLayer &rhs) : KisLayer(rhs), m_d(new Private()) { m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data()); m_d->x = rhs.m_d->x; m_d->y = rhs.m_d->y; m_d->paintDevice->setDefaultPixel(const_cast(&rhs)->m_d->paintDevice->defaultPixel()); } KisGroupLayer::~KisGroupLayer() { delete m_d; } bool KisGroupLayer::checkCloneLayer(KisCloneLayerSP clone) const { KisNodeSP source = clone->copyFrom(); if (source) { if(!allowAsChild(source)) return false; if (source->inherits("KisGroupLayer")) { KisNodeSP newParent = const_cast(this); while (newParent) { if (newParent == source) { return false; } newParent = newParent->parent(); } } } return true; } bool KisGroupLayer::checkNodeRecursively(KisNodeSP node) const { KisCloneLayerSP cloneLayer = dynamic_cast(node.data()); if(cloneLayer) { return checkCloneLayer(cloneLayer); } else if (node->inherits("KisGroupLayer")) { KisNodeSP child = node->firstChild(); while (child) { if (!checkNodeRecursively(child)) { return false; } child = child->nextSibling(); } } return true; } bool KisGroupLayer::allowAsChild(KisNodeSP node) const { return checkNodeRecursively(node) && (parent() || (node->inherits("KisSelectionMask") && !selectionMask()) || !node->inherits("KisMask")); } const KoColorSpace * KisGroupLayer::colorSpace() const { return m_d->paintDevice->colorSpace(); } QIcon KisGroupLayer::icon() const { return KisIconUtils::loadIcon("folder"); } void KisGroupLayer::setImage(KisImageWSP image) { m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(image)); KisLayer::setImage(image); } KisLayerSP KisGroupLayer::createMergedLayer(KisLayerSP prevLayer) { KisGroupLayer *prevGroup = dynamic_cast(prevLayer.data()); if (prevGroup && canMergeAndKeepBlendOptions(prevLayer)) { KisSharedPtr merged(new KisGroupLayer(*prevGroup)); KisNodeSP child, cloned; for (child = firstChild(); child; child = child->nextSibling()) { cloned = child->clone(); image()->addNode(cloned, merged); } image()->refreshGraphAsync(merged); return merged; } else return KisLayer::createMergedLayer(prevLayer); } void KisGroupLayer::resetCache(const KoColorSpace *colorSpace) { if (!colorSpace) colorSpace = image()->colorSpace(); Q_ASSERT(colorSpace); if (!m_d->paintDevice) { m_d->paintDevice = new KisPaintDevice(this, colorSpace, new KisDefaultBounds(image())); m_d->paintDevice->setX(m_d->x); m_d->paintDevice->setY(m_d->y); } else if(!(*m_d->paintDevice->colorSpace() == *colorSpace)) { KisPaintDeviceSP dev = new KisPaintDevice(this, colorSpace, new KisDefaultBounds(image())); dev->setX(m_d->x); dev->setY(m_d->y); quint8* defaultPixel = new quint8[colorSpace->pixelSize()]; m_d->paintDevice->colorSpace()-> convertPixelsTo(m_d->paintDevice->defaultPixel(), defaultPixel, colorSpace, 1, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); dev->setDefaultPixel(defaultPixel); delete[] defaultPixel; m_d->paintDevice = dev; } else { m_d->paintDevice->clear(); } } KisLayer* KisGroupLayer::onlyMeaningfulChild() const { KisNode *child = firstChild().data(); KisLayer *onlyLayer = 0; while (child) { KisLayer *layer = dynamic_cast(child); if (layer) { if (onlyLayer) return 0; onlyLayer = layer; } child = child->nextSibling().data(); } return onlyLayer; } KisPaintDeviceSP KisGroupLayer::tryObligeChild() const { const KisLayer *child = onlyMeaningfulChild(); if (child && child->channelFlags().isEmpty() && child->projection() && child->visible() && (child->compositeOpId() == COMPOSITE_OVER || child->compositeOpId() == COMPOSITE_ALPHA_DARKEN || child->compositeOpId() == COMPOSITE_COPY) && child->opacity() == OPACITY_OPAQUE_U8 && *child->projection()->colorSpace() == *colorSpace() && !child->layerStyle()) { quint8 defaultOpacity = m_d->paintDevice->colorSpace()->opacityU8( m_d->paintDevice->defaultPixel()); if(defaultOpacity == OPACITY_TRANSPARENT_U8) { return child->projection(); } } return 0; } KisPaintDeviceSP KisGroupLayer::original() const { /** * We are too lazy! Let's our children work for us. * Try to use children's paintDevice if it's the only * one in stack and meets some conditions */ KisPaintDeviceSP realOriginal = tryObligeChild(); if (!realOriginal) { if (!childCount() && !m_d->paintDevice->extent().isEmpty()) { m_d->paintDevice->clear(); } realOriginal = m_d->paintDevice; } return realOriginal; } void KisGroupLayer::setDefaultProjectionColor(KoColor color) { color.convertTo(m_d->paintDevice->colorSpace()); m_d->paintDevice->setDefaultPixel(color.data()); } KoColor KisGroupLayer::defaultProjectionColor() const { KoColor color(m_d->paintDevice->defaultPixel(), m_d->paintDevice->colorSpace()); return color; } bool KisGroupLayer::passThroughMode() const { return m_d->passThroughMode; } void KisGroupLayer::setPassThroughMode(bool value) { m_d->passThroughMode = value; } KisDocumentSectionModel::PropertyList KisGroupLayer::sectionModelProperties() const { KisDocumentSectionModel::PropertyList l = KisLayer::sectionModelProperties(); // XXX: get right icons l << KisDocumentSectionModel::Property(i18n("Pass Through"), KisIconUtils::loadIcon("passthrough-enabled"), KisIconUtils::loadIcon("passthrough-disabled"), passThroughMode()); return l; } void KisGroupLayer::setSectionModelProperties(const KisDocumentSectionModel::PropertyList &properties) { foreach (const KisDocumentSectionModel::Property &property, properties) { if (property.name == i18n("Pass Through")) { setPassThroughMode(property.state.toBool()); } } KisLayer::setSectionModelProperties(properties); } bool KisGroupLayer::accept(KisNodeVisitor &v) { return v.visit(this); } void KisGroupLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } qint32 KisGroupLayer::x() const { return m_d->x; } qint32 KisGroupLayer::y() const { return m_d->y; } void KisGroupLayer::setX(qint32 x) { qint32 delta = x - m_d->x; m_d->x = x; if(m_d->paintDevice) { m_d->paintDevice->setX(m_d->paintDevice->x() + delta); Q_ASSERT(m_d->paintDevice->x() == m_d->x); } } void KisGroupLayer::setY(qint32 y) { qint32 delta = y - m_d->y; m_d->y = y; if(m_d->paintDevice) { m_d->paintDevice->setY(m_d->paintDevice->y() + delta); Q_ASSERT(m_d->paintDevice->y() == m_d->y); } } struct ExtentPolicy { inline QRect operator() (const KisNode *node) { return node->extent(); } }; struct ExactBoundsPolicy { inline QRect operator() (const KisNode *node) { return node->exactBounds(); } }; template QRect collectRects(const KisNode *node, MetricPolicy policy) { QRect accumulator; const KisNode *child = node->firstChild(); while (child) { if (!qobject_cast(child)) { accumulator |= policy(child); } child = child->nextSibling(); } return accumulator; } QRect KisGroupLayer::extent() const { return m_d->passThroughMode ? collectRects(this, ExtentPolicy()) : KisLayer::extent(); } QRect KisGroupLayer::exactBounds() const { return m_d->passThroughMode ? collectRects(this, ExactBoundsPolicy()) : KisLayer::exactBounds(); } diff --git a/krita/image/kis_image.cc b/krita/image/kis_image.cc index f440edd6c0a..794d06262ad 100644 --- a/krita/image/kis_image.cc +++ b/krita/image/kis_image.cc @@ -1,1940 +1,1940 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * 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.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorConversionTransformation.h" #include "KoColorProfile.h" #include #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_perspective_grid.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_types.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include #include // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q) : q(_q) {} KisImage *q; quint32 lockCount; KisPerspectiveGrid* perspectiveGrid; qint32 width; qint32 height; double xres; double yres; const KoColorSpace * colorSpace; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList dirtyLayers; // for thumbnails QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted; KisNameServer *nserver; KisUndoStore *undoStore; KisUndoAdapter *legacyUndoAdapter; KisPostExecutionUndoAdapter *postExecutionUndoAdapter; KisActionRecorder *recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; QAtomicInt disableDirtyRequests; KisImageSignalRouter *signalRouter; KisUpdateScheduler *scheduler; KisCompositeProgressProxy *compositeProgressProxy; bool startProjection; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name, bool startProjection) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this)) { setObjectName(name); dbgImage << "creating" << name; m_d->startProjection = startProjection; if (colorSpace == 0) { colorSpace = KoColorSpaceRegistry::instance()->rgb8(); } m_d->lockCount = 0; m_d->perspectiveGrid = 0; m_d->scheduler = 0; m_d->wrapAroundModePermitted = false; m_d->compositeProgressProxy = new KisCompositeProgressProxy(); { KisImageConfig cfg; m_d->scheduler = new KisUpdateScheduler(this); if (cfg.enableProgressReporting()) { m_d->scheduler->setProgressProxy(m_d->compositeProgressProxy); } } m_d->signalRouter = new KisImageSignalRouter(this); if (!undoStore) { undoStore = new KisDumbUndoStore(); } m_d->undoStore = undoStore; m_d->legacyUndoAdapter = new KisLegacyUndoAdapter(m_d->undoStore, this); m_d->postExecutionUndoAdapter = new KisPostExecutionUndoAdapter(m_d->undoStore, this); m_d->nserver = new KisNameServer(1); m_d->colorSpace = colorSpace; setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); m_d->xres = 1.0; m_d->yres = 1.0; m_d->width = width; m_d->height = height; m_d->recorder = new KisActionRecorder(this); connect(this, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); /** * First delete the nodes, while strokes * and undo are still alive */ m_d->rootLayer = 0; KisUpdateScheduler *scheduler = m_d->scheduler; m_d->scheduler = 0; delete scheduler; delete m_d->postExecutionUndoAdapter; delete m_d->legacyUndoAdapter; delete m_d->undoStore; delete m_d->compositeProgressProxy; delete m_d->signalRouter; delete m_d->perspectiveGrid; delete m_d->nserver; delete m_d; disconnect(); // in case Qt gets confused } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter->emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { stopIsolatedMode(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { stopIsolatedMode(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter->emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter->emitNodeChanged(node); } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } Q_ASSERT(m_d->rootLayer->childCount() > 0); Q_ASSERT(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter->emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName() const { if (m_d->nserver->currentSeed() == 0) { m_d->nserver->number(); return i18n("background"); } return i18n("Layer %1", m_d->nserver->number()); } void KisImage::rollBackLayerName() { m_d->nserver->rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock() { if (!locked()) { requestStrokeEnd(); if (m_d->scheduler) { m_d->scheduler->barrierLock(); } } m_d->lockCount++; } bool KisImage::tryBarrierLock() { bool result = true; if (!locked()) { if (m_d->scheduler) { result = m_d->scheduler->tryBarrierLock(); } } if (result) { m_d->lockCount++; } return result; } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); if (m_d->scheduler) { m_d->scheduler->lock(); } } m_d->lockCount++; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { if (m_d->scheduler) { m_d->scheduler->unlock(); } } } } void KisImage::blockUpdates() { m_d->scheduler->blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler->unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = dynamic_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal sx, qreal sy, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); shearImpl(kundo2_i18n("Shear layer"), node, false, angleX, angleY, shearOrigin); } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); return m_d->rootLayer->accept(visitor); } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter->emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter->emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint((int)pixelCoord.x(), (int)pixelCoord.y()); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QRect KisImage::documentToIntPixel(const QRectF &documentRect) const { return documentToPixel(documentRect).toAlignedRect(); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void KisImage::refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { refreshGraph(rootNode, rc, realNodeRect); } } } void KisImage::flatten() { KisGroupLayerSP oldRootLayer = m_d->rootLayer; KisGroupLayerSP newRootLayer = new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8); refreshHiddenArea(oldRootLayer, bounds()); lock(); KisPaintDeviceSP projectionCopy = new KisPaintDevice(oldRootLayer->projection()->colorSpace()); projectionCopy->makeCloneFrom(oldRootLayer->projection(), oldRootLayer->exactBounds()); unlock(); KisPaintLayerSP flattenLayer = new KisPaintLayer(this, nextLayerName(), OPACITY_OPAQUE_U8, projectionCopy); Q_CHECK_PTR(flattenLayer); addNode(flattenLayer, newRootLayer, 0); undoAdapter()->beginMacro(kundo2_i18n("Flatten Image")); // NOTE: KisImageChangeLayersCommand performs all the locking for us undoAdapter()->addCommand(new KisImageChangeLayersCommand(KisImageWSP(this), oldRootLayer, newRootLayer)); undoAdapter()->endMacro(); setModified(); } bool checkIsSourceForClone(KisNodeSP src, const QList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } void KisImage::safeRemoveMultipleNodes(QList nodes) { while (!nodes.isEmpty()) { QList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, node)); it = nodes.erase(it); } else { ++it; } } } } bool checkIsChildOf(KisNodeSP node, const QList &parents) { QList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } void filterMergableNodes(QList &nodes) { QList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!dynamic_cast(it->data()) || checkIsChildOf(*it, nodes)) { dbgKrita << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, QList &inputNodes, QList &outputNodes) { QList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } void fetchSelectionMasks(QList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { KisLayerSP layer = dynamic_cast(node.data()); KisSelectionMaskSP mask; if (layer && (mask = layer->selectionMask())) { selectionMasks.append(mask); } } } void reparentSelectionMasks(KisLayerSP newLayer, const QVector &selectionMasks) { KisImageSP image = newLayer->image(); foreach (KisSelectionMaskSP mask, selectionMasks) { image->undoAdapter()->addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); image->undoAdapter()->addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } KisNodeSP tryMergeSelectionMasks(KisImageSP image, QList mergedNodes) { if (mergedNodes.isEmpty()) return 0; QList selectionMasks; foreach (KisNodeSP node, mergedNodes) { KisSelectionMaskSP mask = dynamic_cast(node.data()); if (!mask) return 0; selectionMasks.append(mask); } KisLayerSP parentLayer = dynamic_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisSelectionSP selection = new KisSelection(); foreach (KisMaskSP mask, selectionMasks) { selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } image->undoAdapter()->beginMacro(kundo2_i18n("Merge Selection Masks")); KisSelectionMaskSP mergedMask = new KisSelectionMask(image); mergedMask->initSelection(parentLayer); image->undoAdapter()->addCommand(new KisImageLayerAddCommand(image, mergedMask, parentLayer, parentLayer->lastChild())); mergedMask->setSelection(selection); image->undoAdapter()->addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); image->safeRemoveMultipleNodes(mergedNodes); image->undoAdapter()->endMacro(); return mergedMask; } KisNodeSP KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { { KisNodeSP mask; if ((mask = tryMergeSelectionMasks(this, mergedNodes))) { return mask; } } filterMergableNodes(mergedNodes); { QList tempNodes; qSwap(mergedNodes, tempNodes); sortMergableNodes(m_d->rootLayer, tempNodes, mergedNodes); } if (mergedNodes.size() <= 1) return KisNodeSP(); // fetch selection masks to move them into the destination layer QVector selectionMasks; fetchSelectionMasks(mergedNodes, selectionMasks); foreach (KisNodeSP layer, mergedNodes) { refreshHiddenArea(layer, bounds()); } KisPaintDeviceSP mergedDevice = new KisPaintDevice(colorSpace()); KisPainter gc(mergedDevice); { KisImageBarrierLocker l(this); foreach (KisNodeSP layer, mergedNodes) { QRect rc = layer->exactBounds() | bounds(); layer->projectionPlane()->apply(&gc, rc); } } const QString mergedLayerSuffix = i18n("Merged"); QString mergedLayerName = mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } KisLayerSP newLayer = new KisPaintLayer(this, mergedLayerName, OPACITY_OPAQUE_U8, mergedDevice); undoAdapter()->beginMacro(kundo2_i18n("Merge Selected Nodes")); if (!putAfter) { putAfter = mergedNodes.last(); } // Add the new merged node on top of the active node -- checking // whether the parent is going to be deleted KisNodeSP parent = putAfter->parent(); while (mergedNodes.contains(parent)) { parent = parent->parent(); } if (parent == putAfter->parent()) { undoAdapter()->addCommand(new KisImageLayerAddCommand(this, newLayer, parent, putAfter)); } else { undoAdapter()->addCommand(new KisImageLayerAddCommand(this, newLayer, parent, parent->lastChild())); } // reparent selection masks into the newly created node reparentSelectionMasks(newLayer, selectionMasks); safeRemoveMultipleNodes(mergedNodes); undoAdapter()->endMacro(); return newLayer; } KisLayerSP KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return 0; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = dynamic_cast(layer->prevSibling().data()); if (!prevLayer) return 0; refreshHiddenArea(layer, bounds()); refreshHiddenArea(prevLayer, bounds()); QVector selectionMasks; { QList mergedNodes; mergedNodes << layer; mergedNodes << prevLayer; fetchSelectionMasks(mergedNodes, selectionMasks); } QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); // actual merging done by KisLayer::createMergedLayer (or specialized decendant) KisLayerSP mergedLayer = layer->createMergedLayer(prevLayer); Q_CHECK_PTR(mergedLayer); // Merge meta data QList srcs; srcs.append(prevLayer->metaData()); srcs.append(layer->metaData()); QList scores; int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); strategy->merge(mergedLayer->metaData(), srcs, scores); KisNodeSP parent = layer->parent(); // parent is set to null when the layer is removed from the node dbgImage << ppVar(parent); // FIXME: "Merge Down"? undoAdapter()->beginMacro(kundo2_i18n("Merge with Layer Below")); undoAdapter()->addCommand(new KisImageLayerAddCommand(this, mergedLayer, parent, layer)); // reparent selection masks into the newly created node reparentSelectionMasks(mergedLayer, selectionMasks); safeRemoveTwoNodes(layer, prevLayer); undoAdapter()->endMacro(); return mergedLayer; } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void KisImage::safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2) { KisCloneLayer *clone1 = dynamic_cast(node1.data()); if (clone1 && KisNodeSP(clone1->copyFrom()) == node2) { undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, node1)); undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, node2)); } else { undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, node2)); undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, node1)); } } KisLayerSP KisImage::flattenLayer(KisLayerSP layer) { if (!layer->firstChild()) return layer; refreshHiddenArea(layer, bounds()); bool resetComposition = false; KisPaintDeviceSP mergedDevice; lock(); if (layer->layerStyle()) { mergedDevice = new KisPaintDevice(layer->colorSpace()); mergedDevice->prepareClone(layer->projection()); QRect updateRect = layer->projection()->extent() | bounds(); KisPainter gc(mergedDevice); layer->projectionPlane()->apply(&gc, updateRect); resetComposition = true; } else { mergedDevice = new KisPaintDevice(*layer->projection()); } unlock(); KisPaintLayerSP newLayer = new KisPaintLayer(this, layer->name(), layer->opacity(), mergedDevice); if (!resetComposition) { newLayer->setCompositeOp(layer->compositeOp()->id()); newLayer->setChannelFlags(layer->channelFlags()); } undoAdapter()->beginMacro(kundo2_i18n("Flatten Layer")); undoAdapter()->addCommand(new KisImageLayerAddCommand(this, newLayer, layer->parent(), layer)); undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, layer)); QList srcs; srcs.append(layer->metaData()); const KisMetaData::MergeStrategy* strategy = KisMetaData::MergeStrategyRegistry::instance()->get("Smart"); QList scores; scores.append(1.0); //Just give some score, there only is one layer strategy->merge(newLayer->metaData(), srcs, scores); undoAdapter()->endMacro(); return newLayer; } void KisImage::setModified() { m_d->signalRouter->emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QRect& scaledRect, const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledRect.isEmpty() || scaledImageSize.isEmpty()) { return QImage(); } try { qint32 imageWidth = width(); qint32 imageHeight = height(); quint32 pixelSize = colorSpace()->pixelSize(); double xScale = static_cast(imageWidth) / scaledImageSize.width(); double yScale = static_cast(imageHeight) / scaledImageSize.height(); QRect srcRect; srcRect.setLeft(static_cast(scaledRect.left() * xScale)); srcRect.setRight(static_cast(ceil((scaledRect.right() + 1) * xScale)) - 1); srcRect.setTop(static_cast(scaledRect.top() * yScale)); srcRect.setBottom(static_cast(ceil((scaledRect.bottom() + 1) * yScale)) - 1); KisPaintDeviceSP mergedImage = projection(); quint8 *scaledImageData = new quint8[scaledRect.width() * scaledRect.height() * pixelSize]; quint8 *imageRow = new quint8[srcRect.width() * pixelSize]; const qint32 imageRowX = srcRect.x(); for (qint32 y = 0; y < scaledRect.height(); ++y) { qint32 dstY = scaledRect.y() + y; qint32 dstX = scaledRect.x(); qint32 srcY = (dstY * imageHeight) / scaledImageSize.height(); mergedImage->readBytes(imageRow, imageRowX, srcY, srcRect.width(), 1); quint8 *dstPixel = scaledImageData + (y * scaledRect.width() * pixelSize); quint32 columnsRemaining = scaledRect.width(); while (columnsRemaining > 0) { qint32 srcX = (dstX * imageWidth) / scaledImageSize.width(); memcpy(dstPixel, imageRow + ((srcX - imageRowX) * pixelSize), pixelSize); ++dstX; dstPixel += pixelSize; --columnsRemaining; } } delete [] imageRow; QImage image = colorSpace()->convertToQImage(scaledImageData, scaledRect.width(), scaledRect.height(), const_cast(profile), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); delete [] scaledImageData; return image; } catch (std::bad_alloc) { warnKrita << "KisImage::convertToQImage ran out of memory"; return QImage(); } } void KisImage::notifyLayersChanged() { m_d->signalRouter->emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { return m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter->setUndoStore(undoStore); m_d->postExecutionUndoAdapter->setUndoStore(undoStore); delete m_d->undoStore; m_d->undoStore = undoStore; } KisUndoStore* KisImage::undoStore() { return m_d->undoStore; } KisUndoAdapter* KisImage::undoAdapter() const { return m_d->legacyUndoAdapter; } KisActionRecorder* KisImage::actionRecorder() const { return m_d->recorder; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { stopIsolatedMode(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor.setColor(original->defaultPixel(), original->colorSpace()); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); defaultProjectionColor.convertTo(newOriginal->colorSpace()); newOriginal->setDefaultPixel(defaultProjectionColor.data()); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisPerspectiveGrid* KisImage::perspectiveGrid() { if (m_d->perspectiveGrid == 0) m_d->perspectiveGrid = new KisPerspectiveGrid(); return m_d->perspectiveGrid; } KisImageSignalRouter* KisImage::signalRouter() { return m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); if (m_d->scheduler) { m_d->scheduler->waitForDone(); } } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ requestStrokeEnd(); /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } KisStrokeId id; if (m_d->scheduler) { id = m_d->scheduler->startStroke(strokeStrategy); } return id; } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(boost::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { if (!tryBarrierLock()) return false; unlock(); m_d->isolatedRootNode = node; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; KisNodeSP oldRootNode = m_d->isolatedRootNode; m_d->isolatedRootNode = 0; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); if (m_d->scheduler) { m_d->scheduler->addJob(id, data); } } void KisImage::endStroke(KisStrokeId id) { if (m_d->scheduler) { m_d->scheduler->endStroke(id); } } bool KisImage::cancelStroke(KisStrokeId id) { bool result = false; if (m_d->scheduler) { result = m_d->scheduler->cancelStroke(id); } return result; } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { bool result = false; if (scheduler) { result = scheduler->tryCancelCurrentStrokeAsync(); } return result; } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; if (m_d->scheduler) { m_d->scheduler->fullRefresh(root, rc, cropRect); } } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; if (m_d->scheduler) { m_d->scheduler->fullRefreshAsync(root, rc, cropRect); } } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); if (m_d->scheduler) { m_d->scheduler->updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { if (m_d->scheduler) { m_d->scheduler->addSpontaneousJob(spontaneousJob); } } void KisImage::disableDirtyRequests() { m_d->disableDirtyRequests.ref(); } void KisImage::enableDirtyRequests() { m_d->disableDirtyRequests.deref(); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { emit sigImageUpdated(rc); } } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter->emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QRect &rect, const QRect &cropRect) { KisNodeGraphListener::requestProjectionUpdate(node, rect); if (m_d->scheduler) { m_d->scheduler->updateProjection(node, rect, cropRect); } } void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect) { if (m_d->disableDirtyRequests) return; /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QRect boundRect = bounds(); KisWrappedRect splitRect(rect, boundRect); foreach (const QRect &rc, splitRect) { requestProjectionUpdateImpl(node, rc, boundRect); } } else { requestProjectionUpdateImpl(node, rect, bounds()); } } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerComposition* composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerComposition* composition) { m_d->compositions.removeAll(composition); delete composition; } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler && m_d->scheduler->wrapAroundModeSupported(); } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } diff --git a/krita/image/kis_layer.cc b/krita/image/kis_layer.cc index f7819d747ea..6582c8f3974 100644 --- a/krita/image/kis_layer.cc +++ b/krita/image/kis_layer.cc @@ -1,809 +1,809 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * 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_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_lock); if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*prototype); } if(!m_projection || !(*m_projection->colorSpace() == *prototype->colorSpace())) { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); } return m_projection; } void freeDevice() { QMutexLocker locker(&m_lock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: QMutex m_lock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { foreach(KisCloneLayerWSP clone, m_clonesList) { clone->setDirtyOriginal(rect); } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; struct Q_DECL_HIDDEN KisLayer::Private { KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisAbstractProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() , m_d(new Private) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private()) { if (this != &rhs) { m_d->image = rhs.m_d->image; m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); if (rhs.m_d->layerStyle) { setLayerStyle(rhs.m_d->layerStyle->clone()); } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { if (m_d->image) return m_d->image->colorSpace(); return 0; } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisAbstractProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisAbstractProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisAbstractProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisDocumentSectionModel::PropertyList KisLayer::sectionModelProperties() const { KisDocumentSectionModel::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisDocumentSectionModel::Property(i18n("Opacity"), i18n("%1%", percentOpacity())); if (compositeOp()) { l << KisDocumentSectionModel::Property(i18n("Composite Mode"), compositeOp()->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisDocumentSectionModel::Property(i18n("Layer Style"), KisIconUtils::loadIcon("layer-style-enabled"), KisIconUtils::loadIcon("layer-style-disabled"), m_d->layerStyle->isEnabled()); } l << KisDocumentSectionModel::Property(i18n("Inherit Alpha"), KisIconUtils::loadIcon("transparency-disabled"), KisIconUtils::loadIcon("transparency-enabled"), alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisDocumentSectionModel::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); foreach (const KisDocumentSectionModel::Property &property, properties) { if (property.name == i18n("Inherit Alpha")) { disableAlphaChannel(property.state.toBool()); } if (property.name == i18n("Layer Style")) { if (m_d->layerStyle) { m_d->layerStyle->setEnabled(property.state.toBool()); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { nodeProperties().setProperty("temporary", t); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = image; for (uint i = 0; i < childCount(); ++i) { // Only layers know about the image KisLayer *layer = dynamic_cast(at(i).data()); if (layer) { layer->setImage(image); } // We lied, through the defaultBounds, masks also know about the image KisMask *mask = dynamic_cast(at(i).data()); if (mask) { mask->setImage(image); } } } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayer(KisLayerSP prevLayer) { KisImageSP my_image = image(); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice; bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); if (!keepBlendingOptions) { KisNodeSP parentNode = parent(); const KoColorSpace *dstCs = parentNode && parentNode->colorSpace() ? parentNode->colorSpace() : my_image->colorSpace(); mergedDevice = new KisPaintDevice(dstCs, "merged"); KisPainter gc(mergedDevice); //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | my_image->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | my_image->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer my_image->lock(); mergedDevice = new KisPaintDevice(*prevLayer->projection()); my_image->unlock(); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } KisLayerSP newLayer = new KisPaintLayer(my_image, prevLayer->name(), OPACITY_OPAQUE_U8, mergedDevice); if (keepBlendingOptions) { newLayer->setCompositeOp(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } KisSelectionMaskSP KisLayer::selectionMask() const { KoProperties properties; properties.setProperty("active", true); QList masks = childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask foreach (KisNodeSP mask, masks) { if (mask->visible()) { return dynamic_cast(mask.data()); } } return 0; } KisSelectionSP KisLayer::selection() const { if (selectionMask()) { return selectionMask()->selection(); } else if (m_d->image) { return m_d->image->globalSelection(); } else { return 0; } } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks(KisNodeSP lastNode) const { QList masks; if (childCount() > 0) { KoProperties properties; properties.setProperty("visible", true); QList nodes = childNodes(QStringList("KisEffectMask"), properties); foreach(const KisNodeSP& node, nodes) { if (node == lastNode) break; KisEffectMaskSP mask = dynamic_cast(const_cast(node.data())); if (mask) masks.append(mask); } } return masks; } bool KisLayer::hasEffectMasks() const { if (childCount() == 0) return false; KisNodeSP node = firstChild(); while (node) { if (node->inherits("KisEffectMask") && node->visible()) { return true; } node = node->nextSibling(); } return false; } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; foreach(const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } foreach(const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; foreach(const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection.getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? m_d->layerStyleProjectionPlane : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags) : QImage(); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()) : QImage(); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::extent() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->extent() : QRect(); } QRect KisLayer::exactBounds() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->exactBounds() : QRect(); } KisLayerSP KisLayer::parentLayer() const { return dynamic_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/krita/image/kis_mask.cc b/krita/image/kis_mask.cc index 06814e2307f..f9273a3fc2e 100644 --- a/krita/image/kis_mask.cc +++ b/krita/image/kis_mask.cc @@ -1,356 +1,356 @@ /* * Copyright (c) 2006 Boudewijn Rempt * (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_mask.h" #include // to prevent incomplete class types on "delete selection->flatten();" #include #include #include #include #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "kis_painter.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_cached_paint_device.h" #include "kis_mask_projection_plane.h" struct Q_DECL_HIDDEN KisMask::Private { Private(KisMask *_q) : q(_q), projectionPlane(new KisMaskProjectionPlane(q)) { } mutable KisSelectionSP selection; KisCachedPaintDevice paintDeviceCache; KisMask *q; /** * Due to the design of the Kra format the X,Y offset of the paint * device belongs to the node, but not to the device itself. So * the offset is set when the node is created, but not when the * selection is initialized. This causes the X,Y values to be * lost, since the selection doen not exist at the moment. That is * why we save it separately. */ QScopedPointer deferredSelectionOffset; KisAbstractProjectionPlaneSP projectionPlane; void initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice); }; KisMask::KisMask(const QString & name) : KisNode() , m_d(new Private(this)) { setName(name); } KisMask::KisMask(const KisMask& rhs) : KisNode(rhs) , KisIndirectPaintingSupport() , m_d(new Private(this)) { setName(rhs.name()); if (rhs.m_d->selection) { m_d->selection = new KisSelection(*rhs.m_d->selection.data()); m_d->selection->setParentNode(this); } } KisMask::~KisMask() { delete m_d; } void KisMask::setImage(KisImageWSP image) { KisPaintDeviceSP parentPaintDevice = parent() ? parent()->original() : 0; KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice, image); m_d->selection->setDefaultBounds(defaultBounds); } bool KisMask::allowAsChild(KisNodeSP node) const { Q_UNUSED(node); return false; } const KoColorSpace * KisMask::colorSpace() const { KisNodeSP parentNode = parent(); return parentNode ? parentNode->colorSpace() : 0; } const KoCompositeOp * KisMask::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisLayer. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } void KisMask::initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer) { m_d->initSelectionImpl(copyFrom, parentLayer, 0); } void KisMask::initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, copyFromDevice); } void KisMask::initSelection(KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, 0); } void KisMask::Private::initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice) { Q_ASSERT(parentLayer); KisPaintDeviceSP parentPaintDevice = parentLayer->original(); if (copyFrom) { /** * We can't use setSelection as we may not have parent() yet */ selection = new KisSelection(*copyFrom); selection->setDefaultBounds(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); if (copyFrom->hasShapeSelection()) { delete selection->flatten(); } } else if (copyFromDevice) { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); QRect rc(copyFromDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), copyFromDevice, selection->pixelSelection(), rc); selection->pixelSelection()->invalidateOutlineCache(); } else { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); quint8 newDefaultPixel = MAX_SELECTED; selection->pixelSelection()->setDefaultPixel(&newDefaultPixel); if (deferredSelectionOffset) { selection->setX(deferredSelectionOffset->x()); selection->setY(deferredSelectionOffset->y()); deferredSelectionOffset.reset(); } } selection->setParentNode(q); selection->updateProjection(); } KisSelectionSP KisMask::selection() const { return m_d->selection; } KisPaintDeviceSP KisMask::paintDevice() const { return selection()->pixelSelection(); } KisPaintDeviceSP KisMask::original() const { return paintDevice(); } KisPaintDeviceSP KisMask::projection() const { return paintDevice(); } KisAbstractProjectionPlaneSP KisMask::projectionPlane() const { return m_d->projectionPlane; } void KisMask::setSelection(KisSelectionSP selection) { m_d->selection = selection; if (parent()) { const KisLayer *parentLayer = qobject_cast(parent()); m_d->selection->setDefaultBounds(new KisDefaultBounds(parentLayer->image())); } m_d->selection->setParentNode(this); } void KisMask::select(const QRect & rc, quint8 selectedness) { KisSelectionSP sel = selection(); KisPixelSelectionSP psel = sel->pixelSelection(); psel->select(rc, selectedness); sel->updateProjection(rc); } QRect KisMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const { Q_UNUSED(src); Q_UNUSED(dst); Q_UNUSED(maskPos); Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors"); return rc; } void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy maskPos) const { if (selection()) { m_d->selection->updateProjection(applyRect); if(!extent().intersects(applyRect)) return; KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos); // masks don't have any compositioning KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, m_d->selection); m_d->paintDeviceCache.putDevice(cacheDevice); } else { KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); cacheDevice->makeCloneFromRough(projection, needRect); projection->clear(needRect); decorateRect(cacheDevice, projection, applyRect, maskPos); m_d->paintDeviceCache.putDevice(cacheDevice); } } QRect KisMask::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); QRect resultRect = rect; if (m_d->selection) resultRect &= m_d->selection->selectedRect(); return resultRect; } QRect KisMask::changeRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); QRect resultRect = rect; if (m_d->selection) resultRect &= m_d->selection->selectedRect(); return resultRect; } QRect KisMask::extent() const { return m_d->selection ? m_d->selection->selectedRect() : parent() ? parent()->extent() : QRect(); } QRect KisMask::exactBounds() const { return m_d->selection ? m_d->selection->selectedExactRect() : parent() ? parent()->exactBounds() : QRect(); } qint32 KisMask::x() const { return m_d->selection ? m_d->selection->x() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->x() : parent() ? parent()->x() : 0; } qint32 KisMask::y() const { return m_d->selection ? m_d->selection->y() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->y() : parent() ? parent()->y() : 0; } void KisMask::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(x, 0)); } else { m_d->deferredSelectionOffset->rx() = x; } } void KisMask::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(0, y)); } else { m_d->deferredSelectionOffset->ry() = y; } } QImage KisMask::createThumbnail(qint32 w, qint32 h) { KisPaintDeviceSP originalDevice = selection() ? selection()->projection() : 0; return originalDevice ? originalDevice->createThumbnail(w, h, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags) : QImage(); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()) : QImage(); } void KisMask::testingInitSelection(const QRect &rect) { m_d->selection = new KisSelection(); m_d->selection->pixelSelection()->select(rect, OPACITY_OPAQUE_U8); m_d->selection->updateProjection(rect); m_d->selection->setParentNode(this); } diff --git a/krita/image/kis_paint_device.cc b/krita/image/kis_paint_device.cc index 72d8aa240ad..a57b046e19f 100644 --- a/krita/image/kis_paint_device.cc +++ b/krita/image/kis_paint_device.cc @@ -1,1197 +1,1197 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * 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_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_types.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "commands/kis_paintdevice_convert_type_command.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lock_free_cache.h" class PaintDeviceCache { public: PaintDeviceCache(KisPaintDevice *paintDevice) : m_paintDevice(paintDevice), m_exactBoundsCache(paintDevice), m_nonDefaultPixelAreaCache(paintDevice), m_regionCache(paintDevice) { } void setupCache() { invalidate(); } void invalidate() { m_thumbnailsValid = false; m_exactBoundsCache.invalidate(); m_nonDefaultPixelAreaCache.invalidate(); m_regionCache.invalidate(); } QRect exactBounds() { return m_exactBoundsCache.getValue(); } QRect nonDefaultPixelArea() { return m_nonDefaultPixelAreaCache.getValue(); } QRegion region() { return m_regionCache.getValue(); } QImage createThumbnail(qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QImage thumbnail; if(m_thumbnailsValid) { thumbnail = findThumbnail(w, h); } else { m_thumbnails.clear(); m_thumbnailsValid = true; } if(thumbnail.isNull()) { thumbnail = m_paintDevice->createThumbnail(w, h, QRect(), renderingIntent, conversionFlags); cacheThumbnail(w, h, thumbnail); } Q_ASSERT(!thumbnail.isNull() || m_paintDevice->extent().isEmpty()); return thumbnail; } private: inline QImage findThumbnail(qint32 w, qint32 h) { QImage resultImage; if (m_thumbnails.contains(w) && m_thumbnails[w].contains(h)) { resultImage = m_thumbnails[w][h]; } return resultImage; } inline void cacheThumbnail(qint32 w, qint32 h, QImage image) { m_thumbnails[w][h] = image; } private: KisPaintDevice *m_paintDevice; struct ExactBoundsCache : KisLockFreeCache { ExactBoundsCache(KisPaintDevice *paintDevice) : m_paintDevice(paintDevice) {} QRect calculateNewValue() const { return m_paintDevice->calculateExactBounds(false); } private: KisPaintDevice *m_paintDevice; }; struct NonDefaultPixelCache : KisLockFreeCache { NonDefaultPixelCache(KisPaintDevice *paintDevice) : m_paintDevice(paintDevice) {} QRect calculateNewValue() const { return m_paintDevice->calculateExactBounds(true); } private: KisPaintDevice *m_paintDevice; }; struct RegionCache : KisLockFreeCache { RegionCache(KisPaintDevice *paintDevice) : m_paintDevice(paintDevice) {} QRegion calculateNewValue() const { return m_paintDevice->dataManager()->region(); } private: KisPaintDevice *m_paintDevice; }; ExactBoundsCache m_exactBoundsCache; NonDefaultPixelCache m_nonDefaultPixelAreaCache; RegionCache m_regionCache; bool m_thumbnailsValid; QMap > m_thumbnails; }; struct Q_DECL_HIDDEN KisPaintDevice::Private { class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; Private(KisPaintDevice *paintDevice); KisPaintDevice *q; KisDataManagerSP dataManager; KisNodeWSP parent; KisDefaultBoundsBaseSP defaultBounds; PaintDeviceCache cache; qint32 x; qint32 y; const KoColorSpace* colorSpace; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; KisPaintDeviceStrategy* currentStrategy(); }; #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), cache(paintDevice), x(0), y(0), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)) { } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(defaultBounds->bounds(), q, this)); } return wrappedStrategy.data(); } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(0, colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(0, colorSpace, defaultBounds, parent, name); } KisPaintDevice::KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name) : QObject(0) , m_d(new Private(this)) { init(explicitDataManager, src->colorSpace(), src->defaultBounds(), 0, name); m_d->x = src->x(); m_d->y = src->y(); } void KisPaintDevice::init(KisDataManagerSP explicitDataManager, const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); if (!defaultBounds) { defaultBounds = new KisDefaultBounds(); } m_d->colorSpace = colorSpace; Q_ASSERT(m_d->colorSpace); if(explicitDataManager) { m_d->dataManager = explicitDataManager; } else { const qint32 pixelSize = colorSpace->pixelSize(); quint8* defaultPixel = new quint8[colorSpace->pixelSize()]; colorSpace->fromQColor(Qt::transparent, defaultPixel); m_d->dataManager = new KisDataManager(pixelSize, defaultPixel); delete[] defaultPixel; Q_CHECK_PTR(m_d->dataManager); } m_d->cache.setupCache(); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { m_d->colorSpace = rhs.m_d->colorSpace; Q_ASSERT(m_d->colorSpace); m_d->x = rhs.m_d->x; m_d->y = rhs.m_d->y; Q_ASSERT(rhs.m_d->dataManager); m_d->dataManager = new KisDataManager(*rhs.m_d->dataManager); Q_CHECK_PTR(m_d->dataManager); m_d->cache.setupCache(); setDefaultBounds(rhs.defaultBounds()); setParentNode(0); } } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { clear(); m_d->x = src->x(); m_d->y = src->y(); if(!(*colorSpace() == *src->colorSpace())) { if (m_d->colorSpace->pixelSize() != src->colorSpace()->pixelSize()) { m_d->dataManager = 0; m_d->dataManager = new KisDataManager(src->pixelSize(), src->defaultPixel()); m_d->cache.setupCache(); } m_d->colorSpace = src->colorSpace(); } setDefaultPixel(src->defaultPixel()); setDefaultBounds(src->defaultBounds()); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache.invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache.invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache.invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache.invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache.invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::move(const QPoint &pt) { m_d->currentStrategy()->move(pt); } void KisPaintDevice::move(qint32 x, qint32 y) { move(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { move(QPoint(x, m_d->y)); } void KisPaintDevice::setY(qint32 y) { move(QPoint(m_d->x, y)); } qint32 KisPaintDevice::x() const { return m_d->x; } qint32 KisPaintDevice::y() const { return m_d->y; } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache.nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache.exactBounds(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h -1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = m_d->colorSpace->opacityU8(defaultPixel()); if(defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { m_d->dataManager->purge(m_d->dataManager->extent()); } void KisPaintDevice::setDefaultPixel(const quint8 *defPixel) { m_d->dataManager->setDefaultPixel(defPixel); m_d->cache.invalidate(); } const quint8 *KisPaintDevice::defaultPixel() const { return m_d->dataManager->defaultPixel(); } void KisPaintDevice::clear() { m_d->dataManager->clear(); m_d->cache.invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { Q_ASSERT(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval = m_d->dataManager->read(stream); m_d->cache.invalidate(); return retval; } KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { m_d->cache.invalidate(); dbgImage << this << colorSpace()->id() << dstColorSpace->id() << renderingIntent << conversionFlags; if (*colorSpace() == *dstColorSpace) { return 0; } KisPaintDevice dst(dstColorSpace); dst.setX(x()); dst.setY(y()); qint32 x, y, w, h; QRect rc = exactBounds(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); if (w == 0 || h == 0) { quint8 *defPixel = new quint8[dstColorSpace->pixelSize()]; memset(defPixel, 0, pixelSize()); m_d->colorSpace->convertPixelsTo(defaultPixel(), defPixel, dstColorSpace, 1, renderingIntent, conversionFlags); setDefaultPixel(defPixel); delete[] defPixel; } else { KisRandomConstAccessorSP srcIt = createRandomConstAccessorNG(x, y); KisRandomAccessorSP dstIt = dst.createRandomAccessorNG(x, y); for (qint32 row = y; row < y + h; ++row) { qint32 column = x; qint32 columnsRemaining = w; while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(column); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(column); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); srcIt->moveTo(column, row); dstIt->moveTo(column, row); const quint8 *srcData = srcIt->rawDataConst(); quint8 *dstData = dstIt->rawData(); m_d->colorSpace->convertPixelsTo(srcData, dstData, dstColorSpace, columns, renderingIntent, conversionFlags); column += columns; columnsRemaining -= columns; } } } KisDataManagerSP oldData = m_d->dataManager; const KoColorSpace *oldColorSpace = m_d->colorSpace; KisPaintDeviceConvertTypeCommand* cmd = new KisPaintDeviceConvertTypeCommand(this, oldData, oldColorSpace, dst.m_d->dataManager, dstColorSpace); setDataManager(dst.m_d->dataManager, dstColorSpace); return cmd; } void KisPaintDevice::setProfile(const KoColorProfile * profile) { if (profile == 0) return; m_d->cache.invalidate(); const KoColorSpace * dstSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (dstSpace) { m_d->colorSpace = dstSpace; } emit profileChanged(profile); } void KisPaintDevice::setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace) { m_d->dataManager = data; m_d->cache.setupCache(); if(colorSpace) { m_d->colorSpace = colorSpace; emit colorSpaceChanged(colorSpace); } } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache.invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile * dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect) const { KisPaintDeviceSP thumbnail = new KisPaintDevice(colorSpace()); int srcWidth, srcHeight; int srcX0, srcY0; QRect e = rect.isValid() ? rect : extent(); e.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (w > srcWidth) { w = srcWidth; h = qint32(double(srcWidth) / w * h); } if (h > srcHeight) { h = srcHeight; w = qint32(double(srcHeight) / h * w); } if (srcWidth > srcHeight) h = qint32(double(srcHeight) / srcWidth * w); else if (srcHeight > srcWidth) w = qint32(double(srcWidth) / srcHeight * h); const qint32 pixelSize = this->pixelSize(); KisRandomConstAccessorSP iter = createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = 0; y < h; ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = 0; x < w; ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; iter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), iter->rawDataConst(), pixelSize); } } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { KisPaintDeviceSP dev = createThumbnailDevice(w, h, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { return m_d->cache.createThumbnail(w, h, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache.invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(x, y, w); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(x, y, w); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache.invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { KisDataManager* dm = const_cast< KisDataManager*>(m_d->dataManager.data()); return new KisRepeatHLineConstIteratorNG(dm, x, y, w, m_d->x, m_d->y, _dataWidth); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { KisDataManager* dm = const_cast< KisDataManager*>(m_d->dataManager.data()); return new KisRepeatVLineConstIteratorNG(dm, x, y, h, m_d->x, m_d->y, _dataWidth); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache.invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds(); if (r.isValid()) { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const quint8* defaultPixel_ = defaultPixel(); bool transparentDefault = (m_d->colorSpace->opacityU8(defaultPixel_) == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches m_d->colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && m_d->colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel_, m_d->colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } m_d->dataManager->purge(r.translated(-m_d->x, -m_d->y)); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache.invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace) { KoColor kc2(kc, m_d->colorSpace); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace->pixelSize()); } m_d->cache.invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->x == src->x() && m_d->y == src->y() && *colorSpace() == *src->colorSpace(); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager; } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace != 0); return m_d->colorSpace; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); qSort(channels); foreach(KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } diff --git a/krita/image/kis_paint_device.h b/krita/image/kis_paint_device.h index 571026a0740..c9c48aacfd0 100644 --- a/krita/image/kis_paint_device.h +++ b/krita/image/kis_paint_device.h @@ -1,786 +1,786 @@ /* * Copyright (c) 2002 patrick julien * Copyright (c) 2006 Boudewijn Rempt * * 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_PAINT_DEVICE_IMPL_H_ #define KIS_PAINT_DEVICE_IMPL_H_ #include #include #include #include "kis_debug.h" #include #include "kis_types.h" #include "kis_global.h" #include "kis_shared.h" #include "kis_default_bounds_base.h" #include class KUndo2Command; class QRect; class QImage; class QPoint; class QString; class QColor; class QIODevice; class KoColor; class KoColorSpace; class KoColorProfile; class KisDataManager; class KisPaintDeviceWriter; typedef KisSharedPtr KisDataManagerSP; /** * A paint device contains the actual pixel data and offers methods * to read and write pixels. A paint device has an integer x,y position * (i.e., are not positioned on the image with sub-pixel accuracy). * A KisPaintDevice doesn't have any fixed size, the size changes dynamically * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice : public QObject , public KisShared { Q_OBJECT public: /** * Create a new paint device with the specified colorspace. * * @param colorSpace the colorspace of this paint device * @param name for debugging purposes */ KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString()); /** * Create a new paint device with the specified colorspace. The * parent node will be notified of changes to this paint device. * * @param parent the node that contains this paint device * @param colorSpace the colorspace of this paint device * @param defaultBounds boundaries of the device in case it is empty * @param name for debugging purposes */ KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = 0, const QString& name = QString()); KisPaintDevice(const KisPaintDevice& rhs); virtual ~KisPaintDevice(); protected: /** * A special constructor for usage in KisPixelSelection. It allows * two paint devices to share a data manager. * * @param explicitDataManager data manager to use inside paint device * @param src source paint device to copy parameters from * @param name for debugging purposes */ KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name = QString()); public: /** * Write the pixels of this paint device into the specified file store. */ bool write(KisPaintDeviceWriter &store); /** * Fill this paint device with the pixels from the specified file store. */ bool read(QIODevice *stream); public: /** * set the parent node of the paint device */ void setParentNode(KisNodeWSP parent); /** * set the default bounds for the paint device when * the default pixel in not completely transarent */ void setDefaultBounds(KisDefaultBoundsBaseSP bounds); /** * the default bounds rect of the paint device */ KisDefaultBoundsBaseSP defaultBounds() const; /** * Moves the device to these new coordinates (so no incremental move or so) */ void move(qint32 x, qint32 y); /** * Convenience method for the above. */ virtual void move(const QPoint& pt); /** * The X offset of the paint device */ qint32 x() const; /** * The Y offset of the paint device */ qint32 y() const; /** * set the X offset of the paint device */ void setX(qint32 x); /** * set the Y offset of the paint device */ void setY(qint32 y); /** * Retrieve the bounds of the paint device. The size is not exact, * but may be larger if the underlying datamanager works that way. * For instance, the tiled datamanager keeps the extent to the nearest * multiple of 64. * * If default pixel is not transparent, then the actual extent * rect is united with the defaultBounds()->bounds() value * (the size of the image, usually). */ QRect extent() const; /// Convience method for the above void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; /** * Get the exact bounds of this paint device. The real solution is * very slow because it does a linear scanline search, but it * uses caching, so calling to this function without changing * the device is quite cheap. * * Exactbounds follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned *
  • if default pixel is not transparent, then the union * (defaultBounds()->bounds() | nonDefaultPixelArea()) is * returned *
* \see calculateExactBounds() */ QRect exactBounds() const; /** * Retuns exact rectangle of the paint device that contains * non-default pixels. For paint devices with fully transparent * default pixel is equivalent to exactBounds(). * * nonDefaultPixelArea() follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned. The same as exactBounds() *
  • if default pixel is not transparent, then calculates the * rectangle of non-default pixels. May be smaller or greater * than image bounds *
* \see calculateExactBounds() */ QRect nonDefaultPixelArea() const; /** * Returns a rough approximation of region covered by device. * For tiled data manager, it region will consist of a number * of rects each corresponding to a tile. */ QRegion region() const; /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ void crop(qint32 x, qint32 y, qint32 w, qint32 h); /// Convience method for the above void crop(const QRect & r); /** * Complete erase the current paint device. Its size will become 0. This * does not take the selection into account. */ virtual void clear(); /** * Clear the given rectangle to transparent black. The paint device will expand to * contain the given rect. */ void clear(const QRect & rc); /** * Frees the memory occupied by the pixels containing default * values. The extents() and exactBounds() of the image will * probably also shrink */ void purgeDefaultPixels(); /** * Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the * caller still owns the pointer and needs to delete it to avoid memory leaks. */ void setDefaultPixel(const quint8 *defPixel); /** * Get a pointer to the default pixel. */ const quint8 *defaultPixel() const; /** * Fill the given rectangle with the given pixel. The paint device will expand to * contain the given rect. */ void fill(const QRect & rc, const KoColor &color); /** * Overloaded function. For legacy purposes only. * Please use fill(const QRect & rc, const KoColor &color) instead */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); public: /** * Prepares the device for fastBitBlt opreration. It clears * the device, switches x,y shifts and colorspace if needed. * After this call fastBitBltPossible will return true. * May be used for initialization of temporary devices. */ void prepareClone(KisPaintDeviceSP src); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * After calling this function: * (this->extent() >= this->exactBounds() == rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * Be careful, this function will copy *at least* \a rect * of pixels. Actual copy area will be a bigger - it will * be aligned by tiles borders. So after calling this function: * (this->extent() == this->exactBounds() >= rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect); protected: friend class KisPaintDeviceTest; friend class DataReaderThread; /** * Checks whether a src paint device can be used as source * of fast bitBlt operation. The result of the check may * depend on whether color spaces coinside, whether there is * any shift of tiles between the devices and etc. * * WARNING: This check must be done before performing any * fast bitBlt operation! * * \see fastBitBlt * \see fastBitBltRough */ bool fastBitBltPossible(KisPaintDeviceSP src); /** * Clones rect from another paint device. The cloned area will be * shared between both paint devices as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, * * \see fastBitBltPossible * \see fastBitBltRough */ void fastBitBlt(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBlt() but reads old data */ void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect); /** * Clones rect from another paint device in a rough and fast way. * All the tiles touched by rect will be shared, between both * devices, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. * * \see fastBitBltPossible * \see fastBitBlt */ void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBltRough() but reads old data */ void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect); public: /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. */ void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Read the bytes representing the rectangle rect into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. * @param data The address of the memory to receive the bytes read * @param rect The rectangle in the paint device to read from */ void readBytes(quint8 * data, const QRect &rect) const; /** * Copy the bytes in data into the rect specified by x, y, w, h. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. */ void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h); /** * Copy the bytes in data into the rectangle rect. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. * @param data The address of the memory to write bytes from * @param rect The rectangle in the paint device to write to */ void writeBytes(const quint8 * data, const QRect &rect); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channes. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. * * XXX: what about undo? */ void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Converts the paint device to a different colorspace * * @return a command that can be used to undo the conversion. */ KUndo2Command* convertTo(const KoColorSpace * dstColorSpace, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Changes the profile of the colorspace of this paint device to the given * profile. If the given profile is 0, nothing happens. */ void setProfile(const KoColorProfile * profile); /** * Fill this paint device with the data from image; starting at (offsetX, offsetY) * @param srcProfileName name of the RGB profile to interpret the image as. 0 is interpreted as sRGB */ void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ virtual QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags) const; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Overridden method for convenience */ QImage convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags) const; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ QImage convertToQImage(const KoColorProfile * dstProfile, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags) const; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining * the aspect ratio. The width and height of the returned device * won't exceed \p maxw and \p maxw, but they may be smaller. * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * */ KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. * The colors are not corrected for display! * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail */ QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ QImage createThumbnail(qint32 maxw, qint32 maxh, - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Fill c and opacity with the values found at x and y. * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, QColor *c) const; /** * Fill kc with the values found at x and y. This method differs * from the above in using KoColor, which can be of any colorspace * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, KoColor * kc) const; /** * Set the specified pixel to the specified color. Note that this * bypasses KisPainter. the PaintDevice is here used as an equivalent * to QImage, not QPixmap. This means that this is not undoable; also, * there is no compositing with an existing value at this location. * * The color values will be transformed from the display profile to * the paint device profile. * * Note that this will use 8-bit values and may cause a significant * degradation when used on 16-bit or hdr quality images. * * @return true if the operation was successful */ bool setPixel(qint32 x, qint32 y, const QColor& c); /// Convience method for the above bool setPixel(qint32 x, qint32 y, const KoColor& kc); /** * @return the colorspace of the pixels in this paint device */ const KoColorSpace* colorSpace() const; /** * There is quite a common technique in Krita. It is used in * cases, when we want to paint something over a paint device * using the composition, opacity or selection. E.g. painting a * dab in a paint op, filling the selection in the Fill Tool. * Such work is usually done in the following way: * * 1) Create a paint device * * 2) Fill it with the desired color or data * * 3) Create a KisPainter and set all the properties of the * trasaction: selection, compositeOp, opacity and etc. * * 4) Paint a newly created paint device over the destination * device. * * The following two methods (createCompositionSourceDevice() or * createCompositionSourceDeviceFixed())should be used for the * accomplishing the step 1). The point is that the desired color * space of the temporary device may not coincide with the color * space of the destination. That is the case, for example, for * the alpha8() colorspace used in the selections. So for such * devices the temporary target would have a different (grayscale) * color space. * * So there are two rules of thumb: * * 1) If you need a temporary device which you are going to fill * with some data and then paint over the paint device, create * it with either createCompositionSourceDevice() or * createCompositionSourceDeviceFixed(). * * 2) Do *not* expect that the color spaces of the destination and * the temporary device would coincide. If you need to copy a * single pixel from one device to another, you can use * KisCrossDeviceColorPicker class, that will handle all the * necessary conversions for you. * * \see createCompositionSourceDeviceFixed() * \see compositionSourceColorSpace() * \see KisCrossDeviceColorPicker * \see KisCrossDeviceColorPickerInt */ KisPaintDeviceSP createCompositionSourceDevice() const; /** * The same as createCompositionSourceDevice(), but initializes the * newly created device with the content of \p cloneSource * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const; /** * The same as createCompositionSourceDevice(), but initializes * the newly created device with the *rough* \p roughRect of * \p cloneSource. * * "Rough rect" means that it may copy a bit more than * requested. It is expected that the caller will not use the area * outside \p roughRect. * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const; /** * This is a convenience method for createCompositionSourceDevice() * * \see createCompositionSourceDevice() */ KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const; /** * This is a lowlevel method for the principle used in * createCompositionSourceDevice(). In most of the cases the paint * device creation methods should be used instead of this function. * * \see createCompositionSourceDevice() * \see createCompositionSourceDeviceFixed() */ virtual const KoColorSpace* compositionSourceColorSpace() const; /** * @return the internal datamanager that keeps the pixels. */ KisDataManagerSP dataManager() const; /** * Replace the pixel data, color strategy, and profile. */ void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0); /** * Return the number of bytes a pixel takes. */ quint32 pixelSize() const; /** * Return the number of channels a pixel takes */ quint32 channelCount() const; public: /** * Add the specified rect to the parent layer's set of dirty rects * (if there is a parent layer) */ void setDirty(const QRect & rc); /** * Add the specified region to the parent layer's dirty region * (if there is a parent layer) */ void setDirty(const QRegion & region); /** * Set the parent layer completely dirty, if this paint device has * as parent layer. */ void setDirty(); void setDirty(const QVector rects); public: KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w); KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const; KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h); KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const; KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y); KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param rc indicates the rectangle that truly contains data */ KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param rc indicates the rectangle that trully contains data */ KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const; /** * This function create a random accessor which can easily access to sub pixel values. * @param selection an up-to-date selection that has the same origin as the paint device */ KisRandomSubAccessorSP createRandomSubAccessor() const; /** Clear the selected pixels from the paint device */ void clearSelection(KisSelectionSP selection); Q_SIGNALS: void profileChanged(const KoColorProfile * profile); void colorSpaceChanged(const KoColorSpace *colorspace); public: friend class PaintDeviceCache; /** * Caclculates exact bounds of the device. Used internally * by a transparent caching system. The solution is very slow * because it does a linear scanline search. So the complexity * is n*n at worst. * * \see exactBounds(), nonDefaultPixelArea() */ QRect calculateExactBounds(bool nonDefaultOnly) const; public: struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject(); }; static MemoryReleaseObject* createMemoryReleaseObject(); private: KisPaintDevice& operator=(const KisPaintDevice&); void init(KisDataManagerSP explicitDataManager, const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name); // Only KisPainter is allowed to have access to these low-level methods friend class KisPainter; /** * Return a vector with in order the size in bytes of the channels * in the colorspace of this paint device. */ QVector channelSizes() const; protected: friend class KisSelectionTest; KisNodeWSP parentNode() const; private: struct Private; Private * const m_d; }; #endif // KIS_PAINT_DEVICE_IMPL_H_ diff --git a/krita/image/kis_painter.cc b/krita/image/kis_painter.cc index 94c674029a7..2b39e14ab08 100644 --- a/krita/image/kis_painter.cc +++ b/krita/image/kis_painter.cc @@ -1,2822 +1,2822 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * Copyright (c) 2011 Silvio Heinrich * * 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_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_types.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_paintop.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include "kis_paint_information.h" #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #define trunc(x) ((int)(x)) #ifndef Q_OS_WIN #endif struct Q_DECL_HIDDEN KisPainter::Private { Private(KisPainter *_q) : q(_q) {} KisPainter *q; KisPaintDeviceSP device; KisSelectionSP selection; KisTransaction* transaction; KoUpdater* progressUpdater; QVector dirtyRects; KisPaintOp* paintOp; KoColor paintColor; KoColor backgroundColor; const KisFilterConfiguration* generator; KisPaintLayer* sourceLayer; FillStyle fillStyle; StrokeStyle strokeStyle; bool antiAliasPolygonFill; const KoPattern* pattern; QPointF duplicateOffset; quint32 pixelSize; const KoColorSpace* colorSpace; KoColorProfile* profile; const KoCompositeOp* compositeOp; const KoAbstractGradient* gradient; KisPaintOpPresetSP paintOpPreset; QImage polygonMaskImage; QPainter* maskPainter; KisFillPainter* fillPainter; KisPaintDeviceSP polygon; qint32 maskImageWidth; qint32 maskImageHeight; QPointF axesCenter; bool mirrorHorizontaly; bool mirrorVerticaly; bool isOpacityUnit; // TODO: move into ParameterInfo KoCompositeOp::ParameterInfo paramInfo; KoColorConversionTransformation::Intent renderingIntent; KoColorConversionTransformation::ConversionFlags conversionFlags; bool tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY); void fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect); }; KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this)) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this)) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->pattern = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->gradient = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontaly = false; d->mirrorVerticaly = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); - d->renderingIntent = KoColorConversionTransformation::InternalRenderingIntent; - d->conversionFlags = KoColorConversionTransformation::InternalConversionFlags; + d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); + d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstRect.topLeft(), src, srcRect); } else { gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection = d->selection->projection(); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection = d->selection->projection(); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection = d->selection->projection(); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (std::bad_alloc) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { if (index >= (int) points.count()) return; if (numPoints < 0) numPoints = points.count(); if (index + numPoints > (int) points.count()) numPoints = points.count() - index; KisDistanceInformation saveDist; for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance; for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontaly || d->mirrorVerticaly) { QTransform C1 = QTransform::fromTranslate(-d->axesCenter.x(), -d->axesCenter.y()); QTransform C2 = QTransform::fromTranslate(d->axesCenter.x(), d->axesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontaly) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVerticaly) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontaly && d->mirrorVerticaly) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: // Fall through case FillStyleGradient: // Currently unsupported, fall through case FillStyleStrokes: // Currently unsupported, fall through warnImage << "Unknown or unsupported fill style in fillPolygon\n"; case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... fillPainter->fillRect(fillRect, pattern); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect) { // we are drawing mask, it has to be white // color of the path is given by paintColor() Q_ASSERT(pen.color() == Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; if (x2device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = int(start.x()); int y = int(start.y()); int x2 = int(end.x()); int y2 = int(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = (float)yd / (float)xd; float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor mycolor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = int(fx + 1) - fx; float br2 = fx - (int)fx; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = int(fy + 1) - fy; float br2 = fy - (int)fy; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor lineColor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; ix1 = (int)x1; ix2 = (int)x2; iy1 = (int)y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; iy1 = (int)y1; iy2 = (int)y2; ix1 = (int)x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y interger coordinates xend = static_cast(x1 + 0.5f); yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = trunc(x2 + 0.5f); yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, int (yf)); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, int (yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y interger coordinates yend = static_cast(y1 + 0.5f); xend = x1 + grad * (yend - y1); ygap = invertFrac(y1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = trunc(y2 + 0.5f); xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(int (xf), y); if (selectionAccessor) selectionAccessor->moveTo(int (xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(int (xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int (xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - int (yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - int (yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, (int)yfa); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfa); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, (int)yfb); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfb); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, int (yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, int (yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = yfa + 1; i <= yfb; i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = yfa + 1; i >= yfb; i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - int (xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - int (xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(int (xfa), y); if (selectionAccessor) selectionAccessor->moveTo(int (xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(int(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = (int) xfa + 1; i <= (int) xfb; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = (int) xfb; i <= (int) xfa + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPattern * pattern) { d->pattern = pattern; } const KoPattern * KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(const KisFilterConfiguration * generator) { d->generator = generator; } const KisFilterConfiguration * KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradient* gradient) { d->gradient = gradient; } const KoAbstractGradient* KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontaly, bool mirrorVerticaly) { d->axesCenter = axesCenter; d->mirrorHorizontaly = mirrorHorizontaly; d->mirrorVerticaly = mirrorVerticaly; } void KisPainter::copyMirrorInformation(KisPainter* painter) { painter->setMirrorInformation(d->axesCenter, d->mirrorHorizontaly, d->mirrorVerticaly); } bool KisPainter::hasMirroring() const { return d->mirrorHorizontaly || d->mirrorVerticaly; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontaly && !d->mirrorVerticaly) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontaly && !d->mirrorVerticaly) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); int mirrorX = -((x+rc.width()) - d->axesCenter.x()) + d->axesCenter.x(); int mirrorY = -((y+rc.height()) - d->axesCenter.y()) + d->axesCenter.y(); if (d->mirrorHorizontaly && d->mirrorVerticaly){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontaly){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVerticaly){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); int mirrorX = -((x+rc.width()) - d->axesCenter.x()) + d->axesCenter.x(); int mirrorY = -((y+rc.height()) - d->axesCenter.y()) + d->axesCenter.y(); if (d->mirrorHorizontaly && d->mirrorVerticaly){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontaly){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVerticaly){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontaly || d->mirrorVerticaly){ KisFixedPaintDeviceSP mirrorDab = new KisFixedPaintDevice(dab->colorSpace()); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->initialize(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontaly || d->mirrorVerticaly){ KisFixedPaintDeviceSP mirrorDab = new KisFixedPaintDevice(dab->colorSpace()); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->initialize(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); int mirrorX = -((x+rc.width()) - d->axesCenter.x()) + d->axesCenter.x(); int mirrorY = -((y+rc.height()) - d->axesCenter.y()) + d->axesCenter.y(); rects << rc; if (d->mirrorHorizontaly && d->mirrorVerticaly){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontaly) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVerticaly) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } foreach(const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } foreach(const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } foreach(const QRect &rc, rects) { renderMirrorMask(rc, dab); } } diff --git a/krita/image/kis_selection_based_layer.cpp b/krita/image/kis_selection_based_layer.cpp index fc90d63dae9..d3872c5ba11 100644 --- a/krita/image/kis_selection_based_layer.cpp +++ b/krita/image/kis_selection_based_layer.cpp @@ -1,269 +1,269 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * 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_selection_based_layer.h" #include #include "kis_debug.h" #include #include "kis_image.h" #include "kis_painter.h" #include "kis_default_bounds.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter.h" struct Q_DECL_HIDDEN KisSelectionBasedLayer::Private { public: Private() : useSelectionInProjection(true) {} KisSelectionSP selection; KisPaintDeviceSP paintDevice; bool useSelectionInProjection; }; KisSelectionBasedLayer::KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfiguration *filterConfig, bool useGeneratorRegistry) : KisLayer(image.data(), name, OPACITY_OPAQUE_U8), KisNodeFilterInterface(filterConfig, useGeneratorRegistry), m_d(new Private()) { if (!selection) initSelection(); else setInternalSelection(selection); m_d->paintDevice = new KisPaintDevice(this, image->colorSpace(), new KisDefaultBounds(image)); } KisSelectionBasedLayer::KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs) : KisLayer(rhs) , KisIndirectPaintingSupport() , KisNodeFilterInterface(rhs) , m_d(new Private()) { setInternalSelection(rhs.m_d->selection); m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data()); } KisSelectionBasedLayer::~KisSelectionBasedLayer() { delete m_d; } void KisSelectionBasedLayer::initSelection() { m_d->selection = new KisSelection(); m_d->selection->pixelSelection()->select(image()->bounds()); m_d->selection->setParentNode(this); m_d->selection->updateProjection(); } void KisSelectionBasedLayer::setImage(KisImageWSP image) { m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(image)); KisLayer::setImage(image); } bool KisSelectionBasedLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } KisPaintDeviceSP KisSelectionBasedLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const { return m_d->selection->pixelSelection(); } bool KisSelectionBasedLayer::needProjection() const { return m_d->selection; } void KisSelectionBasedLayer::setUseSelectionInProjection(bool value) const { m_d->useSelectionInProjection = value; } KisSelectionSP KisSelectionBasedLayer::fetchComposedInternalSelection(const QRect &rect) const { if (!m_d->selection) return 0; m_d->selection->updateProjection(rect); KisSelectionSP tempSelection = m_d->selection; lockTemporaryTarget(); if (hasTemporaryTarget()) { /** * Cloning a selection with COW * FIXME: check whether it's faster than usual bitBlt'ing */ tempSelection = new KisSelection(*tempSelection); KisPainter gc2(tempSelection->pixelSelection()); setupTemporaryPainter(&gc2); gc2.bitBlt(rect.topLeft(), temporaryTarget(), rect); } unlockTemporaryTarget(); return tempSelection; } void KisSelectionBasedLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisSelectionSP tempSelection; if (m_d->useSelectionInProjection) { tempSelection = fetchComposedInternalSelection(rect); } projection->clear(rect); KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect, tempSelection); } QRect KisSelectionBasedLayer::cropChangeRectBySelection(const QRect &rect) const { return m_d->selection ? rect & m_d->selection->selectedRect() : rect; } QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } void KisSelectionBasedLayer::resetCache(const KoColorSpace *colorSpace) { if (!colorSpace) colorSpace = image()->colorSpace(); if (!m_d->paintDevice || !(*m_d->paintDevice->colorSpace() == *colorSpace)) { m_d->paintDevice = new KisPaintDevice(colorSpace); } else { m_d->paintDevice->clear(); } } KisSelectionSP KisSelectionBasedLayer::internalSelection() const { return m_d->selection; } void KisSelectionBasedLayer::setInternalSelection(KisSelectionSP selection) { if (selection) { m_d->selection = new KisSelection(*selection.data()); m_d->selection->setParentNode(this); m_d->selection->updateProjection(); } else m_d->selection = 0; } qint32 KisSelectionBasedLayer::x() const { return m_d->selection ? m_d->selection->x() : 0; } qint32 KisSelectionBasedLayer::y() const { return m_d->selection ? m_d->selection->y() : 0; } void KisSelectionBasedLayer::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } } void KisSelectionBasedLayer::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } } void KisSelectionBasedLayer::setDirty(const QRect & rect) { KisLayer::setDirty(rect); } void KisSelectionBasedLayer::setDirty() { Q_ASSERT(image()); setDirty(image()->bounds()); } QRect KisSelectionBasedLayer::extent() const { Q_ASSERT(image()); return m_d->selection ? m_d->selection->selectedRect() : image()->bounds(); } QRect KisSelectionBasedLayer::exactBounds() const { Q_ASSERT(image()); return m_d->selection ? m_d->selection->selectedExactRect() : image()->bounds(); } QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h) { KisSelectionSP originalSelection = internalSelection(); KisPaintDeviceSP originalDevice = original(); return originalDevice && originalSelection ? originalDevice->createThumbnail(w, h, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags) : + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()) : QImage(); } diff --git a/krita/image/tests/kis_colorspace_convert_visitor_test.cpp b/krita/image/tests/kis_colorspace_convert_visitor_test.cpp index 4cf2ebbb38c..69781c2b229 100644 --- a/krita/image/tests/kis_colorspace_convert_visitor_test.cpp +++ b/krita/image/tests/kis_colorspace_convert_visitor_test.cpp @@ -1,46 +1,46 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_colorspace_convert_visitor_test.h" #include #include #include #include #include "kis_colorspace_convert_visitor.h" #include "kis_paint_layer.h" #include "kis_image.h" void KisColorSpaceConvertVisitorTest::testCreation() { const KoColorSpace * rgb = KoColorSpaceRegistry::instance()->rgb16(); QVERIFY(rgb); const KoColorSpace * lab = KoColorSpaceRegistry::instance()->lab16(); QVERIFY(lab); KisImageSP image = new KisImage(0, 100, 100, lab, "test"); KisPaintLayerSP layer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8, lab); KisColorSpaceConvertVisitor test(image, lab, rgb, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); layer->accept(test); QVERIFY(layer->colorSpace()->colorModelId() == rgb->colorModelId()); } QTEST_MAIN(KisColorSpaceConvertVisitorTest) diff --git a/krita/image/tests/kis_image_test.cpp b/krita/image/tests/kis_image_test.cpp index 1e3ed4ccee8..97fdc07a731 100644 --- a/krita/image/tests/kis_image_test.cpp +++ b/krita/image/tests/kis_image_test.cpp @@ -1,624 +1,624 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfiguration *configuration = filter->defaultConfiguration(0); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer 2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); } #include "testutil.h" #include "kis_group_layer.h" #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOp(COMPOSITE_ADD); layer8->setCompositeOp(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOp(COMPOSITE_ADD); group1->setCompositeOp(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ExternalImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ExternalImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = p.image->flattenLayer(p.layer2); p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = p.image->flattenLayer(p.group1); p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = p.image->flattenLayer(p.layer5); p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = p.image->mergeDown(p.layer5, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = p.image->mergeDown(p.layer2, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = p.image->mergeDown(p.group1, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = p.image->mergeDown(p.layer2, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = p.image->mergeDown(p.layer6, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = p.image->mergeDown(p.group1, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = p.image->mergeDown(p.layer8, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { qSwap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = p.image->mergeDown(layer3, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = p.image->mergeDown(layer3, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } QTEST_MAIN(KisImageTest) diff --git a/krita/image/tests/kis_selection_test.cpp b/krita/image/tests/kis_selection_test.cpp index bcf7620cf5a..853a04bbf57 100644 --- a/krita/image/tests/kis_selection_test.cpp +++ b/krita/image/tests/kis_selection_test.cpp @@ -1,340 +1,340 @@ /* * Copyright (c) 2007 Sven Langkamp * * 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_selection_test.h" #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_pixel_selection.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_mask.h" #include "kis_image.h" #include "kis_transparency_mask.h" #include "testutil.h" #include void KisSelectionTest::testGrayColorspaceConversion() { const KoColorSpace *csA = KoColorSpaceRegistry::instance()-> colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()); const KoColorSpace *csNoA = KoColorSpaceRegistry::instance()->alpha8(); QVERIFY(csA); QVERIFY(csNoA); QCOMPARE(csA->pixelSize(), 2U); QCOMPARE(csNoA->pixelSize(), 1U); quint8 color1[1] = {128}; quint8 color2[2] = {64,32}; csA->convertPixelsTo(color2, color1, csNoA, 1, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color1[0], 8); csNoA->convertPixelsTo(color1, color2, csA, 1, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color2[0], 8); QCOMPARE((int)color2[1], 255); } void KisSelectionTest::testGrayColorspaceOverComposition() { const KoColorSpace *csA = KoColorSpaceRegistry::instance()-> colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()); const KoColorSpace *csNoA = KoColorSpaceRegistry::instance()->alpha8(); QVERIFY(csA); QVERIFY(csNoA); QCOMPARE(csA->pixelSize(), 2U); QCOMPARE(csNoA->pixelSize(), 1U); quint8 color0[2] = {32,255}; quint8 color1[2] = {128,64}; quint8 color3[1] = {32}; KoCompositeOp::ParameterInfo params; params.dstRowStart = color0; params.dstRowStride = 0; params.srcRowStart = color1; params.srcRowStride = 0; params.maskRowStart = 0; params.maskRowStride = 0; params.rows = 1; params.cols = 1; params.opacity = 1.0; params.flow = 1.0; csA->bitBlt(csA, params, csA->compositeOp(COMPOSITE_OVER), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color0[0], 56); QCOMPARE((int)color0[1], 255); params.dstRowStart = color3; csNoA->bitBlt(csA, params, csNoA->compositeOp(COMPOSITE_OVER), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color3[0], 56); } void KisSelectionTest::testSelectionComponents() { KisSelectionSP selection = new KisSelection(); QCOMPARE(selection->hasPixelSelection(), false); QCOMPARE(selection->hasShapeSelection(), false); QCOMPARE(selection->shapeSelection(), (void*)0); selection->pixelSelection()->select(QRect(10,10,10,10)); QCOMPARE(selection->hasPixelSelection(), true); QCOMPARE(selection->selectedExactRect(), QRect(10,10,10,10)); } void KisSelectionTest::testSelectionActions() { KisSelectionSP selection = new KisSelection(); QVERIFY(selection->hasPixelSelection() == false); QVERIFY(selection->hasShapeSelection() == false); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 20, 20)); KisPixelSelectionSP tmpSel = new KisPixelSelection(); tmpSel->select(QRect(10, 0, 20, 20)); pixelSelection->applySelection(tmpSel, SELECTION_ADD); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0, 0, 30, 20)); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 30, 20)); pixelSelection->clear(); pixelSelection->select(QRect(0, 0, 20, 20)); pixelSelection->applySelection(tmpSel, SELECTION_SUBTRACT); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0, 0, 10, 20)); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 10, 20)); pixelSelection->clear(); pixelSelection->select(QRect(0, 0, 20, 20)); pixelSelection->applySelection(tmpSel, SELECTION_INTERSECT); QCOMPARE(pixelSelection->selectedExactRect(), QRect(10, 0, 10, 20)); QCOMPARE(selection->selectedExactRect(), QRect(10, 0, 10, 20)); } void KisSelectionTest::testInvertSelection() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1024, 1024, cs, "stest"); KisSelectionSP selection = new KisSelection(new KisDefaultBounds(image)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(20, 20, 20, 20)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 30, 30), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MIN_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 512, 512), MIN_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(20, 20, 20, 20)); pixelSelection->invert(); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 100, 100), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 22, 22), MIN_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 512, 512), MAX_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(pixelSelection->selectedRect(), QRect(0,0,1024,1024)); selection->updateProjection(); QCOMPARE(selection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(selection->selectedRect(), QRect(0,0,1024,1024)); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 100, 100), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 22, 22), MIN_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 10, 10), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 0, 0), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 512, 512), MAX_SELECTED); } void KisSelectionTest::testInvertSelectionSemi() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1024, 1024, cs, "stest"); KisSelectionSP selection = new KisSelection(new KisDefaultBounds(image)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); quint8 selectedness = 42; pixelSelection->select(QRect(20, 20, 20, 20), selectedness); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 30, 30), selectedness); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MIN_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(20, 20, 20, 20)); pixelSelection->invert(); quint8 invertedSelectedness = MAX_SELECTED - selectedness; QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 30, 30), invertedSelectedness); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MAX_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(pixelSelection->selectedRect(), QRect(0,0,1024,1024)); selection->updateProjection(); QCOMPARE(selection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(selection->selectedRect(), QRect(0,0,1024,1024)); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 30, 30), invertedSelectedness); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 0, 0), MAX_SELECTED); } void KisSelectionTest::testCopy() { KisSelectionSP sel = new KisSelection(); sel->pixelSelection()->select(QRect(10, 10, 200, 200), 128); sel->updateProjection(); KisSelectionSP sel2 = new KisSelection(*sel.data()); QCOMPARE(sel2->selectedExactRect(), sel->selectedExactRect()); QPoint errpoint; if (!TestUtil::comparePaintDevices(errpoint, sel->projection(), sel2->projection())) { sel2->projection()->convertToQImage(0, 0, 0, 200, 200).save("merge_visitor6.png"); QFAIL(QString("Failed to copy selection, first different pixel: %1,%2 ") .arg(errpoint.x()) .arg(errpoint.y()) .toLatin1()); } } void KisSelectionTest::testSelectionExactBounds() { QRect referenceImageRect(0,0,1000,1000); QRect referenceDeviceRect(100,100,1040,1040); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, referenceImageRect.width(), referenceImageRect.height(), cs, "stest"); KisPaintDeviceSP device = new KisPaintDevice(cs); device->fill(referenceDeviceRect, KoColor(Qt::white, cs)); QCOMPARE(device->exactBounds(), referenceDeviceRect); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(device, image)); quint8 defaultPixel = MAX_SELECTED; selection->pixelSelection()->setDefaultPixel(&defaultPixel); // the selection uses device's extent only for performance reasons // \see bug 320213 QCOMPARE(selection->selectedExactRect(), device->extent() | referenceImageRect); } void KisSelectionTest::testSetParentNodeAfterCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 100, 100, cs, "stest"); KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); QCOMPARE(selection->parentNode(), KisNodeWSP(0)); QCOMPARE(selection->pixelSelection()->parentNode(), KisNodeWSP(0)); selection->setParentNode(image->root()); QCOMPARE(selection->parentNode(), KisNodeWSP(image->root())); QCOMPARE(selection->pixelSelection()->parentNode(), KisNodeWSP(image->root())); } void KisSelectionTest::testSetParentNodeBeforeCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 100, 100, cs, "stest"); KisSelectionSP selection = new KisSelection(); selection->setParentNode(image->root()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); QCOMPARE(selection->parentNode(), KisNodeWSP(image->root())); QCOMPARE(selection->pixelSelection()->parentNode(), KisNodeWSP(image->root())); } void KisSelectionTest::testOutlineGeneration() { KisSelectionSP sel = new KisSelection(); sel->pixelSelection()->select(QRect(428,436, 430,211), 128); QVERIFY(sel->outlineCacheValid()); QPainterPath originalOutline = sel->outlineCache(); sel->pixelSelection()->invalidateOutlineCache(); sel->recalculateOutlineCache(); QPainterPath calculatedOutline = sel->outlineCache(); QPainterPath closedSubPath = calculatedOutline; closedSubPath.closeSubpath(); /** * Our outline generation code has a small problem: it can * generate a polygon, which isn't closed (it'll repeat the first * point instead). There is a special workaround for it in * KisPixelSelection::recalculateOutlineCache(), which explicitly * closes the path, so here we just check it. */ bool isClosed = closedSubPath == calculatedOutline; QVERIFY(isClosed); } QTEST_MAIN(KisSelectionTest) diff --git a/krita/libbrush/kis_gbr_brush.cpp b/krita/libbrush/kis_gbr_brush.cpp index b9ed23bda59..cc29f36e182 100644 --- a/krita/libbrush/kis_gbr_brush.cpp +++ b/krita/libbrush/kis_gbr_brush.cpp @@ -1,524 +1,524 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Adrian Page * Copyright (c) 2005 Bart Coppens * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * 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 // htonl #include "kis_gbr_brush.h" #include "kis_brush.h" #include #include #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_image.h" struct GimpBrushV1Header { quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ quint32 version; /* brush file version # */ quint32 width; /* width of brush */ quint32 height; /* height of brush */ quint32 bytes; /* depth of brush in bytes */ }; /// All fields are in MSB on disk! struct GimpBrushHeader { quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ quint32 version; /* brush file version # */ quint32 width; /* width of brush */ quint32 height; /* height of brush */ quint32 bytes; /* depth of brush in bytes */ /* The following are only defined in version 2 */ quint32 magic_number; /* GIMP brush magic number */ quint32 spacing; /* brush spacing as % of width & height, 0 - 1000 */ }; // Needed, or the GIMP won't open it! quint32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0); struct KisGbrBrush::Private { QByteArray data; bool ownData; /* seems to indicate that @ref data is owned by the brush, but in Qt4.x this is already guaranteed... so in reality it seems more to indicate whether the data is loaded from file (ownData = true) or memory (ownData = false) */ bool useColorAsMask; quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ quint32 version; /* brush file version # */ quint32 bytes; /* depth of brush in bytes */ quint32 magic_number; /* GIMP brush magic number */ }; #define DEFAULT_SPACING 0.25 KisGbrBrush::KisGbrBrush(const QString& filename) : KisBrush(filename) , d(new Private) { d->ownData = true; d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); } KisGbrBrush::KisGbrBrush(const QString& filename, const QByteArray& data, qint32 & dataPos) : KisBrush(filename) , d(new Private) { d->ownData = false; d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); d->data = QByteArray::fromRawData(data.data() + dataPos, data.size() - dataPos); init(); d->data.clear(); dataPos += d->header_size + (width() * height() * d->bytes); } KisGbrBrush::KisGbrBrush(KisPaintDeviceSP image, int x, int y, int w, int h) : KisBrush() , d(new Private) { d->ownData = true; d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); initFromPaintDev(image, x, y, w, h); } KisGbrBrush::KisGbrBrush(const QImage& image, const QString& name) : KisBrush() , d(new Private) { d->ownData = false; d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); setBrushTipImage(image); setName(name); } KisGbrBrush::KisGbrBrush(const KisGbrBrush& rhs) : KisBrush(rhs) , d(new Private(*rhs.d)) { setName(rhs.name()); d->data = QByteArray(); setValid(rhs.valid()); } KisGbrBrush::~KisGbrBrush() { delete d; } bool KisGbrBrush::load() { QFile file(filename()); if (file.size() == 0) return false; file.open(QIODevice::ReadOnly); bool res = loadFromDevice(&file); file.close(); return res; } bool KisGbrBrush::loadFromDevice(QIODevice *dev) { if (d->ownData) { d->data = dev->readAll(); } return init(); } bool KisGbrBrush::init() { GimpBrushHeader bh; if (sizeof(GimpBrushHeader) > (uint)d->data.size()) { return false; } memcpy(&bh, d->data, sizeof(GimpBrushHeader)); bh.header_size = ntohl(bh.header_size); d->header_size = bh.header_size; bh.version = ntohl(bh.version); d->version = bh.version; bh.width = ntohl(bh.width); bh.height = ntohl(bh.height); bh.bytes = ntohl(bh.bytes); d->bytes = bh.bytes; bh.magic_number = ntohl(bh.magic_number); d->magic_number = bh.magic_number; if (bh.version == 1) { // No spacing in version 1 files so use Gimp default bh.spacing = static_cast(DEFAULT_SPACING * 100); } else { bh.spacing = ntohl(bh.spacing); if (bh.spacing > 1000) { return false; } } setSpacing(bh.spacing / 100.0); if (bh.header_size > (uint)d->data.size() || bh.header_size == 0) { return false; } QString name; if (bh.version == 1) { // Version 1 has no magic number or spacing, so the name // is at a different offset. Character encoding is undefined. const char *text = d->data.constData() + sizeof(GimpBrushV1Header); name = QString::fromLatin1(text, bh.header_size - sizeof(GimpBrushV1Header) - 1); } else { // ### Version = 3->cinepaint; may be float16 data! // Version >=2: UTF-8 encoding is used name = QString::fromUtf8(d->data.constData() + sizeof(GimpBrushHeader), bh.header_size - sizeof(GimpBrushHeader) - 1); } setName(name); if (bh.width == 0 || bh.height == 0) { return false; } QImage::Format imageFormat; if (bh.bytes == 1) { imageFormat = QImage::Format_Indexed8; } else { imageFormat = QImage::Format_ARGB32; } QImage image(QImage(bh.width, bh.height, imageFormat)); if (image.isNull()) { return false; } qint32 k = bh.header_size; if (bh.bytes == 1) { QVector table; for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i)); image.setColorTable(table); // Grayscale if (static_cast(k + bh.width * bh.height) > d->data.size()) { return false; } setHasColor(false); for (quint32 y = 0; y < bh.height; y++) { uchar *pixel = reinterpret_cast(image.scanLine(y)); for (quint32 x = 0; x < bh.width; x++, k++) { qint32 val = 255 - static_cast(d->data[k]); *pixel = val; ++pixel; } } } else if (bh.bytes == 4) { // RGBA if (static_cast(k + (bh.width * bh.height * 4)) > d->data.size()) { return false; } setHasColor(true); for (quint32 y = 0; y < bh.height; y++) { QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (quint32 x = 0; x < bh.width; x++, k += 4) { *pixel = qRgba(d->data[k], d->data[k + 1], d->data[k + 2], d->data[k + 3]); ++pixel; } } } else { warnKrita << "WARNING: loading of GBR brushes with" << bh.bytes << "bytes per pixel is not supported"; return false; } setWidth(image.width()); setHeight(image.height()); if (d->ownData) { d->data.resize(0); // Save some memory, we're using enough of it as it is. } setValid(image.width() != 0 && image.height() != 0); setBrushTipImage(image); return true; } bool KisGbrBrush::initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h) { // Forcefully convert to RGBA8 // XXX profile and exposure? - setBrushTipImage(image->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags)); + setBrushTipImage(image->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags())); setName(image->objectName()); setHasColor(true); return true; } bool KisGbrBrush::save() { QFile file(filename()); file.open(QIODevice::WriteOnly | QIODevice::Truncate); bool ok = saveToDevice(&file); file.close(); return ok; } bool KisGbrBrush::saveToDevice(QIODevice* dev) const { GimpBrushHeader bh; QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8 char const* name = utf8Name.data(); int nameLength = qstrlen(name); int wrote; bh.header_size = htonl(sizeof(GimpBrushHeader) + nameLength + 1); bh.version = htonl(2); // Only RGBA8 data needed atm, no cinepaint stuff bh.width = htonl(width()); bh.height = htonl(height()); // Hardcoded, 4 bytes RGBA or 1 byte GREY if (!hasColor()) { bh.bytes = htonl(1); } else { bh.bytes = htonl(4); } bh.magic_number = htonl(GimpV2BrushMagic); bh.spacing = htonl(static_cast(spacing() * 100.0)); // Write header: first bh, then the name QByteArray bytes = QByteArray::fromRawData(reinterpret_cast(&bh), sizeof(GimpBrushHeader)); wrote = dev->write(bytes); bytes.clear(); if (wrote == -1) { return false; } wrote = dev->write(name, nameLength + 1); if (wrote == -1) { return false; } int k = 0; QImage image = brushTipImage(); if (!hasColor()) { bytes.resize(width() * height()); for (qint32 y = 0; y < height(); y++) { for (qint32 x = 0; x < width(); x++) { QRgb c = image.pixel(x, y); bytes[k++] = static_cast(255 - qRed(c)); // red == blue == green } } } else { bytes.resize(width() * height() * 4); for (qint32 y = 0; y < height(); y++) { for (qint32 x = 0; x < width(); x++) { // order for gimp brushes, v2 is: RGBA QRgb pixel = image.pixel(x, y); bytes[k++] = static_cast(qRed(pixel)); bytes[k++] = static_cast(qGreen(pixel)); bytes[k++] = static_cast(qBlue(pixel)); bytes[k++] = static_cast(qAlpha(pixel)); } } } wrote = dev->write(bytes); if (wrote == -1) { return false; } KoResource::saveToDevice(dev); return true; } QImage KisGbrBrush::brushTipImage() const { QImage image = KisBrush::brushTipImage(); if (hasColor() && useColorAsMask()) { for (int y = 0; y < image.height(); y++) { QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { QRgb c = pixel[x]; int a = qGray(c); pixel[x] = qRgba(a, a, a, qAlpha(c)); } } } return image; } enumBrushType KisGbrBrush::brushType() const { return !hasColor() || useColorAsMask() ? MASK : IMAGE; } void KisGbrBrush::setBrushType(enumBrushType type) { Q_UNUSED(type); qFatal("FATAL: protected member setBrushType has no meaning for KisGbrBrush"); } void KisGbrBrush::setBrushTipImage(const QImage& image) { KisBrush::setBrushTipImage(image); setValid(true); } /*QImage KisGbrBrush::outline(double pressure) { KisLayerSP layer = image(KoColorSpaceRegistry::instance()->colorSpace("RGBA",0), KisPaintInformation(pressure)); KisBoundary bounds(layer.data()); int w = maskWidth(pressure); int h = maskHeight(pressure); bounds.generateBoundary(w, h); QPixmap pix(bounds.pixmap(w, h)); QImage result; result = pix; return result; }*/ void KisGbrBrush::makeMaskImage() { if (!hasColor()) { return; } QImage brushTip = brushTipImage(); if (brushTip.width() == width() && brushTip.height() == height()) { int imageWidth = width(); int imageHeight = height(); QImage image(imageWidth, imageHeight, QImage::Format_Indexed8); QVector table; for (int i = 0; i < 256; ++i) { table.append(qRgb(i, i, i)); } image.setColorTable(table); for (int y = 0; y < imageHeight; y++) { QRgb *pixel = reinterpret_cast(brushTip.scanLine(y)); uchar * dstPixel = image.scanLine(y); for (int x = 0; x < imageWidth; x++) { QRgb c = pixel[x]; float alpha = qAlpha(c) / 255.0f; // linear interpolation with maximum gray value which is transparent in the mask //int a = (qGray(c) * alpha) + ((1.0 - alpha) * 255); // single multiplication version int a = 255 + alpha * (qGray(c) - 255); dstPixel[x] = (uchar)a; } } setBrushTipImage(image); } setHasColor(false); setUseColorAsMask(false); resetBoundary(); clearBrushPyramid(); } KisGbrBrush* KisGbrBrush::clone() const { return new KisGbrBrush(*this); } void KisGbrBrush::toXML(QDomDocument& d, QDomElement& e) const { predefinedBrushToXML("gbr_brush", e); e.setAttribute("ColorAsMask", QString::number((int)useColorAsMask())); KisBrush::toXML(d, e); } void KisGbrBrush::setUseColorAsMask(bool useColorAsMask) { /** * WARNING: There is a problem in the brush server, since it * returns not copies of brushes, but direct pointers to them. It * means that the brushes are shared among all the currently * present paintops, which might be a problem for e.g. Multihand * Brush Tool. * * Right now, all the instances of Multihand Brush Tool share the * same brush, so there is no problem in this sharing, unless we * reset the internal state of the brush on our way. */ if (useColorAsMask != d->useColorAsMask) { d->useColorAsMask = useColorAsMask; resetBoundary(); clearBrushPyramid(); } } bool KisGbrBrush::useColorAsMask() const { return d->useColorAsMask; } QString KisGbrBrush::defaultFileExtension() const { return QString(".gbr"); } diff --git a/krita/plugins/extensions/dockers/advancedcolorselector/kis_common_colors.cpp b/krita/plugins/extensions/dockers/advancedcolorselector/kis_common_colors.cpp index 00b3a1482a8..84f133f4226 100644 --- a/krita/plugins/extensions/dockers/advancedcolorselector/kis_common_colors.cpp +++ b/krita/plugins/extensions/dockers/advancedcolorselector/kis_common_colors.cpp @@ -1,139 +1,139 @@ /* * Copyright (c) 2010 Adam Celarek * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_common_colors.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoColor.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_config.h" #include "kis_common_colors_recalculation_runner.h" KisCommonColors::KisCommonColors(QWidget *parent) : KisColorPatches("commonColors", parent) { m_reloadButton = new QPushButton(); m_reloadButton->setIcon(KisIconUtils::loadIcon("view-refresh")); m_reloadButton->setToolTip(i18n("Create a list of colors from the image")); connect(m_reloadButton, SIGNAL(clicked()), this, SLOT(recalculate())); QList tmpList; tmpList.append(m_reloadButton); setAdditionalButtons(tmpList); updateSettings(); m_recalculationTimer.setInterval(2000); m_recalculationTimer.setSingleShot(true); connect(&m_recalculationTimer, SIGNAL(timeout()), this, SLOT(recalculate())); } void KisCommonColors::setCanvas(KisCanvas2 *canvas) { KisColorPatches::setCanvas(canvas); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (cfg.readEntry("commonColorsAutoUpdate", false)) { if (m_image) { m_image->disconnect(this); } if (m_canvas) { connect(m_canvas->image(), SIGNAL(sigImageUpdated(const QRect &)), &m_recalculationTimer, SLOT(start()), Qt::UniqueConnection); m_image = m_canvas->image(); } else { m_image = 0; } } } KisColorSelectorBase* KisCommonColors::createPopup() const { KisCommonColors* ret = new KisCommonColors(); ret->setCanvas(m_canvas); ret->setColors(colors()); return ret; } void KisCommonColors::updateSettings() { KisColorPatches::updateSettings(); if(!(m_canvas && m_canvas->image())) return; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if(cfg.readEntry("commonColorsAutoUpdate", false)) { connect(m_canvas->image(), SIGNAL(sigImageUpdated(const QRect &)), &m_recalculationTimer, SLOT(start()), Qt::UniqueConnection); } else { disconnect(m_canvas->image(), SIGNAL(sigImageUpdated(const QRect &)), &m_recalculationTimer, SLOT(start())); } m_reloadButton->setEnabled(true); } void KisCommonColors::setColors(QList colors) { QMutexLocker locker(&m_mutex); KisColorPatches::setColors(colors); m_reloadButton->setEnabled(true); m_calculatedColors = colors; } void KisCommonColors::recalculate() { if (!m_canvas) { return; } if(m_reloadButton->isEnabled()==false) { // on old computation is still running // try later to recalculate m_recalculationTimer.start(); return; } m_reloadButton->setEnabled(false); qApp->processEvents(); KisImageWSP kisImage = m_canvas->image(); - QImage image = kisImage->projection()->createThumbnail(1024, 1024, kisImage->bounds(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + QImage image = kisImage->projection()->createThumbnail(1024, 1024, kisImage->bounds(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KisCommonColorsRecalculationRunner* runner = new KisCommonColorsRecalculationRunner(image, patchCount(), this); QThreadPool::globalInstance()->start(runner); } diff --git a/krita/plugins/extensions/dockers/historydocker/KisUndoModel.cpp b/krita/plugins/extensions/dockers/historydocker/KisUndoModel.cpp index 76c02930fc6..31a5394d8e9 100644 --- a/krita/plugins/extensions/dockers/historydocker/KisUndoModel.cpp +++ b/krita/plugins/extensions/dockers/historydocker/KisUndoModel.cpp @@ -1,282 +1,282 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "KisUndoModel.h" #include KisUndoModel::KisUndoModel(QObject *parent) : QAbstractItemModel(parent) { m_blockOutgoingHistoryChange = false; m_stack = 0; m_canvas = 0; m_sel_model = new QItemSelectionModel(this, this); connect(m_sel_model, SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(setStackCurrentIndex(QModelIndex))); m_empty_label = i18n(""); } QItemSelectionModel *KisUndoModel::selectionModel() const { return m_sel_model; } KUndo2QStack *KisUndoModel::stack() const { return m_stack; } void KisUndoModel::setStack(KUndo2QStack *stack) { if (m_stack == stack) return; if (m_stack != 0) { disconnect(m_stack, SIGNAL(canRedoChanged(bool)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*))); disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int))); } m_stack = stack; if (m_stack != 0) { connect(m_stack, SIGNAL(canRedoChanged(bool)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*))); connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int))); } stackChanged(); } void KisUndoModel::stackDestroyed(QObject *obj) { if (obj != m_stack) return; m_stack = 0; stackChanged(); } void KisUndoModel::stackChanged() { reset(); m_blockOutgoingHistoryChange = true; m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect); m_blockOutgoingHistoryChange = false; } void KisUndoModel::setStackCurrentIndex(const QModelIndex &index) { if (m_blockOutgoingHistoryChange) return; if (m_stack == 0) return; if (index == selectedIndex()) return; if (index.column() != 0) return; m_stack->setIndex(index.row()); } QModelIndex KisUndoModel::selectedIndex() const { return m_stack == 0 ? QModelIndex() : createIndex(m_stack->index(), 0); } QModelIndex KisUndoModel::index(int row, int column, const QModelIndex &parent) const { if (m_stack == 0) return QModelIndex(); if (parent.isValid()) return QModelIndex(); if (column != 0) return QModelIndex(); if (row < 0 || row > m_stack->count()) return QModelIndex(); return createIndex(row, column); } QModelIndex KisUndoModel::parent(const QModelIndex&) const { return QModelIndex(); } int KisUndoModel::rowCount(const QModelIndex &parent) const { if (m_stack == 0) return 0; if (parent.isValid()) return 0; return m_stack->count() + 1; } int KisUndoModel::columnCount(const QModelIndex&) const { return 1; } QVariant KisUndoModel::data(const QModelIndex &index, int role) const { if (m_stack == 0){ return QVariant(); } if (index.column() != 0){ return QVariant(); } if (index.row() < 0 || index.row() > m_stack->count()){ return QVariant(); } if (role == Qt::DisplayRole) { if (index.row() == 0){ return m_empty_label; } KUndo2Command* currentCommand = const_cast(m_stack->command(index.row() - 1)); return currentCommand->isMerged()?m_stack->text(index.row() - 1)+"(Merged)":m_stack->text(index.row() - 1); } else if (role == Qt::DecorationRole) { if (index.row() > 0) { const KUndo2Command* currentCommand = m_stack->command(index.row() - 1); if (m_imageMap.contains(currentCommand)) { return m_imageMap[currentCommand]; } } } return QVariant(); } QString KisUndoModel::emptyLabel() const { return m_empty_label; } void KisUndoModel::setEmptyLabel(const QString &label) { m_empty_label = label; stackChanged(); } void KisUndoModel::setCleanIcon(const QIcon &icon) { m_clean_icon = icon; stackChanged(); } QIcon KisUndoModel::cleanIcon() const { return m_clean_icon; } void KisUndoModel::setCanvas(KisCanvas2 *canvas) { m_canvas = canvas; } void KisUndoModel::addImage(int idx) { if (m_stack == 0 || m_stack->count() == 0) { return; } const KUndo2Command* currentCommand = m_stack->command(idx-1); if (m_stack->count() == idx && !m_imageMap.contains(currentCommand)) { KisImageWSP historyImage = m_canvas->viewManager()->image(); KisPaintDeviceSP paintDevice = historyImage->projection(); QImage image = paintDevice->createThumbnail(32, 32, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); m_imageMap[currentCommand] = image; } QList list; for (int i = 0; i < m_stack->count(); ++i) { list << m_stack->command(i); } for (QMap:: iterator it = m_imageMap.begin(); it != m_imageMap.end();) { if (!list.contains(it.key())) { it = m_imageMap.erase(it); } else { ++it; } } } bool KisUndoModel::checkMergedCommand(int index) { Q_UNUSED(index) return false; } diff --git a/krita/plugins/extensions/dockers/lut/tests/kis_ocio_display_filter_test.cpp b/krita/plugins/extensions/dockers/lut/tests/kis_ocio_display_filter_test.cpp index baf815b707b..673fb97506c 100644 --- a/krita/plugins/extensions/dockers/lut/tests/kis_ocio_display_filter_test.cpp +++ b/krita/plugins/extensions/dockers/lut/tests/kis_ocio_display_filter_test.cpp @@ -1,119 +1,119 @@ /* * Copyright (c) 2015 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_ocio_display_filter_test.h" #include #include #include "KoColorModelStandardIds.h" #include "../../../../../sdk/tests/stroke_testing_utils.h" #include "../../../../../sdk/tests/testutil.h" #include "kis_exposure_gamma_correction_interface.h" #include "../ocio_display_filter.h" #include "kis_display_color_converter.h" #include "kis_canvas_resource_provider.h" #include void KisOcioDisplayFilterTest::test() { KisExposureGammaCorrectionInterface *egInterface = new KisDumbExposureGammaCorrectionInterface(); OcioDisplayFilter filter(egInterface); QString configFile = TestUtil::fetchDataFileLazy("./psyfiTestingConfig-master/config.ocio"); dbgKrita << ppVar(configFile); Q_ASSERT(QFile::exists(configFile)); OCIO::ConstConfigRcPtr ocioConfig = OCIO::Config::CreateFromFile(configFile.toUtf8()); filter.config = ocioConfig; filter.inputColorSpaceName = ocioConfig->getColorSpaceNameByIndex(0); filter.displayDevice = ocioConfig->getDisplay(1); filter.view = ocioConfig->getView(filter.displayDevice, 0); filter.gamma = 1.0; filter.exposure = 0.0; filter.swizzle = RGBA; filter.blackPoint = 0.0; filter.whitePoint = 1.0; filter.forceInternalColorManagement = false; filter.setLockCurrentColorVisualRepresentation(false); filter.updateProcessor(); dbgKrita << ppVar(filter.inputColorSpaceName); dbgKrita << ppVar(filter.displayDevice); dbgKrita << ppVar(filter.view); dbgKrita << ppVar(filter.gamma); dbgKrita << ppVar(filter.exposure); const KoColorSpace *paintingCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0); KisImageSP image = utils::createImage(0, QSize(100, 100)); - image->convertImageColorSpace(paintingCS, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + image->convertImageColorSpace(paintingCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); dbgKrita << ppVar(paintingCS) << ppVar(image->root()->firstChild()->colorSpace()); KoCanvasResourceManager *resourceManager = utils::createResourceManager(image, image->root(), ""); KisDisplayColorConverter converter(resourceManager, 0); dbgKrita << ppVar(image->root()->firstChild()); QVariant v; v.setValue(KisNodeWSP(image->root()->firstChild())); resourceManager->setResource(KisCanvasResourceProvider::CurrentKritaNode, v); converter.setDisplayFilter(&filter); dbgKrita << ppVar(converter.paintingColorSpace()); { QColor refColor(255, 128, 0); KoColor realColor = converter.approximateFromRenderedQColor(refColor); QColor roundTripColor = converter.toQColor(realColor); dbgKrita << ppVar(refColor); dbgKrita << ppVar(realColor.colorSpace()) << ppVar(KoColor::toQString(realColor)); dbgKrita << ppVar(roundTripColor); } { KoColor realColor(Qt::red, paintingCS); QColor roundTripColor = converter.toQColor(realColor); dbgKrita << ppVar(realColor.colorSpace()) << ppVar(KoColor::toQString(realColor)); dbgKrita << ppVar(roundTripColor); } } QTEST_MAIN(KisOcioDisplayFilterTest) diff --git a/krita/plugins/extensions/gmic/kis_gmic_simple_convertor.cpp b/krita/plugins/extensions/gmic/kis_gmic_simple_convertor.cpp index 0f6461231d8..a5971ecd161 100644 --- a/krita/plugins/extensions/gmic/kis_gmic_simple_convertor.cpp +++ b/krita/plugins/extensions/gmic/kis_gmic_simple_convertor.cpp @@ -1,872 +1,872 @@ /* * Copyright (c) 2013 Lukáš Tvrdý #include #include #include #include #include #include using namespace cimg_library; #define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< _channel_type_, float>::scaleToA( v ) #define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, _channel_type_>::scaleToA( v ) template class KisColorToFloatConvertor : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorToFloatConvertor(){} virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { const RGBPixel* srcPixel = reinterpret_cast(src); KoRgbF32Traits::Pixel* dstPixel = reinterpret_cast(dst); while(nPixels > 0) { dstPixel->red = SCALE_TO_FLOAT(srcPixel->red); dstPixel->green = SCALE_TO_FLOAT(srcPixel->green); dstPixel->blue = SCALE_TO_FLOAT(srcPixel->blue); dstPixel->alpha = SCALE_TO_FLOAT(srcPixel->alpha); --nPixels; ++srcPixel; ++dstPixel; } } }; template class KisColorFromFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromFloat(float gmicUnitValue = 255.0f):m_gmicUnitValue(gmicUnitValue){} virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; while(nPixels > 0) { dstPixel->red = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->green = SCALE_FROM_FLOAT(srcPixel->green * gmicUnitValue2KritaUnitValue); dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->blue * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->alpha * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; template class KisColorFromGrayScaleFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromGrayScaleFloat(float gmicUnitValue = 255.0f):m_gmicUnitValue(gmicUnitValue){} virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; // warning: green and blue channels on input contain random data!!! see that we copy only one channel // when gmic image has grayscale colorspace while(nPixels > 0) { dstPixel->red = dstPixel->green = dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->alpha * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; template class KisColorFromGrayScaleAlphaFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromGrayScaleAlphaFloat(float gmicUnitValue = 255.0f):m_gmicUnitValue(gmicUnitValue){} virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; // warning: green and blue channels on input contain random data!!! see that we copy only one channel // when gmic image has grayscale colorspace while(nPixels > 0) { dstPixel->red = dstPixel->green = dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->green * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; static KoColorTransformation* createTransformationFromGmic(const KoColorSpace* colorSpace, quint32 gmicSpectrum,float gmicUnitValue) { KoColorTransformation * colorTransformation = 0; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space for fast pixel tranformation to gmic pixel format" << colorSpace->id(); return 0; } if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< float, KoRgbTraits < float > >(gmicUnitValue); }else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat >(gmicUnitValue); }else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat >(gmicUnitValue); } } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< half, KoRgbTraits < half > >(gmicUnitValue); }else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< half, KoRgbTraits < half > >(gmicUnitValue); }else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< half, KoRgbTraits < half > >(gmicUnitValue); } } #endif else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); }else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); }else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); } } else if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); }else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); }else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); } } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel tranformation to gmic pixel format"; return 0; } return colorTransformation; } static KoColorTransformation* createTransformation(const KoColorSpace* colorSpace) { KoColorTransformation * colorTransformation = 0; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space for fast pixel tranformation to gmic pixel format" << colorSpace->id(); return 0; } if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< float, KoRgbTraits < float > >(); } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< half, KoRgbTraits < half > >(); } #endif else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< quint16, KoBgrTraits < quint16 > >(); } else if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< quint8, KoBgrTraits < quint8 > >(); } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel tranformation to gmic pixel format"; return 0; } return colorTransformation; } void KisGmicSimpleConvertor::convertFromGmicFast(gmic_image& gmicImage, KisPaintDeviceSP dst, float gmicUnitValue) { const KoColorSpace * dstColorSpace = dst->colorSpace(); KoColorTransformation * gmicToDstPixelFormat = createTransformationFromGmic(dstColorSpace,gmicImage._spectrum,gmicUnitValue); if (gmicToDstPixelFormat == 0) { dbgPlugins << "Fall-back to slow color conversion"; convertFromGmicImage(gmicImage, dst, gmicUnitValue); return; } qint32 x = 0; qint32 y = 0; qint32 width = gmicImage._width; qint32 height = gmicImage._height; width = width < 0 ? 0 : width; height = height < 0 ? 0 : height; const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); // this function always convert to rgba or rgb with various color depth quint32 dstNumChannels = rgbaFloat32bitcolorSpace->channelCount(); // number of channels that we will copy quint32 numChannels = gmicImage._spectrum; // gmic image has 4, 3, 2, 1 channel QVector planes(dstNumChannels); int channelOffset = gmicImage._width * gmicImage._height; for (unsigned int channelIndex = 0; channelIndex < gmicImage._spectrum; channelIndex++) { planes[channelIndex] = gmicImage._data + channelOffset * channelIndex; } for (unsigned int channelIndex = gmicImage._spectrum; channelIndex < dstNumChannels; channelIndex++) { planes[channelIndex] = 0; //turn off } qint32 dataY = 0; qint32 imageY = y; qint32 rowsRemaining = height; const qint32 floatPixelSize = rgbaFloat32bitcolorSpace->pixelSize(); KisRandomAccessorSP it = dst->createRandomAccessorNG(dst->x(), dst->y()); // 0,0 int tileWidth = it->numContiguousColumns(dst->x()); int tileHeight = it->numContiguousRows(dst->y()); Q_ASSERT(tileWidth == 64); Q_ASSERT(tileHeight == 64); quint8 * convertedTile = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * tileWidth * tileHeight]; // grayscale and rgb case does not have alpha, so let's fill 4th channel of rgba tile with opacity opaque if (gmicImage._spectrum == 1 || gmicImage._spectrum == 3) { quint32 nPixels = tileWidth * tileHeight; quint32 pixelIndex = 0; KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(convertedTile); while (pixelIndex < nPixels) { srcPixel->alpha = gmicUnitValue; ++srcPixel; ++pixelIndex; } } while (rowsRemaining > 0) { qint32 dataX = 0; qint32 imageX = x; qint32 columnsRemaining = width; qint32 numContiguousImageRows = it->numContiguousRows(imageY); qint32 rowsToWork = qMin(numContiguousImageRows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousImageColumns = it->numContiguousColumns(imageX); qint32 columnsToWork = qMin(numContiguousImageColumns, columnsRemaining); const qint32 dataIdx = dataX + dataY * width; const qint32 tileRowStride = (tileWidth - columnsToWork) * floatPixelSize; quint8 *tileItStart = convertedTile; // copy gmic channels to float tile qint32 channelSize = sizeof(float); for(quint32 i=0; imoveTo(imageX, imageY); quint8 *dstTileItStart = it->rawData(); tileItStart = convertedTile; // back to the start of the converted tile // copy float tile to dst colorspace based on input colorspace (rgb or grayscale) for (qint32 row = 0; row < rowsToWork; row++) { gmicToDstPixelFormat->transform(tileItStart, dstTileItStart, columnsToWork); dstTileItStart += dstColorSpace->pixelSize() * tileWidth; tileItStart += floatPixelSize * tileWidth; } imageX += columnsToWork; dataX += columnsToWork; columnsRemaining -= columnsToWork; } imageY += rowsToWork; dataY += rowsToWork; rowsRemaining -= rowsToWork; } delete [] convertedTile; delete gmicToDstPixelFormat; } void KisGmicSimpleConvertor::convertToGmicImageFast(KisPaintDeviceSP dev, CImg< float >& gmicImage, QRect rc) { KoColorTransformation * pixelToGmicPixelFormat = createTransformation(dev->colorSpace()); if (pixelToGmicPixelFormat == 0) { dbgPlugins << "Fall-back to slow color conversion method"; convertToGmicImage(dev, gmicImage, rc); return; } if (rc.isEmpty()) { dbgPlugins << "Image rectangle is empty! Using supplied gmic layer dimension"; rc = QRect(0,0,gmicImage._width, gmicImage._height); } qint32 x = rc.x(); qint32 y = rc.y(); qint32 width = rc.width(); qint32 height = rc.height(); width = width < 0 ? 0 : width; height = height < 0 ? 0 : height; const qint32 numChannels = 4; int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; QVector planes; planes.append(gmicImage._data); planes.append(gmicImage._data + greenOffset); planes.append(gmicImage._data + blueOffset); planes.append(gmicImage._data + alphaOffset); KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(dev->x(), dev->y()); int tileWidth = it->numContiguousColumns(dev->x()); int tileHeight = it->numContiguousRows(dev->y()); Q_ASSERT(tileWidth == 64); Q_ASSERT(tileHeight == 64); const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); Q_CHECK_PTR(rgbaFloat32bitcolorSpace); const qint32 dstPixelSize = rgbaFloat32bitcolorSpace->pixelSize(); const qint32 srcPixelSize = dev->pixelSize(); quint8 * dstTile = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * tileWidth * tileHeight]; qint32 dataY = 0; qint32 imageX = x; qint32 imageY = y; it->moveTo(imageX, imageY); qint32 rowsRemaining = height; while (rowsRemaining > 0) { qint32 dataX = 0; imageX = x; qint32 columnsRemaining = width; qint32 numContiguousImageRows = it->numContiguousRows(imageY); qint32 rowsToWork = qMin(numContiguousImageRows, rowsRemaining); qint32 convertedTileY = tileHeight - rowsToWork; Q_ASSERT(convertedTileY >= 0); while (columnsRemaining > 0) { qint32 numContiguousImageColumns = it->numContiguousColumns(imageX); qint32 columnsToWork = qMin(numContiguousImageColumns, columnsRemaining); qint32 convertedTileX = tileWidth - columnsToWork; Q_ASSERT(convertedTileX >= 0); const qint32 dataIdx = dataX + dataY * width; const qint32 dstTileIndex = convertedTileX + convertedTileY * tileWidth; const qint32 tileRowStride = (tileWidth - columnsToWork) * dstPixelSize; const qint32 srcTileRowStride = (tileWidth - columnsToWork) * srcPixelSize; it->moveTo(imageX, imageY); quint8 *tileItStart = dstTile + dstTileIndex * dstPixelSize; // transform tile row by row quint8 *dstTileIt = tileItStart; quint8 *srcTileIt = const_cast(it->rawDataConst()); qint32 row = rowsToWork; while (row > 0) { pixelToGmicPixelFormat->transform(srcTileIt, dstTileIt , columnsToWork); srcTileIt += columnsToWork * srcPixelSize; srcTileIt += srcTileRowStride; dstTileIt += columnsToWork * dstPixelSize; dstTileIt += tileRowStride; row--; } // here we want to copy floats to dstTile, so tileItStart has to point to float buffer qint32 channelSize = sizeof(float); for(qint32 i=0; i& gmicImage, QRect rc) { Q_ASSERT(!dev.isNull()); Q_ASSERT(gmicImage._spectrum == 4); // rgba if (rc.isEmpty()) { rc = QRect(0,0,gmicImage._width, gmicImage._height); } const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); Q_CHECK_PTR(rgbaFloat32bitcolorSpace); int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent; - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(); const KoColorSpace * colorSpace = dev->colorSpace(); KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(0,0); int optimalBufferSize = 64; // most common numContiguousColumns, tile size? quint8 * floatRGBApixel = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * optimalBufferSize]; quint32 pixelSize = rgbaFloat32bitcolorSpace->pixelSize(); int pos = 0; for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); colorSpace->convertPixelsTo(it->rawDataConst(), floatRGBApixel, rgbaFloat32bitcolorSpace, numContiguousColumns, renderingIntent, conversionFlags); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { memcpy(gmicImage._data + pos ,floatRGBApixel + bx * pixelSize , 4); memcpy(gmicImage._data + pos + greenOffset ,floatRGBApixel + bx * pixelSize + 4, 4); memcpy(gmicImage._data + pos + blueOffset ,floatRGBApixel + bx * pixelSize + 8, 4); memcpy(gmicImage._data + pos + alphaOffset ,floatRGBApixel + bx * pixelSize + 12, 4); pos++; } x += numContiguousColumns; } } delete [] floatRGBApixel; } void KisGmicSimpleConvertor::convertFromGmicImage(gmic_image& gmicImage, KisPaintDeviceSP dst, float gmicMaxChannelValue) { Q_ASSERT(!dst.isNull()); const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); const KoColorSpace *dstColorSpace = dst->colorSpace(); if (dstColorSpace == 0) { dstColorSpace = rgbaFloat32bitcolorSpace; } KisPaintDeviceSP dev = dst; int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; QRect rc(0,0,gmicImage._width, gmicImage._height); KisRandomAccessorSP it = dev->createRandomAccessorNG(0,0); int pos; float r,g,b,a; int optimalBufferSize = 64; // most common numContiguousColumns, tile size? quint8 * floatRGBApixel = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * optimalBufferSize]; quint32 pixelSize = rgbaFloat32bitcolorSpace->pixelSize(); - KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent; - KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::InternalConversionFlags; + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(); // Krita needs rgba in 0.0...1.0 float multiplied = KoColorSpaceMathsTraits::unitValue / gmicMaxChannelValue; switch (gmicImage._spectrum) { case 1: { // convert grayscale to rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = g = b = gmicImage._data[pos] * multiplied; a = KoColorSpaceMathsTraits::unitValue; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 2: { // convert grayscale alpha to rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = g = b = gmicImage._data[pos] * multiplied; a = gmicImage._data[pos + greenOffset] * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 3: { // convert rgb -> rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = gmicImage._data[pos] * multiplied; g = gmicImage._data[pos + greenOffset] * multiplied; b = gmicImage._data[pos + blueOffset ] * multiplied; a = gmicMaxChannelValue * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 4: { for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = gmicImage._data[pos] * multiplied; g = gmicImage._data[pos + greenOffset] * multiplied; b = gmicImage._data[pos + blueOffset ] * multiplied; a = gmicImage._data[pos + alphaOffset] * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } default: { dbgPlugins << "Unsupported gmic output format : " << gmicImage._width << gmicImage._height << gmicImage._depth << gmicImage._spectrum; } } } QImage KisGmicSimpleConvertor::convertToQImage(gmic_image& gmicImage, float gmicActualMaxChannelValue) { QImage image = QImage(gmicImage._width, gmicImage._height, QImage::Format_ARGB32); dbgPlugins << image.format() <<"first pixel:"<< gmicImage._data[0] << gmicImage._width << gmicImage._height << gmicImage._spectrum; int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int pos = 0; // always put 255 to qimage float multiplied = 255.0f / gmicActualMaxChannelValue; for (unsigned int y = 0; y < gmicImage._height; y++) { QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (unsigned int x = 0; x < gmicImage._width; x++) { pos = y * gmicImage._width + x; float r = gmicImage._data[pos] * multiplied; float g = gmicImage._data[pos + greenOffset] * multiplied; float b = gmicImage._data[pos + blueOffset] * multiplied; pixel[x] = qRgb(int(r),int(g), int(b)); } } return image; } void KisGmicSimpleConvertor::convertFromQImage(const QImage& image, CImg< float >& gmicImage, float gmicUnitValue) { int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; int pos = 0; // QImage has 0..255 float qimageUnitValue = 255.0f; float multiplied = gmicUnitValue / qimageUnitValue; Q_ASSERT(image.width() == int(gmicImage._width)); Q_ASSERT(image.height() == int(gmicImage._height)); Q_ASSERT(image.format() == QImage::Format_ARGB32); switch (gmicImage._spectrum) { case 1: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage._width + x; gmicImage._data[pos] = qGray(pixel[x]) * multiplied; } } break; } case 2: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage._width + x; gmicImage._data[pos] = qGray(pixel[x]) * multiplied; gmicImage._data[pos + greenOffset] = qAlpha(pixel[x]) * multiplied; } } break; } case 3: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage._width + x; gmicImage._data[pos] = qRed(pixel[x]) * multiplied; gmicImage._data[pos + greenOffset] = qGreen(pixel[x]) * multiplied; gmicImage._data[pos + blueOffset] = qBlue(pixel[x]) * multiplied; } } break; } case 4: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage._width + x; gmicImage._data[pos] = qRed(pixel[x]) * multiplied; gmicImage._data[pos + greenOffset] = qGreen(pixel[x]) * multiplied; gmicImage._data[pos + blueOffset] = qBlue(pixel[x]) * multiplied; gmicImage._data[pos + alphaOffset] = qAlpha(pixel[x]) * multiplied; } } break; } default: { Q_ASSERT(false); dbgKrita << "Unexpected gmic image format"; break; } } } diff --git a/krita/plugins/filters/fastcolortransfer/fastcolortransfer.cpp b/krita/plugins/filters/fastcolortransfer/fastcolortransfer.cpp index 813e1c00bda..e3e42e8e6d9 100644 --- a/krita/plugins/filters/fastcolortransfer/fastcolortransfer.cpp +++ b/krita/plugins/filters/fastcolortransfer/fastcolortransfer.cpp @@ -1,175 +1,175 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 "fastcolortransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_fastcolortransfer.h" #include "ui_wdgfastcolortransfer.h" #include K_PLUGIN_FACTORY_WITH_JSON(KritaFastColorTransferFactory, "kritafastcolortransfer.json", registerPlugin();) FastColorTransferPlugin::FastColorTransferPlugin(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterFastColorTransfer()); } FastColorTransferPlugin::~FastColorTransferPlugin() { } KisFilterFastColorTransfer::KisFilterFastColorTransfer() : KisFilter(id(), categoryColors(), i18n("&Color Transfer...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsThreading(false); setSupportsPainting(false); setSupportsAdjustmentLayers(false); } KisConfigWidget * KisFilterFastColorTransfer::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const { Q_UNUSED(dev); return new KisWdgFastColorTransfer(parent); } KisFilterConfiguration* KisFilterFastColorTransfer::factoryConfiguration(const KisPaintDeviceSP) const { KisFilterConfiguration* config = new KisFilterConfiguration(id().id(), 1); config->setProperty("filename", ""); // TODO: put an exemple image in share/krita, like a sunset that what's give the best results return config; } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) void KisFilterFastColorTransfer::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfiguration* config, KoUpdater* progressUpdater) const { Q_ASSERT(device != 0); dbgPlugins << "Start transferring color"; // Convert ref and src to LAB const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16(); if (!labCS) { dbgPlugins << "The LAB colorspace is not available."; return; } dbgPlugins << "convert a copy of src to lab"; const KoColorSpace* oldCS = device->colorSpace(); KisPaintDeviceSP srcLAB = new KisPaintDevice(*device.data()); dbgPlugins << "srcLab : " << srcLAB->extent(); - KUndo2Command* cmd = srcLAB->convertTo(labCS, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + KUndo2Command* cmd = srcLAB->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); delete cmd; if (progressUpdater) { progressUpdater->setRange(0, 2 * applyRect.width() * applyRect.height()); } int count = 0; // Compute the means and sigmas of src dbgPlugins << "Compute the means and sigmas of src"; double meanL_src = 0., meanA_src = 0., meanB_src = 0.; double sigmaL_src = 0., sigmaA_src = 0., sigmaB_src = 0.; KisSequentialConstIterator srcIt(srcLAB, applyRect); do { const quint16* data = reinterpret_cast(srcIt.oldRawData()); quint32 L = data[0]; quint32 A = data[1]; quint32 B = data[2]; meanL_src += L; meanA_src += A; meanB_src += B; sigmaL_src += L * L; sigmaA_src += A * A; sigmaB_src += B * B; if (progressUpdater) progressUpdater->setValue(++count); } while (srcIt.nextPixel() && !(progressUpdater && progressUpdater->interrupted())); double totalSize = 1. / (applyRect.width() * applyRect.height()); meanL_src *= totalSize; meanA_src *= totalSize; meanB_src *= totalSize; sigmaL_src *= totalSize; sigmaA_src *= totalSize; sigmaB_src *= totalSize; dbgPlugins << totalSize << "" << meanL_src << "" << meanA_src << "" << meanB_src << "" << sigmaL_src << "" << sigmaA_src << "" << sigmaB_src; double meanL_ref = config->getDouble("meanL"); double meanA_ref = config->getDouble("meanA"); double meanB_ref = config->getDouble("meanB"); double sigmaL_ref = config->getDouble("sigmaL"); double sigmaA_ref = config->getDouble("sigmaA"); double sigmaB_ref = config->getDouble("sigmaB"); // Transfer colors dbgPlugins << "Transfer colors"; { double coefL = sqrt((sigmaL_ref - meanL_ref * meanL_ref) / (sigmaL_src - meanL_src * meanL_src)); double coefA = sqrt((sigmaA_ref - meanA_ref * meanA_ref) / (sigmaA_src - meanA_src * meanA_src)); double coefB = sqrt((sigmaB_ref - meanB_ref * meanB_ref) / (sigmaB_src - meanB_src * meanB_src)); KisHLineConstIteratorSP srcLABIt = srcLAB->createHLineConstIteratorNG(applyRect.x(), applyRect.y(), applyRect.width()); KisHLineIteratorSP dstIt = device->createHLineIteratorNG(applyRect.x(), applyRect.y(), applyRect.width()); quint16 labPixel[4]; for (int y = 0; y < applyRect.height() && !(progressUpdater && progressUpdater->interrupted()); ++y) { do { const quint16* data = reinterpret_cast(srcLABIt->oldRawData()); labPixel[0] = (quint16)CLAMP(((double)data[0] - meanL_src) * coefL + meanL_ref, 0., 65535.); labPixel[1] = (quint16)CLAMP(((double)data[1] - meanA_src) * coefA + meanA_ref, 0., 65535.); labPixel[2] = (quint16)CLAMP(((double)data[2] - meanB_src) * coefB + meanB_ref, 0., 65535.); labPixel[3] = data[3]; oldCS->fromLabA16(reinterpret_cast(labPixel), dstIt->rawData(), 1); if (progressUpdater) progressUpdater->setValue(++count); srcLABIt->nextPixel(); } while(dstIt->nextPixel()); dstIt->nextRow(); srcLABIt->nextRow(); } } } #include "fastcolortransfer.moc" diff --git a/krita/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp b/krita/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp index 363aa54fac0..a124262336e 100644 --- a/krita/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp +++ b/krita/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp @@ -1,145 +1,145 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 "kis_wdg_fastcolortransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgfastcolortransfer.h" KisWdgFastColorTransfer::KisWdgFastColorTransfer(QWidget * parent) : KisConfigWidget(parent) { m_widget = new Ui_WdgFastColorTransfer(); m_widget->setupUi(this); connect(m_widget->fileNameURLRequester, SIGNAL(textChanged(const QString&)), this, SIGNAL(sigConfigurationItemChanged())); } KisWdgFastColorTransfer::~KisWdgFastColorTransfer() { delete m_widget; } void KisWdgFastColorTransfer::setConfiguration(const KisPropertiesConfiguration* config) { QVariant value; if (config->getProperty("filename", value)) { widget()->fileNameURLRequester->setUrl(QUrl::fromUserInput(value.toString())); } } KisPropertiesConfiguration* KisWdgFastColorTransfer::configuration() const { KisFilterConfiguration* config = new KisFilterConfiguration("colortransfer", 1); QString fileName = this->widget()->fileNameURLRequester->url().url(); if (fileName.isEmpty()) return config; KisPaintDeviceSP ref; dbgPlugins << "Use as reference file : " << fileName; KisDocument *d = KisPart::instance()->createDocument(); KisImportExportManager manager(d); KisImportExportFilter::ConversionStatus status; QString s = manager.importDocument(fileName, QString(), status); dbgPlugins << "import returned" << s << "and status" << status; KisImageWSP importedImage = d->image(); if (importedImage) { ref = importedImage->projection(); } if (!ref) { dbgPlugins << "No reference image was specified."; delete d; return config; } // Convert ref to LAB const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16(); if (!labCS) { dbgPlugins << "The LAB colorspace is not available."; delete d; return config; } dbgPlugins << "convert ref to lab"; - KUndo2Command* cmd = ref->convertTo(labCS, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + KUndo2Command* cmd = ref->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); delete cmd; // Compute the means and sigmas of ref double meanL_ref = 0., meanA_ref = 0., meanB_ref = 0.; double sigmaL_ref = 0., sigmaA_ref = 0., sigmaB_ref = 0.; KisSequentialConstIterator refIt(ref, importedImage->bounds()); do { const quint16* data = reinterpret_cast(refIt.oldRawData()); quint32 L = data[0]; quint32 A = data[1]; quint32 B = data[2]; meanL_ref += L; meanA_ref += A; meanB_ref += B; sigmaL_ref += L * L; sigmaA_ref += A * A; sigmaB_ref += B * B; } while (refIt.nextPixel()); double totalSize = 1. / (importedImage->width() * importedImage->height()); meanL_ref *= totalSize; meanA_ref *= totalSize; meanB_ref *= totalSize; sigmaL_ref *= totalSize; sigmaA_ref *= totalSize; sigmaB_ref *= totalSize; dbgPlugins << totalSize << "" << meanL_ref << "" << meanA_ref << "" << meanB_ref << "" << sigmaL_ref << "" << sigmaA_ref << "" << sigmaB_ref; config->setProperty("filename", fileName); config->setProperty("meanL", meanL_ref); config->setProperty("meanA", meanA_ref); config->setProperty("meanB", meanB_ref); config->setProperty("sigmaL", sigmaL_ref); config->setProperty("sigmaA", sigmaA_ref); config->setProperty("sigmaB", sigmaB_ref); delete d; return config; } diff --git a/krita/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp b/krita/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp index df94b16e8d9..26e64263209 100644 --- a/krita/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp +++ b/krita/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp @@ -1,237 +1,237 @@ /* * Copyright (c) 2010-2011 José Luis Vergara * * 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_phong_bumpmap_filter.h" #include "kis_phong_bumpmap_config_widget.h" #include "phong_pixel_processor.h" #include "kis_debug.h" #include "kis_paint_device.h" #include "kis_config_widget.h" #include "KoUpdater.h" #include "kis_math_toolbox.h" #include "KoColorSpaceRegistry.h" #include #include #include "kis_iterator_ng.h" #include "kundo2command.h" #include "kis_painter.h" KisFilterPhongBumpmap::KisFilterPhongBumpmap() : KisFilter(KoID("phongbumpmap" , i18n("PhongBumpmap")), KisFilter::categoryMap(), i18n("&PhongBumpmap...")) { setColorSpaceIndependence(TO_LAB16); setSupportsPainting(true); } void KisFilterPhongBumpmap::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfiguration *config, KoUpdater *progressUpdater ) const { if (!config) return; if (progressUpdater) progressUpdater->setProgress(0); QString userChosenHeightChannel = config->getString(PHONG_HEIGHT_CHANNEL, "FAIL"); bool m_usenormalmap = config->getBool(USE_NORMALMAP_IS_ENABLED); if (userChosenHeightChannel == "FAIL") { qDebug("FIX YOUR FILTER"); return; } KoChannelInfo *m_heightChannel = 0; foreach (KoChannelInfo* channel, device->colorSpace()->channels()) { if (userChosenHeightChannel == channel->name()) { m_heightChannel = channel; } } if (!m_heightChannel) { m_heightChannel = device->colorSpace()->channels().first(); } KIS_ASSERT_RECOVER_RETURN(m_heightChannel); QRect inputArea = applyRect; QRect outputArea = applyRect; if (m_usenormalmap==false) { inputArea.adjust(-1, -1, 1, 1); } quint32 posup; quint32 posdown; quint32 posleft; quint32 posright; QColor I; //Reflected light if (progressUpdater) progressUpdater->setProgress(1); //======Preparation paraphlenalia======= //Hardcoded facts about Phong Bumpmap: it _will_ generate an RGBA16 bumpmap const quint8 BYTE_DEPTH_OF_BUMPMAP = 2; // 16 bits per channel const quint8 CHANNEL_COUNT_OF_BUMPMAP = 4; // RGBA const quint32 pixelsOfInputArea = abs(inputArea.width() * inputArea.height()); const quint32 pixelsOfOutputArea = abs(outputArea.width() * outputArea.height()); const quint8 pixelSize = BYTE_DEPTH_OF_BUMPMAP * CHANNEL_COUNT_OF_BUMPMAP; const quint32 bytesToFillBumpmapArea = pixelsOfOutputArea * pixelSize; QVector bumpmap(bytesToFillBumpmapArea); quint8 *bumpmapDataPointer = bumpmap.data(); quint32 ki = KoChannelInfo::displayPositionToChannelIndex(m_heightChannel->displayPosition(), device->colorSpace()->channels()); PhongPixelProcessor tileRenderer(pixelsOfInputArea, config); if (progressUpdater) progressUpdater->setProgress(2); //===============RENDER================= QVector toDoubleFuncPtr(device->colorSpace()->channels().count()); KisMathToolbox *mathToolbox = KisMathToolboxRegistry::instance()->value(device->colorSpace()->mathToolboxId().id()); if (!mathToolbox->getToDoubleChannelPtr(device->colorSpace()->channels(), toDoubleFuncPtr)) { return; } KisHLineConstIteratorSP iterator; quint32 curPixel = 0; iterator = device->createHLineConstIteratorNG(inputArea.x(), inputArea.y(), inputArea.width() ); if (m_usenormalmap==false) { for (qint32 srcRow = 0; srcRow < inputArea.height(); ++srcRow) { do { const quint8 *data = iterator->oldRawData(); tileRenderer.realheightmap[curPixel] = toDoubleFuncPtr[ki](data, device->colorSpace()->channels()[ki]->pos()); curPixel++; } while (iterator->nextPixel()); iterator->nextRow(); } if (progressUpdater) progressUpdater->setProgress(50); const int tileHeightMinus1 = inputArea.height() - 1; const int tileWidthMinus1 = inputArea.width() - 1; // Foreach INNER pixel in tile for (int y = 1; y < tileHeightMinus1; ++y) { for (int x = 1; x < tileWidthMinus1; ++x) { posup = (y + 1) * inputArea.width() + x; posdown = (y - 1) * inputArea.width() + x; posleft = y * inputArea.width() + x - 1; posright = y * inputArea.width() + x + 1; memcpy(bumpmapDataPointer, tileRenderer.IlluminatePixelFromHeightmap(posup, posdown, posleft, posright).data(), pixelSize); bumpmapDataPointer += pixelSize; } } } else { for (qint32 srcRow = 0; srcRow < inputArea.height(); ++srcRow) { do { const quint8 *data = iterator->oldRawData(); tileRenderer.realheightmap[curPixel] = toDoubleFuncPtr[ki](data, device->colorSpace()->channels()[ki]->pos()); QVector current_pixel_values(4); device->colorSpace()->normalisedChannelsValue(data, current_pixel_values ); //dbgKrita<< "Vector:" << current_pixel_values[2] << "," << current_pixel_values[1] << "," << current_pixel_values[0]; memcpy(bumpmapDataPointer, tileRenderer.IlluminatePixelFromNormalmap(current_pixel_values[2], current_pixel_values[1], current_pixel_values[0]).data(), pixelSize); curPixel++; //pointer that crashes here, but not in the other if statement. bumpmapDataPointer += pixelSize; } while (iterator->nextPixel()); iterator->nextRow(); } } if (progressUpdater) progressUpdater->setProgress(90); KisPaintDeviceSP bumpmapPaintDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb16()); bumpmapPaintDevice->writeBytes(bumpmap.data(), outputArea.x(), outputArea.y(), outputArea.width(), outputArea.height()); - KUndo2Command *leaker = bumpmapPaintDevice->convertTo(device->colorSpace(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + KUndo2Command *leaker = bumpmapPaintDevice->convertTo(device->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KisPainter copier(device); copier.bitBlt(outputArea.x(), outputArea.y(), bumpmapPaintDevice, outputArea.x(), outputArea.y(), outputArea.width(), outputArea.height()); //device->prepareClone(bumpmapPaintDevice); //device->makeCloneFrom(bumpmapPaintDevice, bumpmapPaintDevice->extent()); // THIS COULD BE BUG GY delete leaker; if (progressUpdater) progressUpdater->setProgress(100); } KisFilterConfiguration *KisFilterPhongBumpmap::factoryConfiguration(const KisPaintDeviceSP) const { KisFilterConfiguration *config = new KisFilterConfiguration(id(), 2); config->setProperty(PHONG_AMBIENT_REFLECTIVITY, 0.2); config->setProperty(PHONG_DIFFUSE_REFLECTIVITY, 0.5); config->setProperty(PHONG_SPECULAR_REFLECTIVITY, 0.3); config->setProperty(PHONG_SHINYNESS_EXPONENT, 2); config->setProperty(USE_NORMALMAP_IS_ENABLED, false); config->setProperty(PHONG_DIFFUSE_REFLECTIVITY_IS_ENABLED, true); config->setProperty(PHONG_SPECULAR_REFLECTIVITY_IS_ENABLED, true); // Indexes are off by 1 simply because arrays start at 0 and the GUI naming scheme started at 1 config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[0], true); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[1], true); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[2], false); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[3], false); config->setProperty(PHONG_ILLUMINANT_COLOR[0], QColor(255, 255, 0)); config->setProperty(PHONG_ILLUMINANT_COLOR[1], QColor(255, 0, 0)); config->setProperty(PHONG_ILLUMINANT_COLOR[2], QColor(0, 0, 255)); config->setProperty(PHONG_ILLUMINANT_COLOR[3], QColor(0, 255, 0)); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[0], 50); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[1], 100); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[2], 150); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[3], 200); config->setProperty(PHONG_ILLUMINANT_INCLINATION[0], 25); config->setProperty(PHONG_ILLUMINANT_INCLINATION[1], 20); config->setProperty(PHONG_ILLUMINANT_INCLINATION[2], 30); config->setProperty(PHONG_ILLUMINANT_INCLINATION[3], 40); return config; } QRect KisFilterPhongBumpmap::neededRect(const QRect &rect, const KisFilterConfiguration* /*config*/) const { return rect.adjusted(-1, -1, 1, 1); } QRect KisFilterPhongBumpmap::changedRect(const QRect &rect, const KisFilterConfiguration* /*config*/) const { return rect; } KisConfigWidget *KisFilterPhongBumpmap::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const { KisPhongBumpmapConfigWidget *w = new KisPhongBumpmapConfigWidget(dev, parent); return w; } diff --git a/krita/plugins/formats/bmp/kis_bmp_export.cpp b/krita/plugins/formats/bmp/kis_bmp_export.cpp index c5e7858dc11..05c2af23c51 100644 --- a/krita/plugins/formats/bmp/kis_bmp_export.cpp +++ b/krita/plugins/formats/bmp/kis_bmp_export.cpp @@ -1,75 +1,75 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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_bmp_export.h" #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisBMPExportFactory, "krita_bmp_export.json", registerPlugin();) KisBMPExport::KisBMPExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBMPExport::~KisBMPExport() { } KisImportExportFilter::ConversionStatus KisBMPExport::convert(const QByteArray& from, const QByteArray& to) { dbgFile << "BMP export! From:" << from << ", To:" << to << ""; KisDocument *input = m_chain->inputDocument(); QString filename = m_chain->outputFile(); if (!input) return KisImportExportFilter::NoDocumentCreated; if (filename.isEmpty()) return KisImportExportFilter::FileNotFound; if (from != "application/x-krita") return KisImportExportFilter::NotImplemented; QUrl url = QUrl::fromLocalFile(filename); qApp->processEvents(); // For vector layers to be updated input->image()->waitForDone(); QRect rc = input->image()->bounds(); input->image()->refreshGraph(); input->image()->lock(); - QImage image = input->image()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + QImage image = input->image()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); input->image()->unlock(); image.save(url.toLocalFile()); return KisImportExportFilter::OK; } #include "kis_bmp_export.moc" diff --git a/krita/plugins/formats/jpeg/kis_jpeg_converter.cc b/krita/plugins/formats/jpeg/kis_jpeg_converter.cc index ad7b2d2d6da..4d34b0cd2ed 100644 --- a/krita/plugins/formats/jpeg/kis_jpeg_converter.cc +++ b/krita/plugins/formats/jpeg/kis_jpeg_converter.cc @@ -1,743 +1,743 @@ /* * Copyright (c) 2005 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 "kis_jpeg_converter.h" #include #include #include #ifdef HAVE_LCMS2 # include #else # include #endif extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) const char photoshopMarker[] = "Photoshop 3.0\0"; //const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0"; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); namespace { J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs) { if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") { return JCS_GRAYSCALE; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { return JCS_RGB; } if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) { return JCS_CMYK; } return JCS_UNKNOWN; } QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type) { dbgFile << "color_type =" << color_type; if (color_type == JCS_GRAYSCALE) { return GrayAColorModelID.id(); } else if (color_type == JCS_RGB) { return RGBAColorModelID.id(); } else if (color_type == JCS_CMYK) { return CMYKAColorModelID.id(); } return ""; } } KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode) : m_doc(doc) , m_stop(false) , m_batchMode(batchMode) { } KisJPEGConverter::~KisJPEGConverter() { } KisImageBuilder_Result KisJPEGConverter::decode(const QUrl &uri) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); Q_ASSERT(uri.isLocalFile()); // open the file QFile file(uri.toLocalFile()); if (!file.exists()) { return (KisImageBuilder_RESULT_NOT_EXIST); } if (!file.open(QIODevice::ReadOnly)) { return (KisImageBuilder_RESULT_BAD_FETCH); } KisJPEGSource::setSource(&cinfo, &file); jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); /* Save APP0..APP15 markers */ for (int m = 0; m < 16; m++) jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); // setup_read_icc_profile(&cinfo); // read header jpeg_read_header(&cinfo, (boolean)true); // start reading jpeg_start_decompress(&cinfo); // Get the colorspace QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space); if (modelId.isEmpty()) { dbgFile << "unsupported colorspace :" << cinfo.out_color_space; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } uchar* profile_data; uint profile_len; const KoColorProfile* profile = 0; QByteArray profile_rawdata; if (read_icc_profile(&cinfo, &profile_data, &profile_len)) { profile_rawdata.resize(profile_len); memcpy(profile_rawdata.data(), profile_data, profile_len); cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len); if (hProfile != (cmsHPROFILE) NULL) { profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata); Q_CHECK_PTR(profile); dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory( KoColorSpaceRegistry::instance()->colorSpaceId( modelId, Integer8BitsColorDepthID.id()))->profileIsCompatible(profile)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile); } else cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), ""); if (cs == 0) { dbgFile << "unknown colorspace"; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // TODO fixit // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { - transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451. // After 2.3: warn the user! if (transform && !transform->isValid()) { delete transform; transform = 0; } // Creating the KisImageWSP if (! m_image) { m_image = new KisImage(m_doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image"); Q_CHECK_PTR(m_image); } // Set resolution double xres = 72, yres = 72; if (cinfo.density_unit == 1) { xres = cinfo.X_density; yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { xres = cinfo.X_density * 2.54; yres = cinfo.Y_density * 2.54; } if (xres < 72) { xres = 72; } if (yres < 72) { yres = 72; } m_image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points // Create layer KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX)); // Read data JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components]; for (; cinfo.output_scanline < cinfo.image_height;) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width); jpeg_read_scanlines(&cinfo, &row_pointer, 1); quint8 *src = row_pointer; switch (cinfo.out_color_space) { case JCS_GRAYSCALE: do { quint8 *d = it->rawData(); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[1] = quint8_MAX; } while (it->nextPixel()); break; case JCS_RGB: do { quint8 *d = it->rawData(); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[3] = quint8_MAX; } while (it->nextPixel()); break; case JCS_CMYK: do { quint8 *d = it->rawData(); d[0] = quint8_MAX - *(src++); d[1] = quint8_MAX - *(src++); d[2] = quint8_MAX - *(src++); d[3] = quint8_MAX - *(src++); if (transform) transform->transform(d, d, 1); d[4] = quint8_MAX; } while (it->nextPixel()); break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_image->addNode(KisNodeSP(layer.data()), m_image->rootLayer().data()); // Read exif information dbgFile << "Looking for exif information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 14) { continue; /* Exif data is in an APP1 marker of at least 14 octets */ } if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 || GETJOCTET(marker->data[1]) != (JOCTET) 0x78 || GETJOCTET(marker->data[2]) != (JOCTET) 0x69 || GETJOCTET(marker->data[3]) != (JOCTET) 0x66 || GETJOCTET(marker->data[4]) != (JOCTET) 0x00 || GETJOCTET(marker->data[5]) != (JOCTET) 0x00) continue; /* no Exif header */ dbgFile << "Found exif information of length :" << marker->data_length; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6); QBuffer buf(&byteArray); exifIO->loadFrom(layer->metaData(), &buf); // Interpret orientation tag if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) { KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation"); if (entry.value().type() == KisMetaData::Value::Variant) { switch (entry.value().asVariant().toInt()) { case 2: KisTransformWorker::mirrorY(layer->paintDevice()); break; case 3: image()->rotateImage(M_PI); break; case 4: KisTransformWorker::mirrorX(layer->paintDevice()); break; case 5: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorY(layer->paintDevice()); break; case 6: image()->rotateImage(M_PI / 2); break; case 7: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorX(layer->paintDevice()); break; case 8: image()->rotateImage(-M_PI / 2 + M_PI*2); break; default: break; } } entry.value().setVariant(1); } break; } dbgFile << "Looking for IPTC information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) { continue; /* IPTC data is in an APP13 marker of at least 16 octets */ } if (memcmp(marker->data, photoshopMarker, 14) != 0) { for (int i = 0; i < 14; i++) { dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]); } dbgFile << "No photoshop marker"; continue; /* No IPTC Header */ } dbgFile << "Found Photoshop information of length :" << marker->data_length; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); const Exiv2::byte *record = 0; uint32_t sizeIptc = 0; uint32_t sizeHdr = 0; // Find actual Iptc data within the APP13 segment if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14), marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) { if (sizeIptc) { // Decode the IPTC data QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc); iptcIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); } else { dbgFile << "IPTC Not found in Photoshop marker"; } } break; } dbgFile << "Looking for XMP information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) { continue; /* XMP data is in an APP1 marker of at least 31 octets */ } if (memcmp(marker->data, xmpMarker, 29) != 0) { dbgFile << "Not XMP marker"; continue; /* No xmp Header */ } dbgFile << "Found XMP Marker of length " << marker->data_length; QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); break; } // Dump loaded metadata layer->metaData()->debugDump(); // Check whether the metadata has resolution info, too... if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) { double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble(); double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble(); if (xres != 0 && yres != 0) { m_image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points } } // Finish decompression jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row_pointer; return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisJPEGConverter::buildImage(const QUrl &uri) { if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!uri.isLocalFile()) { return KisImageBuilder_RESULT_NOT_EXIST; } return decode(uri); } KisImageWSP KisJPEGConverter::image() { return m_image; } KisImageBuilder_Result KisJPEGConverter::buildFile(const QUrl &uri, KisPaintLayerSP layer, vKisAnnotationSP_it /*annotationsStart*/, vKisAnnotationSP_it /*annotationsEnd*/, KisJPEGOptions options, KisMetaData::Store* metaData) { if (!layer) return KisImageBuilder_RESULT_INVALID_ARG; KisImageWSP image = KisImageWSP(layer->image()); if (!image) return KisImageBuilder_RESULT_EMPTY; if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!uri.isLocalFile()) return KisImageBuilder_RESULT_NOT_LOCAL; const KoColorSpace * cs = layer->colorSpace(); J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs); if (!m_batchMode && cs->colorDepthId() != Integer8BitsColorDepthID) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Warning: JPEG only supports 8 bits per channel. Your image uses: %1. Krita will save your image as 8 bits per channel.", cs->name())); } if (color_type == JCS_UNKNOWN) { if (!m_batchMode) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot export images in %1.\nWill save as RGB.", cs->name())); } - KUndo2Command *tmp = layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + KUndo2Command *tmp = layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); delete tmp; cs = KoColorSpaceRegistry::instance()->rgb8(); color_type = JCS_RGB; } if (options.forceSRGB) { const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); KUndo2Command *tmp = layer->paintDevice()->convertTo(dst); delete tmp; cs = dst; color_type = JCS_RGB; } // Open file for writing QFile file(uri.toLocalFile()); if (!file.open(QIODevice::WriteOnly)) { return (KisImageBuilder_RESULT_FAILURE); } uint height = image->height(); uint width = image->width(); // Initialize structure struct jpeg_compress_struct cinfo; jpeg_create_compress(&cinfo); // Initialize error output struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); // Initialize output stream KisJPEGDestination::setDestination(&cinfo, &file); cinfo.image_width = width; // image width and height, in pixels cinfo.image_height = height; cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */ cinfo.in_color_space = color_type; // colorspace of input image // Set default compression parameters jpeg_set_defaults(&cinfo); // Customize them jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG); if (options.progressive) { jpeg_simple_progression(&cinfo); } // Optimize ? cinfo.optimize_coding = (boolean)options.optimize; // Smoothing cinfo.smoothing_factor = (boolean)options.smooth; // Subsampling switch (options.subsampling) { default: case 0: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 1: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 2: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 3: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; } // Save resolution cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.density_unit = 1; cinfo.write_JFIF_header = (boolean)true; // Start compression jpeg_start_compress(&cinfo, (boolean)true); // Save exif and iptc information if any available if (metaData && !metaData->empty()) { metaData->applyFilters(options.filters); // Save EXIF if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "Exif information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "EXIF information could not be saved."; // TODO: warn the user ? } } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "IPTC information could not be saved."; // TODO: warn the user ? } } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "XMP information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "XMP information could not be saved."; // TODO: warn the user ? } } } KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace()); KoColor c(options.transparencyFillColor, layer->colorSpace()); dev->fill(QRect(0, 0, width, height), c); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height)); gc.end(); if (options.saveProfile) { const KoColorProfile* colorProfile = layer->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size()); } // Write data information JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components]; int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount(); for (; cinfo.next_scanline < height;) { KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width); quint8 *dst = row_pointer; switch (color_type) { case JCS_GRAYSCALE: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_RGB: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_CMYK: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - d[0]; *(dst++) = quint8_MAX - d[1]; *(dst++) = quint8_MAX - d[2]; *(dst++) = quint8_MAX - d[3]; } while (it->nextPixel()); } break; default: delete [] row_pointer; jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED; } jpeg_write_scanlines(&cinfo, &row_pointer, 1); } // Writing is over jpeg_finish_compress(&cinfo); file.close(); delete [] row_pointer; // Free memory jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_OK; } void KisJPEGConverter::cancel() { m_stop = true; } diff --git a/krita/plugins/formats/ppm/kis_ppm_export.cpp b/krita/plugins/formats/ppm/kis_ppm_export.cpp index e2bde2182ce..3c06eb49a1d 100644 --- a/krita/plugins/formats/ppm/kis_ppm_export.cpp +++ b/krita/plugins/formats/ppm/kis_ppm_export.cpp @@ -1,292 +1,292 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_ppm_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_kis_wdg_options_ppm.h" #include #include #include #include #include "kis_iterator_ng.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPPMExportFactory, "krita_ppm_export.json", registerPlugin();) KisPPMExport::KisPPMExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPPMExport::~KisPPMExport() { } class KisPPMFlow { public: KisPPMFlow() { } virtual ~KisPPMFlow() { } virtual void writeBool(quint8 v) = 0; virtual void writeBool(quint16 v) = 0; virtual void writeNumber(quint8 v) = 0; virtual void writeNumber(quint16 v) = 0; virtual void flush() = 0; private: }; class KisPPMAsciiFlow : public KisPPMFlow { public: KisPPMAsciiFlow(QIODevice* device) : m_device(device) { } ~KisPPMAsciiFlow() { } virtual void writeBool(quint8 v) { if (v > 127) { m_device->write("1 "); } else { m_device->write("0 "); } } virtual void writeBool(quint16 v) { writeBool(quint8(v >> 8)); } virtual void writeNumber(quint8 v) { m_device->write(QByteArray::number(v)); m_device->write(" "); } virtual void writeNumber(quint16 v) { m_device->write(QByteArray::number(v)); m_device->write(" "); } virtual void flush() { } private: QIODevice* m_device; }; class KisPPMBinaryFlow : public KisPPMFlow { public: KisPPMBinaryFlow(QIODevice* device) : m_device(device), m_pos(0), m_current(0) { } virtual ~KisPPMBinaryFlow() { } virtual void writeBool(quint8 v) { m_current = m_current << 1; m_current |= (v > 127); ++m_pos; if (m_pos >= 8) { m_current = 0; m_pos = 0; flush(); } } virtual void writeBool(quint16 v) { writeBool(quint8(v >> 8)); } virtual void writeNumber(quint8 v) { m_device->write((char*)&v, 1); } virtual void writeNumber(quint16 v) { quint16 vo = qToBigEndian(v); m_device->write((char*)&vo, 2); } virtual void flush() { m_device->write((char*)&m_current, 1); } private: QIODevice* m_device; int m_pos; quint8 m_current; }; KisImportExportFilter::ConversionStatus KisPPMExport::convert(const QByteArray& from, const QByteArray& to) { dbgFile << "PPM export! From:" << from << ", To:" << to << ""; if (from != "application/x-krita") return KisImportExportFilter::NotImplemented; KisDocument *input = m_chain->inputDocument(); QString filename = m_chain->outputFile(); if (!input) return KisImportExportFilter::NoDocumentCreated; if (filename.isEmpty()) return KisImportExportFilter::FileNotFound; KoDialog* kdb = new KoDialog(0); kdb->setWindowTitle(i18n("PPM Export Options")); kdb->setButtons(KoDialog::Ok | KoDialog::Cancel); Ui::WdgOptionsPPM optionsPPM; QWidget* wdg = new QWidget(kdb); optionsPPM.setupUi(wdg); kdb->setMainWidget(wdg); QApplication::restoreOverrideCursor(); QString filterConfig = KisConfig().exportConfiguration("PPM"); KisPropertiesConfiguration cfg; cfg.fromXML(filterConfig); optionsPPM.type->setCurrentIndex(cfg.getInt("type", 0)); if (!m_chain->manager()->getBatchMode()) { if (kdb->exec() == QDialog::Rejected) { return KisImportExportFilter::OK; // FIXME Cancel doesn't exist :( } } else { qApp->processEvents(); // For vector layers to be updated } input->image()->waitForDone(); bool rgb = (to == "image/x-portable-pixmap"); bool binary = optionsPPM.type->currentIndex() == 0; cfg.setProperty("type", optionsPPM.type->currentIndex()); KisConfig().setExportConfiguration("PPM", cfg); bool bitmap = (to == "image/x-portable-bitmap"); KisImageWSP image = input->image(); Q_CHECK_PTR(image); image->refreshGraph(); image->lock(); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); image->unlock(); // Test color space if (((rgb && (pd->colorSpace()->id() != "RGBA" && pd->colorSpace()->id() != "RGBA16")) || (!rgb && (pd->colorSpace()->id() != "GRAYA" && pd->colorSpace()->id() != "GRAYA16" && pd->colorSpace()->id() != "GRAYAU16")))) { if (rgb) { - pd->convertTo(KoColorSpaceRegistry::instance()->rgb8(0), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + pd->convertTo(KoColorSpaceRegistry::instance()->rgb8(0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } else { - pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } bool is16bit = pd->colorSpace()->id() == "RGBA16" || pd->colorSpace()->id() == "GRAYAU16"; // Open the file for writing QFile fp(filename); fp.open(QIODevice::WriteOnly); // Write the magic if (rgb) { if (binary) fp.write("P6"); else fp.write("P3"); } else if (bitmap) { if (binary) fp.write("P4"); else fp.write("P1"); } else { if (binary) fp.write("P5"); else fp.write("P2"); } fp.write("\n"); // Write the header fp.write(QByteArray::number(image->width())); fp.write(" "); fp.write(QByteArray::number(image->height())); if (!bitmap) { if (is16bit) fp.write(" 65535\n"); else fp.write(" 255\n"); } else { fp.write("\n"); } // Write the data KisPPMFlow* flow = 0; if (binary) flow = new KisPPMBinaryFlow(&fp); else flow = new KisPPMAsciiFlow(&fp); for (int y = 0; y < image->height(); ++y) { KisHLineIteratorSP it = pd->createHLineIteratorNG(0, y, image->width()); if (is16bit) { if (rgb) { do { flow->writeNumber(KoBgrU16Traits::red(it->rawData())); flow->writeNumber(KoBgrU16Traits::green(it->rawData())); flow->writeNumber(KoBgrU16Traits::blue(it->rawData())); } while (it->nextPixel()); } else if (bitmap) { do { flow->writeBool(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } else { do { flow->writeNumber(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } } else { if (rgb) { do { flow->writeNumber(KoBgrTraits::red(it->rawData())); flow->writeNumber(KoBgrTraits::green(it->rawData())); flow->writeNumber(KoBgrTraits::blue(it->rawData())); } while (it->nextPixel()); } else if (bitmap) { do { flow->writeBool(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } else { do { flow->writeNumber(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } } } if (bitmap) { flow->flush(); } delete flow; fp.close(); return KisImportExportFilter::OK; } #include "kis_ppm_export.moc" \ No newline at end of file diff --git a/krita/plugins/formats/tga/kis_tga_export.cpp b/krita/plugins/formats/tga/kis_tga_export.cpp index ab70280ea3f..b7105b573b7 100644 --- a/krita/plugins/formats/tga/kis_tga_export.cpp +++ b/krita/plugins/formats/tga/kis_tga_export.cpp @@ -1,103 +1,103 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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_tga_export.h" #include #include #include #include #include #include #include #include #include #include #include "tga.h" K_PLUGIN_FACTORY_WITH_JSON(KisTGAExportFactory, "krita_tga_export.json", registerPlugin();) KisTGAExport::KisTGAExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTGAExport::~KisTGAExport() { } KisImportExportFilter::ConversionStatus KisTGAExport::convert(const QByteArray& from, const QByteArray& to) { dbgFile << "TGA export! From:" << from << ", To:" << to << ""; KisDocument *input = m_chain->inputDocument(); QString filename = m_chain->outputFile(); if (!input) return KisImportExportFilter::NoDocumentCreated; if (filename.isEmpty()) return KisImportExportFilter::FileNotFound; if (from != "application/x-krita") return KisImportExportFilter::NotImplemented; qApp->processEvents(); // For vector layers to be updated input->image()->waitForDone(); QRect rc = input->image()->bounds(); input->image()->refreshGraph(); input->image()->lock(); - QImage image = input->image()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + QImage image = input->image()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); input->image()->unlock(); QFile f(filename); f.open(QIODevice::WriteOnly); QDataStream s(&f); s.setByteOrder(QDataStream::LittleEndian); const QImage& img = image; const bool hasAlpha = (img.format() == QImage::Format_ARGB32); for (int i = 0; i < 12; i++) s << targaMagic[i]; // write header s << quint16(img.width()); // width s << quint16(img.height()); // height s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha) s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4) for (int y = 0; y < img.height(); y++) { for (int x = 0; x < img.width(); x++) { const QRgb color = img.pixel(x, y); s << quint8(qBlue(color)); s << quint8(qGreen(color)); s << quint8(qRed(color)); if (hasAlpha) s << quint8(qAlpha(color)); } } return KisImportExportFilter::OK; } #include "kis_tga_export.moc" diff --git a/krita/plugins/formats/tiff/kis_tiff_converter.cc b/krita/plugins/formats/tiff/kis_tiff_converter.cc index 74bba200de8..5242902df35 100644 --- a/krita/plugins/formats/tiff/kis_tiff_converter.cc +++ b/krita/plugins/formats/tiff/kis_tiff_converter.cc @@ -1,687 +1,687 @@ /* * Copyright (c) 2005-2006 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 "kis_tiff_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tiff_reader.h" #include "kis_tiff_ycbcr_reader.h" #include "kis_buffer_stream.h" #include "kis_tiff_writer_visitor.h" #if TIFFLIB_VERSION < 20111221 typedef size_t tmsize_t; #endif namespace { QPair getColorSpaceForColorType(uint16 sampletype, uint16 color_type, uint16 color_nb_bits, TIFF *image, uint16 &nbchannels, uint16 &extrasamplescount, uint8 &destDepth) { if (color_type == PHOTOMETRIC_MINISWHITE || color_type == PHOTOMETRIC_MINISBLACK) { if (nbchannels == 0) nbchannels = 1; extrasamplescount = nbchannels - 1; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(GrayAColorModelID.id(), Float32BitsColorDepthID.id()); } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(GrayAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_RGB /*|| color_type == */) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(RGBAColorModelID.id(), Float32BitsColorDepthID.id()); } return QPair(); } else { if (color_nb_bits <= 8) { destDepth = 8; return QPair(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } } } else if (color_type == PHOTOMETRIC_YCBCR) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (color_nb_bits <= 8) { destDepth = 8; return QPair(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_SEPARATED) { if (nbchannels == 0) nbchannels = 4; // SEPARATED is in general CMYK but not always, so we check uint16 inkset; if ((TIFFGetField(image, TIFFTAG_INKSET, &inkset) == 0)) { dbgFile << "Image does not define the inkset."; inkset = 2; } if (inkset != INKSET_CMYK) { dbgFile << "Unsupported inkset (right now, only CMYK is supported)"; char** ink_names; uint16 numberofinks; if (TIFFGetField(image, TIFFTAG_INKNAMES, &ink_names) == 1 && TIFFGetField(image, TIFFTAG_NUMBEROFINKS, &numberofinks) == 1) { dbgFile << "Inks are :"; for (uint i = 0; i < numberofinks; i++) { dbgFile << ink_names[i]; } } else { dbgFile << "inknames are not defined !"; // To be able to read stupid adobe files, if there are no information about inks and four channels, then it's a CMYK file : if (nbchannels - extrasamplescount != 4) { return QPair(); } } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_CIELAB || color_type == PHOTOMETRIC_ICCLAB) { destDepth = 16; if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count return QPair(LABAColorModelID.id(), Integer16BitsColorDepthID.id()); } else if (color_type == PHOTOMETRIC_PALETTE) { destDepth = 16; if (nbchannels == 0) nbchannels = 2; extrasamplescount = nbchannels - 2; // FIX the extrasamples count // <-- we will convert the index image to RGBA16 as the palette is always on 16bits colors return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } return QPair(); } } KisTIFFConverter::KisTIFFConverter(KisDocument *doc) { m_doc = doc; m_stop = false; TIFFSetWarningHandler(0); TIFFSetErrorHandler(0); } KisTIFFConverter::~KisTIFFConverter() { } KisImageBuilder_Result KisTIFFConverter::decode(const QUrl &uri) { dbgFile << "Start decoding TIFF File"; // Opent the TIFF file TIFF *image = 0; if ((image = TIFFOpen(QFile::encodeName(uri.toLocalFile()), "r")) == NULL) { dbgFile << "Could not open the file, either it does not exist, either it is not a TIFF :" << uri.toLocalFile(); return (KisImageBuilder_RESULT_BAD_FETCH); } do { dbgFile << "Read new sub-image"; KisImageBuilder_Result result = readTIFFDirectory(image); if (result != KisImageBuilder_RESULT_OK) { return result; } } while (TIFFReadDirectory(image)); // Freeing memory TIFFClose(image); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisTIFFConverter::readTIFFDirectory(TIFF* image) { // Read information about the tiff uint32 width, height; if (TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0) { dbgFile << "Image does not define its width"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } if (TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height) == 0) { dbgFile << "Image does not define its height"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } float xres; if (TIFFGetField(image, TIFFTAG_XRESOLUTION, &xres) == 0) { dbgFile << "Image does not define x resolution"; // but we don't stop xres = 100; } float yres; if (TIFFGetField(image, TIFFTAG_YRESOLUTION, &yres) == 0) { dbgFile << "Image does not define y resolution"; // but we don't stop yres = 100; } uint16 depth; if ((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &depth) == 0)) { dbgFile << "Image does not define its depth"; depth = 1; } uint16 sampletype; if ((TIFFGetField(image, TIFFTAG_SAMPLEFORMAT, &sampletype) == 0)) { dbgFile << "Image does not define its sample type"; sampletype = SAMPLEFORMAT_UINT; } // Determine the number of channels (useful to know if a file has an alpha or not uint16 nbchannels; if (TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &nbchannels) == 0) { dbgFile << "Image has an undefined number of samples per pixel"; nbchannels = 0; } // Get the number of extrasamples and information about them uint16 *sampleinfo = 0, extrasamplescount; if (TIFFGetField(image, TIFFTAG_EXTRASAMPLES, &extrasamplescount, &sampleinfo) == 0) { extrasamplescount = 0; } // Determine the colorspace uint16 color_type; if (TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &color_type) == 0) { dbgFile << "Image has an undefined photometric interpretation"; color_type = PHOTOMETRIC_MINISWHITE; } uint8 dstDepth = 0; QPair colorSpaceId = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth); if (colorSpaceId.first.isEmpty()) { dbgFile << "Image has an unsupported colorspace :" << color_type << " for this depth :" << depth; TIFFClose(image); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } dbgFile << "Colorspace is :" << colorSpaceId.first << colorSpaceId.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels; // Read image profile dbgFile << "Reading profile"; const KoColorProfile* profile = 0; quint32 EmbedLen; quint8* EmbedBuffer; if (TIFFGetField(image, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer) == 1) { dbgFile << "Profile found"; QByteArray rawdata; rawdata.resize(EmbedLen); memcpy(rawdata.data(), EmbedBuffer, EmbedLen); profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, rawdata); } else { dbgFile << "No Profile found"; } // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory(KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceId.first, colorSpaceId.second))->profileIsCompatible(profile)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceId.first << " " << colorSpaceId.second; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs = 0; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, 0); } if (cs == 0) { dbgFile << "Colorspace" << colorSpaceId.first << colorSpaceId.second << " is not available, please check your installation."; TIFFClose(image); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { dbgFile << "The profile can't be used in krita, need conversion"; - transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile)->createColorConverter(cs, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Check if there is an alpha channel int8 alphapos = -1; // <- no alpha // Check which extra is alpha if any dbgFile << "There are" << nbchannels << " channels and" << extrasamplescount << " extra channels"; if (sampleinfo) { // index images don't have any sampleinfo, and therefor sampleinfo == 0 for (int i = 0; i < extrasamplescount; i ++) { dbgFile << i << "" << extrasamplescount << "" << (cs->colorChannelCount()) << nbchannels << "" << sampleinfo[i]; if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) { // XXX: dangelo: the color values are already multiplied with // the alpha value. This needs to be reversed later (postprocessor?) alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) { // color values are not premultiplied with alpha, and can be used as they are. alphapos = i; } } } dbgFile << "Alpha pos:" << alphapos; // Read META Information KoDocumentInfo * info = m_doc->documentInfo(); char* text; if (TIFFGetField(image, TIFFTAG_ARTIST, &text) == 1) { info->setAuthorInfo("creator", text); } if (TIFFGetField(image, TIFFTAG_DOCUMENTNAME, &text) == 1) { info->setAboutInfo("title", text); } if (TIFFGetField(image, TIFFTAG_IMAGEDESCRIPTION, &text) == 1) { info->setAboutInfo("description", text); } // Get the planar configuration uint16 planarconfig; if (TIFFGetField(image, TIFFTAG_PLANARCONFIG, &planarconfig) == 0) { dbgFile << "Plannar configuration is not define"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } // Creating the KisImageWSP if (! m_image) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); m_image->setResolution( POINT_TO_INCH(xres), POINT_TO_INCH(yres )); // It is the "invert" macro because we convert from pointer-per-inchs to points Q_CHECK_PTR(m_image); } else { if (m_image->width() < (qint32)width || m_image->height() < (qint32)height) { quint32 newwidth = (m_image->width() < (qint32)width) ? width : m_image->width(); quint32 newheight = (m_image->height() < (qint32)height) ? height : m_image->height(); m_image->resizeImage(QRect(0,0,newwidth, newheight)); } } KisPaintLayer* layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX); tdata_t buf = 0; tdata_t* ps_buf = 0; // used only for planar configuration separated KisBufferStreamBase* tiffstream; KisTIFFReaderBase* tiffReader = 0; quint8 poses[5]; KisTIFFPostProcessor* postprocessor = 0; // Configure poses uint8 nbcolorsamples = nbchannels - extrasamplescount; switch (color_type) { case PHOTOMETRIC_MINISWHITE: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessorInvert(nbcolorsamples); } break; case PHOTOMETRIC_MINISBLACK: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_CIELAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessorCIELABtoICCLAB(nbcolorsamples); } break; case PHOTOMETRIC_ICCLAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_RGB: { if (sampletype == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_SEPARATED: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; poses[4] = 4; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; default: break; } // Initisalize tiffReader uint16 * lineSizeCoeffs = new uint16[nbchannels]; uint16 vsubsampling = 1; uint16 hsubsampling = 1; for (uint i = 0; i < nbchannels; i++) { lineSizeCoeffs[i] = 1; } if (color_type == PHOTOMETRIC_PALETTE) { uint16 *red; // No need to free them they are free by libtiff uint16 *green; uint16 *blue; if ((TIFFGetField(image, TIFFTAG_COLORMAP, &red, &green, &blue)) == 0) { dbgFile << "Indexed image does not define a palette"; TIFFClose(image); delete [] lineSizeCoeffs; return KisImageBuilder_RESULT_INVALID_ARG; } tiffReader = new KisTIFFReaderFromPalette(layer->paintDevice(), red, green, blue, poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (color_type == PHOTOMETRIC_YCBCR) { TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRSUBSAMPLING, &hsubsampling, &vsubsampling); lineSizeCoeffs[1] = hsubsampling; lineSizeCoeffs[2] = hsubsampling; uint16 position; TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position); if (dstDepth == 8) { tiffReader = new KisTIFFYCbCrReaderTarget8Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling, (KisTIFFYCbCr::Position)position); } else if (dstDepth == 16) { tiffReader = new KisTIFFYCbCrReaderTarget16Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling, (KisTIFFYCbCr::Position)position); } } else if (dstDepth == 8) { tiffReader = new KisTIFFReaderTarget8bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (dstDepth == 16) { uint16 alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue = 15360; // representation of 1.0 in half } else { alphaValue = quint16_MAX; } tiffReader = new KisTIFFReaderTarget16bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue); } else if (dstDepth == 32) { union { float f; uint32 i; } alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue.f = 1.0f; } else { alphaValue.i = quint32_MAX; } tiffReader = new KisTIFFReaderTarget32bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue.i); } if (TIFFIsTiled(image)) { dbgFile << "tiled image"; uint32 tileWidth, tileHeight; uint32 x, y; TIFFGetField(image, TIFFTAG_TILEWIDTH, &tileWidth); TIFFGetField(image, TIFFTAG_TILELENGTH, &tileHeight); uint32 linewidth = (tileWidth * depth * nbchannels) / 8; if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(TIFFTileSize(image)); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, linewidth); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, linewidth); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, linewidth); } } else { ps_buf = new tdata_t[nbchannels]; uint32 * lineSizes = new uint32[nbchannels]; tmsize_t baseSize = TIFFTileSize(image) / nbchannels; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(baseSize); lineSizes[i] = tileWidth; // baseSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << linewidth << "" << nbchannels << "" << layer->paintDevice()->colorSpace()->colorChannelCount(); for (y = 0; y < height; y += tileHeight) { for (x = 0; x < width; x += tileWidth) { dbgFile << "Reading tile x =" << x << " y =" << y; if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadTile(image, buf, x, y, 0, (tsample_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadTile(image, ps_buf[i], x, y, 0, i); } } uint32 realTileWidth = (x + tileWidth) < width ? tileWidth : width - x; for (uint yintile = 0; y + yintile < height && yintile < tileHeight / vsubsampling;) { tiffReader->copyDataToChannels(x, y + yintile , realTileWidth, tiffstream); yintile += 1; tiffstream->moveToLine(yintile); } tiffstream->restart(); } } } else { dbgFile << "striped image"; tsize_t stripsize = TIFFStripSize(image); uint32 rowsPerStrip; TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip); dbgFile << rowsPerStrip << "" << height; rowsPerStrip = qMin(rowsPerStrip, height); // when TIFFNumberOfStrips(image) == 1 it might happen that rowsPerStrip is incorrectly set if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(stripsize); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, stripsize / rowsPerStrip); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, stripsize / rowsPerStrip); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, stripsize / rowsPerStrip); } } else { ps_buf = new tdata_t[nbchannels]; uint32 scanLineSize = stripsize / rowsPerStrip; dbgFile << " scanLineSize for each plan =" << scanLineSize; uint32 * lineSizes = new uint32[nbchannels]; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(stripsize); lineSizes[i] = scanLineSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << "Scanline size =" << TIFFRasterScanlineSize(image) << " / strip size =" << TIFFStripSize(image) << " / rowsPerStrip =" << rowsPerStrip << " stripsize/rowsPerStrip =" << stripsize / rowsPerStrip; uint32 y = 0; dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image) << " rowsPerStrip =" << rowsPerStrip << " stripsize =" << stripsize; for (uint32 strip = 0; y < height; strip++) { if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, 0) , buf, (tsize_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, i), ps_buf[i], (tsize_t) - 1); } } for (uint32 yinstrip = 0 ; yinstrip < rowsPerStrip && y < height ;) { uint linesread = tiffReader->copyDataToChannels(0, y, width, tiffstream); y += linesread; yinstrip += linesread; tiffstream->moveToLine(yinstrip); } tiffstream->restart(); } } tiffReader->finalize(); delete[] lineSizeCoeffs; delete tiffReader; delete tiffstream; if (planarconfig == PLANARCONFIG_CONTIG) { _TIFFfree(buf); } else { for (uint i = 0; i < nbchannels; i++) { _TIFFfree(ps_buf[i]); } delete[] ps_buf; } m_image->addNode(KisNodeSP(layer), m_image->rootLayer().data()); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisTIFFConverter::buildImage(const QUrl &uri) { if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!uri.isLocalFile()) { return KisImageBuilder_RESULT_NOT_EXIST; } return decode(uri); } KisImageWSP KisTIFFConverter::image() { return m_image; } KisImageBuilder_Result KisTIFFConverter::buildFile(const QUrl &uri, KisImageWSP kisimage, KisTIFFOptions options) { dbgFile << "Start writing TIFF File"; if (!kisimage) return KisImageBuilder_RESULT_EMPTY; if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!uri.isLocalFile()) return KisImageBuilder_RESULT_NOT_LOCAL; // Open file for writing TIFF *image; if ((image = TIFFOpen(QFile::encodeName(uri.toLocalFile()), "w")) == NULL) { dbgFile << "Could not open the file for writing" << uri.toLocalFile(); TIFFClose(image); return (KisImageBuilder_RESULT_FAILURE); } // Set the document information KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData()); } QString abstract = info->aboutInfo("description"); if (!abstract.isEmpty()) { TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData()); } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData()); } dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes()) << " yres: " << INCH_TO_POINT(kisimage->yRes()); TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes())); // It is the "invert" macro because we convert from pointer-per-inchs to points TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes())); KisGroupLayer* root = dynamic_cast(kisimage->rootLayer().data()); if (root == 0) { TIFFClose(image); return KisImageBuilder_RESULT_FAILURE; } KisTIFFWriterVisitor* visitor = new KisTIFFWriterVisitor(image, &options); if (!visitor->visit(root)) { TIFFClose(image); return KisImageBuilder_RESULT_FAILURE; } TIFFClose(image); return KisImageBuilder_RESULT_OK; } void KisTIFFConverter::cancel() { m_stop = true; } diff --git a/krita/plugins/tools/tool_transform2/kis_tool_transform.cc b/krita/plugins/tools/tool_transform2/kis_tool_transform.cc index e05062de197..5d796d20908 100644 --- a/krita/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/krita/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1159 +1,1159 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform.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 #include #include #include #include #include #include "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_changesTracker(&m_transaction) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(const QPointF&)), SLOT(cursorOutlineUpdateRequested(const QPointF&))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged()), this, SLOT(slotTrackerChangedConfig())); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { KisTool::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeData.strokeId()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeData.strokeId()) { useCursor(KisCursor::pointingHandCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeData.strokeId()) { startStroke(m_currentArgs.mode()); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (!MOVE_CONDITION(event, KisTool::PAINT_MODE)) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::touchEvent( QTouchEvent* event ) { //Count all moving touch points int touchCount = 0; foreach( QTouchEvent::TouchPoint tp, event->touchPoints() ) { if( tp.state() == Qt::TouchPointMoved ) { touchCount++; } } //Use the touch point count to determine the gesture switch( touchCount ) { case 1: { //Panning QTouchEvent::TouchPoint tp = event->touchPoints().at( 0 ); QPointF diff = tp.screenPos() - tp.lastScreenPos(); m_currentArgs.setTransformedCenter( m_currentArgs.transformedCenter() + diff ); outlineChanged(); break; } case 2: { //Scaling QTouchEvent::TouchPoint tp1 = event->touchPoints().at( 0 ); QTouchEvent::TouchPoint tp2 = event->touchPoints().at( 1 ); float lastZoom = (tp1.lastScreenPos() - tp2.lastScreenPos()).manhattanLength(); float newZoom = (tp1.screenPos() - tp2.screenPos()).manhattanLength(); float diff = (newZoom - lastZoom) / 100; m_currentArgs.setScaleX( m_currentArgs.scaleX() + diff ); m_currentArgs.setScaleY( m_currentArgs.scaleY() + diff ); outlineChanged(); break; } case 3: { //Rotation /* TODO: implement touch-based rotation. Vector2f center; foreach( const QTouchEvent::TouchPoint &tp, event->touchPoints() ) { if( tp.state() == Qt::TouchPointMoved ) { center += Vector2f( tp.screenPos().x(), tp.screenPos().y() ); } } center /= touchCount; QTouchEvent::TouchPoint tp = event->touchPoints().at(0); Vector2f oldPosition = (Vector2f( tp.lastScreenPos().x(), tp.lastScreenPos().y() ) - center).normalized(); Vector2f newPosition = (Vector2f( tp.screenPos().x(), tp.screenPos().y() ) - center).normalized(); float oldAngle = qAcos( oldPosition.dot( Vector2f( 0.0f, 0.0f ) ) ); float newAngle = qAcos( newPosition.dot( Vector2f( 0.0f, 0.0f ) ) ); float diff = newAngle - oldAngle; m_currentArgs.setAZ( m_currentArgs.aZ() + diff ); outlineChanged(); */ break; } } } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { m_currentArgs = adapter->savedArgs(); initGuiAfterTransformMode(); result = true; } } return result; } bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); KisNodeSP oldRootNode; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode) && args->mode() == mode && oldRootNode == currentNode) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); result = true; } return result; } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { // NOTE: we are requesting an old value of m_currentArgs variable // here, which is global, don't forget about this on higher // levels. QString filterId = m_currentArgs.filterId(); m_currentArgs = ToolTransformArgs(); m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); if (mode == ToolTransformArgs::FREE_TRANSFORM) { m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { m_currentArgs.setMode(ToolTransformArgs::WARP); m_optionsWidget->setDefaultWarpPoints(); m_currentArgs.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { m_currentArgs.setMode(ToolTransformArgs::CAGE); m_currentArgs.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = m_transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::updateSelectionPath() { m_selectionPath = QPainterPath(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), 0, this->canvas()->resourceManager()); QPainterPath selectionOutline; KisSelectionSP selection = resources->activeSelection(); if (selection && selection->outlineCacheValid()) { selectionOutline = selection->outlineCache(); } else { selectionOutline.addRect(m_selectedPortionCache->exactBounds()); } const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, currentNode()); } startStroke(ToolTransformArgs::FREE_TRANSFORM); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeData.strokeId()) return; m_changesTracker.requestUndo(); } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { cancelStroke(); } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode) { Q_ASSERT(!m_strokeData.strokeId()); KisPaintDeviceSP dev; KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), 0, this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) { return; } /** * FIXME: The transform tool is not completely asynchronous, it * needs the content of the layer for creation of the stroke * strategy. It means that we cannot start a new stroke until the * previous one is finished. Ideally, we should create the * m_selectedPortionCache and m_selectionPath somewhere in the * stroke and pass it to the tool somehow. But currently, we will * just disable starting a new stroke asynchronously */ if (image()->tryBarrierLock()) { image()->unlock(); } else { return; } ToolTransformArgs fetchedArgs; bool fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, resources->activeSelection(), image()->postExecutionUndoAdapter()); KisPaintDeviceSP previewDevice = strategy->previewDevice(); KisSelectionSP selection = strategy->realSelection(); QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds(); if (!selection && resources->activeSelection()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); } if (srcRect.isEmpty()) { delete strategy; KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); return; } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode); initThumbnailImage(previewDevice); updateSelectionPath(); if (fetchedFromCommand) { m_currentArgs = fetchedArgs; initGuiAfterTransformMode(); } else if (!tryInitTransformModeFromNode(currentNode)) { initTransformMode(mode); } m_strokeData = StrokeData(image()->startStroke(strategy)); bool haveInvisibleNodes = clearDevices(m_transaction.rootNode(), m_workRecursively); if (haveInvisibleNodes) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } Q_ASSERT(m_changesTracker.isEmpty()); commitChanges(); } void KisToolTransform::endStroke() { if (!m_strokeData.strokeId()) return; if (!m_currentArgs.isIdentity()) { transformDevices(m_transaction.rootNode(), m_workRecursively); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); } void KisToolTransform::cancelStroke() { if (!m_strokeData.strokeId()) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { image()->cancelStroke(m_strokeData.strokeId()); m_strokeData.clear(); m_changesTracker.reset(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; m_changesTracker.commitConfig(m_currentArgs); } void KisToolTransform::slotTrackerChangedConfig() { slotUiChangedConfig(); updateOptionWidget(); } bool KisToolTransform::clearDevices(KisNodeSP node, bool recursive) { bool haveInvisibleNodes = false; if (!node->isEditable(false)) return haveInvisibleNodes; haveInvisibleNodes = !node->visible(false); if (recursive) { // simple tail-recursive iteration KisNodeSP prevNode = node->lastChild(); while(prevNode) { haveInvisibleNodes |= clearDevices(prevNode, recursive); prevNode = prevNode->prevSibling(); } } image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::ClearSelectionData(node)); /** * It might happen that the editablity state of the node would * change during the stroke, so we need to save the set of * applicable nodes right in the beginning of the processing */ m_strokeData.addClearedNode(node); return haveInvisibleNodes; } void KisToolTransform::transformDevices(KisNodeSP node, bool recursive) { if (!node->isEditable()) return; KIS_ASSERT_RECOVER_RETURN(recursive || (m_strokeData.clearedNodes().size() == 1 && KisNodeSP(m_strokeData.clearedNodes().first()) == node)); foreach (KisNodeSP currentNode, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(currentNode); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, currentNode)); } } QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); if (m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { cancelStroke(); image()->waitForDone(); startStroke(savedMode); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); startStroke(savedArgs.mode()); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/krita/ui/canvas/kis_display_color_converter.cpp b/krita/ui/canvas/kis_display_color_converter.cpp index c29693c00bf..c50ae605f54 100644 --- a/krita/ui/canvas/kis_display_color_converter.cpp +++ b/krita/ui/canvas/kis_display_color_converter.cpp @@ -1,629 +1,629 @@ /* * Copyright (c) 2014 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_display_color_converter.h" #include #include #include #include #include #include #include #include #include #include "kis_config_notifier.h" #include "kis_canvas_resource_provider.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_image.h" #include "kis_node.h" #include "kundo2command.h" #include "kis_config.h" #include "kis_paint_device.h" #include "kis_iterator_ng.h" Q_GLOBAL_STATIC(KisDisplayColorConverter, s_instance) struct KisDisplayColorConverter::Private { Private(KisDisplayColorConverter *_q, KoCanvasResourceManager *_resourceManager) : q(_q), resourceManager(_resourceManager), nodeColorSpace(0), paintingColorSpace(0), monitorColorSpace(0), monitorProfile(0), - renderingIntent(KoColorConversionTransformation::InternalRenderingIntent), - conversionFlags(KoColorConversionTransformation::InternalConversionFlags), + renderingIntent(KoColorConversionTransformation::internalRenderingIntent()), + conversionFlags(KoColorConversionTransformation::internalConversionFlags()), displayFilter(0), intermediateColorSpace(0), displayRenderer(new DisplayRenderer(_q, _resourceManager)) { } KisDisplayColorConverter *const q; KoCanvasResourceManager *resourceManager; const KoColorSpace *nodeColorSpace; const KoColorSpace *paintingColorSpace; const KoColorSpace *monitorColorSpace; const KoColorProfile *monitorProfile; KoColorConversionTransformation::Intent renderingIntent; KoColorConversionTransformation::ConversionFlags conversionFlags; KisDisplayFilter *displayFilter; const KoColorSpace *intermediateColorSpace; KoColor intermediateFgColor; KisNodeSP connectedNode; inline KoColor approximateFromQColor(const QColor &qcolor); inline QColor approximateToQColor(const KoColor &color); void slotCanvasResourceChanged(int key, const QVariant &v); void slotUpdateCurrentNodeColorSpace(); void selectPaintingColorSpace(); void updateIntermediateFgColor(const KoColor &color); void setCurrentNode(KisNodeSP node); bool useOcio() const; bool finalIsRgba(const KoColorSpace *cs) const; template QColor floatArrayToQColor(const float *p); template QImage convertToQImageDirect(KisPaintDeviceSP device); class DisplayRenderer : public KoColorDisplayRendererInterface { public: DisplayRenderer(KisDisplayColorConverter *parent, KoCanvasResourceManager *resourceManager) : m_parent(parent), m_resourceManager(resourceManager) { parent->connect(parent, SIGNAL(displayConfigurationChanged()), this, SIGNAL(displayConfigurationChanged())); } QColor toQColor(const KoColor &c) const { return m_parent->toQColor(c); } KoColor approximateFromRenderedQColor(const QColor &c) const { return m_parent->approximateFromRenderedQColor(c); } KoColor fromHsv(int h, int s, int v, int a) const { return m_parent->fromHsv(h, s, v, a); } void getHsv(const KoColor &srcColor, int *h, int *s, int *v, int *a) const { m_parent->getHsv(srcColor, h, s, v, a); } virtual qreal minVisibleFloatValue(const KoChannelInfo *chaninfo) const { return chaninfo->getUIMin(); } virtual qreal maxVisibleFloatValue(const KoChannelInfo *chaninfo) const { qreal maxValue = chaninfo->getUIMax(); if (m_resourceManager) { qreal exposure = m_resourceManager->resource(KisCanvasResourceProvider::HdrExposure).value(); // not sure if *= is what we want maxValue *= std::pow(2.0, -exposure); } return maxValue; } private: KisDisplayColorConverter *m_parent; QPointer m_resourceManager; }; QScopedPointer displayRenderer; }; KisDisplayColorConverter::KisDisplayColorConverter(KoCanvasResourceManager *resourceManager, QObject *parent) : QObject(parent), m_d(new Private(this, resourceManager)) { connect(m_d->resourceManager, SIGNAL(canvasResourceChanged(int, const QVariant&)), SLOT(slotCanvasResourceChanged(int, const QVariant&))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(selectPaintingColorSpace())); m_d->setCurrentNode(0); setMonitorProfile(0); setDisplayFilter(0); } KisDisplayColorConverter::KisDisplayColorConverter() : m_d(new Private(this, 0)) { setDisplayFilter(0); delete m_d->displayFilter; m_d->paintingColorSpace = KoColorSpaceRegistry::instance()->rgb8(); m_d->setCurrentNode(0); setMonitorProfile(0); } KisDisplayColorConverter::~KisDisplayColorConverter() { } KisDisplayColorConverter* KisDisplayColorConverter::dumbConverterInstance() { return s_instance; } KoColorDisplayRendererInterface* KisDisplayColorConverter::displayRendererInterface() const { return m_d->displayRenderer.data(); } bool KisDisplayColorConverter::Private::useOcio() const { return displayFilter && paintingColorSpace->colorModelId() == RGBAColorModelID; } void KisDisplayColorConverter::Private::updateIntermediateFgColor(const KoColor &srcColor) { KIS_ASSERT_RECOVER_RETURN(displayFilter); KoColor color = srcColor; color.convertTo(intermediateColorSpace); displayFilter->approximateForwardTransformation(color.data(), 1); intermediateFgColor = color; } void KisDisplayColorConverter::Private::slotCanvasResourceChanged(int key, const QVariant &v) { if (key == KisCanvasResourceProvider::CurrentKritaNode) { KisNodeSP currentNode = v.value(); setCurrentNode(currentNode); } else if (useOcio() && key == KoCanvasResourceManager::ForegroundColor) { updateIntermediateFgColor(v.value()); } } void KisDisplayColorConverter::Private::slotUpdateCurrentNodeColorSpace() { setCurrentNode(connectedNode); } inline KisPaintDeviceSP findValidDevice(KisNodeSP node) { return node->paintDevice() ? node->paintDevice() : node->original(); } void KisDisplayColorConverter::Private::setCurrentNode(KisNodeSP node) { if (connectedNode) { KisPaintDeviceSP device = findValidDevice(connectedNode); if (device) { q->disconnect(device, 0); } } if (node) { KisPaintDeviceSP device = findValidDevice(node); nodeColorSpace = device ? device->compositionSourceColorSpace() : node->colorSpace(); KIS_ASSERT_RECOVER_NOOP(nodeColorSpace); if (device) { q->connect(device, SIGNAL(profileChanged(const KoColorProfile*)), SLOT(slotUpdateCurrentNodeColorSpace()), Qt::UniqueConnection); q->connect(device, SIGNAL(colorSpaceChanged(const KoColorSpace*)), SLOT(slotUpdateCurrentNodeColorSpace()), Qt::UniqueConnection); } } else { nodeColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } connectedNode = node; selectPaintingColorSpace(); } void KisDisplayColorConverter::Private::selectPaintingColorSpace() { KisConfig cfg; paintingColorSpace = cfg.customColorSelectorColorSpace(); if (!paintingColorSpace || displayFilter) { paintingColorSpace = nodeColorSpace; } emit q->displayConfigurationChanged(); } const KoColorSpace* KisDisplayColorConverter::paintingColorSpace() const { KIS_ASSERT_RECOVER(m_d->paintingColorSpace) { return KoColorSpaceRegistry::instance()->rgb8(); } return m_d->paintingColorSpace; } void KisDisplayColorConverter::setMonitorProfile(const KoColorProfile *monitorProfile) { m_d->monitorColorSpace = KoColorSpaceRegistry::instance()->rgb8(monitorProfile); m_d->monitorProfile = monitorProfile; m_d->renderingIntent = renderingIntent(); m_d->conversionFlags = conversionFlags(); emit displayConfigurationChanged(); } void KisDisplayColorConverter::setDisplayFilter(KisDisplayFilter *displayFilter) { if (m_d->displayFilter && displayFilter && displayFilter->lockCurrentColorVisualRepresentation()) { KoColor color(m_d->intermediateFgColor); displayFilter->approximateInverseTransformation(color.data(), 1); color.convertTo(m_d->paintingColorSpace); m_d->resourceManager->setForegroundColor(color); } m_d->displayFilter = displayFilter; m_d->intermediateColorSpace = 0; if (m_d->displayFilter) { // choosing default profile, which is scRGB const KoColorProfile *intermediateProfile = 0; m_d->intermediateColorSpace = KoColorSpaceRegistry::instance()-> colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), intermediateProfile); KIS_ASSERT_RECOVER(m_d->intermediateColorSpace) { m_d->intermediateColorSpace = m_d->monitorColorSpace; } m_d->updateIntermediateFgColor( m_d->resourceManager->foregroundColor()); } { // sanity check KisConfig cfg; //KIS_ASSERT_RECOVER_NOOP(cfg.useOcio() == (bool) m_d->displayFilter); } m_d->selectPaintingColorSpace(); } KoColorConversionTransformation::Intent KisDisplayColorConverter::renderingIntent() { KisConfig cfg; return (KoColorConversionTransformation::Intent)cfg.monitorRenderIntent(); } KoColorConversionTransformation::ConversionFlags KisDisplayColorConverter::conversionFlags() { KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::HighQuality; KisConfig cfg; if (cfg.useBlackPointCompensation()) conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; if (!cfg.allowLCMSOptimization()) conversionFlags |= KoColorConversionTransformation::NoOptimization; return conversionFlags; } KisDisplayFilter *KisDisplayColorConverter::displayFilter() const { return m_d->displayFilter; } const KoColorProfile* KisDisplayColorConverter::monitorProfile() const { return m_d->monitorProfile; } bool KisDisplayColorConverter::Private::finalIsRgba(const KoColorSpace *cs) const { /** * In Krita RGB color spaces differ: 8/16bit are BGRA, 16f/32f-bit RGBA */ KoID colorDepthId = cs->colorDepthId(); return colorDepthId == Float16BitsColorDepthID || colorDepthId == Float32BitsColorDepthID; } template QColor KisDisplayColorConverter::Private::floatArrayToQColor(const float *p) { if (flipToBgra) { return QColor(KoColorSpaceMaths::scaleToA(p[0]), KoColorSpaceMaths::scaleToA(p[1]), KoColorSpaceMaths::scaleToA(p[2]), KoColorSpaceMaths::scaleToA(p[3])); } else { return QColor(KoColorSpaceMaths::scaleToA(p[2]), KoColorSpaceMaths::scaleToA(p[1]), KoColorSpaceMaths::scaleToA(p[0]), KoColorSpaceMaths::scaleToA(p[3])); } } QColor KisDisplayColorConverter::toQColor(const KoColor &srcColor) const { KoColor c(srcColor); c.convertTo(m_d->paintingColorSpace); if (!m_d->useOcio()) { // we expect the display profile is rgb8, which is BGRA here KIS_ASSERT_RECOVER(m_d->monitorColorSpace->pixelSize() == 4) { return Qt::red; }; c.convertTo(m_d->monitorColorSpace, m_d->renderingIntent, m_d->conversionFlags); const quint8 *p = c.data(); return QColor(p[2], p[1], p[0], p[3]); } else { const KoColorSpace *srcCS = c.colorSpace(); if (m_d->displayFilter->useInternalColorManagement()) { srcCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), m_d->monitorProfile); c.convertTo(srcCS, m_d->renderingIntent, m_d->conversionFlags); } int numChannels = srcCS->channelCount(); QVector normalizedChannels(numChannels); srcCS->normalisedChannelsValue(c.data(), normalizedChannels); m_d->displayFilter->filter((quint8*)normalizedChannels.data(), 1); const float *p = (const float *)normalizedChannels.constData(); return m_d->finalIsRgba(srcCS) ? m_d->floatArrayToQColor(p) : m_d->floatArrayToQColor(p); } } KoColor KisDisplayColorConverter::approximateFromRenderedQColor(const QColor &c) const { return m_d->approximateFromQColor(c); } template QImage KisDisplayColorConverter::Private::convertToQImageDirect(KisPaintDeviceSP device) { QRect bounds = device->exactBounds(); if (bounds.isEmpty()) return QImage(); QImage image(bounds.size(), QImage::Format_ARGB32); KisSequentialConstIterator it(device, bounds); quint8 *dstPtr = image.bits(); const KoColorSpace *cs = device->colorSpace(); int numChannels = cs->channelCount(); QVector normalizedChannels(numChannels); do { cs->normalisedChannelsValue(it.rawDataConst(), normalizedChannels); displayFilter->filter((quint8*)normalizedChannels.data(), 1); const float *p = normalizedChannels.constData(); if (flipToBgra) { dstPtr[0] = KoColorSpaceMaths::scaleToA(p[2]); dstPtr[1] = KoColorSpaceMaths::scaleToA(p[1]); dstPtr[2] = KoColorSpaceMaths::scaleToA(p[0]); dstPtr[3] = KoColorSpaceMaths::scaleToA(p[3]); } else { dstPtr[0] = KoColorSpaceMaths::scaleToA(p[0]); dstPtr[1] = KoColorSpaceMaths::scaleToA(p[1]); dstPtr[2] = KoColorSpaceMaths::scaleToA(p[2]); dstPtr[3] = KoColorSpaceMaths::scaleToA(p[3]); } dstPtr += 4; } while (it.nextPixel()); return image; } QImage KisDisplayColorConverter::toQImage(KisPaintDeviceSP srcDevice) const { KisPaintDeviceSP device = srcDevice; if (!(*device->colorSpace() == *m_d->paintingColorSpace)) { device = new KisPaintDevice(*srcDevice); KUndo2Command *cmd = device->convertTo(m_d->paintingColorSpace); delete cmd; } if (!m_d->useOcio()) { return device->convertToQImage(m_d->monitorProfile, m_d->renderingIntent, m_d->conversionFlags); } else { if (m_d->displayFilter->useInternalColorManagement()) { if (device == srcDevice) { device = new KisPaintDevice(*srcDevice); } const KoColorSpace *srcCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), m_d->monitorProfile); KUndo2Command *cmd = device->convertTo(srcCS, m_d->renderingIntent, m_d->conversionFlags); delete cmd; } return m_d->finalIsRgba(device->colorSpace()) ? m_d->convertToQImageDirect(device) : m_d->convertToQImageDirect(device); } return QImage(); } KoColor KisDisplayColorConverter::Private::approximateFromQColor(const QColor &qcolor) { if (!useOcio()) { return KoColor(qcolor, paintingColorSpace); } else { KoColor color(qcolor, intermediateColorSpace); displayFilter->approximateInverseTransformation(color.data(), 1); color.convertTo(paintingColorSpace); return color; } qFatal("Must not be reachable"); return KoColor(); } QColor KisDisplayColorConverter::Private::approximateToQColor(const KoColor &srcColor) { KoColor color(srcColor); if (useOcio()) { color.convertTo(intermediateColorSpace); displayFilter->approximateForwardTransformation(color.data(), 1); } return color.toQColor(); } KoColor KisDisplayColorConverter::fromHsv(int h, int s, int v, int a) const { // generate HSV from sRGB! QColor qcolor(QColor::fromHsv(h, s, v, a)); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsv(const KoColor &srcColor, int *h, int *s, int *v, int *a) const { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); color.getHsv(h, s, v, a); } KoColor KisDisplayColorConverter::fromHsvF(qreal h, qreal s, qreal v, qreal a) { // generate HSV from sRGB! QColor qcolor(QColor::fromHsvF(h, s, v, a)); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsvF(const KoColor &srcColor, qreal *h, qreal *s, qreal *v, qreal *a) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); color.getHsvF(h, s, v, a); } KoColor KisDisplayColorConverter::fromHslF(qreal h, qreal s, qreal l, qreal a) { // generate HSL from sRGB! QColor qcolor(QColor::fromHslF(h, s, l, a)); if (!qcolor.isValid()) { warnKrita << "Could not construct valid color from h" << h << "s" << s << "l" << l << "a" << a; qcolor = Qt::black; } return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHslF(const KoColor &srcColor, qreal *h, qreal *s, qreal *l, qreal *a) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); color.getHslF(h, s, l, a); } KoColor KisDisplayColorConverter::fromHsiF(qreal h, qreal s, qreal i) { // generate HSI from sRGB! qreal r=0.0; qreal g=0.0; qreal b=0.0; qreal a=1.0; HSIToRGB(h, s, i, &r, &g, &b); QColor qcolor; qcolor.setRgbF(r, g, b, a); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsiF(const KoColor &srcColor, qreal *h, qreal *s, qreal *i) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); qreal r=color.redF(); qreal g=color.greenF(); qreal b=color.blueF(); RGBToHSI(r, g, b, h, s, i); } KoColor KisDisplayColorConverter::fromHsyF(qreal h, qreal s, qreal y, qreal R, qreal G, qreal B) { // generate HSL from sRGB! qreal r=0.0; qreal g=0.0; qreal b=0.0; qreal a=1.0; HSYToRGB(h, s, y, &r, &g, &b, R, G, B); QColor qcolor; qcolor.setRgbF(r, g, b, a); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsyF(const KoColor &srcColor, qreal *h, qreal *s, qreal *y, qreal R, qreal G, qreal B) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); qreal r=color.redF(); qreal g=color.greenF(); qreal b=color.blueF(); RGBToHSY(r, g, b, h, s, y, R, G, B); } #include "moc_kis_display_color_converter.cpp" diff --git a/krita/ui/canvas/kis_image_pyramid.cpp b/krita/ui/canvas/kis_image_pyramid.cpp index 403bc90ad0b..291036f4cb7 100644 --- a/krita/ui/canvas/kis_image_pyramid.cpp +++ b/krita/ui/canvas/kis_image_pyramid.cpp @@ -1,537 +1,537 @@ /* * 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 @value to the lowest integer not smaller than @value and * that is a divident of alignment */ inline void alignByPow2Hi(qint32 &value, qint32 alignment) { qint32 mask = alignment - 1; value |= mask; value++; } /** * Aligns @value to the lowest integer not smaller than @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 @value to the highest integer not exceeding @value and * that is a divident of @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(KisDisplayFilter* 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; 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); + 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); + 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; 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); } 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); } } } } 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; m_useOcio = cfg.useOcio(); } diff --git a/krita/ui/kis_clipboard.cc b/krita/ui/kis_clipboard.cc index 30fc5dac118..83809f36181 100644 --- a/krita/ui/kis_clipboard.cc +++ b/krita/ui/kis_clipboard.cc @@ -1,383 +1,383 @@ /* * Copyright (c) 2004 Boudewijn Rempt * * 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_clipboard.h" #include #include #include #include #include #include #include #include #include #include #include "KoColorSpace.h" #include "KoStore.h" #include #include // kritaimage #include #include #include #include // local #include "kis_config.h" #include "kis_store_paintdevice_writer.h" Q_GLOBAL_STATIC(KisClipboard, s_instance) KisClipboard::KisClipboard() { m_pushedClipboard = false; m_hasClip = false; // Check that we don't already have a clip ready clipboardDataChanged(); // Make sure we are notified when clipboard changes connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged())); } KisClipboard::~KisClipboard() { dbgRegistry << "deleting KisClipBoard"; } KisClipboard* KisClipboard::instance() { return s_instance; } void KisClipboard::setClip(KisPaintDeviceSP dev, const QPoint& topLeft) { if (!dev) return; m_hasClip = true; // We'll create a store (ZIP format) in memory QBuffer buffer; QByteArray mimeType("application/x-krita-selection"); KoStore* store = KoStore::createStore(&buffer, KoStore::Write, mimeType); KisStorePaintDeviceWriter writer(store); Q_ASSERT(store); Q_ASSERT(!store->bad()); // Layer data if (store->open("layerdata")) { if (!dev->write(writer)) { dev->disconnect(); store->close(); delete store; return; } store->close(); } // Coordinates if (store->open("topLeft")) { store->write(QString("%1 %2").arg(topLeft.x()).arg(topLeft.y()).toLatin1()); store->close(); } // ColorSpace id of layer data if (store->open("colormodel")) { QString csName = dev->colorSpace()->colorModelId().id(); store->write(csName.toLatin1()); store->close(); } if (store->open("colordepth")) { QString csName = dev->colorSpace()->colorDepthId().id(); store->write(csName.toLatin1()); store->close(); } if (dev->colorSpace()->profile()) { const KoColorProfile *profile = dev->colorSpace()->profile(); KisAnnotationSP annotation; if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) { annotation = new KisAnnotation("icc", profile->name(), profile->rawData()); if (annotation) { // save layer profile if (store->open("profile.icc")) { store->write(annotation->annotation()); store->close(); } } } } delete store; QMimeData *mimeData = new QMimeData; Q_CHECK_PTR(mimeData); if (mimeData) { mimeData->setData(mimeType, buffer.buffer()); } // We also create a QImage so we can interchange with other applications QImage qimage; KisConfig cfg; const KoColorProfile *monitorProfile = cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow())); - qimage = dev->convertToQImage(monitorProfile, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + qimage = dev->convertToQImage(monitorProfile, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); if (!qimage.isNull() && mimeData) { mimeData->setImageData(qimage); } if (mimeData) { m_pushedClipboard = true; QClipboard *cb = QApplication::clipboard(); cb->setMimeData(mimeData); } } KisPaintDeviceSP KisClipboard::clip(const QRect &imageBounds, bool showPopup) { QByteArray mimeType("application/x-krita-selection"); QClipboard *cb = QApplication::clipboard(); const QMimeData *cbData = cb->mimeData(); KisPaintDeviceSP clip; if (cbData && cbData->hasFormat(mimeType)) { QByteArray encodedData = cbData->data(mimeType); QBuffer buffer(&encodedData); KoStore* store = KoStore::createStore(&buffer, KoStore::Read, mimeType); const KoColorProfile *profile = 0; QString csDepth, csModel; // ColorSpace id of layer data if (store->hasFile("colormodel")) { store->open("colormodel"); csModel = QString(store->read(store->size())); store->close(); } if (store->hasFile("colordepth")) { store->open("colordepth"); csDepth = QString(store->read(store->size())); store->close(); } if (store->hasFile("profile.icc")) { QByteArray data; store->open("profile.icc"); data = store->read(store->size()); store->close(); profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data); } const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile); if (cs) { clip = new KisPaintDevice(cs); if (store->hasFile("layerdata")) { store->open("layerdata"); if (!clip->read(store->device())) { clip = 0; } store->close(); } if (clip && !imageBounds.isEmpty()) { // load topLeft if (store->hasFile("topLeft")) { store->open("topLeft"); QString str = store->read(store->size()); store->close(); QStringList list = str.split(' '); if (list.size() == 2) { QPoint topLeft(list[0].toInt(), list[1].toInt()); clip->setX(topLeft.x()); clip->setY(topLeft.y()); } } QRect clipBounds = clip->exactBounds(); if (!imageBounds.contains(clipBounds) && !imageBounds.intersects(clipBounds)) { QPoint diff = imageBounds.center() - clipBounds.center(); clip->setX(clip->x() + diff.x()); clip->setY(clip->y() + diff.y()); } } } delete store; } if (!clip) { QImage qimage = cb->image(); if (qimage.isNull()) return KisPaintDeviceSP(0); KisConfig cfg; quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK && showPopup) { // Ask user each time. QMessageBox mb(qApp->activeWindow()); mb.setWindowTitle(i18nc("@title:window", "Pasting data from simple source")); mb.setText(i18n("The image data you are trying to paste has no color profile information.\n\nOn the web and in simple applications the data are supposed to be in sRGB color format.\nImporting as web will show it as it is supposed to look.\nMost monitors are not perfect though so if you made the image yourself\nyou might want to import it as it looked on you monitor.\n\nHow do you want to interpret these data?")); mb.addButton(i18n("As &Web"), QMessageBox::AcceptRole); mb.addButton(i18n("As on &Monitor"), QMessageBox::AcceptRole); mb.addButton(i18n("Cancel"), QMessageBox::RejectRole); behaviour = mb.exec(); if (behaviour > 1) { return 0; } } const KoColorSpace * cs; const KoColorProfile *profile = 0; if (behaviour == PASTE_ASSUME_MONITOR) profile = cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow())); cs = KoColorSpaceRegistry::instance()->rgb8(profile); if (!cs) { cs = KoColorSpaceRegistry::instance()->rgb8(); profile = cs->profile(); } clip = new KisPaintDevice(cs); Q_CHECK_PTR(clip); clip->convertFromQImage(qimage, profile); QRect clipBounds = clip->exactBounds(); QPoint diff = imageBounds.center() - clipBounds.center(); clip->setX(diff.x()); clip->setY(diff.y()); } return clip; } void KisClipboard::clipboardDataChanged() { if (!m_pushedClipboard) { m_hasClip = false; QClipboard *cb = QApplication::clipboard(); if (cb->mimeData()->hasImage()) { QImage qimage = cb->image(); const QMimeData *cbData = cb->mimeData(); QByteArray mimeType("application/x-krita-selection"); if (cbData && cbData->hasFormat(mimeType)) m_hasClip = true; if (!qimage.isNull()) m_hasClip = true; } } if (m_hasClip) { emit clipCreated(); } m_pushedClipboard = false; emit clipChanged(); } bool KisClipboard::hasClip() const { return m_hasClip; } QSize KisClipboard::clipSize() const { QClipboard *cb = QApplication::clipboard(); QByteArray mimeType("application/x-krita-selection"); const QMimeData *cbData = cb->mimeData(); KisPaintDeviceSP clip; if (cbData && cbData->hasFormat(mimeType)) { QByteArray encodedData = cbData->data(mimeType); QBuffer buffer(&encodedData); KoStore* store = KoStore::createStore(&buffer, KoStore::Read, mimeType); const KoColorProfile *profile = 0; QString csDepth, csModel; // ColorSpace id of layer data if (store->hasFile("colormodel")) { store->open("colormodel"); csModel = QString(store->read(store->size())); store->close(); } if (store->hasFile("colordepth")) { store->open("colordepth"); csDepth = QString(store->read(store->size())); store->close(); } if (store->hasFile("profile.icc")) { QByteArray data; store->open("profile.icc"); data = store->read(store->size()); store->close(); profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data); } const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile); clip = new KisPaintDevice(cs); if (store->hasFile("layerdata")) { store->open("layerdata"); clip->read(store->device()); store->close(); } delete store; return clip->exactBounds().size(); } else { if (cb->mimeData()->hasImage()) { QImage qimage = cb->image(); return qimage.size(); } } return QSize(); } diff --git a/krita/ui/kis_custom_pattern.cc b/krita/ui/kis_custom_pattern.cc index 6eface74a3b..85540afb57c 100644 --- a/krita/ui/kis_custom_pattern.cc +++ b/krita/ui/kis_custom_pattern.cc @@ -1,175 +1,175 @@ /* * Copyright (c) 2006 Bart Coppens * * 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_custom_pattern.h" #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include "KisViewManager.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include #include "kis_resource_server_provider.h" #include "kis_paint_layer.h" KisCustomPattern::KisCustomPattern(QWidget *parent, const char* name, const QString& caption, KisViewManager* view) : KisWdgCustomPattern(parent, name), m_view(view) { Q_ASSERT(m_view); setWindowTitle(caption); m_pattern = 0; preview->setScaledContents(true); KoResourceServer* rServer = KoResourceServerProvider::instance()->patternServer(false); m_rServerAdapter = QSharedPointer(new KoResourceServerAdapter(rServer)); connect(addButton, SIGNAL(pressed()), this, SLOT(slotAddPredefined())); connect(patternButton, SIGNAL(pressed()), this, SLOT(slotUsePattern())); connect(updateButton, SIGNAL(pressed()), this, SLOT(slotUpdateCurrentPattern())); connect(cmbSource, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCurrentPattern())); } KisCustomPattern::~KisCustomPattern() { delete m_pattern; } void KisCustomPattern::slotUpdateCurrentPattern() { delete m_pattern; m_pattern = 0; if (m_view && m_view->image()) { createPattern(); if (m_pattern) { const qint32 maxSize = 150; if ((m_pattern->width() > maxSize) || (m_pattern->height() > maxSize)) { float aspectRatio = (float)m_pattern->width() / m_pattern->height(); qint32 scaledWidth, scaledHeight; if (m_pattern->width() > m_pattern->height()) { scaledWidth = maxSize; scaledHeight = maxSize / aspectRatio; } else { scaledWidth = maxSize * aspectRatio; scaledHeight = maxSize; } if (scaledWidth == 0) scaledWidth++; if (scaledHeight == 0) scaledHeight++; QPixmap scaledPixmap = QPixmap::fromImage(m_pattern->pattern()); preview->setPixmap(scaledPixmap.scaled(scaledWidth, scaledHeight)); } else { preview->setPixmap(QPixmap::fromImage(m_pattern->pattern())); } } } } void KisCustomPattern::slotAddPredefined() { if (!m_pattern) return; // Save in the directory that is likely to be: ~/.kde/share/apps/krita/patterns // a unique file with this pattern name QString dir = KoResourceServerProvider::instance()->patternServer()->saveLocation(); QString extension; QString tempFileName; { QTemporaryFile file(dir + QLatin1String("/krita_XXXXXX") + QLatin1String(".pat") ); file.setAutoRemove(false); file.open(); tempFileName = file.fileName(); } // Save it to that file m_pattern->setFilename(tempFileName); // Add it to the pattern server, so that it automatically gets to the mediators, and // so to the other pattern choosers can pick it up, if they want to m_rServerAdapter->addResource(m_pattern->clone()); } void KisCustomPattern::slotUsePattern() { if (!m_pattern) return; KoPattern* copy = m_pattern->clone(); Q_CHECK_PTR(copy); emit(activatedResource(copy)); } void KisCustomPattern::createPattern() { if (!m_view) return; KisPaintDeviceSP dev; QString name; KisImageWSP image = m_view->image(); if (!image) return; QRect rc = image->bounds(); if (cmbSource->currentIndex() == 0) { dev = m_view->activeNode()->projection(); name = m_view->activeNode()->name(); QRect rc2 = dev->exactBounds(); rc = rc.intersected(rc2); } else { image->lock(); dev = image->projection(); image->unlock(); name = image->objectName(); } if (!dev) return; // warn when creating large patterns QSize size = rc.size(); if (size.width() > 1000 || size.height() > 1000) { lblWarning->setText(i18n("The current image is too big to create a pattern. " "The pattern will be scaled down.")); size.scale(1000, 1000, Qt::KeepAspectRatio); } QString dir = KoResourceServerProvider::instance()->patternServer()->saveLocation(); m_pattern = new KoPattern(dev->createThumbnail(size.width(), size.height(), rc, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags), name, dir); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()), name, dir); } diff --git a/krita/ui/kis_mimedata.cpp b/krita/ui/kis_mimedata.cpp index 08b034da800..c316d2018e3 100644 --- a/krita/ui/kis_mimedata.cpp +++ b/krita/ui/kis_mimedata.cpp @@ -1,312 +1,312 @@ /* * Copyright (c) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_mimedata.h" #include "kis_config.h" #include "kis_node.h" #include "kis_paint_device.h" #include "kis_shared_ptr.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_shape_layer.h" #include "kis_paint_layer.h" #include "KisDocument.h" #include "kis_shape_controller.h" #include "KisPart.h" #include #include #include #include #include #include #include #include #include #include #include #include KisMimeData::KisMimeData(QList nodes) : QMimeData() , m_nodes(nodes) { Q_ASSERT(m_nodes.size() > 0); } QList KisMimeData::nodes() const { return m_nodes; } QStringList KisMimeData::formats () const { QStringList f = QMimeData::formats(); if (m_nodes.size() > 0) { f << "application/x-krita-node" << "application/x-krita-node-url" << "application/x-qt-image" << "application/zip" << "application/x-krita-node-internal-pointer"; } return f; } KisDocument *createDocument(QList nodes) { KisDocument *doc = KisPart::instance()->createDocument(); QRect rc; foreach(KisNodeSP node, nodes) { rc |= node->exactBounds(); } KisImageSP image = new KisImage(0, rc.width(), rc.height(), nodes.first()->colorSpace(), nodes.first()->name(), false); foreach(KisNodeSP node, nodes) { image->addNode(node->clone()); } doc->setCurrentImage(image); return doc; } QByteArray serializeToByteArray(QList nodes) { QByteArray byteArray; QBuffer buffer(&byteArray); KoStore *store = KoStore::createStore(&buffer, KoStore::Write); Q_ASSERT(!store->bad()); KisDocument *doc = createDocument(nodes); doc->saveNativeFormatCalligra(store); delete doc; return byteArray; } QVariant KisMimeData::retrieveData(const QString &mimetype, QVariant::Type preferredType) const { Q_ASSERT(m_nodes.size() > 0); if (mimetype == "application/x-qt-image") { KisConfig cfg; KisDocument *doc = createDocument(m_nodes); doc->image()->refreshGraph(); doc->image()->waitForDone(); return doc->image()->projection()->convertToQImage(cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow())), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } else if (mimetype == "application/x-krita-node" || mimetype == "application/zip") { QByteArray ba = serializeToByteArray(m_nodes); return ba; } else if (mimetype == "application/x-krita-node-url") { QByteArray ba = serializeToByteArray(m_nodes); QString temporaryPath = QDir::tempPath() + QDir::separator() + QString("krita_tmp_dnd_layer_%1_%2.kra") .arg(QApplication::applicationPid()) .arg(qrand()); QFile file(temporaryPath); file.open(QFile::WriteOnly); file.write(ba); file.flush(); file.close(); return QUrl(temporaryPath).toEncoded(); } else if (mimetype == "application/x-krita-node-internal-pointer") { QDomDocument doc("krita_internal_node_pointer"); QDomElement root = doc.createElement("pointer"); root.setAttribute("application_pid", (qint64)QApplication::applicationPid()); doc.appendChild(root); foreach(KisNodeSP node, m_nodes) { QDomElement element = doc.createElement("node"); element.setAttribute("pointer_value", (qint64)node.data()); root.appendChild(element); } return doc.toByteArray(); } else { return QMimeData::retrieveData(mimetype, preferredType); } } void KisMimeData::initializeExternalNode(KisNodeSP &node, KisImageWSP image, KisShapeController *shapeController) { // layers store a link to the image, so update it KisLayer *layer = dynamic_cast(node.data()); if (layer) { layer->setImage(image); } KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { KisShapeLayer *shapeLayer2 = new KisShapeLayer(shapeController, image, node->name(), node->opacity()); QList shapes = shapeLayer->shapes(); shapeLayer->removeAllShapes(); foreach(KoShape *shape, shapes) { shapeLayer2->addShape(shape); } node = shapeLayer2; } } QList KisMimeData::tryLoadInternalNodes(const QMimeData *data, KisImageWSP image, KisShapeController *shapeController, bool /* IN-OUT */ ©Node) { QList nodes; // Qt 4.7 way const KisMimeData *mimedata = qobject_cast(data); if (mimedata) { nodes = mimedata->nodes(); } // Qt 4.8 way if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-internal-pointer")) { QByteArray nodeXml = data->data("application/x-krita-node-internal-pointer"); QDomDocument doc; doc.setContent(nodeXml); QDomElement element = doc.documentElement(); qint64 pid = element.attribute("application_pid").toLongLong(); if (pid == QApplication::applicationPid()) { QDomNode n = element.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { qint64 pointerValue = e.attribute("pointer_value").toLongLong(); if (pointerValue) { nodes << reinterpret_cast(pointerValue); } } n = n.nextSibling(); } } } if (!nodes.isEmpty() && (copyNode || nodes.first()->graphListener() != image.data())) { QList clones; copyNode = true; foreach(KisNodeSP node, nodes) { node = node->clone(); initializeExternalNode(node, image, shapeController); clones << node; } nodes = clones; } return nodes; } QList KisMimeData::loadNodes(const QMimeData *data, const QRect &imageBounds, const QPoint &preferredCenter, bool forceRecenter, KisImageWSP image, KisShapeController *shapeController) { bool alwaysRecenter = false; QList nodes; if (data->hasFormat("application/x-krita-node")) { QByteArray ba = data->data("application/x-krita-node"); KisDocument *tempDoc = KisPart::instance()->createDocument(); bool result = tempDoc->loadNativeFormatFromByteArray(ba); if (result) { KisImageWSP tempImage = tempDoc->image(); foreach(KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) { nodes << node; tempImage->removeNode(node); initializeExternalNode(node, image, shapeController); } } delete tempDoc; } if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-url")) { QByteArray ba = data->data("application/x-krita-node-url"); QString localFile = QUrl::fromEncoded(ba).toLocalFile(); KisDocument *tempDoc = KisPart::instance()->createDocument(); bool result = tempDoc->loadNativeFormat(localFile); if (result) { KisImageWSP tempImage = tempDoc->image(); foreach(KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) { nodes << node; tempImage->removeNode(node); initializeExternalNode(node, image, shapeController); } } delete tempDoc; QFile::remove(localFile); } if (nodes.isEmpty() && data->hasImage()) { QImage qimage = qvariant_cast(data->imageData()); KisPaintDeviceSP device = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); device->convertFromQImage(qimage, 0); nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device); alwaysRecenter = true; } if (!nodes.isEmpty()) { foreach(KisNodeSP node, nodes) { QRect bounds = node->projection()->exactBounds(); if (alwaysRecenter || forceRecenter || (!imageBounds.contains(bounds) && !imageBounds.intersects(bounds))) { QPoint pt = preferredCenter - bounds.center(); node->setX(pt.x()); node->setY(pt.y()); } } } return nodes; } diff --git a/krita/ui/kis_png_converter.cpp b/krita/ui/kis_png_converter.cpp index a60c8b0b950..affa07f3645 100644 --- a/krita/ui/kis_png_converter.cpp +++ b/krita/ui/kis_png_converter.cpp @@ -1,1236 +1,1236 @@ /* * Copyright (c) 2005-2007 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 "kis_png_converter.h" // A big thank to Glenn Randers-Pehrson for his wonderful // documentation of libpng available at // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html #ifndef PNG_MAX_UINT // Removed in libpng 1.4 #define PNG_MAX_UINT PNG_UINT_31_MAX #endif #include // WORDS_BIGENDIAN #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 "dialogs/kis_dlg_png_import.h" #include "kis_clipboard.h" namespace { int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha) { QString id = cs->id(); if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") { return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY; } if (id == "RGBA" || id == "RGBA16") { return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; } return -1; } bool colorSpaceIdSupported(const QString &id) { return id == "RGBA" || id == "RGBA16" || id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16"; } QPair getColorSpaceForColorType(int color_type, int color_nb_bits) { QPair r; if (color_type == PNG_COLOR_TYPE_PALETTE) { r.first = RGBAColorModelID.id(); r.second = Integer8BitsColorDepthID.id(); } else { if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { r.first = GrayAColorModelID.id(); } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) { r.first = RGBAColorModelID.id(); } if (color_nb_bits == 16) { r.second = Integer16BitsColorDepthID.id(); } else if (color_nb_bits <= 8) { r.second = Integer8BitsColorDepthID.id(); } } return r; } void fillText(png_text* p_text, const char* key, QString& text) { p_text->compression = PNG_TEXT_COMPRESSION_zTXt; p_text->key = const_cast(key); char* textc = new char[text.length()+1]; strcpy(textc, text.toLatin1()); p_text->text = textc; p_text->text_length = text.length() + 1; } long formatStringList(char *string, const size_t length, const char *format, va_list operands) { int n = vsnprintf(string, length, format, operands); if (n < 0) string[length-1] = '\0'; return((long) n); } long formatString(char *string, const size_t length, const char *format, ...) { long n; va_list operands; va_start(operands, format); n = (long) formatStringList(string, length, format, operands); va_end(operands); return(n); } void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data) { png_textp text; png_uint_32 allocated_length, description_length; const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl; text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); description_length = profile_type.length(); allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length); text[0].text = (png_charp) png_malloc(ping, allocated_length); QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1(); QByteArray keyData = key.toLatin1(); text[0].key = keyData.data(); uchar* sp = (uchar*)profile_data.data(); png_charp dp = text[0].text; *dp++ = '\n'; memcpy(dp, profile_type.toLatin1().constData(), profile_type.length()); dp += description_length; *dp++ = '\n'; formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length()); dp += 8; for (long i = 0; i < (long) profile_data.length(); i++) { if (i % 36 == 0) *dp++ = '\n'; *(dp++) = (char) hex[((*sp >> 4) & 0x0f)]; *(dp++) = (char) hex[((*sp++) & 0x0f)]; } *dp++ = '\n'; *dp = '\0'; text[0].text_length = (png_size_t)(dp - text[0].text); text[0].compression = -1; if (text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); png_free(ping, text[0].text); png_free(ping, text); } QByteArray png_read_raw_profile(png_textp text) { QByteArray profile; static const unsigned char unhex[103] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; png_charp sp = text[0].text + 1; /* look for newline */ while (*sp != '\n') sp++; /* look for length */ while (*sp == '\0' || *sp == ' ' || *sp == '\n') sp++; png_uint_32 length = (png_uint_32) atol(sp); while (*sp != ' ' && *sp != '\n') sp++; if (length == 0) { return profile; } profile.resize(length); /* copy profile, skipping white space and column 1 "=" signs */ unsigned char *dp = (unsigned char*)profile.data(); png_uint_32 nibbles = length * 2; for (png_uint_32 i = 0; i < nibbles; i++) { while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { if (*sp == '\0') { return QByteArray(); } sp++; } if (i % 2 == 0) *dp = (unsigned char)(16 * unhex[(int) *sp++]); else (*dp++) += unhex[(int) *sp++]; } return profile; } void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize) { dbgFile << "Decoding " << type << " " << text[0].key; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type); Q_ASSERT(exifIO); QByteArray rawProfile = png_read_raw_profile(text); if (headerSize > 0) { rawProfile.remove(0, headerSize); } if (rawProfile.size() > 0) { QBuffer buffer; buffer.setData(rawProfile); exifIO->loadFrom(store, &buffer); } else { dbgFile << "Decoding failed"; } } } KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode) { // Q_ASSERT(doc); // Q_ASSERT(adapter); m_doc = doc; m_stop = false; m_max_row = 0; m_image = 0; m_batchMode = batchMode; } KisPNGConverter::~KisPNGConverter() { } class KisPNGReadStream { public: KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { } int nextValue() { if (m_posinc == 0) { m_posinc = 8; m_buf++; } m_posinc -= m_depth; return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1)); } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGWriteStream { public: KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { *m_buf = 0; } void setNextValue(int v) { if (m_posinc == 0) { m_posinc = 8; m_buf++; *m_buf = 0; } m_posinc -= m_depth; *m_buf = (v << m_posinc) | *m_buf; } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGReaderAbstract { public: KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {} virtual ~KisPNGReaderAbstract() {} virtual png_bytep readLine() = 0; protected: png_structp png_ptr; int width, height; }; class KisPNGReaderLineByLine : public KisPNGReaderAbstract { public: KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) { png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); row_pointer = new png_byte[rowbytes]; } virtual ~KisPNGReaderLineByLine() { delete[] row_pointer; } virtual png_bytep readLine() { png_read_row(png_ptr, row_pointer, NULL); return row_pointer; } private: png_bytep row_pointer; }; class KisPNGReaderFullImage : public KisPNGReaderAbstract { public: KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) { row_pointers = new png_bytep[height]; png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); for (int i = 0; i < height; i++) { row_pointers[i] = new png_byte[rowbytes]; } png_read_image(png_ptr, row_pointers); } virtual ~KisPNGReaderFullImage() { for (int i = 0; i < height; i++) { delete[] row_pointers[i]; } delete[] row_pointers; } virtual png_bytep readLine() { return row_pointers[y++]; } private: png_bytepp row_pointers; int y; }; static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr); while (length) { int nr = in->read((char*)data, length); if (nr <= 0) { png_error(png_ptr, "Read Error"); return; } length -= nr; } } static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr); uint nr = out->write((char*)data, length); if (nr != length) { png_error(png_ptr, "Write Error"); return; } } static void _flush_fn(png_structp png_ptr) { Q_UNUSED(png_ptr); } KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod) { dbgFile << "Start decoding PNG File"; if (!iod->open(QIODevice::ReadOnly)) { dbgFile << "Failed to open PNG File"; return (KisImageBuilder_RESULT_FAILURE); } png_byte signature[8]; iod->peek((char*)signature, 8); #if PNG_LIBPNG_VER < 10400 if (!png_check_sig(signature, 8)) { #else if (png_sig_cmp(signature, 0, 8) != 0) { #endif iod->close(); return (KisImageBuilder_RESULT_BAD_FETCH); } // Initialize the internal structures png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { iod->close(); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Catch errors if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the special png_set_read_fn(png_ptr, iod, _read_fn); #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif // read all PNG info up to image data png_read_info(png_ptr, info_ptr); if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_expand(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_packing(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE && (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) { png_set_expand(png_ptr); } png_read_update_info(png_ptr, info_ptr); // Read information about the png png_uint_32 width, height; int color_nb_bits, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, NULL, NULL); dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl; // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Determine the colorspace QPair csName = getColorSpaceForColorType(color_type, color_nb_bits); if (csName.first.isEmpty()) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA); // Read image profile png_charp profile_name; #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_bytep profile_data; #else png_charp profile_data; #endif int compression_type; png_uint_32 proflen; const KoColorProfile* profile = 0; if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { QByteArray profile_rawdata; // XXX: Hardcoded for icc type -- is that correct for us? profile_rawdata.resize(proflen); memcpy(profile_rawdata.data(), profile_data, proflen); profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata); Q_CHECK_PTR(profile); if (profile) { // dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } else { dbgFile << "no embedded profile, will use the default profile"; if (color_nb_bits == 16 && !qAppName().toLower().contains("test") && !m_batchMode) { KisConfig cfg; quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK) { KisDlgPngImport dlg(m_path, csName.first, csName.second); QApplication::restoreOverrideCursor(); dlg.exec(); if (!dlg.profile().isEmpty()) { QString s = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s); if (csf) { QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); foreach(const KoColorProfile *p, profileList) { if (p->name() == dlg.profile()) { profile = p; break; } } } } QApplication::setOverrideCursor(Qt::WaitCursor); } } dbgFile << "no embedded profile, will use the default profile"; } // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory( KoColorSpaceRegistry::instance()->colorSpaceId( csName.first, csName.second))->profileIsCompatible(profile)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile: " << profile->name() << "\n"; cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile); } else { if (csName.first == RGBAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc"); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0); } } if (cs == 0) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } //TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { - transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Creating the KisImageWSP if (m_image == 0) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); Q_CHECK_PTR(m_image); } // Read resolution int unit_type; png_uint_32 x_resolution, y_resolution; png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type); if (unit_type == PNG_RESOLUTION_METER) { m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points } double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1); KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX); // Read comments/texts... png_text* text_ptr; int num_comments; png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); if (m_doc) { KoDocumentInfo * info = m_doc->documentInfo(); dbgFile << "There are " << num_comments << " comments in the text"; for (int i = 0; i < num_comments; i++) { QString key = text_ptr[i].key; dbgFile << "key is |" << text_ptr[i].key << "| containing " << text_ptr[i].text << " " << (key == "Raw profile type exif "); if (key == "title") { info->setAboutInfo("title", text_ptr[i].text); } else if (key == "abstract") { info->setAboutInfo("description", text_ptr[i].text); } else if (key == "author") { info->setAuthorInfo("creator", text_ptr[i].text); } else if (key.contains("Raw profile type exif")) { decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6); } else if (key.contains("Raw profile type iptc")) { decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14); } else if (key.contains("Raw profile type xmp")) { decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0); } else if (key == "version") { m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text))); } else if (key == "preset") { m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text))); } } } // Read image data KisPNGReaderAbstract* reader = 0; try { if (interlace_type == PNG_INTERLACE_ADAM7) { reader = new KisPNGReaderFullImage(png_ptr, info_ptr, width, height); } else { reader = new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height); } } catch (std::bad_alloc& e) { // new png_byte[] may raise such an exception if the image // is invalid / to large. dbgFile << "bad alloc: " << e.what(); // Free only the already allocated png_byte instances. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return (KisImageBuilder_RESULT_FAILURE); } // Read the palette if the file is indexed png_colorp palette ; int num_palette; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); } // Read the transparency palette quint8 palette_alpha[256]; memset(palette_alpha, 255, 256); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { if (color_type == PNG_COLOR_TYPE_PALETTE) { png_bytep alpha_ptr; int num_alpha; png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, NULL); for (int i = 0; i < num_alpha; ++i) { palette_alpha[i] = alpha_ptr[i]; } } } for (png_uint_32 y = 0; y < height; y++) { KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width); png_bytep row_pointer = reader->readLine(); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[0] = *(src++); if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1); if (hasalpha) { d[1] = *(src++); } else { d[1] = quint16_MAX; } } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[0] = (quint8)(stream.nextValue() * coeff); if (transform) transform->transform(d, d, 1); if (hasalpha) { d[1] = (quint8)(stream.nextValue() * coeff); } else { d[1] = UCHAR_MAX; } } while (it->nextPixel()); } // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits" break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1); if (hasalpha) d[3] = *(src++); else d[3] = quint16_MAX; } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[2] = (quint8)(stream.nextValue() * coeff); d[1] = (quint8)(stream.nextValue() * coeff); d[0] = (quint8)(stream.nextValue() * coeff); if (transform) transform->transform(d, d, 1); if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff); else d[3] = UCHAR_MAX; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); quint8 index = stream.nextValue(); quint8 alpha = palette_alpha[ index ]; if (alpha == 0) { memset(d, 0, 4); } else { png_color c = palette[ index ]; d[2] = c.red; d[1] = c.green; d[0] = c.blue; d[3] = alpha; } } while (it->nextPixel()); } break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_image->addNode(layer.data(), m_image->rootLayer().data()); png_read_end(png_ptr, end_info); iod->close(); // Freeing memory png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); delete reader; return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisPNGConverter::buildImage(const QUrl &uri) { dbgFile << QFile::encodeName(uri.path()) << " " << uri.path() << " " << uri; if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!uri.isLocalFile()) { return KisImageBuilder_RESULT_NOT_LOCAL; } m_path = uri.toDisplayString(); QFile fp(uri.toLocalFile()); if (fp.exists()) { return buildImage(&fp); } return (KisImageBuilder_RESULT_NOT_EXIST); } KisImageWSP KisPNGConverter::image() { return m_image; } bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData) { if (store->open(filename)) { KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { dbgFile << "Could not open for writing:" << filename; return false; } KisPNGConverter pngconv(0); vKisAnnotationSP_it annotIt = 0; KisMetaData::Store* metaDataStore = 0; if (metaData) { metaDataStore = new KisMetaData::Store(*metaData); } KisPNGOptions options; options.compression = 0; options.interlace = false; options.tryToSaveAsIndexed = false; options.alpha = true; bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); if (success != KisImageBuilder_RESULT_OK) { dbgFile << "Saving PNG failed:" << filename; delete metaDataStore; return false; } delete metaDataStore; io.close(); if (!store->close()) { return false; } } else { dbgFile << "Opening of data file failed :" << filename; return false; } return true; } KisImageBuilder_Result KisPNGConverter::buildFile(const QUrl &uri, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { dbgFile << "Start writing PNG File " << uri; if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!uri.isLocalFile()) return KisImageBuilder_RESULT_NOT_LOCAL; // Open a QIODevice for writing QFile *fp = new QFile(uri.toLocalFile()); KisImageBuilder_Result result = buildFile(fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); delete fp; return result; // TODO: if failure do KIO::del(uri); // async } KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { if (!iodevice->open(QIODevice::WriteOnly)) { dbgFile << "Failed to open PNG File for writing"; return (KisImageBuilder_RESULT_FAILURE); } if (!device) return KisImageBuilder_RESULT_INVALID_ARG; if (!options.alpha) { KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); KoColor c(options.transparencyFillColor, device->colorSpace()); tmp->fill(imageRect, c); KisPainter gc(tmp); gc.bitBlt(imageRect.topLeft(), device, imageRect); gc.end(); device = tmp; } if (options.forceSRGB) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); device = new KisPaintDevice(*device); device->convertTo(cs); } // Initialize structures png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { return (KisImageBuilder_RESULT_FAILURE); } #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED png_set_check_for_invalid_index(png_ptr, 0); #endif png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return (KisImageBuilder_RESULT_FAILURE); } // If an error occurs during writing, libpng will jump here if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the writing // png_init_io(png_ptr, fp); // Setup the progress function // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);" // setProgressTotalSteps(100/*height*/); /* set the zlib compression level */ png_set_compression_level(png_ptr, options.compression); png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn); /* set other zlib parameters */ png_set_compression_mem_level(png_ptr, 8); png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); png_set_compression_window_bits(png_ptr, 15); png_set_compression_method(png_ptr, 8); png_set_compression_buffer_size(png_ptr, 8192); int color_nb_bits = 8 * device->pixelSize() / device->channelCount(); int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha); Q_ASSERT(color_type > -1); // Try to compute a table of color if the colorspace is RGB8f png_colorp palette = 0; int num_palette = 0; if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8 palette = new png_color[255]; KisSequentialIterator it(device, imageRect); bool toomuchcolor = false; do { const quint8* c = it.oldRawData(); bool findit = false; for (int i = 0; i < num_palette; i++) { if (palette[i].red == c[2] && palette[i].green == c[1] && palette[i].blue == c[0]) { findit = true; break; } } if (!findit) { if (num_palette == 255) { toomuchcolor = true; break; } palette[num_palette].red = c[2]; palette[num_palette].green = c[1]; palette[num_palette].blue = c[0]; num_palette++; } } while (it.nextPixel()); if (!toomuchcolor) { dbgFile << "Found a palette of " << num_palette << " colors"; color_type = PNG_COLOR_TYPE_PALETTE; if (num_palette <= 2) { color_nb_bits = 1; } else if (num_palette <= 4) { color_nb_bits = 2; } else if (num_palette <= 16) { color_nb_bits = 4; } else { color_nb_bits = 8; } } else { delete [] palette; } } int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR(png_ptr, info_ptr, imageRect.width(), imageRect.height(), color_nb_bits, color_type, interlacetype, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); if (!options.saveSRGBProfile && sRGB) { png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_ABSOLUTE); } // set the palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(png_ptr, info_ptr, palette, num_palette); } // Save annotation vKisAnnotationSP_it it = annotationsStart; while (it != annotationsEnd) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size(); if ((*it) -> type().startsWith(QString("krita_attribute:"))) { // // Attribute // XXX: it should be possible to save krita_attributes in the \"CHUNKs\"" dbgFile << "cannot save this annotation : " << (*it) -> type(); } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) { dbgFile << "Saving preset information " << (*it)->description(); png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text)); QByteArray keyData = (*it)->description().toLatin1(); text[0].key = keyData.data(); text[0].text = (char*)(*it)->annotation().data(); text[0].text_length = (*it)->annotation().size(); text[0].compression = -1; png_set_text(png_ptr, info_ptr, text, 1); png_free(png_ptr, text); } it++; } // Save the color profile const KoColorProfile* colorProfile = device->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); if (!sRGB || options.saveSRGBProfile) { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (const png_bytep)colorProfileData.constData(), colorProfileData . size()); #else png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (char*)colorProfileData.constData(), colorProfileData . size()); #endif } // read comments from the document information if (m_doc) { png_text texts[3]; int nbtexts = 0; KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("creator"); if (!title.isEmpty()) { fillText(texts + nbtexts, "title", title); nbtexts++; } QString abstract = info->aboutInfo("description"); if (!abstract.isEmpty()) { fillText(texts + nbtexts, "abstract", abstract); nbtexts++; } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { fillText(texts + nbtexts, "author", author); nbtexts++; } png_set_text(png_ptr, info_ptr, texts, nbtexts); } // Save metadata following imagemagick way // Save exif if (metaData && !metaData->empty()) { if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); writeRawProfile(png_ptr, info_ptr, "exif", buffer.data()); } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data()); } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader); dbgFile << "XMP information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data()); } } #if 0 // Unimplemented? // Save resolution int unit_type; png_uint_32 x_resolution, y_resolution; #endif png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points // Save the information to the file png_write_info(png_ptr, info_ptr); png_write_flush(png_ptr); // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Write the PNG // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); // Fill the data structure png_byte** row_pointers = new png_byte*[imageRect.height()]; for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++) { KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width()); row_pointers[y] = new png_byte[imageRect.width() * device->pixelSize()]; switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(row_pointers[y]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } else { quint8 *dst = row_pointers[y]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(row_pointers[y]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } else { quint8 *dst = row_pointers[y]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { quint8 *dst = row_pointers[y]; KisPNGWriteStream writestream(dst, color_nb_bits); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < num_palette; i++) { if (palette[i].red == d[2] && palette[i].green == d[1] && palette[i].blue == d[0]) { break; } } writestream.setNextValue(i); } while (it->nextPixel()); } break; default: delete[] row_pointers; return KisImageBuilder_RESULT_UNSUPPORTED; } } png_write_image(png_ptr, row_pointers); // Writing is over png_write_end(png_ptr, info_ptr); // Free memory png_destroy_write_struct(&png_ptr, &info_ptr); for (int y = 0; y < imageRect.height(); y++) { delete[] row_pointers[y]; } delete[] row_pointers; if (color_type == PNG_COLOR_TYPE_PALETTE) { delete [] palette; } iodevice->close(); return KisImageBuilder_RESULT_OK; } void KisPNGConverter::cancel() { m_stop = true; } void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass) { if (png_ptr == NULL || row_number > PNG_MAX_UINT || pass > 7) return; // setProgress(row_number); } bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs) { return colorSpaceIdSupported(cs->id()); } diff --git a/krita/ui/tests/kis_prescaled_projection_test.cpp b/krita/ui/tests/kis_prescaled_projection_test.cpp index d285db98574..90a14b9d9cd 100644 --- a/krita/ui/tests/kis_prescaled_projection_test.cpp +++ b/krita/ui/tests/kis_prescaled_projection_test.cpp @@ -1,475 +1,475 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * 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_prescaled_projection_test.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_prescaled_projection.h" #include "../../sdk/tests/testutil.h" bool KisPrescaledProjectionTest::testProjectionScenario(KisPrescaledProjection & projection, KoZoomHandler * viewConverter, const QString & name) { projection.notifyCanvasSizeChanged(QSize(1000, 1000)); projection.prescaledQImage().save(name + "_prescaled_projection_01.png"); viewConverter->setZoom(0.5); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_021.png"); viewConverter->setZoom(0.6); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_022.png"); viewConverter->setZoom(0.71); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_023.png"); viewConverter->setZoom(0.84); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_024.png"); viewConverter->setZoom(0.9); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_025.png"); viewConverter->setZoom(1.9); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_03.png"); viewConverter->setZoom(2.0); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_04.png"); viewConverter->setZoom(2.5); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_05.png"); viewConverter->setZoom(16.0); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_06.png"); viewConverter->setZoom(1.0); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_07.png"); projection.viewportMoved(QPoint(50, 50)); projection.prescaledQImage().save(name + "_prescaled_projection_08.png"); projection.viewportMoved(QPoint(100, 100)); projection.prescaledQImage().save(name + "_prescaled_projection_081.png"); projection.viewportMoved(QPoint(200, 200)); projection.prescaledQImage().save(name + "_prescaled_projection_082.png"); projection.viewportMoved(QPoint(250, 250)); projection.prescaledQImage().save(name + "_prescaled_projection_083.png"); projection.viewportMoved(QPoint(150, 200)); projection.prescaledQImage().save(name + "_prescaled_projection_084.png"); projection.viewportMoved(QPoint(100, 200)); projection.prescaledQImage().save(name + "_prescaled_projection_085.png"); projection.viewportMoved(QPoint(50, 200)); projection.prescaledQImage().save(name + "_prescaled_projection_086.png"); projection.viewportMoved(QPoint(0, 200)); projection.prescaledQImage().save(name + "_prescaled_projection_087.png"); projection.notifyCanvasSizeChanged(QSize(750, 750)); projection.prescaledQImage().save(name + "_prescaled_projection_09.png"); viewConverter->setZoom(1.0); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_10.png"); projection.notifyCanvasSizeChanged(QSize(350, 350)); projection.prescaledQImage().save(name + "_prescaled_projection_11.png"); projection.viewportMoved(QPoint(100, 100)); projection.prescaledQImage().save(name + "_prescaled_projection_12.png"); viewConverter->setZoom(0.75); projection.preScale(); projection.prescaledQImage().save(name + "_prescaled_projection_13.png"); projection.viewportMoved(QPoint(10, 10)); projection.prescaledQImage().save(name + "_prescaled_projection_14.png"); projection.viewportMoved(QPoint(0, 0)); projection.prescaledQImage().save(name + "_prescaled_projection_15.png"); projection.viewportMoved(QPoint(10, 10)); projection.prescaledQImage().save(name + "_prescaled_projection_16.png"); projection.viewportMoved(QPoint(30, 50)); projection.prescaledQImage().save(name + "_prescaled_projection_17.png"); return true; } void KisPrescaledProjectionTest::testCreation() { KisPrescaledProjection * prescaledProjection = 0; prescaledProjection = new KisPrescaledProjection(); QVERIFY(prescaledProjection != 0); QVERIFY(prescaledProjection->prescaledQImage().isNull()); delete prescaledProjection; } void KisPrescaledProjectionTest::testScalingUndeferredSmoothingPixelForPixel() { // Set up a nice image QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "lena.png"); // Undo adapter not necessary const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, qimage.width(), qimage.height(), cs, "projection test"); // 300 dpi recalculated to pixels per point (of which there are 72 // to the inch) image->setResolution(100 / 72 , 100 / 72); KisPaintLayerSP layer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8, cs); image->addNode(layer.data(), image->rootLayer(), 0); layer->paintDevice()->convertFromQImage(qimage, 0); KisPrescaledProjection projection; KisCoordinatesConverter converter; converter.setImage(image); projection.setCoordinatesConverter(&converter); projection.setMonitorProfile(0, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); projection.setImage(image); // pixel-for-pixel, at 100% zoom converter.setResolution(image->xRes(), image->yRes()); testProjectionScenario(projection, &converter, "pixel_for_pixel"); } void KisPrescaledProjectionTest::testScalingUndeferredSmoothing() { // Set up a nice image QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "lena.png"); // Undo adapter not necessary const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, qimage.width(), qimage.height(), cs, "projection test"); // 300 dpi recalculated to pixels per point (of which there are 72 // to the inch) image->setResolution(100, 100); KisPaintLayerSP layer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8, cs); image->addNode(layer.data(), image->rootLayer(), 0); layer->paintDevice()->convertFromQImage(qimage, 0); KisPrescaledProjection projection; KisCoordinatesConverter converter; converter.setImage(image); projection.setCoordinatesConverter(&converter); projection.setMonitorProfile(0, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); projection.setImage(image); testProjectionScenario(projection, &converter, "120dpi"); } //#include void KisPrescaledProjectionTest::benchmarkUpdate() { QImage referenceImage(QString(FILES_DATA_DIR) + QDir::separator() + "lena.png"); QRect imageRect = QRect(QPoint(0,0), referenceImage.size()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "projection test"); // set up 300dpi image->setResolution(300 / 72 , 300 / 72); KisPaintLayerSP layer = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, cs); layer->paintDevice()->convertFromQImage(referenceImage, 0); image->addNode(layer, image->rootLayer(), 0); KisPrescaledProjection projection; KisCoordinatesConverter converter; converter.setImage(image); projection.setCoordinatesConverter(&converter); projection.setMonitorProfile(0, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); projection.setImage(image); // Emulate "Use same aspect as pixels" converter.setResolution(image->xRes(), image->yRes()); converter.setZoom(1.0); KisUpdateInfoSP info = projection.updateCache(image->bounds()); projection.recalculateCache(info); QCOMPARE(imageRect, QRect(0,0,512,512)); QRect dirtyRect(0,0,20,20); const qint32 numShifts = 25; const QPoint offset(dirtyRect.width(),dirtyRect.height()); //CALLGRIND_START_INSTRUMENTATION; QBENCHMARK { for(qint32 i = 0; i < numShifts; i++) { KisUpdateInfoSP tempInfo = projection.updateCache(dirtyRect); projection.recalculateCache(tempInfo); dirtyRect.translate(offset); } } //CALLGRIND_STOP_INSTRUMENTATION; } class PrescaledProjectionTester { public: PrescaledProjectionTester() { sourceImage = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "lena.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); image = new KisImage(0, sourceImage.width(), sourceImage.height(), cs, "projection test"); image->setResolution(100, 100); layer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8, cs); layer->paintDevice()->convertFromQImage(sourceImage, 0); image->addNode(layer, image->rootLayer(), 0); converter.setResolution(100, 100); converter.setZoom(1.); converter.setImage(image); converter.setCanvasWidgetSize(QSize(100,100)); converter.setDocumentOffset(QPoint(100,100)); projection.setCoordinatesConverter(&converter); projection.setMonitorProfile(0, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); projection.setImage(image); projection.notifyCanvasSizeChanged(QSize(100,100)); projection.notifyZoomChanged(); } QImage sourceImage; KisImageSP image; KisPaintLayerSP layer; KisCoordinatesConverter converter; KisPrescaledProjection projection; }; void KisPrescaledProjectionTest::testScrollingZoom100() { PrescaledProjectionTester t; QImage result = t.projection.prescaledQImage(); QImage reference = t.sourceImage.copy(QRect(100,100,100,100)); QPoint pt; QVERIFY(TestUtil::compareQImages(pt, result, reference)); // Test actual scrolling t.converter.setDocumentOffset(QPoint(150,150)); t.projection.viewportMoved(QPoint(-50,-50)); result = t.projection.prescaledQImage(); reference = t.sourceImage.copy(QRect(150,150,100,100)); QVERIFY(TestUtil::compareQImages(pt, result, reference)); } void KisPrescaledProjectionTest::testScrollingZoom50() { PrescaledProjectionTester t; t.converter.setDocumentOffset(QPoint(0,0)); t.converter.setCanvasWidgetSize(QSize(300,300)); t.projection.notifyCanvasSizeChanged(QSize(300,300)); QVERIFY(TestUtil::checkQImage(t.projection.prescaledQImage(), "prescaled_projection_test", "testScrollingZoom50", "initial")); t.converter.setZoom(0.5); t.projection.notifyZoomChanged(); QVERIFY(TestUtil::checkQImage(t.projection.prescaledQImage(), "prescaled_projection_test", "testScrollingZoom50", "zoom50")); t.converter.setDocumentOffset(QPoint(50,50)); t.projection.viewportMoved(QPoint(-50,-50)); QVERIFY(TestUtil::checkQImage(t.projection.prescaledQImage(), "prescaled_projection_test", "testScrollingZoom50", "zoom50_moved50")); } void KisPrescaledProjectionTest::testUpdates() { PrescaledProjectionTester t; t.converter.setDocumentOffset(QPoint(10,10)); t.converter.setCanvasWidgetSize(2*QSize(300,300)); t.projection.notifyCanvasSizeChanged(2*QSize(300,300)); t.converter.setZoom(0.50); t.projection.notifyZoomChanged(); QVERIFY(TestUtil::checkQImage(t.projection.prescaledQImage(), "prescaled_projection_test", "testUpdates", "zoom50")); t.layer->setVisible(false); KisUpdateInfoSP info = t.projection.updateCache(t.image->bounds()); t.projection.recalculateCache(info); QVERIFY(TestUtil::checkQImage(t.projection.prescaledQImage(), "prescaled_projection_test", "testUpdates", "cleared")); t.layer->setVisible(true); t.image->refreshGraph(); // Update incrementally const int step = 73; const int patchOffset = -7; const int patchSize = 93; QList infos; for(int y = 0; y < t.image->height(); y+=step) { for(int x = 0; x < t.image->width(); x+=step) { QRect patchRect(x - patchOffset, y - patchOffset, patchSize, patchSize); infos.append(t.projection.updateCache(patchRect)); } } foreach(KisUpdateInfoSP info, infos) { t.projection.recalculateCache(info); } QEXPECT_FAIL("", "Testcase for bug: https://bugs.kde.org/show_bug.cgi?id=289915", Continue); QVERIFY(TestUtil::checkQImage(t.projection.prescaledQImage(), "prescaled_projection_test", "testUpdates", "zoom50", 1)); } void KisPrescaledProjectionTest::testQtScaling() { // See: https://bugreports.qt.nokia.com/browse/QTBUG-22827 /** * Curently we rely on this behavior, so let's test for it. */ // Create a canvas image QImage canvas(6, 6, QImage::Format_ARGB32); canvas.fill(0); // Image we are going to scale down QImage image(7, 7, QImage::Format_ARGB32); QPainter imagePainter(&image); imagePainter.fillRect(QRect(0,0,7,7),Qt::green); imagePainter.end(); QPainter gc(&canvas); // Scale down transformation qreal scale = 3.49/7.0; gc.setTransform(QTransform::fromScale(scale,scale)); // Draw a rect scale*(7x7) gc.fillRect(QRectF(0,0,7,7), Qt::red); // Draw an image scale*(7x7) gc.drawImage(QPointF(), image, QRectF(0,0,7,7)); gc.end(); // Create an expected result QImage expectedResult(6, 6, QImage::Format_ARGB32); expectedResult.fill(0); QPainter expectedPainter(&expectedResult); expectedPainter.fillRect(QRect(0,0,4,4), Qt::red); expectedPainter.fillRect(QRect(0,0,3,3), Qt::green); expectedPainter.end(); QCOMPARE(canvas, expectedResult); } QTEST_MAIN(KisPrescaledProjectionTest) diff --git a/krita/ui/widgets/kis_scratch_pad.cpp b/krita/ui/widgets/kis_scratch_pad.cpp index ddbb493ab96..b3ca7184328 100644 --- a/krita/ui/widgets/kis_scratch_pad.cpp +++ b/krita/ui/widgets/kis_scratch_pad.cpp @@ -1,458 +1,458 @@ /* This file is part of the KDE project * Copyright 2010 (C) Boudewijn Rempt * Copyright 2011 (C) Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_scratch_pad.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_undo_stores.h" #include "kis_update_scheduler.h" #include "kis_post_execution_undo_adapter.h" #include "kis_scratch_pad_event_filter.h" #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "kis_image_patch.h" #include "kis_canvas_widget_base.h" #include "kis_layer_projection_plane.h" class KisScratchPadNodeListener : public KisNodeGraphListener { public: KisScratchPadNodeListener(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } void requestProjectionUpdate(KisNode *node, const QRect& rect) { KisNodeGraphListener::requestProjectionUpdate(node, rect); QMutexLocker locker(&m_lock); m_scratchPad->imageUpdated(rect); } private: KisScratchPad *m_scratchPad; QMutex m_lock; }; class KisScratchPadDefaultBounds : public KisDefaultBounds { public: KisScratchPadDefaultBounds(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } virtual ~KisScratchPadDefaultBounds() {} virtual QRect bounds() const { return m_scratchPad->imageBounds(); } private: Q_DISABLE_COPY(KisScratchPadDefaultBounds) KisScratchPad *m_scratchPad; }; KisScratchPad::KisScratchPad(QWidget *parent) : QWidget(parent) , m_toolMode(HOVERING) , m_paintLayer(0) , m_displayProfile(0) , m_resourceProvider(0) { setAutoFillBackground(false); m_cursor = KisCursor::load("tool_freehand_cursor.png", 5, 5); setCursor(m_cursor); KisConfig cfg; QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize()); m_checkBrush = QBrush(checkImage); // We are not supposed to use updates here, // so just set the listener to null m_updateScheduler = new KisUpdateScheduler(0); m_undoStore = new KisSurrogateUndoStore(); m_undoAdapter = new KisPostExecutionUndoAdapter(m_undoStore, m_updateScheduler); m_nodeListener = new KisScratchPadNodeListener(this); connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection); // filter will be deleted by the QObject hierarchy m_eventFilter = new KisScratchPadEventFilter(this); connect(m_eventFilter, SIGNAL(mousePressSignal(KoPointerEvent*)), SLOT(slotMousePress(KoPointerEvent*)), Qt::DirectConnection); connect(m_eventFilter, SIGNAL(mouseReleaseSignal(KoPointerEvent*)), SLOT(slotMouseRelease(KoPointerEvent*)), Qt::DirectConnection); connect(m_eventFilter, SIGNAL(mouseMoveSignal(KoPointerEvent*)), SLOT(slotMouseMove(KoPointerEvent*)), Qt::DirectConnection); m_infoBuilder = new KisPaintingInformationBuilder(); m_helper = new KisToolFreehandHelper(m_infoBuilder); m_scaleBorderWidth = 1; } KisScratchPad::~KisScratchPad() { delete m_helper; delete m_infoBuilder; delete m_undoAdapter; delete m_undoStore; delete m_updateScheduler; delete m_nodeListener; } void KisScratchPad::slotMousePress(KoPointerEvent *event) { if (event->button() == Qt::LeftButton) { m_toolMode = PAINTING; beginStroke(event); event->accept(); } else if (event->button() == Qt::MidButton) { m_toolMode = PANNING; beginPan(event); event->accept(); } else if (event->button() == Qt::RightButton) { m_toolMode = PICKING; event->accept(); } } void KisScratchPad::slotMouseRelease(KoPointerEvent *event) { if (m_toolMode == PAINTING) { endStroke(event); m_toolMode = HOVERING; event->accept(); } else if (m_toolMode == PANNING) { endPan(event); m_toolMode = HOVERING; event->accept(); } else if (m_toolMode == PICKING) { event->accept(); m_toolMode = HOVERING; } } void KisScratchPad::slotMouseMove(KoPointerEvent *event) { if (m_toolMode == PAINTING) { doStroke(event); event->accept(); } else if (m_toolMode == PANNING) { doPan(event); event->accept(); } else if (m_toolMode == PICKING) { pick(event); event->accept(); } } void KisScratchPad::beginStroke(KoPointerEvent *event) { KoCanvasResourceManager *resourceManager = m_resourceProvider->resourceManager(); m_helper->initPaint(event, resourceManager, 0, 0, m_updateScheduler, m_undoAdapter, m_paintLayer, m_paintLayer->paintDevice()->defaultBounds()); } void KisScratchPad::doStroke(KoPointerEvent *event) { m_helper->paint(event); } void KisScratchPad::endStroke(KoPointerEvent *event) { Q_UNUSED(event); m_helper->endPaint(); } void KisScratchPad::beginPan(KoPointerEvent *event) { setCursor(QCursor(Qt::ClosedHandCursor)); m_panDocPoint = event->point; } void KisScratchPad::doPan(KoPointerEvent *event) { QPointF docOffset = event->point - m_panDocPoint; m_translateTransform.translate(-docOffset.x(), -docOffset.y()); updateTransformations(); update(); } void KisScratchPad::endPan(KoPointerEvent *event) { Q_UNUSED(event); setCursor(m_cursor); } void KisScratchPad::pick(KoPointerEvent *event) { KoColor color; if (KisToolUtils::pick(m_paintLayer->projection(), event->point.toPoint(), &color)) { emit colorSelected(color); } } void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY) { m_scaleBorderWidth = BORDER_SIZE(qMax(scaleX, scaleY)); m_scaleTransform = QTransform::fromScale(scaleX, scaleY); updateTransformations(); update(); } QTransform KisScratchPad::documentToWidget() const { return m_translateTransform.inverted() * m_scaleTransform; } QTransform KisScratchPad::widgetToDocument() const { return m_scaleTransform.inverted() * m_translateTransform; } void KisScratchPad::updateTransformations() { m_eventFilter->setWidgetToDocumentTransform(widgetToDocument()); } QRect KisScratchPad::imageBounds() const { return widgetToDocument().mapRect(rect()); } void KisScratchPad::imageUpdated(const QRect &rect) { emit sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect()); } void KisScratchPad::slotUpdateCanvas(const QRect &rect) { update(rect); } void KisScratchPad::paintEvent ( QPaintEvent * event ) { if(!m_paintLayer) return; QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect())); QRect alignedImageRect = imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth, m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect(); QPointF offset = alignedImageRect.topLeft(); m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer); KisPaintDeviceSP projection = m_paintLayer->projection(); QImage image = projection->convertToQImage(m_displayProfile, alignedImageRect.x(), alignedImageRect.y(), alignedImageRect.width(), alignedImageRect.height(), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); QPainter gc(this); gc.fillRect(event->rect(), m_checkBrush); gc.setRenderHints(QPainter::SmoothPixmapTransform); gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset)); QBrush brush(Qt::lightGray); QPen pen(brush, 1, Qt::DotLine); gc.setPen(pen); if (m_cutoutOverlay.isValid()) { gc.drawRect(m_cutoutOverlay); } if(!isEnabled()) { QColor color(Qt::lightGray); color.setAlphaF(0.5); QBrush disabledBrush(color); gc.fillRect(event->rect(), disabledBrush); } gc.end(); } void KisScratchPad::setupScratchPad(KisCanvasResourceProvider* resourceProvider, const QColor &defaultColor) { m_resourceProvider = resourceProvider; KisConfig cfg; setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this))); connect(m_resourceProvider, SIGNAL(sigDisplayProfileChanged(const KoColorProfile*)), SLOT(setDisplayProfile(const KoColorProfile*))); connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)), SLOT(setOnScreenResolution(qreal,qreal))); m_defaultColor = KoColor(defaultColor, KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP paintDevice = new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad"); m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice); m_paintLayer->setGraphListener(m_nodeListener); m_paintLayer->paintDevice()->setDefaultBounds(new KisScratchPadDefaultBounds(this)); fillDefault(); } void KisScratchPad::setCutoutOverlayRect(const QRect& rc) { m_cutoutOverlay = rc; } QImage KisScratchPad::cutoutOverlay() const { if(!m_paintLayer) return QImage(); KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect rc = widgetToDocument().mapRect(m_cutoutOverlay); - QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); return scaledImage; } void KisScratchPad::setPresetImage(const QImage& image) { m_presetImage = image; } void KisScratchPad::paintPresetImage() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay); QRect imageRect(QPoint(), overlayRect.size()); QImage scaledImage = m_presetImage.scaled(overlayRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); device->convertFromQImage(scaledImage, 0); KisPainter painter(paintDevice); painter.bitBlt(overlayRect.topLeft(), device, imageRect); update(); } void KisScratchPad::setDisplayProfile(const KoColorProfile *colorProfile) { m_displayProfile = colorProfile; QWidget::update(); } void KisScratchPad::fillDefault() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); paintDevice->setDefaultPixel(m_defaultColor.data()); paintDevice->clear(); update(); } void KisScratchPad::fillGradient() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KoAbstractGradient* gradient = m_resourceProvider->currentGradient(); QRect gradientRect = widgetToDocument().mapRect(rect()); paintDevice->clear(); KisGradientPainter painter(paintDevice); painter.setGradient(gradient); painter.setGradientShape(KisGradientPainter::GradientShapeLinear); painter.paintGradient(gradientRect.topLeft(), gradientRect.bottomRight(), KisGradientPainter::GradientRepeatNone, 0.2, false, gradientRect.left(), gradientRect.top(), gradientRect.width(), gradientRect.height()); update(); } void KisScratchPad::fillBackground() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KoColor c(m_resourceProvider->bgColor(), paintDevice->colorSpace()); paintDevice->setDefaultPixel(c.data()); paintDevice->clear(); update(); } void KisScratchPad::fillLayer() { // TODO } diff --git a/libs/pigment/KoColor.cpp b/libs/pigment/KoColor.cpp index edce117859d..39cc733a02a 100644 --- a/libs/pigment/KoColor.cpp +++ b/libs/pigment/KoColor.cpp @@ -1,330 +1,330 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColor.h" #include #include #include "DebugPigment.h" #include "KoColorModelStandardIds.h" #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoChannelInfo.h" class Q_DECL_HIDDEN KoColor::Private { public: Private() : data(0), colorSpace(0) {} ~Private() { delete [] data; } quint8 * data; const KoColorSpace * colorSpace; }; KoColor::KoColor() : d(new Private()) { d->colorSpace = KoColorSpaceRegistry::instance()->rgb16(0); d->data = new quint8[d->colorSpace->pixelSize()]; d->colorSpace->fromQColor(Qt::black, d->data); d->colorSpace->setOpacity(d->data, OPACITY_OPAQUE_U8, 1); } KoColor::KoColor(const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[d->colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); } KoColor::~KoColor() { delete d; } KoColor::KoColor(const QColor & color, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(color.isValid()); Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); d->colorSpace->fromQColor(color, d->data); } KoColor::KoColor(const quint8 * data, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); Q_ASSERT(data); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); memmove(d->data, data, colorSpace->pixelSize()); } KoColor::KoColor(const KoColor &src, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); - src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace, 1, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } KoColor::KoColor(const KoColor & rhs) : d(new Private()) { d->colorSpace = rhs.colorSpace(); Q_ASSERT(*d->colorSpace == *KoColorSpaceRegistry::instance()->permanentColorspace(d->colorSpace)); if (d->colorSpace && rhs.d->data) { d->data = new quint8[d->colorSpace->pixelSize()]; memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize()); } } KoColor & KoColor::operator=(const KoColor & rhs) { if (this == &rhs) return *this; delete [] d->data; d->data = 0; d->colorSpace = rhs.colorSpace(); if (rhs.d->colorSpace && rhs.d->data) { Q_ASSERT(d->colorSpace == KoColorSpaceRegistry::instance()->permanentColorspace(d->colorSpace)); // here we want to do a check on pointer, since d->colorSpace is supposed to already be a permanent one d->data = new quint8[d->colorSpace->pixelSize()]; memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize()); } return * this; } bool KoColor::operator==(const KoColor &other) const { if (!(*colorSpace() == *other.colorSpace())) return false; return memcmp(d->data, other.d->data, d->colorSpace->pixelSize()) == 0; } void KoColor::convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { //dbgPigment <<"Our colormodel:" << d->colorSpace->id().name() // << ", new colormodel: " << cs->id().name() << "\n"; if (*d->colorSpace == *cs) return; quint8 * data = new quint8[cs->pixelSize()]; memset(data, 0, cs->pixelSize()); d->colorSpace->convertPixelsTo(d->data, data, cs, 1, renderingIntent, conversionFlags); delete [] d->data; d->data = data; d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(cs); } void KoColor::convertTo(const KoColorSpace * cs) { convertTo(cs, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } void KoColor::setColor(const quint8 * data, const KoColorSpace * colorSpace) { Q_ASSERT(data); Q_ASSERT(colorSpace); if(d->colorSpace->pixelSize() != colorSpace->pixelSize()) { delete [] d->data; d->data = new quint8[colorSpace->pixelSize()]; } memcpy(d->data, data, colorSpace->pixelSize()); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); } // To save the user the trouble of doing color->colorSpace()->toQColor(color->data(), &c, &a, profile void KoColor::toQColor(QColor *c) const { Q_ASSERT(c); if (d->colorSpace && d->data) { d->colorSpace->toQColor(d->data, c); } } QColor KoColor::toQColor() const { QColor c; toQColor(&c); return c; } void KoColor::fromQColor(const QColor& c) const { if (d->colorSpace && d->data) { d->colorSpace->fromQColor(c, d->data); } } #ifndef NDEBUG void KoColor::dump() const { dbgPigment <<"KoColor (" << this <<")," << d->colorSpace->id() <<""; QList channels = d->colorSpace->channels(); QList::const_iterator begin = channels.constBegin(); QList::const_iterator end = channels.constEnd(); for (QList::const_iterator it = begin; it != end; ++it) { KoChannelInfo * ch = (*it); // XXX: setNum always takes a byte. if (ch->size() == sizeof(quint8)) { // Byte dbgPigment <<"Channel (byte):" << ch->name() <<":" << QString().setNum(d->data[ch->pos()]) <<""; } else if (ch->size() == sizeof(quint16)) { // Short (may also by an nvidia half) dbgPigment <<"Channel (short):" << ch->name() <<":" << QString().setNum(*((const quint16 *)(d->data+ch->pos()))) <<""; } else if (ch->size() == sizeof(quint32)) { // Integer (may also be float... Find out how to distinguish these!) dbgPigment <<"Channel (int):" << ch->name() <<":" << QString().setNum(*((const quint32 *)(d->data+ch->pos()))) <<""; } } } #endif void KoColor::fromKoColor(const KoColor& src) { - src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace(), 1, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace(), 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } const KoColorProfile * KoColor::profile() const { return d->colorSpace->profile(); } quint8 * KoColor::data() { return d->data; } const quint8 * KoColor::data() const { return d->data; } const KoColorSpace * KoColor::colorSpace() const { return d->colorSpace; } void KoColor::toXML(QDomDocument& doc, QDomElement& colorElt) const { d->colorSpace->colorToXML(d->data, doc, colorElt); } void KoColor::setOpacity(quint8 alpha) { d->colorSpace->setOpacity(d->data, alpha, 1); } void KoColor::setOpacity(qreal alpha) { d->colorSpace->setOpacity(d->data, alpha, 1); } quint8 KoColor::opacityU8() const { return d->colorSpace->opacityU8(d->data); } qreal KoColor::opacityF() const { return d->colorSpace->opacityF(d->data); } KoColor KoColor::fromXML(const QDomElement& elt, const QString & bitDepthId, const QHash & aliases) { QString modelId; if (elt.tagName() == "CMYK") { modelId = CMYKAColorModelID.id(); } else if (elt.tagName() == "RGB") { modelId = RGBAColorModelID.id(); } else if (elt.tagName() == "sRGB") { modelId = RGBAColorModelID.id(); } else if (elt.tagName() == "Lab") { modelId = LABAColorModelID.id(); } else if (elt.tagName() == "XYZ") { modelId = XYZAColorModelID.id(); } else if (elt.tagName() == "Gray") { modelId = GrayAColorModelID.id(); } else if (elt.tagName() == "YCbCr") { modelId = YCbCrAColorModelID.id(); } QString profileName; if (elt.tagName() != "sRGB") { profileName = elt.attribute("space", ""); if (aliases.contains(profileName)) { profileName = aliases.value(profileName); } if (!KoColorSpaceRegistry::instance()->profileByName(profileName)) { profileName.clear(); } } const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, bitDepthId, profileName); if (cs == 0) { QList list = KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces); if (!list.empty()) { cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, list[0].id(), profileName); } } if (cs) { KoColor c(cs); cs->colorFromXML(c.data(), elt); return c; } else { return KoColor(); } } QString KoColor::toQString(const KoColor &color) { QStringList ls; foreach(KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(color.colorSpace()->channels())) { int realIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), color.colorSpace()->channels()); ls << channel->name(); ls << color.colorSpace()->channelValueText(color.data(), realIndex); } return ls.join(" "); } diff --git a/libs/pigment/KoColorConversionSystem.cpp b/libs/pigment/KoColorConversionSystem.cpp index d0a6867ed2e..111f663d77a 100644 --- a/libs/pigment/KoColorConversionSystem.cpp +++ b/libs/pigment/KoColorConversionSystem.cpp @@ -1,536 +1,536 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorConversionSystem.h" #include "KoColorConversionSystem_p.h" #include #include #include "KoColorConversionAlphaTransformation.h" #include "KoColorConversionTransformation.h" #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoCopyColorConversionTransformation.h" #include "KoMultipleColorConversionTransformation.h" KoColorConversionSystem::KoColorConversionSystem() : d(new Private) { // Create the Alpha 8bit d->alphaNode = new Node; d->alphaNode->modelId = AlphaColorModelID.id(); d->alphaNode->depthId = Integer8BitsColorDepthID.id(); d->alphaNode->crossingCost = 1000000; d->alphaNode->isInitialized = true; d->alphaNode->isGray = true; // <- FIXME: it's a little bit hacky as alpha doesn't really have color information d->graph[ NodeKey(d->alphaNode->modelId, d->alphaNode->depthId, "default")] = d->alphaNode; Vertex* v = createVertex(d->alphaNode, d->alphaNode); v->setFactoryFromSrc(new KoCopyColorConversionTransformationFactory(AlphaColorModelID.id(), Integer8BitsColorDepthID.id(), "default")); } KoColorConversionSystem::~KoColorConversionSystem() { qDeleteAll(d->graph); qDeleteAll(d->vertexes); delete d; } void KoColorConversionSystem::connectToEngine(Node* _node, Node* _engine) { Vertex* v1 = createVertex(_node, _engine); Vertex* v2 = createVertex(_engine, _node); v1->conserveColorInformation = !_node->isGray; v2->conserveColorInformation = !_node->isGray; v1->conserveDynamicRange = _engine->isHdr; v2->conserveDynamicRange = _engine->isHdr; } KoColorConversionSystem::Node* KoColorConversionSystem::insertEngine(const KoColorSpaceEngine* engine) { NodeKey key(engine->id(), engine->id(), engine->id()); Node* n = new Node; n->modelId = engine->id(); n->depthId = engine->id(); n->profileName = engine->id(); n->referenceDepth = 64; // engine don't have reference depth, d->graph[ key ] = n; n->init(engine); return n; } void KoColorConversionSystem::insertColorSpace(const KoColorSpaceFactory* csf) { dbgPigment << "Inserting color space " << csf->name() << " (" << csf->id() << ") Model: " << csf->colorModelId() << " Depth: " << csf->colorDepthId() << " into the CCS"; QList profiles = KoColorSpaceRegistry::instance()->profilesFor(csf); QString modelId = csf->colorModelId().id(); QString depthId = csf->colorDepthId().id(); if (profiles.isEmpty()) { // There is no profile for this CS, create a node without profile name if the color engine isn't icc-based if (csf->colorSpaceEngine() != "icc") { Node* n = nodeFor(modelId, depthId, "default"); n->init(csf); } else { dbgPigment << "Cannot add node for " << csf->name() << ", since there are no profiles available"; } } else { // Initialise the nodes foreach(const KoColorProfile* profile, profiles) { Node* n = nodeFor(modelId, depthId, profile->name()); n->init(csf); if (!csf->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(csf->colorSpaceEngine()); Q_ASSERT(engine); NodeKey engineKey(engine->id(), engine->id(), engine->id()); Node* engineNode = 0; if (d->graph.contains(engineKey)) { engineNode = d->graph[engineKey]; } else { engineNode = insertEngine(engine); } connectToEngine(n, engineNode); } } } // Construct a link for "custom" transformation QList cctfs = csf->colorConversionLinks(); foreach(KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } void KoColorConversionSystem::insertColorProfile(const KoColorProfile* _profile) { dbgPigmentCCS << _profile->name(); const QList< const KoColorSpaceFactory* >& factories = KoColorSpaceRegistry::instance()->colorSpacesFor(_profile); foreach(const KoColorSpaceFactory* factory, factories) { QString modelId = factory->colorModelId().id(); QString depthId = factory->colorDepthId().id(); Node* n = nodeFor(modelId, depthId, _profile->name()); n->init(factory); if (!factory->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(factory->colorSpaceEngine()); Q_ASSERT(engine); Node* engineNode = d->graph[ NodeKey(engine->id(), engine->id(), engine->id())]; Q_ASSERT(engineNode); connectToEngine(n, engineNode); } QList cctfs = factory->colorConversionLinks(); foreach(KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); if (srcNode == n || dstNode == n) { // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } } } const KoColorSpace* KoColorConversionSystem::defaultColorSpaceForNode(const Node* node) const { return KoColorSpaceRegistry::instance()->colorSpace(node->modelId, node->depthId, node->profileName); } KoColorConversionSystem::Node* KoColorConversionSystem::createNode(const QString& _modelId, const QString& _depthId, const QString& _profileName) { Node* n = new Node; n->modelId = _modelId; n->depthId = _depthId; n->profileName = _profileName; d->graph[ NodeKey(_modelId, _depthId, _profileName)] = n; Q_ASSERT(vertexBetween(d->alphaNode, n) == 0); // The two color spaces should not be connected yet Vertex* vFromAlpha = createVertex(d->alphaNode, n); vFromAlpha->setFactoryFromSrc(new KoColorConversionFromAlphaTransformationFactory(_modelId, _depthId, _profileName)); Q_ASSERT(vertexBetween(n, d->alphaNode) == 0); // The two color spaces should not be connected yet Vertex* vToAlpha = createVertex(n, d->alphaNode); vToAlpha->setFactoryFromDst(new KoColorConversionToAlphaTransformationFactory(_modelId, _depthId, _profileName)); return n; } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorSpace* _colorSpace) const { const KoColorProfile* profile = _colorSpace->profile(); return nodeFor(_colorSpace->colorModelId().id(), _colorSpace->colorDepthId().id(), profile ? profile->name() : "default"); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) const { dbgPigmentCCS << "Look for node: " << _colorModelId << " " << _colorDepthId << " " << _profileName; return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const NodeKey& key) const { dbgPigmentCCS << "Look for node: " << key.modelId << " " << key.depthId << " " << key.profileName << " " << d->graph.value(key); return d->graph.value(key); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) { return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorConversionSystem::NodeKey& key) { if (d->graph.contains(key)) { return d->graph.value(key); } else { return createNode(key.modelId, key.depthId, key.profileName); } } QList KoColorConversionSystem::nodesFor(const QString& _modelId, const QString& _depthId) { QList nodes; foreach(Node* node, d->graph) { if (node->modelId == _modelId && node->depthId == _depthId) { nodes << node; } } return nodes; } KoColorConversionTransformation* KoColorConversionSystem::createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*srcColorSpace == *dstColorSpace) { return new KoCopyColorConversionTransformation(srcColorSpace); } Q_ASSERT(srcColorSpace); Q_ASSERT(dstColorSpace); dbgPigmentCCS << srcColorSpace->id() << (srcColorSpace->profile() ? srcColorSpace->profile()->name() : "default"); dbgPigmentCCS << dstColorSpace->id() << (dstColorSpace->profile() ? dstColorSpace->profile()->name() : "default"); Path* path = findBestPath( nodeFor(srcColorSpace), nodeFor(dstColorSpace)); Q_ASSERT(path); KoColorConversionTransformation* transfo = createTransformationFromPath(path, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); delete path; Q_ASSERT(*transfo->srcColorSpace() == *srcColorSpace); Q_ASSERT(*transfo->dstColorSpace() == *dstColorSpace); Q_ASSERT(transfo); return transfo; } void KoColorConversionSystem::createColorConverters(const KoColorSpace* colorSpace, QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const { // TODO This function currently only select the best conversion only based on the transformation // from colorSpace to one of the color spaces in the list, but not the other way around // it might be worth to look also the return path. const Node* csNode = nodeFor(colorSpace); PathQualityChecker pQC(csNode->referenceDepth, !csNode->isHdr, !csNode->isGray); // Look for a color conversion Path* bestPath = 0; typedef QPair KoID2KoID; foreach(const KoID2KoID & possibility, possibilities) { const KoColorSpaceFactory* csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(KoColorSpaceRegistry::instance()->colorSpaceId(possibility.first.id(), possibility.second.id())); if (csf) { Path* path = findBestPath(csNode, nodeFor(csf->colorModelId().id(), csf->colorDepthId().id(), csf->defaultProfile())); Q_ASSERT(path); path->isGood = pQC.isGoodPath(path); if (!bestPath) { bestPath = path; } else if ((!bestPath->isGood && path->isGood) || pQC.lessWorseThan(path, bestPath)) { delete bestPath; bestPath = path; } else { delete path; } } } Q_ASSERT(bestPath); const KoColorSpace* endColorSpace = defaultColorSpaceForNode(bestPath->endNode()); - fromCS = createTransformationFromPath(bestPath, colorSpace, endColorSpace, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + fromCS = createTransformationFromPath(bestPath, colorSpace, endColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Path* returnPath = findBestPath(bestPath->endNode(), csNode); Q_ASSERT(returnPath); - toCS = createTransformationFromPath(returnPath, endColorSpace, colorSpace, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + toCS = createTransformationFromPath(returnPath, endColorSpace, colorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Q_ASSERT(*toCS->dstColorSpace() == *fromCS->srcColorSpace()); Q_ASSERT(*fromCS->dstColorSpace() == *toCS->srcColorSpace()); } KoColorConversionTransformation* KoColorConversionSystem::createTransformationFromPath(const Path* path, const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(srcColorSpace->colorModelId().id() == path->startNode()->modelId); Q_ASSERT(srcColorSpace->colorDepthId().id() == path->startNode()->depthId); Q_ASSERT(dstColorSpace->colorModelId().id() == path->endNode()->modelId); Q_ASSERT(dstColorSpace->colorDepthId().id() == path->endNode()->depthId); KoColorConversionTransformation* transfo; QList< Path::node2factory > pathOfNode = path->compressedPath(); if (pathOfNode.size() == 2) { // Direct connection transfo = pathOfNode[1].second->createColorTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { KoMultipleColorConversionTransformation* mccTransfo = new KoMultipleColorConversionTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); transfo = mccTransfo; // Get the first intermediary color space dbgPigmentCCS << pathOfNode[ 0 ].first->id() << " to " << pathOfNode[ 1 ].first->id(); const KoColorSpace* intermCS = defaultColorSpaceForNode(pathOfNode[1].first); mccTransfo->appendTransfo(pathOfNode[1].second->createColorTransformation(srcColorSpace, intermCS, renderingIntent, conversionFlags)); for (int i = 2; i < pathOfNode.size() - 1; i++) { dbgPigmentCCS << pathOfNode[ i - 1 ].first->id() << " to " << pathOfNode[ i ].first->id(); const KoColorSpace* intermCS2 = defaultColorSpaceForNode(pathOfNode[i].first); Q_ASSERT(intermCS2); mccTransfo->appendTransfo(pathOfNode[i].second->createColorTransformation(intermCS, intermCS2, renderingIntent, conversionFlags)); intermCS = intermCS2; } dbgPigmentCCS << pathOfNode[ pathOfNode.size() - 2 ].first->id() << " to " << pathOfNode[ pathOfNode.size() - 1 ].first->id(); mccTransfo->appendTransfo(pathOfNode.last().second->createColorTransformation(intermCS, dstColorSpace, renderingIntent, conversionFlags)); } return transfo; } KoColorConversionSystem::Vertex* KoColorConversionSystem::vertexBetween(KoColorConversionSystem::Node* srcNode, KoColorConversionSystem::Node* dstNode) { foreach(Vertex* oV, srcNode->outputVertexes) { if (oV->dstNode == dstNode) { return oV; } } return 0; } KoColorConversionSystem::Vertex* KoColorConversionSystem::createVertex(Node* srcNode, Node* dstNode) { Vertex* v = new Vertex(srcNode, dstNode); srcNode->outputVertexes.append(v); d->vertexes.append(v); return v; } // -- Graph visualization functions -- QString KoColorConversionSystem::vertexToDot(KoColorConversionSystem::Vertex* v, QString options) const { return QString(" \"%1\" -> \"%2\" %3\n").arg(v->srcNode->id()).arg(v->dstNode->id()).arg(options); } QString KoColorConversionSystem::toDot() const { QString dot = "digraph CCS {\n"; foreach(Vertex* oV, d->vertexes) { dot += vertexToDot(oV, "default") ; } dot += "}\n"; return dot; } bool KoColorConversionSystem::existsPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { dbgPigmentCCS << "srcModelId = " << srcModelId << " srcDepthId = " << srcDepthId << " srcProfileName = " << srcProfileName << " dstModelId = " << dstModelId << " dstDepthId = " << dstDepthId << " dstProfileName = " << dstProfileName; const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path* path = findBestPath(srcNode, dstNode); bool exist = path; delete path; return exist; } bool KoColorConversionSystem::existsGoodPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path* path = findBestPath(srcNode, dstNode); bool existAndGood = path && path->isGood; delete path; return existAndGood; } QString KoColorConversionSystem::bestPathToDot(const QString& srcKey, const QString& dstKey) const { const Node* srcNode = 0; const Node* dstNode = 0; foreach(Node* node, d->graph) { if (node->id() == srcKey) { srcNode = node; } if (node->id() == dstKey) { dstNode = node; } } Path* p = findBestPath(srcNode, dstNode); Q_ASSERT(p); QString dot = "digraph CCS {\n" + QString(" \"%1\" [color=red]\n").arg(srcNode->id()) + QString(" \"%1\" [color=red]\n").arg(dstNode->id()); foreach(Vertex* oV, d->vertexes) { QString options; if (p->vertexes.contains(oV)) { options = "[color=red]"; } dot += vertexToDot(oV, options) ; } dot += "}\n"; return dot; } void KoColorConversionSystem::deletePaths(QList paths) const { foreach(Path* path, paths) { delete path; } } inline KoColorConversionSystem::Path* KoColorConversionSystem::findBestPathImpl2(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr, bool ignoreColorCorrectness) const { PathQualityChecker pQC(qMin(srcNode->referenceDepth, dstNode->referenceDepth), ignoreHdr, ignoreColorCorrectness); Node2PathHash node2path; // current best path to reach a given node QList currentPaths; // list of all paths // Generate the initial list of paths foreach(Vertex* v, srcNode->outputVertexes) { if (v->dstNode->isInitialized) { Path* p = new Path; p->appendVertex(v); Node* endNode = p->endNode(); if (endNode == dstNode) { Q_ASSERT(pQC.isGoodPath(p)); // <- it's a direct link, it has to be a good path, damn it not or go fix your color space not deletePaths(currentPaths); // clean up p->isGood = true; return p; } else { Q_ASSERT(!node2path.contains(endNode)); // That would be a total fuck up if there are two vertexes between two nodes node2path[ endNode ] = new Path(*p); currentPaths.append(p); } } } Path* lessWorsePath = 0; // Now loop until a path has been found while (currentPaths.size() > 0) { foreach(Path* p, currentPaths) { Node* endNode = p->endNode(); foreach(Vertex* v, endNode->outputVertexes) { if (!p->contains(v->dstNode) && v->dstNode->isInitialized) { Path* newP = new Path(*p); newP->appendVertex(v); Node* newEndNode = newP->endNode(); if (newEndNode == dstNode) { if (pQC.isGoodPath(newP)) { // Victory deletePaths(currentPaths); // clean up newP->isGood = true; return newP; } else if (!lessWorsePath) { lessWorsePath = newP; } else if (pQC.lessWorseThan(newP, lessWorsePath)) { Q_ASSERT(newP->startNode()->id() == lessWorsePath->startNode()->id()); Q_ASSERT(newP->endNode()->id() == lessWorsePath->endNode()->id()); warnPigment << pQC.lessWorseThan(newP, lessWorsePath) << " " << newP << " " << lessWorsePath; delete lessWorsePath; lessWorsePath = newP; } else { delete newP; } } else { if (node2path.contains(newEndNode)) { Path* p2 = node2path[newEndNode]; if (pQC.lessWorseThan(newP, p2)) { node2path[ newEndNode ] = new Path(*newP); currentPaths.append(newP); delete p2; } else { delete newP; } } else { node2path[ newEndNode ] = new Path(*newP); currentPaths.append(newP); } } } } currentPaths.removeAll(p); delete p; } } if (lessWorsePath) { warnPigment << "No good path from " << srcNode->id() << " to " << dstNode->id() << " found : length = " << lessWorsePath->length() << " cost = " << lessWorsePath->cost << " referenceDepth = " << lessWorsePath->referenceDepth << " respectColorCorrectness = " << lessWorsePath->respectColorCorrectness << " isGood = " << lessWorsePath->isGood ; return lessWorsePath; } errorPigment << "No path from " << srcNode->id() << " to " << dstNode->id() << " found not "; return 0; } inline KoColorConversionSystem::Path* KoColorConversionSystem::findBestPathImpl(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); return findBestPathImpl2(srcNode, dstNode, ignoreHdr, (srcNode->isGray || dstNode->isGray)); } KoColorConversionSystem::Path* KoColorConversionSystem::findBestPath(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); dbgPigmentCCS << "Find best path between " << srcNode->id() << " and " << dstNode->id(); if (srcNode->isHdr && dstNode->isHdr) { return findBestPathImpl(srcNode, dstNode, false); } else { return findBestPathImpl(srcNode, dstNode, true); } } diff --git a/libs/pigment/KoColorConversionTransformation.cpp b/libs/pigment/KoColorConversionTransformation.cpp index b32e41ddb58..5e4bdfdb4c6 100644 --- a/libs/pigment/KoColorConversionTransformation.cpp +++ b/libs/pigment/KoColorConversionTransformation.cpp @@ -1,88 +1,81 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorConversionTransformation.h" #include "KoColorSpace.h" -KoColorConversionTransformation::Intent KoColorConversionTransformation::InternalRenderingIntent = IntentPerceptual; -KoColorConversionTransformation::ConversionFlags KoColorConversionTransformation::InternalConversionFlags = BlackpointCompensation; - -KoColorConversionTransformation::Intent KoColorConversionTransformation::AdjustmentRenderingIntent = IntentPerceptual; -KoColorConversionTransformation::ConversionFlags KoColorConversionTransformation::AdjustmentConversionFlags = BlackpointCompensation | NoWhiteOnWhiteFixup; - - struct Q_DECL_HIDDEN KoColorConversionTransformation::Private { const KoColorSpace* srcColorSpace; const KoColorSpace* dstColorSpace; Intent renderingIntent; ConversionFlags conversionFlags; }; KoColorConversionTransformation::KoColorConversionTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, ConversionFlags conversionFlags) : d(new Private) { Q_ASSERT(srcCs); Q_ASSERT(dstCs); d->srcColorSpace = srcCs; d->dstColorSpace = dstCs; d->renderingIntent = renderingIntent; d->conversionFlags = conversionFlags; } KoColorConversionTransformation::~KoColorConversionTransformation() { delete d; } const KoColorSpace* KoColorConversionTransformation::srcColorSpace() const { return d->srcColorSpace; } const KoColorSpace* KoColorConversionTransformation::dstColorSpace() const { return d->dstColorSpace; } KoColorConversionTransformation::Intent KoColorConversionTransformation::renderingIntent() const { return d->renderingIntent; } KoColorConversionTransformation::ConversionFlags KoColorConversionTransformation::conversionFlags() const { return d->conversionFlags; } void KoColorConversionTransformation::setSrcColorSpace(const KoColorSpace* cs) const { Q_ASSERT(*d->srcColorSpace == *cs); d->srcColorSpace = cs; } void KoColorConversionTransformation::setDstColorSpace(const KoColorSpace* cs) const { Q_ASSERT(*d->dstColorSpace == *cs); d->dstColorSpace = cs; } diff --git a/libs/pigment/KoColorConversionTransformation.h b/libs/pigment/KoColorConversionTransformation.h index 541b3b7742a..af7f39a546b 100644 --- a/libs/pigment/KoColorConversionTransformation.h +++ b/libs/pigment/KoColorConversionTransformation.h @@ -1,139 +1,139 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_COLOR_CONVERSION_TRANSFORMATION_H_ #define _KO_COLOR_CONVERSION_TRANSFORMATION_H_ #include "KoColorTransformation.h" #include "pigment_export.h" class KoColorSpace; class KoColorConversionCache; /** * This is the base class of all color transform that convert the color of a pixel */ class PIGMENTCMS_EXPORT KoColorConversionTransformation : public KoColorTransformation { friend class KoColorConversionCache; struct Private; public: /** * Possible value for the intent of a color conversion (useful only for ICC * transformations) */ enum Intent { IntentPerceptual = 0, IntentRelativeColorimetric = 1, IntentSaturation = 2, IntentAbsoluteColorimetric = 3 }; /** * Flags for the color conversion, see lcms2 documentation for more information */ enum ConversionFlag { Empty = 0x0, NoOptimization = 0x0100, GamutCheck = 0x1000, // Out of Gamut alarm SoftProofing = 0x4000, // Do softproofing BlackpointCompensation = 0x2000, NoWhiteOnWhiteFixup = 0x0004, // Don't fix scum dot HighQuality = 0x0400, // Use more memory to give better accurancy LowQuality = 0x0800 // Use less memory to minimize resouces }; Q_DECLARE_FLAGS(ConversionFlags, ConversionFlag) /** * We have numerous places where we need to convert color spaces. * * In several cases the user asks us about the conversion * explicitly, e.g. when changing the image type or converting * pixel data to the monitor profile. Doing this explicitly the * user can choose what rendering intent and conversion flags to * use. * * But there are also cases when we have to do a conversion * internally (transparently for the user), for example, when * merging heterogeneous images, creating thumbnails, converting * data to/from QImage or while doing some adjustments. We cannot * ask the user about parameters for every single * conversion. That's why in all these non-critical cases the * following default values should be used. */ - static Intent InternalRenderingIntent; - static ConversionFlags InternalConversionFlags; + static Intent internalRenderingIntent() { return IntentPerceptual; } + static ConversionFlags internalConversionFlags() { return BlackpointCompensation; } - static Intent AdjustmentRenderingIntent; - static ConversionFlags AdjustmentConversionFlags; + static Intent adjustmentRenderingIntent() { return IntentPerceptual; } + static ConversionFlags adjustmentConversionFlags() { return ConversionFlags(BlackpointCompensation | NoWhiteOnWhiteFixup); } public: KoColorConversionTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, ConversionFlags conversionFlags); ~KoColorConversionTransformation(); public: /** * @return the source color space for this transformation. */ const KoColorSpace* srcColorSpace() const; /** * @return the destination color space for this transformation. */ const KoColorSpace* dstColorSpace() const; /** * @return the rendering intent of this transformation (this is only useful * for ICC transformations) */ Intent renderingIntent() const; /** * @return the conversion flags */ ConversionFlags conversionFlags() const; /** * perform the color conversion between two buffers. * @param nPixels the number of pixels in the buffers. */ virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const = 0; /** * @return false if the transformation is not valid */ virtual bool isValid() const { return true; } private: void setSrcColorSpace(const KoColorSpace*) const; void setDstColorSpace(const KoColorSpace*) const; Private * const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KoColorConversionTransformation::ConversionFlags) #endif diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp index 02b52b76f19..7a558620ed8 100644 --- a/libs/pigment/KoColorSpace.cpp +++ b/libs/pigment/KoColorSpace.cpp @@ -1,358 +1,358 @@ /* * Copyright (c) 2005 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSpace.h" #include "KoColorSpace_p.h" #include "KoChannelInfo.h" #include "DebugPigment.h" #include "KoCompositeOp.h" #include "KoColorTransformation.h" #include "KoColorTransformationFactory.h" #include "KoColorTransformationFactoryRegistry.h" #include "KoColorConversionCache.h" #include "KoColorConversionSystem.h" #include "KoColorSpaceRegistry.h" #include "KoColorProfile.h" #include "KoCopyColorConversionTransformation.h" #include "KoFallBackColorTransformation.h" #include "KoUniqueNumberForIdServer.h" #include "KoMixColorsOp.h" #include "KoConvolutionOp.h" #include "KoCompositeOpRegistry.h" #include #include #include KoColorSpace::KoColorSpace() : d(new Private()) { } KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp) : d(new Private()) { d->id = id; d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id); d->name = name; d->mixColorsOp = mixColorsOp; d->convolutionOp = convolutionOp; d->transfoToRGBA16 = 0; d->transfoFromRGBA16 = 0; d->transfoToLABA16 = 0; d->transfoFromLABA16 = 0; d->deletability = NotOwnedByRegistry; } KoColorSpace::~KoColorSpace() { Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete); qDeleteAll(d->compositeOps); foreach(KoChannelInfo * channel, d->channels) { delete channel; } if (d->deletability == NotOwnedByRegistry) { KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache(); if (cache) { cache->colorSpaceIsDestroyed(this); } } delete d->mixColorsOp; delete d->convolutionOp; delete d->transfoToRGBA16; delete d->transfoFromRGBA16; delete d->transfoToLABA16; delete d->transfoFromLABA16; delete d; } bool KoColorSpace::operator==(const KoColorSpace& rhs) const { const KoColorProfile* p1 = rhs.profile(); const KoColorProfile* p2 = profile(); return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2)); } QString KoColorSpace::id() const { return d->id; } QString KoColorSpace::name() const { return d->name; } QList KoColorSpace::channels() const { return d->channels; } QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const { QBitArray ba(d->channels.size()); if (!color && !alpha) return ba; for (int i = 0; i < d->channels.size(); ++i) { KoChannelInfo * channel = d->channels.at(i); if ((color && channel->channelType() == KoChannelInfo::COLOR) || (alpha && channel->channelType() == KoChannelInfo::ALPHA)) ba.setBit(i, true); } return ba; } void KoColorSpace::addChannel(KoChannelInfo * ci) { d->channels.push_back(ci); } bool KoColorSpace::hasCompositeOp(const QString& id) const { return d->compositeOps.contains(id); } QList KoColorSpace::compositeOps() const { return d->compositeOps.values(); } KoMixColorsOp* KoColorSpace::mixColorsOp() const { return d->mixColorsOp; } KoConvolutionOp* KoColorSpace::convolutionOp() const { return d->convolutionOp; } const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const { if (d->compositeOps.contains(id)) return d->compositeOps.value(id); else { warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER; return d->compositeOps.value(COMPOSITE_OVER); } } void KoColorSpace::addCompositeOp(const KoCompositeOp * op) { if (op->colorSpace()->id() == id()) { d->compositeOps.insert(op->id(), const_cast(op)); } } const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const { if (!d->transfoToLABA16) { - d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(""), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ; + d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToLABA16; } const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const { if (!d->transfoFromLABA16) { - d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(""), this, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ; + d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(""), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromLABA16; } const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const { if (!d->transfoToRGBA16) { - d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(""), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ; + d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToRGBA16; } const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const { if (!d->transfoFromRGBA16) { - d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16("") , this, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ; + d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16("") , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromRGBA16; } void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toRgbA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromRgbA16Converter()->transform(src, dst, nPixels); } KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { return new KoCopyColorConversionTransformation(this); } else { return KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags); } } bool KoColorSpace::convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { if (src != dst) { memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize()); } } else { KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags); cct.transformation()->transform(src, dst, numPixels); } return true; } void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT_X(*op->colorSpace() == *this, "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1()); if(params.rows <= 0 || params.cols <= 0) return; if(!(*this == *srcSpace)) { if (preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(op->id())) { quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize(); QVector * conversionDstCache = threadLocalConversionCache(params.rows * conversionDstBufferStride); quint8* conversionDstData = conversionDstCache->data(); for(qint32 row=0; rowcompositeOp(op->id()); KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.dstRowStart = conversionDstData; paramInfo.dstRowStride = conversionDstBufferStride; otherOp->composite(paramInfo); for(qint32 row=0; rowconvertPixelsTo(conversionDstData + row * conversionDstBufferStride, params.dstRowStart + row * params.dstRowStride, this, params.cols, renderingIntent, conversionFlags); } } else { quint32 conversionBufferStride = params.cols * pixelSize(); QVector * conversionCache = threadLocalConversionCache(params.rows * conversionBufferStride); quint8* conversionData = conversionCache->data(); for(qint32 row=0; rowconvertPixelsTo(params.srcRowStart + row * params.srcRowStride, conversionData + row * conversionBufferStride, this, params.cols, renderingIntent, conversionFlags); } KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.srcRowStart = conversionData; paramInfo.srcRowStride = conversionBufferStride; op->composite(paramInfo); } } else { op->composite(params); } } QVector * KoColorSpace::threadLocalConversionCache(quint32 size) const { QVector * ba = 0; if (!d->conversionCache.hasLocalData()) { ba = new QVector(size, '0'); d->conversionCache.setLocalData(ba); } else { ba = d->conversionCache.localData(); if ((quint8)ba->size() < size) ba->resize(size); } return ba; } KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash & parameters) const { KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id); if (!factory) return 0; QPair model(colorModelId(), colorDepthId()); QList< QPair > models = factory->supportedModels(); if (models.isEmpty() || models.contains(model)) { return factory->createTransformation(this, parameters); } else { // Find the best solution // TODO use the color conversion cache KoColorConversionTransformation* csToFallBack = 0; KoColorConversionTransformation* fallBackToCs = 0; KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverters(this, models, csToFallBack, fallBackToCs); Q_ASSERT(csToFallBack); Q_ASSERT(fallBackToCs); KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters); return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo); } } QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { QImage img = QImage(width, height, QImage::Format_ARGB32); const KoColorSpace * dstCS = KoColorSpaceRegistry::instance()->rgb8(dstProfile); if (data) this->convertPixelsTo(const_cast(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags); return img; } bool KoColorSpace::preferCompositionInSourceColorSpace() const { return false; } diff --git a/libs/pigment/KoCopyColorConversionTransformation.cpp b/libs/pigment/KoCopyColorConversionTransformation.cpp index 0a95b2f2118..f0c7fc2152d 100644 --- a/libs/pigment/KoCopyColorConversionTransformation.cpp +++ b/libs/pigment/KoCopyColorConversionTransformation.cpp @@ -1,57 +1,57 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCopyColorConversionTransformation.h" #include // --- KoCopyColorConversionTransformation --- KoCopyColorConversionTransformation::KoCopyColorConversionTransformation(const KoColorSpace* cs) - : KoColorConversionTransformation(cs, cs, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) + : KoColorConversionTransformation(cs, cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) { } void KoCopyColorConversionTransformation::transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const { memcpy(dstU8, srcU8, nPixels * srcColorSpace()->pixelSize()); } // --- KoCopyColorConversionTransformationFactory --- KoCopyColorConversionTransformationFactory::KoCopyColorConversionTransformationFactory(const QString& _colorModelId, const QString& _depthId, const QString& _profileName) : KoColorConversionTransformationFactory(_colorModelId, _depthId, _profileName, _colorModelId, _depthId, _profileName) {} KoColorConversionTransformation* KoCopyColorConversionTransformationFactory::createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_UNUSED(renderingIntent); Q_UNUSED(conversionFlags); #ifdef QT_NO_DEBUG Q_UNUSED(dstColorSpace); #endif Q_UNUSED(dstColorSpace); Q_ASSERT(canBeSource(srcColorSpace)); Q_ASSERT(canBeDestination(dstColorSpace)); Q_ASSERT(srcColorSpace->id() == dstColorSpace->id()); return new KoCopyColorConversionTransformation(srcColorSpace); } bool KoCopyColorConversionTransformationFactory::conserveColorInformation() const { return true; } bool KoCopyColorConversionTransformationFactory::conserveDynamicRange() const { return true; } diff --git a/libs/pigment/KoFallBackColorTransformation.cpp b/libs/pigment/KoFallBackColorTransformation.cpp index 59863a7df6e..e46c5f5016b 100644 --- a/libs/pigment/KoFallBackColorTransformation.cpp +++ b/libs/pigment/KoFallBackColorTransformation.cpp @@ -1,109 +1,109 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoFallBackColorTransformation.h" #include "KoColorConversionTransformation.h" #include "KoColorSpace.h" #include "KoColorTransformation.h" #include "KoColorConversionCache.h" #include "KoColorSpaceRegistry.h" #include "DebugPigment.h" struct Q_DECL_HIDDEN KoFallBackColorTransformation::Private { const KoColorSpace* fallBackColorSpace; KoCachedColorConversionTransformation* csToFallBackCache; KoCachedColorConversionTransformation* fallBackToCsCache; const KoColorConversionTransformation* csToFallBack; const KoColorConversionTransformation* fallBackToCs; KoColorTransformation* colorTransformation; mutable quint8* buff; mutable qint32 buffSize; }; KoFallBackColorTransformation::KoFallBackColorTransformation(const KoColorSpace* _cs, const KoColorSpace* _fallBackCS, KoColorTransformation* _transfo) : d(new Private) { d->fallBackColorSpace = _fallBackCS; - d->csToFallBackCache = new KoCachedColorConversionTransformation(KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(_cs, _fallBackCS, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags)); + d->csToFallBackCache = new KoCachedColorConversionTransformation(KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(_cs, _fallBackCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags())); d->csToFallBack = d->csToFallBackCache->transformation(); - d->fallBackToCsCache = new KoCachedColorConversionTransformation(KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(_fallBackCS, _cs, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags)); + d->fallBackToCsCache = new KoCachedColorConversionTransformation(KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(_fallBackCS, _cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags())); d->fallBackToCs = d->fallBackToCsCache->transformation(); d->colorTransformation = _transfo; d->buff = 0; d->buffSize = 0; } KoFallBackColorTransformation::KoFallBackColorTransformation(KoColorConversionTransformation* _csToFallBack, KoColorConversionTransformation* _fallBackToCs, KoColorTransformation* _transfo) : d(new Private) { Q_ASSERT(*_csToFallBack->srcColorSpace() == *_fallBackToCs->dstColorSpace()); Q_ASSERT(*_fallBackToCs->srcColorSpace() == *_csToFallBack->dstColorSpace()); d->fallBackColorSpace = _fallBackToCs->srcColorSpace(); d->csToFallBack = _csToFallBack; d->fallBackToCs = _fallBackToCs; d->csToFallBackCache = 0; d->fallBackToCsCache = 0; d->colorTransformation = _transfo; d->buff = 0; d->buffSize = 0; } KoFallBackColorTransformation::~KoFallBackColorTransformation() { if (d->csToFallBackCache) { delete d->csToFallBackCache; } else { delete d->csToFallBack; } if (d->csToFallBackCache) { delete d->fallBackToCsCache; } else { delete d->fallBackToCs; } delete d->colorTransformation; delete[] d->buff; delete d; } void KoFallBackColorTransformation::transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { if (d->buffSize < nPixels) { // Expand the buffer if needed d->buffSize = nPixels; delete[] d->buff; d->buff = new quint8[ d->buffSize * d->fallBackColorSpace->pixelSize()]; } d->csToFallBack->transform(src, d->buff, nPixels); d->colorTransformation->transform(d->buff, d->buff, nPixels); d->fallBackToCs->transform(d->buff, dst, nPixels); } QList KoFallBackColorTransformation::parameters() const { return d->colorTransformation->parameters(); } int KoFallBackColorTransformation::parameterId(const QString& name) const { return d->colorTransformation->parameterId(name); } void KoFallBackColorTransformation::setParameter(int id, const QVariant& parameter) { d->colorTransformation->setParameter(id, parameter); } diff --git a/libs/pigment/KoScaleColorConversionTransformation.h b/libs/pigment/KoScaleColorConversionTransformation.h index a8493814ded..16101cafc62 100644 --- a/libs/pigment/KoScaleColorConversionTransformation.h +++ b/libs/pigment/KoScaleColorConversionTransformation.h @@ -1,74 +1,74 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KO_SCALE_COLOR_CONVERSION_TRANSFORMATION_H_ #define _KO_SCALE_COLOR_CONVERSION_TRANSFORMATION_H_ #include #include /** * This transformation allows to convert between two color spaces with the same * color model but different channel type. */ template class KoScaleColorConversionTransformation : public KoColorConversionTransformation { public: KoScaleColorConversionTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs) : KoColorConversionTransformation(srcCs, dstCs) { Q_ASSERT(srcCs->colorModelId() == dstCs->colorModelId()); } virtual void transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const { const typename _src_CSTraits_::channels_type* src = _src_CSTraits_::nativeArray(srcU8); typename _dst_CSTraits_::channels_type* dst = _dst_CSTraits_::nativeArray(dstU8); for (quint32 i = 0; i < _src_CSTraits_::channels_nb * nPixels; i++) { dst[i] = KoColorSpaceMaths::scaleToA(src[i]); } } }; /** * Factory to create KoScaleColorConversionTransformation. */ template class KoScaleColorConversionTransformationFactory : public KoColorConversionTransformationFactory { public: KoScaleColorConversionTransformationFactory(const QString& _colorModelId, const QString& _profileName, const QString& _srcDepthId, const QString& _dstDepthId) : KoColorConversionTransformationFactory(_colorModelId, _srcDepthId, _profileName, _colorModelId, _dstDepthId, _profileName), hdr(((srcColorDepthId() == Float16BitsColorDepthID.id()) && (dstColorDepthId() == Float32BitsColorDepthID.id())) || ((srcColorDepthId() == Float32BitsColorDepthID.id()) && (dstColorDepthId() == Float16BitsColorDepthID.id()))) { } - virtual KoColorConversionTransformation* createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::InternalRenderingIntent) const { + virtual KoColorConversionTransformation* createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent()) const { Q_UNUSED(renderingIntent); Q_ASSERT(canBeSource(srcColorSpace)); Q_ASSERT(canBeDestination(dstColorSpace)); return new KoScaleColorConversionTransformation<_src_CSTraits_, _dst_CSTraits_>(srcColorSpace, dstColorSpace); } virtual bool conserveColorInformation() const { return true; } virtual bool conserveDynamicRange() const { return hdr; } private: bool hdr; }; #endif diff --git a/libs/pigment/colorspaces/KoSimpleColorSpace.h b/libs/pigment/colorspaces/KoSimpleColorSpace.h index ce711ed28a0..10145086110 100644 --- a/libs/pigment/colorspaces/KoSimpleColorSpace.h +++ b/libs/pigment/colorspaces/KoSimpleColorSpace.h @@ -1,213 +1,213 @@ /* * Copyright (c) 2004-2009 Boudewijn Rempt * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOSIMPLECOLORSPACE_H #define KOSIMPLECOLORSPACE_H #include #include #include "DebugPigment.h" #include "KoColorSpaceAbstract.h" #include "KoColorSpaceTraits.h" #include "KoSimpleColorSpaceFactory.h" #include "KoColorModelStandardIds.h" #include "colorprofiles/KoDummyColorProfile.h" template class KoSimpleColorSpace : public KoColorSpaceAbstract<_CSTraits> { public: KoSimpleColorSpace(const QString& id, const QString& name, const KoID& colorModelId, const KoID& colorDepthId) : KoColorSpaceAbstract<_CSTraits>(id, name) , m_name(name) , m_colorModelId(colorModelId) , m_colorDepthId(colorDepthId) , m_profile(new KoDummyColorProfile) { } virtual ~KoSimpleColorSpace() { delete m_profile; } virtual KoID colorModelId() const { return m_colorModelId; } virtual KoID colorDepthId() const { return m_colorDepthId; } virtual bool willDegrade(ColorSpaceIndependence independence) const { Q_UNUSED(independence); return false; } virtual bool profileIsCompatible(const KoColorProfile* /*profile*/) const { return true; } virtual quint8 difference(const quint8 *src1, const quint8 *src2) const { Q_UNUSED(src1); Q_UNUSED(src2); warnPigment << i18n("Undefined operation in the %1 space", m_name); return 0; } virtual quint8 differenceA(const quint8 *src1, const quint8 *src2) const { Q_UNUSED(src1); Q_UNUSED(src2); warnPigment << i18n("Undefined operation in the %1 space", m_name); return 0; } virtual quint32 colorSpaceType() const { return 0; } virtual bool hasHighDynamicRange() const { return false; } virtual const KoColorProfile* profile() const { return m_profile; } virtual KoColorTransformation* createBrightnessContrastAdjustment(const quint16*) const { warnPigment << i18n("Undefined operation in the %1 color space", m_name); return 0; } virtual KoColorTransformation* createDesaturateAdjustment() const { warnPigment << i18n("Undefined operation in the %1 color space", m_name); return 0; } virtual KoColorTransformation* createPerChannelAdjustment(const quint16* const*) const { warnPigment << i18n("Undefined operation in the %1 color space", m_name); return 0; } virtual KoColorTransformation *createDarkenAdjustment(qint32 , bool , qreal) const { warnPigment << i18n("Undefined operation in the %1 color space", m_name); return 0; } virtual void invertColor(quint8*, qint32) const { warnPigment << i18n("Undefined operation in the %1 color space", m_name); } virtual void colorToXML(const quint8* , QDomDocument& , QDomElement&) const { warnPigment << i18n("Undefined operation in the %1 color space", m_name); } virtual void colorFromXML(quint8* , const QDomElement&) const { warnPigment << i18n("Undefined operation in the %1 color space", m_name); } virtual void toLabA16(const quint8* src, quint8* dst, quint32 nPixels) const { if (colorDepthId() == Integer16BitsColorDepthID && colorModelId() == LABAColorModelID) { memcpy(dst, src, nPixels * 2); } else { const KoColorSpace* dstCs = KoColorSpaceRegistry::instance()->lab16(); convertPixelsTo(src, dst, dstCs, nPixels, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } } virtual void fromLabA16(const quint8* src, quint8* dst, quint32 nPixels) const { if (colorDepthId() == Integer16BitsColorDepthID && colorModelId() == LABAColorModelID) { memcpy(dst, src, nPixels * 2); } else { const KoColorSpace* srcCs = KoColorSpaceRegistry::instance()->lab16(); srcCs->convertPixelsTo(src, dst, this, nPixels, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } } virtual void toRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const { if (colorDepthId() == Integer16BitsColorDepthID && colorModelId() == RGBAColorModelID) { memcpy(dst, src, nPixels * 2); } else { const KoColorSpace* dstCs = KoColorSpaceRegistry::instance()->rgb16(); convertPixelsTo(src, dst, dstCs, nPixels, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } } virtual void fromRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const { if (colorDepthId() == Integer16BitsColorDepthID && colorModelId() == RGBAColorModelID) { memcpy(dst, src, nPixels * 2); } else { const KoColorSpace* srcCs = KoColorSpaceRegistry::instance()->rgb16(); srcCs->convertPixelsTo(src, dst, this, nPixels, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); } } virtual bool convertPixelsTo(const quint8 *src, quint8 *dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_UNUSED(renderingIntent); Q_UNUSED(conversionFlags); QColor c; quint32 srcPixelsize = this->pixelSize(); quint32 dstPixelsize = dstColorSpace->pixelSize(); while (numPixels > 0) { this->toQColor(src, &c); dstColorSpace->fromQColor(c, dst); src += srcPixelsize; dst += dstPixelsize; --numPixels; } return true; } virtual QString colorSpaceEngine() const { return "simple"; } private: QString m_name; KoID m_colorModelId; KoID m_colorDepthId; KoColorProfile* m_profile; }; #endif // KOSIMPLECOLORSPACE_H diff --git a/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.cpp b/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.cpp index 56ff5d6f51c..26fcd647d79 100644 --- a/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.cpp +++ b/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.cpp @@ -1,87 +1,87 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSimpleColorSpaceEngine.h" #include "KoColorModelStandardIds.h" #include #include "KoColorSpace.h" #include "DebugPigment.h" #include // -- KoSimpleColorConversionTransformation -- class KoSimpleColorConversionTransformation : public KoColorConversionTransformation { public: KoSimpleColorConversionTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs) : KoColorConversionTransformation(srcCs, dstCs, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags) { + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()) { } ~KoSimpleColorConversionTransformation() { } virtual void transform(const quint8 *src, quint8 *dst, qint32 numPixels) const { const KoColorSpace* srcCs = srcColorSpace(); const KoColorSpace* dstCs = dstColorSpace(); quint32 srcPixelsize = srcCs->pixelSize(); quint32 dstPixelsize = dstCs->pixelSize(); QColor c; while (numPixels > 0) { srcCs->toQColor(src, &c); dstCs->fromQColor(c, dst); src += srcPixelsize; dst += dstPixelsize; --numPixels; } } }; KoSimpleColorSpaceEngine::KoSimpleColorSpaceEngine() : KoColorSpaceEngine("simple", i18n("Simple Color Conversion Engine")) { } KoSimpleColorSpaceEngine::~KoSimpleColorSpaceEngine() { } KoColorConversionTransformation* KoSimpleColorSpaceEngine::createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_UNUSED(renderingIntent); Q_UNUSED(conversionFlags); return new KoSimpleColorConversionTransformation(srcColorSpace, dstColorSpace); } diff --git a/plugins/colorengines/lcms2/LcmsColorSpace.h b/plugins/colorengines/lcms2/LcmsColorSpace.h index bf9a262c2eb..1e7aba57f53 100644 --- a/plugins/colorengines/lcms2/LcmsColorSpace.h +++ b/plugins/colorengines/lcms2/LcmsColorSpace.h @@ -1,446 +1,446 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005-2006 C. Boemann * Copyright (c) 2004,2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOLCMSCOLORSPACE_H_ #define KOLCMSCOLORSPACE_H_ #include #include #include class KoLcmsInfo { struct Private { cmsUInt32Number cmType; // The colorspace type as defined by littlecms cmsColorSpaceSignature colorSpaceSignature; // The colorspace signature as defined in icm/icc files }; public: KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : d(new Private) { d->cmType = cmType; d->colorSpaceSignature = colorSpaceSignature; } virtual ~KoLcmsInfo() { delete d; } virtual quint32 colorSpaceType() const { return d->cmType; } virtual cmsColorSpaceSignature colorSpaceSignature() const { return d->colorSpaceSignature; } private: Private* const d; }; struct KoLcmsDefaultTransformations { cmsHTRANSFORM toRGB; cmsHTRANSFORM fromRGB; static cmsHPROFILE s_RGBProfile; static QMap< QString, QMap< LcmsColorProfileContainer*, KoLcmsDefaultTransformations* > > s_transformations; }; /** * This is the base class for all colorspaces that are based on the lcms library, for instance * RGB 8bits and 16bits, CMYK 8bits and 16bits, LAB... */ template class LcmsColorSpace : public KoColorSpaceAbstract<_CSTraits>, public KoLcmsInfo { struct KoLcmsColorTransformation : public KoColorTransformation { KoLcmsColorTransformation(const KoColorSpace* colorSpace) : KoColorTransformation() , m_colorSpace(colorSpace) { csProfile = 0; cmstransform = 0; cmsAlphaTransform = 0; profiles[0] = 0; profiles[1] = 0; profiles[2] = 0; } ~KoLcmsColorTransformation() { if (cmstransform) cmsDeleteTransform(cmstransform); if (profiles[0] && profiles[0] != csProfile) cmsCloseProfile(profiles[0]); if (profiles[1] && profiles[1] != csProfile) cmsCloseProfile(profiles[1]); if (profiles[2] && profiles[2] != csProfile) cmsCloseProfile(profiles[2]); } virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { cmsDoTransform(cmstransform, const_cast(src), dst, nPixels); qint32 numPixels = nPixels; qint32 pixelSize = m_colorSpace->pixelSize(); int index = 0; if (cmsAlphaTransform) { qreal *alpha = new qreal[nPixels]; qreal *dstalpha = new qreal[nPixels]; while (index < nPixels) { alpha[index] = m_colorSpace->opacityF(src); src += pixelSize; index++; } cmsDoTransform(cmsAlphaTransform, const_cast(alpha), static_cast(dstalpha), nPixels); for(int i = 0 ; i < numPixels ; i++) { m_colorSpace->setOpacity(dst, dstalpha[i], 1); dst += pixelSize; } delete [] alpha; delete [] dstalpha; } else { while (numPixels > 0) { qreal alpha = m_colorSpace->opacityF(src); m_colorSpace->setOpacity(dst, alpha, 1); src += pixelSize; dst += pixelSize; numPixels--; } } } const KoColorSpace* m_colorSpace; cmsHPROFILE csProfile; cmsHPROFILE profiles[3]; cmsHTRANSFORM cmstransform; cmsHTRANSFORM cmsAlphaTransform; }; struct Private { mutable quint8 *qcolordata; // A small buffer for conversion from and to qcolor. KoLcmsDefaultTransformations* defaultTransformations; mutable cmsHPROFILE lastRGBProfile; // Last used profile to transform to/from RGB mutable cmsHTRANSFORM lastToRGB; // Last used transform to transform to RGB mutable cmsHTRANSFORM lastFromRGB; // Last used transform to transform from RGB LcmsColorProfileContainer *profile; KoColorProfile* colorProfile; }; protected: LcmsColorSpace(const QString &id, const QString &name, cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature, KoColorProfile *p) : KoColorSpaceAbstract<_CSTraits>(id, name) , KoLcmsInfo(cmType, colorSpaceSignature) , d(new Private()) { Q_ASSERT(p); // No profile means the lcms color space can't work Q_ASSERT(profileIsCompatible(p)); d->profile = asLcmsProfile(p); Q_ASSERT(d->profile); d->colorProfile = p; d->qcolordata = 0; d->lastRGBProfile = 0; d->lastToRGB = 0; d->lastFromRGB = 0; d->defaultTransformations = 0; } virtual ~LcmsColorSpace() { delete d->colorProfile; delete[] d->qcolordata; delete d; } void init() { // Default pixel buffer for QColor conversion d->qcolordata = new quint8[3]; Q_CHECK_PTR(d->qcolordata); Q_ASSERT(d->profile); if (KoLcmsDefaultTransformations::s_RGBProfile == 0) { KoLcmsDefaultTransformations::s_RGBProfile = cmsCreate_sRGBProfile(); } d->defaultTransformations = KoLcmsDefaultTransformations::s_transformations[this->id()][ d->profile]; if (!d->defaultTransformations) { d->defaultTransformations = new KoLcmsDefaultTransformations; d->defaultTransformations->fromRGB = cmsCreateTransform(KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); Q_ASSERT(d->defaultTransformations->fromRGB); d->defaultTransformations->toRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); Q_ASSERT(d->defaultTransformations->toRGB); KoLcmsDefaultTransformations::s_transformations[ this->id()][ d->profile ] = d->defaultTransformations; } } public: virtual bool hasHighDynamicRange() const { return false; } virtual const KoColorProfile * profile() const { return d->colorProfile; } virtual bool profileIsCompatible(const KoColorProfile* profile) const { const IccColorProfile* p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * koprofile = 0) const { d->qcolordata[2] = color.red(); d->qcolordata[1] = color.green(); d->qcolordata[0] = color.blue(); LcmsColorProfileContainer* profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB Q_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); cmsDoTransform(d->defaultTransformations->fromRGB, d->qcolordata, dst, 1); } else { if (d->lastFromRGB == 0 || (d->lastFromRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastFromRGB = cmsCreateTransform(profile->lcmsProfile(), TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } cmsDoTransform(d->lastFromRGB, d->qcolordata, dst, 1); } this->setOpacity(dst, (quint8)(color.alpha()) , 1); } virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * koprofile = 0) const { LcmsColorProfileContainer* profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB transform Q_ASSERT(d->defaultTransformations && d->defaultTransformations->toRGB); cmsDoTransform(d->defaultTransformations->toRGB, const_cast (src), d->qcolordata, 1); } else { if (d->lastToRGB == 0 || (d->lastToRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastToRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), profile->lcmsProfile(), TYPE_BGR_8, - KoColorConversionTransformation::InternalRenderingIntent, - KoColorConversionTransformation::InternalConversionFlags); + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } cmsDoTransform(d->lastToRGB, const_cast (src), d->qcolordata, 1); } c->setRgb(d->qcolordata[2], d->qcolordata[1], d->qcolordata[0]); c->setAlpha(this->opacityU8(src)); } virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const { if (!d->profile) return 0; cmsToneCurve* transferFunctions[3]; transferFunctions[0] = cmsBuildTabulatedToneCurve16( 0, 256, transferValues); transferFunctions[1] = cmsBuildGamma(0, 1.0); transferFunctions[2] = cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigLabData, transferFunctions); cmsSetDeviceClass(adj->profiles[1], cmsSigAbstractClass); adj->profiles[0] = d->profile->lcmsProfile(); adj->profiles[2] = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateMultiprofileTransform(adj->profiles, 3, this->colorSpaceType(), this->colorSpaceType(), - KoColorConversionTransformation::AdjustmentRenderingIntent, - KoColorConversionTransformation::AdjustmentConversionFlags); + KoColorConversionTransformation::adjustmentRenderingIntent(), + KoColorConversionTransformation::adjustmentConversionFlags()); adj->csProfile = d->profile->lcmsProfile(); return adj; } virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const*transferValues) const { if (!d->profile) return 0; cmsToneCurve ** transferFunctions = new cmsToneCurve*[ this->colorChannelCount()]; for (uint ch = 0; ch < this->colorChannelCount(); ch++) { transferFunctions[ch] = transferValues[ch] ? cmsBuildTabulatedToneCurve16( 0, 256, transferValues[ch]) : cmsBuildGamma(0, 1.0); } cmsToneCurve ** alphaTransferFunctions = new cmsToneCurve*[1]; alphaTransferFunctions[0] = transferValues[this->colorChannelCount()] ? cmsBuildTabulatedToneCurve16( 0, 256, transferValues[this->colorChannelCount()]) : cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[0] = cmsCreateLinearizationDeviceLink(this->colorSpaceSignature(), transferFunctions); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigGrayData, alphaTransferFunctions); adj->profiles[2] = NULL; adj->csProfile = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateTransform(adj->profiles[0], this->colorSpaceType(), NULL, this->colorSpaceType(), - KoColorConversionTransformation::AdjustmentRenderingIntent, - KoColorConversionTransformation::AdjustmentConversionFlags); + KoColorConversionTransformation::adjustmentRenderingIntent(), + KoColorConversionTransformation::adjustmentConversionFlags()); adj->cmsAlphaTransform = cmsCreateTransform(adj->profiles[1], TYPE_GRAY_DBL, NULL, TYPE_GRAY_DBL, - KoColorConversionTransformation::AdjustmentRenderingIntent, - KoColorConversionTransformation::AdjustmentConversionFlags); + KoColorConversionTransformation::adjustmentRenderingIntent(), + KoColorConversionTransformation::adjustmentConversionFlags()); delete [] transferFunctions; delete [] alphaTransferFunctions; return adj; } virtual quint8 difference(const quint8* src1, const quint8* src2) const { quint8 lab1[8], lab2[8]; cmsCIELab labF1, labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); qreal diff = cmsDeltaE(&labF1, &labF2); if (diff > 255.0) return 255; else return quint8(diff); } virtual quint8 differenceA(const quint8* src1, const quint8* src2) const { quint8 lab1[8]; quint8 lab2[8]; cmsCIELab labF1; cmsCIELab labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); cmsFloat64Number dL; cmsFloat64Number da; cmsFloat64Number db; cmsFloat64Number dAlpha; dL = fabs((qreal)(labF1.L - labF2.L)); da = fabs((qreal)(labF1.a - labF2.a)); db = fabs((qreal)(labF1.b - labF2.b)); static const int LabAAlphaPos = 3; static const cmsFloat64Number alphaScale = 100.0 / KoColorSpaceMathsTraits::max; quint16 alpha1 = reinterpret_cast(lab1)[LabAAlphaPos]; quint16 alpha2 = reinterpret_cast(lab2)[LabAAlphaPos]; dAlpha = fabs((qreal)(alpha1 - alpha2)) * alphaScale; qreal diff = pow(dL * dL + da * da + db * db + dAlpha * dAlpha, 0.5); if (diff > 255.0) return 255; else return quint8(diff); } private: inline LcmsColorProfileContainer* lcmsProfile() const { return d->profile; } inline static LcmsColorProfileContainer* asLcmsProfile(const KoColorProfile* p) { if (!p) return 0; const IccColorProfile* iccp = dynamic_cast(p); if (!iccp) { return 0; } Q_ASSERT(iccp->asLcms()); return iccp->asLcms(); } Private * const d; }; /** * Base class for all LCMS based ColorSpace factories. */ class LcmsColorSpaceFactory : public KoColorSpaceFactory, private KoLcmsInfo { public: LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : KoLcmsInfo(cmType, colorSpaceSignature) { } virtual bool profileIsCompatible(const KoColorProfile* profile) const { const IccColorProfile* p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } virtual QString colorSpaceEngine() const { return "icc"; } virtual bool isHdr() const { return false; } virtual QList colorConversionLinks() const; virtual KoColorProfile* createColorProfile(const QByteArray& rawData) const; }; #endif diff --git a/plugins/colorengines/lcms2/tests/TestKoCompositeOps.cpp b/plugins/colorengines/lcms2/tests/TestKoCompositeOps.cpp index 548695e2080..a4fc27bbf19 100644 --- a/plugins/colorengines/lcms2/tests/TestKoCompositeOps.cpp +++ b/plugins/colorengines/lcms2/tests/TestKoCompositeOps.cpp @@ -1,1299 +1,1299 @@ /* * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestKoCompositeOps.h" #include #include #include "../compositeops/KoCompositeOpAlphaDarken.h" #include "../compositeops/KoCompositeOpOver.h" #include #define FULL_OPACITY KoColorSpaceMathsTraits::unitValue #define HALF_OPACITY (FULL_OPACITY/2) #define QUARTER_OPACITY (FULL_OPACITY/4) #define QCOMPAREui(a,b) QCOMPARE(a, (quint16)b) #include #include #include #include #include #include #include #include #include #include #include void TestKoCompositeOps::testCompositeOver() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpOver over(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 12510); QCOMPAREui(p16f1.green, 7972); QCOMPAREui(p16f1.blue, 17992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 12510); QCOMPAREui(p16f1.green, 7972); QCOMPAREui(p16f1.blue, 17992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 13760); QCOMPAREui(p16f1.green, 4472); QCOMPAREui(p16f1.blue, 16992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 12501); QCOMPAREui(p16f1.green, 7999); QCOMPAREui(p16f1.blue, 17999); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src, dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 11667); QCOMPAREui(p16f1.green, 10333); QCOMPAREui(p16f1.blue, 18666); QCOMPAREui(p16f1.alpha, 49151); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 13001); QCOMPAREui(p16f1.green, 6599); QCOMPAREui(p16f1.blue, 17599); QCOMPAREui(p16f1.alpha, 40959); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; over.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 11000); QCOMPAREui(p16f1.green, 12200); QCOMPAREui(p16f1.blue, 19200); QCOMPAREui(p16f1.alpha, 40959); } void TestKoCompositeOps::testCompositeAlphaDarken() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpAlphaDarken alphaDarken(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 12510); QCOMPAREui(p16f1.green, 7972); QCOMPAREui(p16f1.blue, 17992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 12510); QCOMPAREui(p16f1.green, 7972); QCOMPAREui(p16f1.blue, 17992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 13760); QCOMPAREui(p16f1.green, 4472); QCOMPAREui(p16f1.blue, 16992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 12501); QCOMPAREui(p16f1.green, 7999); QCOMPAREui(p16f1.blue, 17999); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 12501); QCOMPAREui(p16f1.green, 7999); QCOMPAREui(p16f1.blue, 17999); QCOMPAREui(p16f1.alpha, 49150); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 13751); QCOMPAREui(p16f1.green, 4499); QCOMPAREui(p16f1.blue, 16999); QCOMPAREui(p16f1.alpha, 40958); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; alphaDarken.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 12501); QCOMPAREui(p16f1.green, 7999); QCOMPAREui(p16f1.blue, 17999); QCOMPAREui(p16f1.alpha, 40958); } void TestKoCompositeOps::testCompositeDivide() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpDivide divide(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 65535); QCOMPAREui(p16f1.green, 4369); QCOMPAREui(p16f1.blue, 52426); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 40168); QCOMPAREui(p16f1.green, 2677); QCOMPAREui(p16f1.blue, 34141); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 40168); QCOMPAREui(p16f1.green, 2677); QCOMPAREui(p16f1.blue, 34141); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 27534); QCOMPAREui(p16f1.green, 1835); QCOMPAREui(p16f1.blue, 25034); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 48690); QCOMPAREui(p16f1.green, 3246); QCOMPAREui(p16f1.blue, 40284); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 40267); QCOMPAREui(p16f1.green, 2684); QCOMPAREui(p16f1.blue, 34212); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 48690); QCOMPAREui(p16f1.green, 3246); QCOMPAREui(p16f1.blue, 40284); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 35213); QCOMPAREui(p16f1.green, 2347); QCOMPAREui(p16f1.blue, 30569); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; divide.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 43877); QCOMPAREui(p16f1.green, 2925); QCOMPAREui(p16f1.blue, 36815); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); } void TestKoCompositeOps::testCompositeDodge() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpDodge dodge(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 17700); QCOMPAREui(p16f1.green, 1296); QCOMPAREui(p16f1.blue, 23027); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 16344); QCOMPAREui(p16f1.green, 1147); QCOMPAREui(p16f1.blue, 19499); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 16344); QCOMPAREui(p16f1.green, 1147); QCOMPAREui(p16f1.blue, 19499); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 15669); QCOMPAREui(p16f1.green, 1073); QCOMPAREui(p16f1.blue, 17742); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 16800); QCOMPAREui(p16f1.green, 1197); QCOMPAREui(p16f1.blue, 20684); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 16349); QCOMPAREui(p16f1.green, 1147); QCOMPAREui(p16f1.blue, 19513); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 16800); QCOMPAREui(p16f1.green, 1197); QCOMPAREui(p16f1.blue, 20684); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 16079); QCOMPAREui(p16f1.green, 1118); QCOMPAREui(p16f1.blue, 18810); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; dodge.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 16542); QCOMPAREui(p16f1.green, 1169); QCOMPAREui(p16f1.blue, 20015); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); } void TestKoCompositeOps::testCompositeInversedSubtract() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpInversedSubtract inversedSubtract(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 0); QCOMPAREui(p16f1.green, 14000); QCOMPAREui(p16f1.blue, 4000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 7530); QCOMPAREui(p16f1.green, 7474); QCOMPAREui(p16f1.blue, 10024); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 7530); QCOMPAREui(p16f1.green, 7474); QCOMPAREui(p16f1.blue, 10024); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 11280); QCOMPAREui(p16f1.green, 4224); QCOMPAREui(p16f1.blue, 13024); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 5000); QCOMPAREui(p16f1.green, 9666); QCOMPAREui(p16f1.blue, 8000); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 7501); QCOMPAREui(p16f1.green, 7499); QCOMPAREui(p16f1.blue, 10001); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 5000); QCOMPAREui(p16f1.green, 9666); QCOMPAREui(p16f1.blue, 8000); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 9001); QCOMPAREui(p16f1.green, 6199); QCOMPAREui(p16f1.blue, 11201); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; inversedSubtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 6429); QCOMPAREui(p16f1.green, 8428); QCOMPAREui(p16f1.blue, 9143); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); } void TestKoCompositeOps::testCompositeMulitply() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpMultiply mulitply(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 2289); QCOMPAREui(p16f1.green, 229); QCOMPAREui(p16f1.blue, 4883); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 8670); QCOMPAREui(p16f1.green, 617); QCOMPAREui(p16f1.blue, 10464); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 8670); QCOMPAREui(p16f1.green, 617); QCOMPAREui(p16f1.blue, 10464); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 11848); QCOMPAREui(p16f1.green, 809); QCOMPAREui(p16f1.blue, 13243); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 6526); QCOMPAREui(p16f1.green, 486); QCOMPAREui(p16f1.blue, 8589); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 8645); QCOMPAREui(p16f1.green, 615); QCOMPAREui(p16f1.blue, 10442); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 6526); QCOMPAREui(p16f1.green, 486); QCOMPAREui(p16f1.blue, 8589); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 9916); QCOMPAREui(p16f1.green, 692); QCOMPAREui(p16f1.blue, 11554); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; mulitply.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 7737); QCOMPAREui(p16f1.green, 560); QCOMPAREui(p16f1.blue, 9648); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); } void TestKoCompositeOps::testCompositeOverlay() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpOverlay overlay(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 6963); QCOMPAREui(p16f1.green, 466); QCOMPAREui(p16f1.blue, 11288); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 10998); QCOMPAREui(p16f1.green, 735); QCOMPAREui(p16f1.blue, 13654); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 10998); QCOMPAREui(p16f1.green, 735); QCOMPAREui(p16f1.blue, 13654); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 13007); QCOMPAREui(p16f1.green, 868); QCOMPAREui(p16f1.blue, 14832); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 9642); QCOMPAREui(p16f1.green, 644); QCOMPAREui(p16f1.blue, 12859); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10982); QCOMPAREui(p16f1.green, 734); QCOMPAREui(p16f1.blue, 13645); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 9642); QCOMPAREui(p16f1.green, 644); QCOMPAREui(p16f1.blue, 12859); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 11786); QCOMPAREui(p16f1.green, 787); QCOMPAREui(p16f1.blue, 14116); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; overlay.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10408); QCOMPAREui(p16f1.green, 695); QCOMPAREui(p16f1.blue, 13308); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); } void TestKoCompositeOps::testCompositeScreen() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpScreen screen(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 22711); QCOMPAREui(p16f1.green, 15771); QCOMPAREui(p16f1.blue, 31117); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 18840); QCOMPAREui(p16f1.green, 8356); QCOMPAREui(p16f1.blue, 23528); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 18840); QCOMPAREui(p16f1.green, 8356); QCOMPAREui(p16f1.blue, 23528); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 16912); QCOMPAREui(p16f1.green, 4663); QCOMPAREui(p16f1.blue, 19749); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 20140); QCOMPAREui(p16f1.green, 10847); QCOMPAREui(p16f1.blue, 26078); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 18855); QCOMPAREui(p16f1.green, 8385); QCOMPAREui(p16f1.blue, 23558); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 20140); QCOMPAREui(p16f1.green, 10847); QCOMPAREui(p16f1.blue, 26078); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 18084); QCOMPAREui(p16f1.green, 6908); QCOMPAREui(p16f1.blue, 22046); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; screen.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 19406); QCOMPAREui(p16f1.green, 9440); QCOMPAREui(p16f1.blue, 24638); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); } void TestKoCompositeOps::testCompositeSubtract() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpSubtract subtract(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 5000); QCOMPAREui(p16f1.green, 0); QCOMPAREui(p16f1.blue, 0); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 10020); QCOMPAREui(p16f1.green, 502); QCOMPAREui(p16f1.blue, 8032); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 10020); QCOMPAREui(p16f1.green, 502); QCOMPAREui(p16f1.blue, 8032); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 12520); QCOMPAREui(p16f1.green, 752); QCOMPAREui(p16f1.blue, 12032); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 15000); QCOMPAREui(p16f1.green, 1000); QCOMPAREui(p16f1.blue, 16000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 8334); QCOMPAREui(p16f1.green, 334); QCOMPAREui(p16f1.blue, 5334); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10001); QCOMPAREui(p16f1.green, 501); QCOMPAREui(p16f1.blue, 8001); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 8334); QCOMPAREui(p16f1.green, 334); QCOMPAREui(p16f1.blue, 5334); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 11001); QCOMPAREui(p16f1.green, 601); QCOMPAREui(p16f1.blue, 9601); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; subtract.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 9286); QCOMPAREui(p16f1.green, 429); QCOMPAREui(p16f1.blue, 6858); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); } void TestKoCompositeOps::testCompositeCopy2() { KoBgrU16Traits::Pixel p16f; KoBgrU16Traits::Pixel p16f1; quint8* p16fPtr = reinterpret_cast(&p16f); quint8* p16fPtr1 = reinterpret_cast(&p16f1); KoCompositeOpCopy2 copy(0); // Test no mask, full opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 12510); QCOMPAREui(p16f1.green, 7972); QCOMPAREui(p16f1.blue, 17992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, full opacity quint8 mask; mask = 127; p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 255); QCOMPAREui(p16f1.red, 12510); QCOMPAREui(p16f1.green, 7972); QCOMPAREui(p16f1.blue, 17992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test mask, half opacity p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 1, 1, 1, 127); QCOMPAREui(p16f1.red, 13760); QCOMPAREui(p16f1.green, 4472); QCOMPAREui(p16f1.blue, 16992); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, transparent source p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = 0; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, 0); // Test no mask, full opacity, transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = 0; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = FULL_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, FULL_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = FULL_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, full opacity, quarter-transparent src, half-transparent dst p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = QUARTER_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = HALF_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, QUARTER_OPACITY); // Test no mask, full opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 255); QCOMPAREui(p16f1.red, 10000); QCOMPAREui(p16f1.green, 15000); QCOMPAREui(p16f1.blue, 20000); QCOMPAREui(p16f1.alpha, HALF_OPACITY); // Test no mask, half opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, 0, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 12510); QCOMPAREui(p16f1.green, 7972); QCOMPAREui(p16f1.blue, 17992); QCOMPAREui(p16f1.alpha, 24542); // Test mask, half opacity, quarter-transparent dst, half-transparent src p16f.red = 10000; p16f.green = 15000; p16f.blue = 20000; p16f.alpha = HALF_OPACITY; p16f1.red = 15000; p16f1.green = 1000; p16f1.blue = 16000; p16f1.alpha = QUARTER_OPACITY; copy.composite(p16fPtr1, KoBgrU16Traits::pixelSize, p16fPtr, KoBgrU16Traits::pixelSize, &mask, 0, 1, 1, 127); QCOMPAREui(p16f1.red, 13760); QCOMPAREui(p16f1.green, 4472); QCOMPAREui(p16f1.blue, 16992); QCOMPAREui(p16f1.alpha, 20447); } void TestKoCompositeOps::testCompositeCopy() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoCompositeOp * copy = cs->compositeOp(COMPOSITE_COPY); KoColor black(Qt::black, cs); KoColor white(Qt::white, cs); KoColor opaque(QColor(0,0,0,0), cs); int w = 512; int h = 512; int pixelCount = w * h; quint32 pixelSize = cs->pixelSize(); // dst quint8 * layer = new quint8[pixelCount * pixelSize]; quint8 * iter = layer; for (int i = 0; i < pixelCount; i++){ memcpy(iter, white.data() , pixelSize); iter += pixelSize; } // full white image - //cs->convertToQImage(layer, w, h, 0,KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags).save("0dst.png"); + //cs->convertToQImage(layer, w, h, 0,KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()).save("0dst.png"); // src quint8 * dab = new quint8[pixelCount * pixelSize]; iter = dab; for (int i = 0; i < pixelCount; i++){ memcpy(iter, black.data() , pixelSize); iter += pixelSize; } // full black image - //cs->convertToQImage(dab, w, h, 0,KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags).save("1src.png"); + //cs->convertToQImage(dab, w, h, 0,KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()).save("1src.png"); // selection quint32 selectionPixelSize = KoColorSpaceRegistry::instance()->alpha8()->pixelSize(); quint8 * selection = new quint8[pixelCount * selectionPixelSize]; iter = selection; for (int height = 0; height < h; height++){ for (int width = 0; width < w; width++){ if ((height > 128) && (height < 256) && (width > 128) && (width < 256)){ *iter = 255; }else{ *iter = 0; } iter += selectionPixelSize; } } // white rectangle at 128,128 - //KoColorSpaceRegistry::instance()->alpha8()->convertToQImage(selection, w, h, 0, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags).save("1mask.png"); + //KoColorSpaceRegistry::instance()->alpha8()->convertToQImage(selection, w, h, 0, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()).save("1mask.png"); copy->composite(layer,w * pixelSize, dab, w * pixelSize, 0,0, h, w, 255, QBitArray()); // full black image - //cs->convertToQImage(layer, w, h, 0,KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags).save("2result.png"); + //cs->convertToQImage(layer, w, h, 0,KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()).save("2result.png"); copy->composite(layer,w * pixelSize, opaque.data(), 0, 0,0, h,w, 255, QBitArray() ); // full opaque image - //cs->convertToQImage(layer, w, h, 0,KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags).save("3result.png"); + //cs->convertToQImage(layer, w, h, 0,KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()).save("3result.png"); copy->composite(layer,w * pixelSize, dab, w * pixelSize, selection, w * selectionPixelSize, h,w, 255, QBitArray() ); // black rectangle on opaque background - QImage result = cs->convertToQImage(layer, w, h, 0, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + QImage result = cs->convertToQImage(layer, w, h, 0, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QImage expectedResult(QString(FILES_DATA_DIR) + QDir::separator() + "CopyWithSelectionExpectedResult.png"); bool testOk = (result == expectedResult); if (!testOk){ qDebug() << "Saving the result"; result.save("CopyWithSelection.png"); } QVERIFY2(testOk, "Images are not equal"); copy->composite(layer, w * pixelSize, white.data(), 0, selection, w * selectionPixelSize, h,w, 255, QBitArray()); - result = cs->convertToQImage(layer, w, h, 0, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); + result = cs->convertToQImage(layer, w, h, 0, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); expectedResult = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "CopySingleWithSelectionExpectedResult.png"); testOk = (result == expectedResult); if (!testOk){ qDebug() << expectedResult.size() << result.size(); for (int row = 0; row < expectedResult.size().height(); ++row) { for (int col = 0; col < expectedResult.size().width(); ++ col) { QRgb res = result.pixel(col, row); QRgb exp = expectedResult.pixel(col, row); if (res != exp) { qDebug() << "wrong pixel:" << col << "," << row << "result:" << qRed(res) << qGreen(res) << qBlue(res) << qAlpha(res) << "expected" << qRed(exp) << qGreen(exp) << qBlue(exp) << qAlpha(exp); } } } expectedResult.save("expected result.png"); result.save("CopySingleWithSelection.png"); QFAIL("Images with single pixel and selection are not equal"); } } QTEST_GUILESS_MAIN(TestKoCompositeOps)