diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h index df314198c4..e10bb70673 100644 --- a/libs/pigment/KoColorSpace.h +++ b/libs/pigment/KoColorSpace.h @@ -1,646 +1,674 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 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 KOCOLORSPACE_H #define KOCOLORSPACE_H #include #include #include #include #include #include #include "KoColorSpaceConstants.h" #include "KoColorConversionTransformation.h" #include "KoColorProofingConversionTransformation.h" #include "KoCompositeOp.h" #include #include "kritapigment_export.h" class QDomDocument; class QDomElement; class KoChannelInfo; class KoColorProfile; class KoColorTransformation; class QBitArray; enum Deletability { OwnedByRegistryDoNotDelete, OwnedByRegistryRegistryDeletes, NotOwnedByRegistry }; enum ColorSpaceIndependence { FULLY_INDEPENDENT, TO_LAB16, TO_RGBA8, TO_RGBA16 }; class KoMixColorsOp; class KoConvolutionOp; /** * A KoColorSpace is the definition of a certain color space. * * A color model and a color space are two related concepts. A color * model is more general in that it describes the channels involved and * how they in broad terms combine to describe a color. Examples are * RGB, HSV, CMYK. * * A color space is more specific in that it also describes exactly how * the channels are combined. So for each color model there can be a * number of specific color spaces. So RGB is the model and sRGB, * adobeRGB, etc are colorspaces. * * In Pigment KoColorSpace acts as both a color model and a color space. * You can think of the class definition as the color model, but the * instance of the class as representing a colorspace. * * A third concept is the profile represented by KoColorProfile. It * represents the info needed to specialize a color model into a color * space. * * KoColorSpace is an abstract class serving as an interface. * * Subclasses implement actual color spaces * Some subclasses implement only some parts and are named Traits * */ class KRITAPIGMENT_EXPORT KoColorSpace : public boost::equality_comparable { friend class KoColorSpaceRegistry; friend class KoColorSpaceFactory; protected: /// Only for use by classes that serve as baseclass for real color spaces KoColorSpace(); public: /// Should be called by real color spaces KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp); virtual bool operator==(const KoColorSpace& rhs) const; protected: virtual ~KoColorSpace(); public: //========== Gamut and other basic info ===================================// /* * @returns QPolygonF with 5*channel samples converted to xyY. * maybe convert to 3d space in future? */ QPolygonF gamutXYY() const; /* * @returns a polygon with 5 samples per channel converted to xyY, but unlike * gamutxyY it focuses on the luminance. This then can be used to visualise * the approximate trc of a given colorspace. */ QPolygonF estimatedTRCXYY() const; QVector lumaCoefficients() const; //========== Channels =====================================================// /// Return a list describing all the channels this color model has. The order /// of the channels in the list is the order of channels in the pixel. To find /// out the preferred display position, use KoChannelInfo::displayPosition. QList channels() const; /** * The total number of channels for a single pixel in this color model */ virtual quint32 channelCount() const = 0; /** * Position of the alpha channel in a pixel */ virtual quint32 alphaPos() const = 0; /** * The total number of color channels (excludes alpha) for a single * pixel in this color model. */ virtual quint32 colorChannelCount() const = 0; /** * returns a QBitArray that contains true for the specified * channel types: * * @param color if true, set all color channels to true * @param alpha if true, set all alpha channels to true * * The order of channels is the colorspace descriptive order, * not the pixel order. */ QBitArray channelFlags(bool color = true, bool alpha = false) const; /** * The size in bytes of a single pixel in this color model */ virtual quint32 pixelSize() const = 0; /** * Return a string with the channel's value suitable for display in the gui. */ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a string with the channel's value with integer * channels normalised to the floating point range 0 to 1, if * appropriate. */ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a QVector of floats with channels' values normalized * to floating point range 0 to 1. */ virtual void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const = 0; /** * Write in the pixel the value from the normalized vector. */ virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const = 0; /** * Convert the value of the channel at the specified position into * an 8-bit value. The position is not the number of bytes, but * the position of the channel as defined in the channel info list. */ virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelPos) const = 0; /** * Set dstPixel to the pixel containing only the given channel of srcPixel. The remaining channels * should be set to whatever makes sense for 'empty' channels of this color space, * with the intent being that the pixel should look like it only has the given channel. */ virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const = 0; //========== Identification ===============================================// /** * ID for use in files and internally: unchanging name. As the id must be unique * it is usually the concatenation of the id of the color model and of the color * depth, for instance "RGBA8" or "CMYKA16" or "XYZA32f". */ QString id() const; /** * User visible name which contains the name of the color model and of the color depth. * For instance "RGBA (8-bits)" or "CMYKA (16-bits)". */ QString name() const; /** * @return a string that identify the color model (for instance "RGB" or "CMYK" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorModelId() const = 0; /** * @return a string that identify the bit depth (for instance "U8" or "F16" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorDepthId() const = 0; /** * @return true if the profile given in argument can be used by this color space */ virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0; /** * If false, images in this colorspace will degrade considerably by * functions, tools and filters that have the given measure of colorspace * independence. * * @param independence the measure to which this colorspace will suffer * from the manipulations of the tool or filter asking * @return false if no degradation will take place, true if degradation will * take place */ virtual bool willDegrade(ColorSpaceIndependence independence) const = 0; //========== Capabilities =================================================// /** * Tests if the colorspace offers the specific composite op. */ virtual bool hasCompositeOp(const QString & id) const; /** * Returns the list of user-visible composite ops supported by this colorspace. */ virtual QList compositeOps() const; /** * Retrieve a single composite op from the ones this colorspace offers. * If the requeste composite op does not exist, COMPOSITE_OVER is returned. */ const KoCompositeOp * compositeOp(const QString & id) const; /** * add a composite op to this colorspace. */ virtual void addCompositeOp(const KoCompositeOp * op); /** * Returns true if the colorspace supports channel values outside the * (normalised) range 0 to 1. */ virtual bool hasHighDynamicRange() const = 0; //========== Display profiles =============================================// /** * Return the profile of this color space. */ virtual const KoColorProfile * profile() const = 0; //================= Conversion functions ==================================// /** * The fromQColor methods take a given color defined as an RGB QColor * and fills a byte array with the corresponding color in the * the colorspace managed by this strategy. * * @param color the QColor that will be used to fill dst * @param dst a pointer to a pixel * @param profile the optional profile that describes the color values of QColor */ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const = 0; /** * The toQColor methods take a byte array that is at least pixelSize() long * and converts the contents to a QColor, using the given profile as a source * profile and the optional profile as a destination profile. * * @param src a pointer to the source pixel * @param c the QColor that will be filled with the color at src * @param profile the optional profile that describes the color in c, for instance the monitor profile */ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const = 0; /** * Convert the pixels in data to (8-bit BGRA) QImage using the specified profiles. * * @param data A pointer to a contiguous memory region containing width * height pixels * @param width in pixels * @param height in pixels * @param dstProfile destination profile * @param renderingIntent the rendering intent * @param conversionFlags conversion flags */ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert the specified data to Lab (D50). All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from Lab (D50). to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit lab format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data to sRGB 16 bits. All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from sRGB 16 bits. to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit rgb format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Create a color conversion transformation. */ virtual KoColorConversionTransformation* createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert a byte array of srcLen pixels *src to the specified color space * and put the converted bytes into the prepared byte array *dst. * * Returns false if the conversion failed, true if it succeeded * * This function is not thread-safe. If you want to apply multiple conversion * in different threads at the same time, you need to create one color converter * per-thread using createColorConverter. */ virtual bool convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; virtual KoColorConversionTransformation *createProofingTransform(const KoColorSpace * dstColorSpace, const KoColorSpace * proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const; /** * @brief proofPixelsTo * @param src source * @param dst destination * @param numPixels the amount of pixels. * @param proofingTransform the intent used for proofing. * @return */ virtual bool proofPixelsTo(const quint8 * src, quint8 * dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const; + /** + * Convert @p nPixels pixels in @p src into their human-visible + * visual representation. The channel is shown as grayscale. + * + * Both buffers are in the same color space. + * + * @param src source buffer in (*this) color space + * @param dst destination buffer in the same color space as @p src + * @param nPixels length of the buffers in number of pixels + * @param pixelSize stride of each pixel in the destination buffer + * @param selectedChannelIndex Index of the selected channel. + */ + virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const = 0; + + /** + * Convert @p nPixels pixels in @p src into their human-visible + * visual representation. The channels are shown as if other channels were null (or, if Lab, L = 1.0, *a = *b = 0.0). + * + * Both buffers are in the same color space. + * + * @param src source buffer in (*this) color space + * @param dst destination buffer in the same color space as @p src + * @param nPixels length of the buffers in number of pixels + * @param pixelSize stride of each pixel in the destination buffer + * @param selectedChannels Bitmap of selected channels + */ + virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const = 0; + //============================== Manipulation functions ==========================// // // The manipulation functions have default implementations that _convert_ the pixel // to a QColor and back. Reimplement these methods in your color strategy! // /** * Get the alpha value of the given pixel, downscaled to an 8-bit value. */ virtual quint8 opacityU8(const quint8 * pixel) const = 0; virtual qreal opacityF(const quint8 * pixel) const = 0; /** * Set the alpha channel of the given run of pixels to the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const = 0; /** * Multiply the alpha channel of the given run of pixels by the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; /** * Applies the specified 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the inverted 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Applies the inverted specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Create an adjustment object for adjusting the brightness and contrast * transferValues is a 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const = 0; /** * Create an adjustment object for adjusting individual channels * transferValues is an array of colorChannelCount number of 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. * * The layout of the channels must be the following: * * 0..N-2 - color channels of the pixel; * N-1 - alpha channel of the pixel (if exists) */ virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const* transferValues) const = 0; /** * Darken all color channels with the given amount. If compensate is true, * the compensation factor will be used to limit the darkening. * */ virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const = 0; /** * Invert color channels of the given pixels * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createInvertTransformation() const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). Only completely * opaque and completely transparent are taken into account when computing the difference; * other transparency levels are not regarded when finding the difference. */ virtual quint8 difference(const quint8* src1, const quint8* src2) const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). This function * takes the Alpha channel of the pixel into account. Alpha channel has the same * weight as Lightness channel. */ virtual quint8 differenceA(const quint8* src1, const quint8* src2) const = 0; /** * @return the mix color operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoMixColorsOp* mixColorsOp() const; /** * @return the convolution operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoConvolutionOp* convolutionOp() const; /** * Calculate the intensity of the given pixel, scaled down to the range 0-255. XXX: Maybe this should be more flexible */ virtual quint8 intensity8(const quint8 * src) const = 0; /* *increase luminosity by step */ virtual void increaseLuminosity(quint8 * pixel, qreal step) const; virtual void decreaseLuminosity(quint8 * pixel, qreal step) const; virtual void increaseSaturation(quint8 * pixel, qreal step) const; virtual void decreaseSaturation(quint8 * pixel, qreal step) const; virtual void increaseHue(quint8 * pixel, qreal step) const; virtual void decreaseHue(quint8 * pixel, qreal step) const; virtual void increaseRed(quint8 * pixel, qreal step) const; virtual void increaseGreen(quint8 * pixel, qreal step) const; virtual void increaseBlue(quint8 * pixel, qreal step) const; virtual void increaseYellow(quint8 * pixel, qreal step) const; virtual void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const = 0; virtual QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const = 0; virtual void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const = 0; virtual QVector fromYUV(qreal *y, qreal *u, qreal *v) const = 0; /** * Compose two arrays of pixels together. If source and target * are not the same color model, the source pixels will be * converted to the target model. We're "dst" -- "dst" pixels are always in _this_ * colorspace. * * @param srcSpace the colorspace of the source pixels that will be composited onto "us" * @param params the information needed for blitting e.g. the source and destination pixel data, * the opacity and flow, ... * @param op the composition operator to use, e.g. COPY_OVER * @param renderingIntent the rendering intent * @param conversionFlags the conversion flags. * */ virtual void bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Serialize this color following Create's swatch color specification available * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * This function doesn't create the \ element but rather the \, * \, \ ... elements. It is assumed that colorElt is the \ * element. * * @param pixel buffer to serialized * @param colorElt root element for the serialization, it is assumed that this * element is \ * @param doc is the document containing colorElt */ virtual void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const = 0; /** * Unserialize a color following Create's swatch color specification available * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * @param pixel buffer where the color will be unserialized * @param elt the element to unserialize (\, \, \) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ virtual void colorFromXML(quint8* pixel, const QDomElement& elt) const = 0; KoColorTransformation* createColorTransformation(const QString & id, const QHash & parameters) const; protected: /** * Use this function in the constructor of your colorspace to add the information about a channel. * @param ci a pointer to the information about a channel */ virtual void addChannel(KoChannelInfo * ci); const KoColorConversionTransformation* toLabA16Converter() const; const KoColorConversionTransformation* fromLabA16Converter() const; const KoColorConversionTransformation* toRgbA16Converter() const; const KoColorConversionTransformation* fromRgbA16Converter() const; /** * Returns the thread-local conversion cache. If it doesn't exist * yet, it is created. If it is currently too small, it is resized. */ QVector * threadLocalConversionCache(quint32 size) const; /** * This function defines the behavior of the bitBlt function * when the composition of pixels in different colorspaces is * requested, that is in case: * * srcCS == any * dstCS == this * * 1) preferCompositionInSourceColorSpace() == false, * * the source pixels are first converted to *this color space * and then composition is performed. * * 2) preferCompositionInSourceColorSpace() == true, * * the destination pixels are first converted into *srcCS color * space, then the composition is done, and the result is finally * converted into *this colorspace. * * This is used by alpha8() color space mostly, because it has * weaker representation of the color, so the composition * should be done in CS with richer functionality. */ virtual bool preferCompositionInSourceColorSpace() const; struct Private; Private * const d; }; inline QDebug operator<<(QDebug dbg, const KoColorSpace *cs) { if (cs) { dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )"; } else { dbg.nospace() << "0x0"; } return dbg.space(); } #endif // KOCOLORSPACE_H diff --git a/libs/pigment/KoColorSpaceAbstract.h b/libs/pigment/KoColorSpaceAbstract.h index 54f4519e63..fd1a6776ae 100644 --- a/libs/pigment/KoColorSpaceAbstract.h +++ b/libs/pigment/KoColorSpaceAbstract.h @@ -1,216 +1,247 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2007 Emanuele Tamponi * * 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 KOCOLORSPACEABSTRACT_H #define KOCOLORSPACEABSTRACT_H #include #include #include #include #include #include #include "KoFallBackColorTransformation.h" #include "KoLabDarkenColorTransformation.h" #include "KoMixColorsOpImpl.h" #include "KoConvolutionOpImpl.h" #include "KoInvertColorTransformation.h" /** * This in an implementation of KoColorSpace which can be used as a base for colorspaces with as many * different channels of the same type. * * The template parameters must be a class which inherits KoColorSpaceTrait (or a class with the same signature). * * SOMETYPE is the type of the channel for instance (quint8, quint32...), * SOMENBOFCHANNELS is the number of channels including the alpha channel * SOMEALPHAPOS is the position of the alpha channel in the pixel (can be equal to -1 if no alpha channel). */ template class KoColorSpaceAbstract : public KoColorSpace { public: typedef _CSTrait ColorSpaceTraits; public: KoColorSpaceAbstract(const QString &id, const QString &name) : KoColorSpace(id, name, new KoMixColorsOpImpl< _CSTrait>(), new KoConvolutionOpImpl< _CSTrait>()) { } quint32 colorChannelCount() const override { if (_CSTrait::alpha_pos == -1) return _CSTrait::channels_nb; else return _CSTrait::channels_nb - 1; } quint32 channelCount() const override { return _CSTrait::channels_nb; } quint32 alphaPos() const override { return _CSTrait::alpha_pos; } quint32 pixelSize() const override { return _CSTrait::pixelSize; } QString channelValueText(const quint8 *pixel, quint32 channelIndex) const override { return _CSTrait::channelValueText(pixel, channelIndex); } QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const override { return _CSTrait::normalisedChannelValueText(pixel, channelIndex); } void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const override { return _CSTrait::normalisedChannelsValue(pixel, channels); } void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const override { return _CSTrait::fromNormalisedChannelsValue(pixel, values); } quint8 scaleToU8(const quint8 * srcPixel, qint32 channelIndex) const override { typename _CSTrait::channels_type c = _CSTrait::nativeArray(srcPixel)[channelIndex]; return KoColorSpaceMaths::scaleToA(c); } void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const override { _CSTrait::singleChannelPixel(dstPixel, srcPixel, channelIndex); } quint8 opacityU8(const quint8 * U8_pixel) const override { return _CSTrait::opacityU8(U8_pixel); } qreal opacityF(const quint8 * U8_pixel) const override { return _CSTrait::opacityF(U8_pixel); } void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const override { _CSTrait::setOpacity(pixels, alpha, nPixels); } void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const override { _CSTrait::setOpacity(pixels, alpha, nPixels); } void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const override { _CSTrait::multiplyAlpha(pixels, alpha, nPixels); } void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const override { _CSTrait::applyAlphaU8Mask(pixels, alpha, nPixels); } void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const override { _CSTrait::applyInverseAlphaU8Mask(pixels, alpha, nPixels); } void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const override { _CSTrait::applyAlphaNormedFloatMask(pixels, alpha, nPixels); } void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const override { _CSTrait::applyInverseAlphaNormedFloatMask(pixels, alpha, nPixels); } quint8 intensity8(const quint8 * src) const override { QColor c; const_cast *>(this)->toQColor(src, &c); return static_cast(c.red() * 0.30 + c.green() * 0.59 + c.blue() * 0.11); } KoColorTransformation* createInvertTransformation() const override { return KoInvertColorTransformation::getTransformator(this); } KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const override { return new KoFallBackColorTransformation(this, KoColorSpaceRegistry::instance()->lab16(""), new KoLabDarkenColorTransformation(shade, compensate, compensation, KoColorSpaceRegistry::instance()->lab16(""))); } bool convertPixelsTo(const quint8 *src, quint8 *dst, const KoColorSpace *dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const override { // check whether we have the same profile and color model, but only a different bit // depth; in that case we don't convert as such, but scale bool scaleOnly = false; // Note: getting the id() is really, really expensive, so only do that if // we are sure there is a difference between the colorspaces if (!(*this == *dstColorSpace)) { scaleOnly = dstColorSpace->colorModelId().id() == colorModelId().id() && dstColorSpace->colorDepthId().id() != colorDepthId().id() && dstColorSpace->profile()->name() == profile()->name(); } if (scaleOnly && dynamic_cast(dstColorSpace)) { typedef typename _CSTrait::channels_type channels_type; switch(dstColorSpace->channels()[0]->channelValueType()) { case KoChannelInfo::UINT8: scalePixels<_CSTrait::pixelSize, 1, channels_type, quint8>(src, dst, numPixels); return true; // case KoChannelInfo::INT8: // scalePixels<_CSTrait::pixelSize, 1, channels_type, qint8>(src, dst, numPixels); // return true; case KoChannelInfo::UINT16: scalePixels<_CSTrait::pixelSize, 2, channels_type, quint16>(src, dst, numPixels); return true; case KoChannelInfo::INT16: scalePixels<_CSTrait::pixelSize, 2, channels_type, qint16>(src, dst, numPixels); return true; case KoChannelInfo::UINT32: scalePixels<_CSTrait::pixelSize, 4, channels_type, quint32>(src, dst, numPixels); return true; default: break; } } return KoColorSpace::convertPixelsTo(src, dst, dstColorSpace, numPixels, renderingIntent, conversionFlags); } + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const override + { + qint32 selectedChannelPos = this->channels()[selectedChannelIndex]->pos(); + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (channel->channelType() == KoChannelInfo::COLOR) { + memcpy(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * _CSTrait::pixelSize) + selectedChannelPos, channelSize); + } else if (channel->channelType() == KoChannelInfo::ALPHA) { + memcpy(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), channelSize); + } + } + } + } + + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const override + { + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (selectedChannels.testBit(channelIndex)) { + memcpy(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), channelSize); + } else { + reinterpret_cast(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize))[0] = _CSTrait::math_trait::zeroValue; + } + } + } + } + private: template void scalePixels(const quint8* src, quint8* dst, quint32 numPixels) const { qint32 dstPixelSize = dstChannelSize * _CSTrait::channels_nb; for(quint32 i=0; i(src + i * srcPixelSize); TDstChannel* dstPixel = reinterpret_cast(dst + i * dstPixelSize); for(quint32 c=0; c<_CSTrait::channels_nb; ++c) dstPixel[c] = Arithmetic::scale(srcPixel[c]); } } }; #endif // KOCOLORSPACEABSTRACT_H diff --git a/libs/pigment/KoColorSpaceTraits.h b/libs/pigment/KoColorSpaceTraits.h index 5c73af4667..f17ae4c51d 100644 --- a/libs/pigment/KoColorSpaceTraits.h +++ b/libs/pigment/KoColorSpaceTraits.h @@ -1,235 +1,238 @@ /* * Copyright (c) 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 _KO_COLORSPACE_TRAITS_H_ #define _KO_COLORSPACE_TRAITS_H_ #include #include "KoColorSpaceConstants.h" #include "KoColorSpaceMaths.h" #include "DebugPigment.h" const int MAX_CHANNELS_TYPE_SIZE = sizeof(double); const int MAX_CHANNELS_NB = 5; const int MAX_PIXEL_SIZE = MAX_CHANNELS_NB * MAX_CHANNELS_TYPE_SIZE; /** * This class is the base class to define the main characteristics of a colorspace * which inherits KoColorSpaceAbstract. * * - _channels_type_ is the type of the value use for each channel, for example quint8 for 8bits per channel * color spaces, or quint16 for 16bits integer per channel, float for 32bits per channel * floating point color spaces * - _channels_nb_ is the total number of channels in an image (for example RGB is 3 but RGBA is four) * - _alpha_pos_ is the position of the alpha channel among the channels, if there is no alpha channel, * then _alpha_pos_ is set to -1 * * For instance a colorspace of three color channels and alpha channel in 16bits, * will be defined as KoColorSpaceTrait\. The same without the alpha * channel is KoColorSpaceTrait\ * */ template struct KoColorSpaceTrait { static_assert(sizeof(_channels_type_) <= MAX_CHANNELS_TYPE_SIZE, "MAX_CHANNELS_TYPE_SIZE too small"); static_assert(_channels_nb_ <= MAX_CHANNELS_NB, "MAX_CHANNELS_NB too small"); /// the type of the value of the channels of this color space typedef _channels_type_ channels_type; /// the number of channels in this color space static const quint32 channels_nb = _channels_nb_; /// the position of the alpha channel in the channels of the pixel (or -1 if no alpha /// channel. static const qint32 alpha_pos = _alpha_pos_; /// the number of bit for each channel static const int depth = KoColorSpaceMathsTraits<_channels_type_>::bits; + /// the associated math class + typedef KoColorSpaceMathsTraits<_channels_type_> math_trait; + /** * @return the size in byte of one pixel */ static const quint32 pixelSize = channels_nb * sizeof(channels_type); /** * @return the value of the alpha channel for this pixel in the 0..255 range */ inline static quint8 opacityU8(const quint8 * U8_pixel) { if (alpha_pos < 0) return OPACITY_OPAQUE_U8; channels_type c = nativeArray(U8_pixel)[alpha_pos]; return KoColorSpaceMaths::scaleToA(c); } inline static qreal opacityF(const quint8 * U8_pixel) { if (alpha_pos < 0) return OPACITY_OPAQUE_F; channels_type c = nativeArray(U8_pixel)[alpha_pos]; return KoColorSpaceMaths::scaleToA(c); } /** * Set the alpha channel for this pixel from a value in the 0..255 range */ inline static void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) { if (alpha_pos < 0) return; qint32 psize = pixelSize; channels_type valpha = KoColorSpaceMaths::scaleToA(alpha); for (; nPixels > 0; --nPixels, pixels += psize) { nativeArray(pixels)[alpha_pos] = valpha; } } inline static void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) { if (alpha_pos < 0) return; qint32 psize = pixelSize; channels_type valpha = KoColorSpaceMaths::scaleToA(alpha); for (; nPixels > 0; --nPixels, pixels += psize) { nativeArray(pixels)[alpha_pos] = valpha; } } /** * Convenient function for transforming a quint8* array in a pointer of the native channels type */ inline static const channels_type* nativeArray(const quint8 * a) { return reinterpret_cast(a); } /** * Convenient function for transforming a quint8* array in a pointer of the native channels type */ inline static channels_type* nativeArray(quint8 * a) { return reinterpret_cast< channels_type*>(a); } /** * Allocate nPixels pixels for this colorspace. */ inline static quint8* allocate(quint32 nPixels) { return new quint8[ nPixels * pixelSize ]; } inline static void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) { const channels_type* src = nativeArray(srcPixel); channels_type* dst = nativeArray(dstPixel); for (uint i = 0; i < channels_nb; i++) { if (i != channelIndex) { dst[i] = 0; } else { dst[i] = src[i]; } } } inline static QString channelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; return QString().setNum(c); } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; return QString().setNum(100. *((qreal)c) / KoColorSpaceMathsTraits< channels_type>::unitValue); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { c = nativeArray(pixel)[i]; channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); c = (channels_type)b; nativeArray(pixel)[i] = c; } } inline static void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) { if (alpha_pos < 0) return; channels_type valpha = KoColorSpaceMaths::scaleToA(alpha); for (; nPixels > 0; --nPixels, pixels += pixelSize) { channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = KoColorSpaceMaths::scaleToA(*alpha); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = KoColorSpaceMaths::scaleToA(OPACITY_OPAQUE_U8 - *alpha); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = channels_type(KoColorSpaceMathsTraits::unitValue * (*alpha)); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyInverseAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = channels_type(KoColorSpaceMathsTraits::unitValue * (1.0f - *alpha)); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } }; #include "KoRgbColorSpaceTraits.h" #include "KoBgrColorSpaceTraits.h" #include "KoGrayColorSpaceTraits.h" #include "KoLabColorSpaceTraits.h" #include "KoXyzColorSpaceTraits.h" #include "KoYcbcrColorSpaceTraits.h" #include "KoCmykColorSpaceTraits.h" #endif diff --git a/libs/pigment/colorspaces/KoLabColorSpace.cpp b/libs/pigment/colorspaces/KoLabColorSpace.cpp index 03a37feb27..562324e213 100644 --- a/libs/pigment/colorspaces/KoLabColorSpace.cpp +++ b/libs/pigment/colorspaces/KoLabColorSpace.cpp @@ -1,196 +1,284 @@ /* * 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. */ #include "KoLabColorSpace.h" #include #include #include #include #include #include #include "KoChannelInfo.h" #include "KoID.h" #include "KoIntegerMaths.h" #include "KoColorConversions.h" #include "../compositeops/KoCompositeOps.h" KoLabColorSpace::KoLabColorSpace() : KoSimpleColorSpace(colorSpaceId(), i18n("L*a*b* (16-bit integer/channel, unmanaged)"), LABAColorModelID, Integer16BitsColorDepthID) { addChannel(new KoChannelInfo(i18n("Lightness"), CHANNEL_L * sizeof(quint16), CHANNEL_L, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(100, 100, 100))); addChannel(new KoChannelInfo(i18n("a*"), CHANNEL_A * sizeof(quint16), CHANNEL_A, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(150, 150, 150))); addChannel(new KoChannelInfo(i18n("b*"), CHANNEL_B * sizeof(quint16), CHANNEL_B, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(200, 200, 200))); addChannel(new KoChannelInfo(i18n("Alpha"), CHANNEL_ALPHA * sizeof(quint16), CHANNEL_ALPHA, KoChannelInfo::ALPHA, KoChannelInfo::UINT16, sizeof(quint16))); // ADD, ALPHA_DARKEN, BURN, DIVIDE, DODGE, ERASE, MULTIPLY, OVER, OVERLAY, SCREEN, SUBTRACT addStandardCompositeOps(this); } KoLabColorSpace::~KoLabColorSpace() { } QString KoLabColorSpace::colorSpaceId() { return QString("LABA"); } KoColorSpace* KoLabColorSpace::clone() const { return new KoLabColorSpace(); } void KoLabColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const { // Convert between RGB and CIE-Lab color spaces // Uses ITU-R recommendation BT.709 with D65 as reference white. // algorithm contributed by "Mark A. Ruzon" int R, G, B, A; c.getRgb(&R, &G, &B, &A); double X, Y, Z, fX, fY, fZ; X = 0.412453 * R + 0.357580 * G + 0.180423 * B; Y = 0.212671 * R + 0.715160 * G + 0.072169 * B; Z = 0.019334 * R + 0.119193 * G + 0.950227 * B; X /= (255 * 0.950456); Y /= 255; Z /= (255 * 1.088754); quint8 L, a, b; if (Y > 0.008856) { fY = pow(Y, 1.0 / 3.0); L = static_cast(116.0 * fY - 16.0 + 0.5); } else { fY = 7.787 * Y + 16.0 / 116.0; L = static_cast(903.3 * Y + 0.5); } if (X > 0.008856) fX = pow(X, 1.0 / 3.0); else fX = 7.787 * X + 16.0 / 116.0; if (Z > 0.008856) fZ = pow(Z, 1.0 / 3.0); else fZ = 7.787 * Z + 16.0 / 116.0; a = static_cast(500.0 * (fX - fY) + 0.5); b = static_cast(200.0 * (fY - fZ) + 0.5); dst[CHANNEL_L] = UINT8_TO_UINT16(L); dst[CHANNEL_A] = UINT8_TO_UINT16(a); dst[CHANNEL_B] = UINT8_TO_UINT16(b); dst[CHANNEL_ALPHA] = UINT8_TO_UINT16(A); } void KoLabColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const { // Convert between RGB and CIE-Lab color spaces // Uses ITU-R recommendation BT.709 with D65 as reference white. // algorithm contributed by "Mark A. Ruzon" quint8 L, a, b, A; L = UINT16_TO_UINT8(src[CHANNEL_L]); a = UINT16_TO_UINT8(src[CHANNEL_A]); b = UINT16_TO_UINT8(src[CHANNEL_B]); A = UINT16_TO_UINT8(src[CHANNEL_ALPHA]); double X, Y, Z, fX, fY, fZ; int RR, GG, BB; fY = pow((L + 16.0) / 116.0, 3.0); if (fY < 0.008856) fY = L / 903.3; Y = fY; if (fY > 0.008856) fY = pow(fY, 1.0 / 3.0); else fY = 7.787 * fY + 16.0 / 116.0; fX = a / 500.0 + fY; if (fX > 0.206893) X = pow(fX, 3.0); else X = (fX - 16.0 / 116.0) / 7.787; fZ = fY - b / 200.0; if (fZ > 0.206893) Z = pow(fZ, 3.0); else Z = (fZ - 16.0 / 116.0) / 7.787; X *= 0.950456 * 255; Y *= 255; Z *= 1.088754 * 255; RR = static_cast(3.240479 * X - 1.537150 * Y - 0.498535 * Z + 0.5); GG = static_cast(-0.969256 * X + 1.875992 * Y + 0.041556 * Z + 0.5); BB = static_cast(0.055648 * X - 0.204043 * Y + 1.057311 * Z + 0.5); quint8 R = RR < 0 ? 0 : RR > 255 ? 255 : RR; quint8 G = GG < 0 ? 0 : GG > 255 ? 255 : GG; quint8 B = BB < 0 ? 0 : BB > 255 ? 255 : BB; c->setRgba(qRgba(R, G, B, A)); } void KoLabColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { LabToLCH(channelValues[0],channelValues[1],channelValues[2], luma, sat, hue); } QVector KoLabColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); LCHToLab(*luma, *sat, *hue, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } void KoLabColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { *y =channelValues[0]; *v=channelValues[1]; *u=channelValues[2]; } QVector KoLabColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); channelValues[0]=*y; channelValues[1]=*v; channelValues[2]=*u; channelValues[3]=1.0; return channelValues; } + +quint8 KoLabColorSpace::scaleToU8(const quint8 *srcPixel, qint32 channelIndex) const +{ + typename ColorSpaceTraits::channels_type c = ColorSpaceTraits::nativeArray(srcPixel)[channelIndex]; + qreal b = 0; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + b = ((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB)); + } else { + b = 0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB)); + } + break; + default: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValue; + break; + } + + return KoColorSpaceMaths::scaleToA(b); +} + +void KoLabColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (channel->channelType() == KoChannelInfo::COLOR) { + if (channelIndex == ColorSpaceTraits::L_pos) { + ColorSpaceTraits::channels_type c = ColorSpaceTraits::parent::nativeArray((src + (pixelIndex * ColorSpaceTraits::pixelSize)))[selectedChannelIndex]; + switch (selectedChannelIndex) { + case ColorSpaceTraits::L_pos: + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + c = ColorSpaceTraits::math_trait::unitValueL * (((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB))); + } else { + c = ColorSpaceTraits::math_trait::unitValueL * (0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB))); + } + break; + // As per KoChannelInfo alpha channels are [0..1] + default: + c = ColorSpaceTraits::math_trait::unitValueL * (qreal)c / ColorSpaceTraits::math_trait::unitValue; + break; + } + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = c; + } else { + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = ColorSpaceTraits::math_trait::halfValueAB; + } + } else if (channel->channelType() == KoChannelInfo::ALPHA) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } + } + } +} + +void KoLabColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (selectedChannels.testBit(channelIndex)) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } else { + ColorSpaceTraits::channels_type v; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + v = ColorSpaceTraits::math_trait::halfValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + v = ColorSpaceTraits::math_trait::halfValueAB; + break; + default: + v = ColorSpaceTraits::math_trait::zeroValue; + break; + } + reinterpret_cast(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize))[0] = v; + } + } + } +} diff --git a/libs/pigment/colorspaces/KoLabColorSpace.h b/libs/pigment/colorspaces/KoLabColorSpace.h index 1168204143..ad2a393701 100644 --- a/libs/pigment/colorspaces/KoLabColorSpace.h +++ b/libs/pigment/colorspaces/KoLabColorSpace.h @@ -1,87 +1,90 @@ /* * Copyright (c) 2004 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 KOLABCOLORSPACE_H #define KOLABCOLORSPACE_H #include #include "KoSimpleColorSpace.h" #include "KoSimpleColorSpaceFactory.h" #include "KoColorModelStandardIds.h" struct KoLabU16Traits; /** * Basic and simple implementation of the LAB colorspace */ class KoLabColorSpace : public KoSimpleColorSpace { public: KoLabColorSpace(); ~KoLabColorSpace() override; static QString colorSpaceId(); virtual KoColorSpace* clone() const; void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const override; void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; + quint8 scaleToU8(const quint8 * srcPixel, qint32 channelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const override; private: static const quint32 CHANNEL_L = 0; static const quint32 CHANNEL_A = 1; static const quint32 CHANNEL_B = 2; static const quint32 CHANNEL_ALPHA = 3; static const quint32 MAX_CHANNEL_L = 0xff00; static const quint32 MAX_CHANNEL_AB = 0xffff; static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000; }; class KoLabColorSpaceFactory : public KoSimpleColorSpaceFactory { public: KoLabColorSpaceFactory() : KoSimpleColorSpaceFactory("LABA", i18n("L*a*b* (16-bit integer/channel, unmanaged)"), true, LABAColorModelID, Integer16BitsColorDepthID) { } KoColorSpace *createColorSpace(const KoColorProfile *) const override { return new KoLabColorSpace(); } }; #endif diff --git a/libs/ui/canvas/kis_image_pyramid.cpp b/libs/ui/canvas/kis_image_pyramid.cpp index 75d2a4f786..f7e46d0b75 100644 --- a/libs/ui/canvas/kis_image_pyramid.cpp +++ b/libs/ui/canvas/kis_image_pyramid.cpp @@ -1,537 +1,507 @@ /* * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_pyramid.h" #include #include #include #include #include #include #include "kis_display_filter.h" #include "kis_painter.h" #include "kis_iterator_ng.h" #include "kis_datamanager.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include "kis_config.h" #include "kis_image_config.h" //#define DEBUG_PYRAMID #include #ifdef HAVE_OCIO #include #include #endif #define ORIGINAL_INDEX 0 #define FIRST_NOT_ORIGINAL_INDEX 1 #define SCALE_FROM_INDEX(idx) (1./qreal(1<<(idx))) /************* AUXILIARY FUNCTIONS **********************************/ #include #ifdef HAVE_OPENEXR #include #endif #define ceiledSize(sz) QSize(ceil((sz).width()), ceil((sz).height())) #define isOdd(x) ((x) & 0x01) /** * Aligns @p value to the lowest integer not smaller than @p value and * that is a divident of alignment */ inline void alignByPow2Hi(qint32 &value, qint32 alignment) { qint32 mask = alignment - 1; value |= mask; value++; } /** * Aligns @p value to the lowest integer not smaller than @p value and * that is, increased by one, a divident of alignment */ inline void alignByPow2ButOneHi(qint32 &value, qint32 alignment) { qint32 mask = alignment - 1; value |= mask; } /** * Aligns @p value to the highest integer not exceeding @p value and * that is a divident of @p alignment */ inline void alignByPow2Lo(qint32 &value, qint32 alignment) { qint32 mask = alignment - 1; value &= ~mask; } inline void alignRectBy2(qint32 &x, qint32 &y, qint32 &w, qint32 &h) { x -= isOdd(x); y -= isOdd(y); w += isOdd(x); w += isOdd(w); h += isOdd(y); h += isOdd(h); } /************* class KisImagePyramid ********************************/ KisImagePyramid::KisImagePyramid(qint32 pyramidHeight) : m_monitorProfile(0) , m_monitorColorSpace(0) , m_pyramidHeight(pyramidHeight) { configChanged(); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); } KisImagePyramid::~KisImagePyramid() { setImage(0); } void KisImagePyramid::setMonitorProfile(const KoColorProfile* monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { m_monitorProfile = monitorProfile; /** * If you change pixel size here, don't forget to change it * in optimized function downsamplePixels() */ m_monitorColorSpace = KoColorSpaceRegistry::instance()->rgb8(monitorProfile); m_renderingIntent = renderingIntent; m_conversionFlags = conversionFlags; rebuildPyramid(); } void KisImagePyramid::setChannelFlags(const QBitArray &channelFlags) { m_channelFlags = channelFlags; int selectedChannels = 0; const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace(); QList channelInfo = projectionCs->channels(); if (channelInfo.size() != m_channelFlags.size()) { m_channelFlags = QBitArray(); } for (int i = 0; i < m_channelFlags.size(); ++i) { if (m_channelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) { selectedChannels++; m_selectedChannelIndex = i; } } m_allChannelsSelected = (selectedChannels == m_channelFlags.size()); m_onlyOneChannelSelected = (selectedChannels == 1); } void KisImagePyramid::setDisplayFilter(QSharedPointer displayFilter) { m_displayFilter = displayFilter; } void KisImagePyramid::rebuildPyramid() { m_pyramid.clear(); for (qint32 i = 0; i < m_pyramidHeight; i++) { m_pyramid.append(new KisPaintDevice(m_monitorColorSpace)); } } void KisImagePyramid::clearPyramid() { for (qint32 i = 0; i < m_pyramidHeight; i++) { m_pyramid[i]->clear(); } } void KisImagePyramid::setImage(KisImageWSP newImage) { if (newImage) { m_originalImage = newImage; clearPyramid(); setImageSize(m_originalImage->width(), m_originalImage->height()); // Get the full image size QRect rc = m_originalImage->projection()->exactBounds(); KisImageConfig config(true); int patchWidth = config.updatePatchWidth(); int patchHeight = config.updatePatchHeight(); if (rc.width() * rc.height() <= patchWidth * patchHeight) { retrieveImageData(rc); } else { qint32 firstCol = rc.x() / patchWidth; qint32 firstRow = rc.y() / patchHeight; qint32 lastCol = (rc.x() + rc.width()) / patchWidth; qint32 lastRow = (rc.y() + rc.height()) / patchHeight; for(qint32 i = firstRow; i <= lastRow; i++) { for(qint32 j = firstCol; j <= lastCol; j++) { QRect maxPatchRect(j * patchWidth, i * patchHeight, patchWidth, patchHeight); QRect patchRect = rc & maxPatchRect; retrieveImageData(patchRect); } } } //TODO: check whether there is needed recalculateCache() } } void KisImagePyramid::setImageSize(qint32 w, qint32 h) { Q_UNUSED(w); Q_UNUSED(h); /* nothing interesting */ } void KisImagePyramid::updateCache(const QRect &dirtyImageRect) { retrieveImageData(dirtyImageRect); } void KisImagePyramid::retrieveImageData(const QRect &rect) { // XXX: use QThreadStorage to cache the two patches (512x512) of pixels. Note // that when we do that, we need to reset that cache when the projection's // colorspace changes. const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace(); KisPaintDeviceSP originalProjection = m_originalImage->projection(); quint32 numPixels = rect.width() * rect.height(); QScopedArrayPointer originalBytes( new quint8[originalProjection->colorSpace()->pixelSize() * numPixels]); originalProjection->readBytes(originalBytes.data(), rect); if (m_displayFilter && m_useOcio && projectionCs->colorModelId() == RGBAColorModelID) { #ifdef HAVE_OCIO const KoColorProfile *destinationProfile = m_displayFilter->useInternalColorManagement() ? m_monitorProfile : projectionCs->profile(); const KoColorSpace *floatCs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), destinationProfile); const KoColorSpace *modifiedMonitorCs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), destinationProfile); if (projectionCs->colorDepthId() == Float32BitsColorDepthID) { m_displayFilter->filter(originalBytes.data(), numPixels); } else { QScopedArrayPointer dst(new quint8[floatCs->pixelSize() * numPixels]); projectionCs->convertPixelsTo(originalBytes.data(), dst.data(), floatCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); m_displayFilter->filter(dst.data(), numPixels); originalBytes.swap(dst); } { QScopedArrayPointer dst(new quint8[modifiedMonitorCs->pixelSize() * numPixels]); floatCs->convertPixelsTo(originalBytes.data(), dst.data(), modifiedMonitorCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); originalBytes.swap(dst); } #endif } else { QList channelInfo = projectionCs->channels(); if (m_channelFlags.size() != channelInfo.size()) { setChannelFlags(QBitArray()); } if (!m_channelFlags.isEmpty() && !m_allChannelsSelected) { QScopedArrayPointer dst(new quint8[projectionCs->pixelSize() * numPixels]); - int channelSize = channelInfo[m_selectedChannelIndex]->size(); - int pixelSize = projectionCs->pixelSize(); - KisConfig cfg(true); if (m_onlyOneChannelSelected && !cfg.showSingleChannelAsColor()) { - int selectedChannelPos = channelInfo[m_selectedChannelIndex]->pos(); - for (uint pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { - for (uint channelIndex = 0; channelIndex < projectionCs->channelCount(); ++channelIndex) { - - if (channelInfo[channelIndex]->channelType() == KoChannelInfo::COLOR) { - memcpy(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - originalBytes.data() + (pixelIndex * pixelSize) + selectedChannelPos, - channelSize); - } - else if (channelInfo[channelIndex]->channelType() == KoChannelInfo::ALPHA) { - memcpy(dst.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - originalBytes.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - channelSize); - } - } - } + projectionCs->convertChannelToVisualRepresentation(originalBytes.data(), dst.data(), numPixels, m_selectedChannelIndex); } 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); - } - } - } - + projectionCs->convertChannelToVisualRepresentation(originalBytes.data(), dst.data(), numPixels, m_channelFlags); } originalBytes.swap(dst); } QScopedArrayPointer dst(new quint8[m_monitorColorSpace->pixelSize() * numPixels]); projectionCs->convertPixelsTo(originalBytes.data(), dst.data(), m_monitorColorSpace, numPixels, m_renderingIntent, m_conversionFlags); originalBytes.swap(dst); } m_pyramid[ORIGINAL_INDEX]->writeBytes(originalBytes.data(), rect); } void KisImagePyramid::recalculateCache(KisPPUpdateInfoSP info) { KisPaintDevice *src; KisPaintDevice *dst; QRect currentSrcRect = info->dirtyImageRectVar; for (int i = FIRST_NOT_ORIGINAL_INDEX; i < m_pyramidHeight; i++) { src = m_pyramid[i-1].data(); dst = m_pyramid[i].data(); if (!currentSrcRect.isEmpty()) { currentSrcRect = downsampleByFactor2(currentSrcRect, src, dst); } } #ifdef DEBUG_PYRAMID QImage image = m_pyramid[ORIGINAL_INDEX]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./PYRAMID_BASE.png"); image = m_pyramid[1]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./LEVEL1.png"); image = m_pyramid[2]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./LEVEL2.png"); image = m_pyramid[3]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags); image.save("./LEVEL3.png"); #endif } QRect KisImagePyramid::downsampleByFactor2(const QRect& srcRect, KisPaintDevice* src, KisPaintDevice* dst) { qint32 srcX, srcY, srcWidth, srcHeight; srcRect.getRect(&srcX, &srcY, &srcWidth, &srcHeight); alignRectBy2(srcX, srcY, srcWidth, srcHeight); // Nothing to do if (srcWidth < 1) return QRect(); if (srcHeight < 1) return QRect(); qint32 dstX = srcX / 2; qint32 dstY = srcY / 2; qint32 dstWidth = srcWidth / 2; qint32 dstHeight = srcHeight / 2; KisHLineConstIteratorSP srcIt0 = src->createHLineConstIteratorNG(srcX, srcY, srcWidth); KisHLineConstIteratorSP srcIt1 = src->createHLineConstIteratorNG(srcX, srcY + 1, srcWidth); KisHLineIteratorSP dstIt = dst->createHLineIteratorNG(dstX, dstY, dstWidth); int conseqPixels = 0; for (int row = 0; row < dstHeight; ++row) { do { int srcItConseq = srcIt0->nConseqPixels(); int dstItConseq = dstIt->nConseqPixels(); conseqPixels = qMin(srcItConseq, dstItConseq * 2); Q_ASSERT(!isOdd(conseqPixels)); downsamplePixels(srcIt0->oldRawData(), srcIt1->oldRawData(), dstIt->rawData(), conseqPixels); srcIt1->nextPixels(conseqPixels); dstIt->nextPixels(conseqPixels / 2); } while (srcIt0->nextPixels(conseqPixels)); srcIt0->nextRow(); srcIt0->nextRow(); srcIt1->nextRow(); srcIt1->nextRow(); dstIt->nextRow(); } return QRect(dstX, dstY, dstWidth, dstHeight); } void KisImagePyramid::downsamplePixels(const quint8 *srcRow0, const quint8 *srcRow1, quint8 *dstRow, qint32 numSrcPixels) { /** * FIXME (mandatory): Use SSE and friends here. */ qint16 b = 0; qint16 g = 0; qint16 r = 0; qint16 a = 0; static const qint32 pixelSize = 4; // This is preview argb8 mode for (qint32 i = 0; i < numSrcPixels / 2; i++) { b = srcRow0[0] + srcRow1[0] + srcRow0[4] + srcRow1[4]; g = srcRow0[1] + srcRow1[1] + srcRow0[5] + srcRow1[5]; r = srcRow0[2] + srcRow1[2] + srcRow0[6] + srcRow1[6]; a = srcRow0[3] + srcRow1[3] + srcRow0[7] + srcRow1[7]; dstRow[0] = b / 4; dstRow[1] = g / 4; dstRow[2] = r / 4; dstRow[3] = a / 4; dstRow += pixelSize; srcRow0 += 2 * pixelSize; srcRow1 += 2 * pixelSize; } } int KisImagePyramid::findFirstGoodPlaneIndex(qreal scale, QSize originalSize) { qint32 nearest = 0; for (qint32 i = 0; i < m_pyramidHeight; i++) { qreal planeScale = SCALE_FROM_INDEX(i); if (planeScale < scale) { if (originalSize*scale == originalSize*planeScale) nearest = i; break; } nearest = i; } // FOR DEBUGGING //nearest = 0; //nearest = qMin(1, nearest); dbgRender << "First good plane:" << nearest << "(sc:" << scale << ")"; return nearest; } void KisImagePyramid::alignSourceRect(QRect& rect, qreal scale) { qint32 index = findFirstGoodPlaneIndex(scale, rect.size()); qint32 alignment = 1 << index; dbgRender << "Before alignment:\t" << rect; /** * Assume that KisImage pixels are always positive * It allows us to use binary op-s for aligning */ Q_ASSERT(rect.left() >= 0 && rect.top() >= 0); qint32 x1, y1, x2, y2; rect.getCoords(&x1, &y1, &x2, &y2); alignByPow2Lo(x1, alignment); alignByPow2Lo(y1, alignment); /** * Here is a workaround of Qt's QRect::right()/bottom() * "historical reasons". It should be one pixel smaller * than actual right/bottom position */ alignByPow2ButOneHi(x2, alignment); alignByPow2ButOneHi(y2, alignment); rect.setCoords(x1, y1, x2, y2); dbgRender << "After alignment:\t" << rect; } KisImagePatch KisImagePyramid::getNearestPatch(KisPPUpdateInfoSP info) { qint32 index = findFirstGoodPlaneIndex(qMax(info->scaleX, info->scaleY), info->imageRect.size()); qreal planeScale = SCALE_FROM_INDEX(index); qint32 alignment = 1 << index; alignByPow2Hi(info->borderWidth, alignment); KisImagePatch patch(info->imageRect, info->borderWidth, planeScale, planeScale); patch.setImage(convertToQImageFast(m_pyramid[index], patch.patchRect())); return patch; } void KisImagePyramid::drawFromOriginalImage(QPainter& gc, KisPPUpdateInfoSP info) { KisImagePatch patch = getNearestPatch(info); patch.drawMe(gc, info->viewportRect, info->renderHints); } QImage KisImagePyramid::convertToQImageFast(KisPaintDeviceSP paintDevice, const QRect& unscaledRect) { qint32 x, y, w, h; unscaledRect.getRect(&x, &y, &w, &h); QImage image = QImage(w, h, QImage::Format_ARGB32); paintDevice->dataManager()->readBytes(image.bits(), x, y, w, h); return image; } void KisImagePyramid::configChanged() { KisConfig cfg(true); m_useOcio = cfg.useOcio(); } diff --git a/libs/ui/opengl/kis_texture_tile_update_info.h b/libs/ui/opengl/kis_texture_tile_update_info.h index 60900c9d58..aa4edc7440 100644 --- a/libs/ui/opengl/kis_texture_tile_update_info.h +++ b/libs/ui/opengl/kis_texture_tile_update_info.h @@ -1,375 +1,345 @@ /* * Copyright (c) 2010, Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TEXTURE_TILE_UPDATE_INFO_H_ #define KIS_TEXTURE_TILE_UPDATE_INFO_H_ #include #include #include -#include +#include "kis_config.h" #include "kis_image.h" #include "kis_paint_device.h" -#include "kis_config.h" -#include +#include "kis_texture_tile_info_pool.h" #include +#include +#include +#include #include -#include "kis_texture_tile_info_pool.h" - class KisTextureTileUpdateInfo; typedef QSharedPointer KisTextureTileUpdateInfoSP; typedef QVector KisTextureTileUpdateInfoSPList; /** * A buffer object for temporary data needed during the update process. * * - the buffer is allocated from the common pool to avoid memory * fragmentation * * - the buffer's lifetime defines the lifetime of the allocated chunk * of memory, so you don't have to thing about free'ing the memory */ class DataBuffer { public: DataBuffer(KisTextureTileInfoPoolSP pool) : m_data(0), m_pixelSize(0), m_pool(pool) { } DataBuffer(int pixelSize, KisTextureTileInfoPoolSP pool) : m_data(0), m_pixelSize(0), m_pool(pool) { allocate(pixelSize); } DataBuffer(DataBuffer &&rhs) : m_data(rhs.m_data), m_pixelSize(rhs.m_pixelSize), m_pool(rhs.m_pool) { rhs.m_data = 0; } DataBuffer& operator=(DataBuffer &&rhs) { swap(rhs); return *this; } ~DataBuffer() { if (m_data) { m_pool->free(m_data, m_pixelSize); } } void allocate(int pixelSize) { Q_ASSERT(!m_data); m_pixelSize = pixelSize; m_data = m_pool->malloc(m_pixelSize); } inline quint8* data() const { return m_data; } void swap(DataBuffer &other) { std::swap(other.m_pixelSize, m_pixelSize); std::swap(other.m_data, m_data); std::swap(other.m_pool, m_pool); } int size() const { return m_data ? m_pool->chunkSize(m_pixelSize) : 0; } KisTextureTileInfoPoolSP pool() const { return m_pool; } int pixelSize() const { return m_pixelSize; } private: Q_DISABLE_COPY(DataBuffer) quint8 *m_data; int m_pixelSize; KisTextureTileInfoPoolSP m_pool; }; class KisTextureTileUpdateInfo { public: KisTextureTileUpdateInfo(KisTextureTileInfoPoolSP pool) : m_patchPixels(pool), m_pool(pool) { } KisTextureTileUpdateInfo(qint32 col, qint32 row, const QRect &tileRect, const QRect &updateRect, const QRect ¤tImageRect, int levelOfDetail, KisTextureTileInfoPoolSP pool) : m_patchPixels(pool), m_pool(pool) { m_tileCol = col; m_tileRow = row; m_tileRect = tileRect; m_originalTileRect = m_tileRect; m_patchRect = m_tileRect & updateRect; m_originalPatchRect = m_patchRect; m_currentImageRect = currentImageRect; m_patchLevelOfDetail = levelOfDetail; if (m_patchLevelOfDetail) { // TODO: check if isBottommost() works correctly when m_originalPatchRect gets aligned // and m_currentImageRect has non-aligned size m_originalPatchRect = KisLodTransform::alignedRect(m_originalPatchRect, m_patchLevelOfDetail); m_patchRect = KisLodTransform::scaledRect(m_originalPatchRect, m_patchLevelOfDetail); m_tileRect = KisLodTransform::scaledRect(m_originalTileRect, m_patchLevelOfDetail); } } ~KisTextureTileUpdateInfo() { } void retrieveData(KisPaintDeviceSP projectionDevice, const QBitArray &channelFlags, bool onlyOneChannelSelected, int selectedChannelIndex) { m_patchColorSpace = projectionDevice->colorSpace(); m_patchPixels.allocate(m_patchColorSpace->pixelSize()); projectionDevice->readBytes(m_patchPixels.data(), m_patchRect.x(), m_patchRect.y(), m_patchRect.width(), m_patchRect.height()); // XXX: if the paint colorspace is rgb, we should do the channel swizzling in // the display shader if (!channelFlags.isEmpty() && selectedChannelIndex >= 0 && selectedChannelIndex < m_patchColorSpace->channels().size()) { DataBuffer conversionCache(m_patchColorSpace->pixelSize(), m_pool); QList channelInfo = m_patchColorSpace->channels(); - int channelSize = channelInfo[selectedChannelIndex]->size(); - int pixelSize = m_patchColorSpace->pixelSize(); quint32 numPixels = m_patchRect.width() * m_patchRect.height(); KisConfig cfg(true); if (onlyOneChannelSelected && !cfg.showSingleChannelAsColor()) { - int selectedChannelPos = channelInfo[selectedChannelIndex]->pos(); - for (uint pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { - for (uint channelIndex = 0; channelIndex < m_patchColorSpace->channelCount(); ++channelIndex) { - - if (channelInfo[channelIndex]->channelType() == KoChannelInfo::COLOR) { - memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - m_patchPixels.data() + (pixelIndex * pixelSize) + selectedChannelPos, - channelSize); - } - else if (channelInfo[channelIndex]->channelType() == KoChannelInfo::ALPHA) { - memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - m_patchPixels.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - channelSize); - } - } - } - } - else { - for (uint pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { - for (uint channelIndex = 0; channelIndex < m_patchColorSpace->channelCount(); ++channelIndex) { - if (channelFlags.testBit(channelIndex)) { - memcpy(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - m_patchPixels.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), - channelSize); - } - else { - memset(conversionCache.data() + (pixelIndex * pixelSize) + (channelIndex * channelSize), 0, channelSize); - } - } - } - + m_patchColorSpace->convertChannelToVisualRepresentation(m_patchPixels.data(), conversionCache.data(), numPixels, selectedChannelIndex); + } else { + m_patchColorSpace->convertChannelToVisualRepresentation(m_patchPixels.data(), conversionCache.data(), numPixels, channelFlags); } conversionCache.swap(m_patchPixels); } } void convertTo(const KoColorSpace* dstCS, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { // we use two-stage check of the color space equivalence: // first check pointers, and if not, check the spaces themselves if ((dstCS == m_patchColorSpace || *dstCS == *m_patchColorSpace) && conversionFlags == KoColorConversionTransformation::Empty) { return; } if (m_patchRect.isValid()) { const qint32 numPixels = m_patchRect.width() * m_patchRect.height(); DataBuffer conversionCache(dstCS->pixelSize(), m_pool); m_patchColorSpace->convertPixelsTo(m_patchPixels.data(), conversionCache.data(), dstCS, numPixels, renderingIntent, conversionFlags); m_patchColorSpace = dstCS; conversionCache.swap(m_patchPixels); } } void proofTo(const KoColorSpace* dstCS, KoColorConversionTransformation::ConversionFlags conversionFlags, KoColorConversionTransformation *proofingTransform) { if (dstCS == m_patchColorSpace && conversionFlags == KoColorConversionTransformation::Empty) return; if (m_patchRect.isValid()) { const qint32 numPixels = m_patchRect.width() * m_patchRect.height(); DataBuffer conversionCache(dstCS->pixelSize(), m_pool); m_patchColorSpace->proofPixelsTo(m_patchPixels.data(), conversionCache.data(), numPixels, proofingTransform); m_patchColorSpace = dstCS; conversionCache.swap(m_patchPixels); } } static KoColorConversionTransformation *generateProofingTransform(const KoColorSpace* srcCS, const KoColorSpace* dstCS, const KoColorSpace* proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KoColor gamutWarning, double adaptationState) { return srcCS->createProofingTransform(dstCS, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning.data(), adaptationState); } inline quint8* data() const { return m_patchPixels.data(); } inline int patchLevelOfDetail() const { return m_patchLevelOfDetail; } inline QPoint realPatchOffset() const { return QPoint(m_patchRect.x() - m_tileRect.x(), m_patchRect.y() - m_tileRect.y()); } inline QSize realPatchSize() const { return m_patchRect.size(); } inline QRect realPatchRect() const { return m_patchRect; } inline QSize realTileSize() const { return m_tileRect.size(); } inline bool isTopmost() const { return m_originalPatchRect.top() == m_currentImageRect.top(); } inline bool isLeftmost() const { return m_originalPatchRect.left() == m_currentImageRect.left(); } inline bool isRightmost() const { return m_originalPatchRect.right() == m_currentImageRect.right(); } inline bool isBottommost() const { return m_originalPatchRect.bottom() == m_currentImageRect.bottom(); } inline bool isEntireTileUpdated() const { return m_patchRect == m_tileRect; } inline qint32 tileCol() const { return m_tileCol; } inline qint32 tileRow() const { return m_tileRow; } inline int pixelSize() const { return m_patchColorSpace->pixelSize(); } inline const KoColorSpace* patchColorSpace() const { return m_patchColorSpace; } inline quint32 patchPixelsLength() const { return m_patchPixels.size(); } inline bool valid() const { return m_patchRect.isValid(); } inline DataBuffer&& takePixelData() { return std::move(m_patchPixels); } inline void putPixelData(DataBuffer &&buffer, const KoColorSpace *colorSpace) { m_patchPixels = std::move(buffer); m_patchColorSpace = colorSpace; } private: Q_DISABLE_COPY(KisTextureTileUpdateInfo) private: qint32 m_tileCol; qint32 m_tileRow; QRect m_currentImageRect; QRect m_tileRect; QRect m_patchRect; const KoColorSpace* m_patchColorSpace; QRect m_realPatchRect; QRect m_realPatchOffset; QRect m_realTileSize; int m_patchLevelOfDetail; QRect m_originalPatchRect; QRect m_originalTileRect; DataBuffer m_patchPixels; KisTextureTileInfoPoolSP m_pool; }; #endif /* KIS_TEXTURE_TILE_UPDATE_INFO_H_ */ diff --git a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp index a11749a3d0..78f3a54d25 100644 --- a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp @@ -1,118 +1,206 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2020 L. E. Segovia * * 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 * Library 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 "LabF32ColorSpace.h" #include #include #include "../compositeops/KoCompositeOps.h" #include #include LabF32ColorSpace::LabF32ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_LabA_FLT, cmsSigLabData, p) { const IccColorProfile *icc_p = dynamic_cast(p); Q_ASSERT(icc_p); QVector uiRanges(icc_p->getFloatUIMinMax()); Q_ASSERT(uiRanges.size() == 3); addChannel(new KoChannelInfo(i18n("Lightness"), 0 * sizeof(float), 0, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), QColor(100, 100, 100), uiRanges[0])); addChannel(new KoChannelInfo(i18n("a*"), 1 * sizeof(float), 1, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), QColor(150, 150, 150), uiRanges[1])); addChannel(new KoChannelInfo(i18n("b*"), 2 * sizeof(float), 2, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), QColor(200, 200, 200), uiRanges[2])); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(float), 3, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT32, sizeof(float))); init(); addStandardCompositeOps(this); dbgPlugins << "La*b* (float) channel bounds for: " << icc_p->name(); dbgPlugins << "L: " << uiRanges[0].minVal << uiRanges[0].maxVal; dbgPlugins << "a: " << uiRanges[1].minVal << uiRanges[1].maxVal; dbgPlugins << "b: " << uiRanges[2].minVal << uiRanges[2].maxVal; } bool LabF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA16) { return true; } else { return false; } } KoColorSpace *LabF32ColorSpace::clone() const { return new LabF32ColorSpace(name(), profile()->clone()); } void LabF32ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoLabF32Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("Lab"); // XML expects 0-1, we need 0-100, -128-+127 // Get the bounds from the channels and adjust the calculations labElt.setAttribute("L", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[0]->getUIUnitValue() * (p->L - this->channels()[0]->getUIMin())))); labElt.setAttribute("a", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[1]->getUIUnitValue() * (p->a - this->channels()[1]->getUIMin())))); labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[2]->getUIUnitValue() * (p->b - this->channels()[2]->getUIMin())))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void LabF32ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoLabF32Traits::Pixel *p = reinterpret_cast(pixel); p->L = this->channels()[0]->getUIMin() + KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("L"))) * this->channels()[0]->getUIUnitValue(); p->a = this->channels()[1]->getUIMin() + KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("a"))) * this->channels()[1]->getUIUnitValue(); p->b = this->channels()[2]->getUIMin() + KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))) * this->channels()[2]->getUIUnitValue(); p->alpha = 1.0; } void LabF32ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { LabToLCH(channelValues[0],channelValues[1],channelValues[2], luma, sat, hue); } QVector LabF32ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); LCHToLab(*luma, *sat, *hue, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } void LabF32ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { *y =channelValues[0]; *u=channelValues[1]; *v=channelValues[2]; } QVector LabF32ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); channelValues[0]=*y; channelValues[1]=*u; channelValues[2]=*v; channelValues[3]=1.0; return channelValues; } + +quint8 LabF32ColorSpace::scaleToU8(const quint8 *srcPixel, qint32 channelIndex) const +{ + typename ColorSpaceTraits::channels_type c = ColorSpaceTraits::nativeArray(srcPixel)[channelIndex]; + qreal b = 0; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + b = ((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB)); + } else { + b = 0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB)); + } + break; + default: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValue; + break; + } + + return KoColorSpaceMaths::scaleToA(b); +} + +void LabF32ColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (channel->channelType() == KoChannelInfo::COLOR) { + if (channelIndex == ColorSpaceTraits::L_pos) { + ColorSpaceTraits::channels_type c = ColorSpaceTraits::parent::nativeArray((src + (pixelIndex * ColorSpaceTraits::pixelSize)))[selectedChannelIndex]; + switch (selectedChannelIndex) { + case ColorSpaceTraits::L_pos: + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + c = ColorSpaceTraits::math_trait::unitValueL * (((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB))); + } else { + c = ColorSpaceTraits::math_trait::unitValueL * (0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB))); + } + break; + // As per KoChannelInfo alpha channels are [0..1] + default: + c = ColorSpaceTraits::math_trait::unitValueL * (qreal)c / ColorSpaceTraits::math_trait::unitValue; + break; + } + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = c; + } else { + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = ColorSpaceTraits::math_trait::halfValueAB; + } + } else if (channel->channelType() == KoChannelInfo::ALPHA) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } + } + } +} + +void LabF32ColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (selectedChannels.testBit(channelIndex)) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } else { + ColorSpaceTraits::channels_type v; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + v = ColorSpaceTraits::math_trait::halfValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + v = ColorSpaceTraits::math_trait::halfValueAB; + break; + default: + v = ColorSpaceTraits::math_trait::zeroValue; + break; + } + reinterpret_cast(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize))[0] = v; + } + } + } +} diff --git a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.h b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.h index aa62bb726b..c9e422d31a 100644 --- a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.h +++ b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.h @@ -1,122 +1,125 @@ /* * 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 * Library 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 LabF32ColorSpace_H_ #define LabF32ColorSpace_H_ #include "LcmsColorSpace.h" #include "KoColorModelStandardIds.h" // XXX: implement normalizedChannelValues? struct KoLabF32Traits; class LabF32ColorSpace : public LcmsColorSpace { public: LabF32ColorSpace(const QString &name, KoColorProfile *p); bool willDegrade(ColorSpaceIndependence independence) const override; static QString colorSpaceId() { return QString("LABAF32"); } KoID colorModelId() const override { return LABAColorModelID; } KoID colorDepthId() const override { return Float32BitsColorDepthID; } virtual KoColorSpace *clone() const; void colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const override; void colorFromXML(quint8* pixel, const QDomElement& elt) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; + quint8 scaleToU8(const quint8 * srcPixel, qint32 channelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const override; bool hasHighDynamicRange() const override { return true; } }; class LabF32ColorSpaceFactory : public LcmsColorSpaceFactory { public: LabF32ColorSpaceFactory() : LcmsColorSpaceFactory(TYPE_LabA_FLT, cmsSigLabData) { } bool userVisible() const override { return true; } QString id() const override { return LabF32ColorSpace::colorSpaceId(); } QString name() const override { return QString("%1 (%2)").arg(LABAColorModelID.name()).arg(Float32BitsColorDepthID.name()); } KoID colorModelId() const override { return LABAColorModelID; } KoID colorDepthId() const override { return Float32BitsColorDepthID; } int referenceDepth() const override { return 32; } KoColorSpace *createColorSpace(const KoColorProfile *p) const override { return new LabF32ColorSpace(name(), p->clone()); } QString defaultProfile() const override { return "Lab identity built-in"; } bool isHdr() const override { return true; } }; #endif diff --git a/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp index 84280ce765..99ae8bf600 100644 --- a/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp @@ -1,144 +1,232 @@ /* * 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 * Library 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 "LabColorSpace.h" #include #include #include "../compositeops/KoCompositeOps.h" #include #include LabU16ColorSpace::LabU16ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_LABA_16, cmsSigLabData, p) { addChannel(new KoChannelInfo(i18n("Lightness"), 0 * sizeof(quint16), 0, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(100, 100, 100))); addChannel(new KoChannelInfo(i18n("a*"), 1 * sizeof(quint16), 1, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(150, 150, 150))); addChannel(new KoChannelInfo(i18n("b*"), 2 * sizeof(quint16), 2, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(200, 200, 200))); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(quint16), 3, KoChannelInfo::ALPHA, KoChannelInfo::UINT16, sizeof(quint16))); init(); addStandardCompositeOps(this); } bool LabU16ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA8) { return true; } else { return false; } } KoColorSpace *LabU16ColorSpace::clone() const { return new LabU16ColorSpace(name(), profile()->clone()); } void LabU16ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoLabU16Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("Lab"); qreal a, b; if (p->a <= KoLabColorSpaceMathsTraits::halfValueAB) { a = (p->a - KoLabColorSpaceMathsTraits::zeroValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB)); } else { a = 0.5 + (p->a - KoLabColorSpaceMathsTraits::halfValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } if (p->b <= KoLabColorSpaceMathsTraits::halfValueAB) { b = (p->b - KoLabColorSpaceMathsTraits::zeroValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB)); } else { b = 0.5 + (p->b - KoLabColorSpaceMathsTraits::halfValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } labElt.setAttribute("L", KisDomUtils::toString(KoColorSpaceMaths::scaleToA(p->L))); labElt.setAttribute("a", KisDomUtils::toString(a)); labElt.setAttribute("b", KisDomUtils::toString(b)); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void LabU16ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoLabU16Traits::Pixel *p = reinterpret_cast(pixel); double a = KisDomUtils::toDouble(elt.attribute("a")); double b = KisDomUtils::toDouble(elt.attribute("b")); p->L = KoColorSpaceMaths::scaleToA(KisDomUtils::toDouble(elt.attribute("L"))); if (a <= 0.5) { p->a = KoLabColorSpaceMathsTraits::zeroValueAB + 2.0 * a * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB); } else { p->a = (KoLabColorSpaceMathsTraits::halfValueAB + 2.0 * (a - 0.5) * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } if (b <= 0.5) { p->b = KoLabColorSpaceMathsTraits::zeroValueAB + 2.0 * b * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB); } else { p->b = (KoLabColorSpaceMathsTraits::halfValueAB + 2.0 * (b - 0.5) * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } p->alpha = KoColorSpaceMathsTraits::max; } void LabU16ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { LabToLCH(channelValues[0],channelValues[1],channelValues[2], luma, sat, hue); } QVector LabU16ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); LCHToLab(*luma, *sat, *hue, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } void LabU16ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { *y =channelValues[0]; *u=channelValues[1]; *v=channelValues[2]; } QVector LabU16ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); channelValues[0]=*y; channelValues[1]=*u; channelValues[2]=*v; channelValues[3]=1.0; return channelValues; } + +quint8 LabU16ColorSpace::scaleToU8(const quint8 *srcPixel, qint32 channelIndex) const +{ + typename ColorSpaceTraits::channels_type c = ColorSpaceTraits::nativeArray(srcPixel)[channelIndex]; + qreal b = 0; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + b = ((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB)); + } else { + b = 0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB)); + } + break; + default: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValue; + break; + } + + return KoColorSpaceMaths::scaleToA(b); +} + +void LabU16ColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (channel->channelType() == KoChannelInfo::COLOR) { + if (channelIndex == ColorSpaceTraits::L_pos) { + ColorSpaceTraits::channels_type c = ColorSpaceTraits::parent::nativeArray((src + (pixelIndex * ColorSpaceTraits::pixelSize)))[selectedChannelIndex]; + switch (selectedChannelIndex) { + case ColorSpaceTraits::L_pos: + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + c = ColorSpaceTraits::math_trait::unitValueL * (((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB))); + } else { + c = ColorSpaceTraits::math_trait::unitValueL * (0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB))); + } + break; + // As per KoChannelInfo alpha channels are [0..1] + default: + c = ColorSpaceTraits::math_trait::unitValueL * (qreal)c / ColorSpaceTraits::math_trait::unitValue; + break; + } + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = c; + } else { + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = ColorSpaceTraits::math_trait::halfValueAB; + } + } else if (channel->channelType() == KoChannelInfo::ALPHA) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } + } + } +} + +void LabU16ColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (selectedChannels.testBit(channelIndex)) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } else { + ColorSpaceTraits::channels_type v; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + v = ColorSpaceTraits::math_trait::halfValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + v = ColorSpaceTraits::math_trait::halfValueAB; + break; + default: + v = ColorSpaceTraits::math_trait::zeroValue; + break; + } + reinterpret_cast(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize))[0] = v; + } + } + } +} diff --git a/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.h b/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.h index 7785273a93..bb9fed6ba5 100644 --- a/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.h +++ b/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.h @@ -1,113 +1,116 @@ /* * 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 * Library 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 LabU16ColorSpace_H_ #define LabU16ColorSpace_H_ #include "LcmsColorSpace.h" #include "KoColorModelStandardIds.h" #define TYPE_LABA_16 (COLORSPACE_SH(PT_Lab) | CHANNELS_SH(3) | BYTES_SH(2) | EXTRA_SH(1)) struct KoLabF32Traits; class LabU16ColorSpace : public LcmsColorSpace { public: LabU16ColorSpace(const QString &name, KoColorProfile *p); bool willDegrade(ColorSpaceIndependence independence) const override; static QString colorSpaceId() { return QString("LABA"); } KoID colorModelId() const override { return LABAColorModelID; } KoID colorDepthId() const override { return Integer16BitsColorDepthID; } virtual KoColorSpace *clone() const; void colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const override; void colorFromXML(quint8* pixel, const QDomElement& elt) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; + quint8 scaleToU8(const quint8 * srcPixel, qint32 channelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const override; }; class LabU16ColorSpaceFactory : public LcmsColorSpaceFactory { public: LabU16ColorSpaceFactory() : LcmsColorSpaceFactory(TYPE_LABA_16, cmsSigLabData) { } bool userVisible() const override { return true; } QString id() const override { return LabU16ColorSpace::colorSpaceId(); } QString name() const override { return QString("%1 (%2)").arg(LABAColorModelID.name()).arg(Integer16BitsColorDepthID.name()); } KoID colorModelId() const override { return LABAColorModelID; } KoID colorDepthId() const override { return Integer16BitsColorDepthID; } int referenceDepth() const override { return 16; } KoColorSpace *createColorSpace(const KoColorProfile *p) const override { return new LabU16ColorSpace(name(), p->clone()); } QString defaultProfile() const override { return "Lab identity built-in"; } }; #endif diff --git a/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp index ae21504f96..1eff58fb4d 100644 --- a/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp @@ -1,139 +1,227 @@ /* * 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 * Library 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 "LabU8ColorSpace.h" #include #include #include "../compositeops/KoCompositeOps.h" #include #include LabU8ColorSpace::LabU8ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_LABA_8, cmsSigLabData, p) { addChannel(new KoChannelInfo(i18n("Lightness"), 0 * sizeof(quint8), 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8, sizeof(quint8), QColor(100, 100, 100))); addChannel(new KoChannelInfo(i18n("a*"), 1 * sizeof(quint8), 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8, sizeof(quint8), QColor(150, 150, 150))); addChannel(new KoChannelInfo(i18n("b*"), 2 * sizeof(quint8), 2, KoChannelInfo::COLOR, KoChannelInfo::UINT8, sizeof(quint8), QColor(200, 200, 200))); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(quint8), 3, KoChannelInfo::ALPHA, KoChannelInfo::UINT8, sizeof(quint8))); init(); addStandardCompositeOps(this); } bool LabU8ColorSpace::willDegrade(ColorSpaceIndependence /*independence*/) const { return false; } KoColorSpace *LabU8ColorSpace::clone() const { return new LabU8ColorSpace(name(), profile()->clone()); } void LabU8ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoLabU8Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("Lab"); double a, b; if (p->a <= KoLabColorSpaceMathsTraits::halfValueAB) { a = (p->a - KoLabColorSpaceMathsTraits::zeroValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB)); } else { a = 0.5 + (p->a - KoLabColorSpaceMathsTraits::halfValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } if (p->b <= KoLabColorSpaceMathsTraits::halfValueAB) { b = (p->b - KoLabColorSpaceMathsTraits::zeroValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB)); } else { b = 0.5 + (p->b - KoLabColorSpaceMathsTraits::halfValueAB) / (2.0 * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } labElt.setAttribute("L", KisDomUtils::toString(KoColorSpaceMaths::scaleToA(p->L))); labElt.setAttribute("a", KisDomUtils::toString(a)); labElt.setAttribute("b", KisDomUtils::toString(b)); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void LabU8ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoLabU8Traits::Pixel *p = reinterpret_cast(pixel); double a = KisDomUtils::toDouble(elt.attribute("a")); double b = KisDomUtils::toDouble(elt.attribute("b")); p->L = KoColorSpaceMaths::scaleToA(KisDomUtils::toDouble(elt.attribute("L"))); if (a <= 0.5) { p->a = KoLabColorSpaceMathsTraits::zeroValueAB + 2.0 * a * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB); } else { p->a = (KoLabColorSpaceMathsTraits::halfValueAB + 2.0 * (a - 0.5) * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } if (b <= 0.5) { p->b = KoLabColorSpaceMathsTraits::zeroValueAB + 2.0 * b * (KoLabColorSpaceMathsTraits::halfValueAB - KoLabColorSpaceMathsTraits::zeroValueAB); } else { p->b = (KoLabColorSpaceMathsTraits::halfValueAB + 2.0 * (b - 0.5) * (KoLabColorSpaceMathsTraits::unitValueAB - KoLabColorSpaceMathsTraits::halfValueAB)); } p->alpha = KoColorSpaceMathsTraits::max; } void LabU8ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { LabToLCH(channelValues[0],channelValues[1],channelValues[2], luma, sat, hue); } QVector LabU8ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); LCHToLab(*luma, *sat, *hue, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } void LabU8ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { *y =channelValues[0]; *u=channelValues[1]; *v=channelValues[2]; } QVector LabU8ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); channelValues[0]=*y; channelValues[1]=*u; channelValues[2]=*v; channelValues[3]=1.0; return channelValues; } + +quint8 LabU8ColorSpace::scaleToU8(const quint8 *srcPixel, qint32 channelIndex) const +{ + typename ColorSpaceTraits::channels_type c = ColorSpaceTraits::nativeArray(srcPixel)[channelIndex]; + qreal b = 0; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + b = ((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB)); + } else { + b = 0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB)); + } + break; + default: + b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValue; + break; + } + + return KoColorSpaceMaths::scaleToA(b); +} + +void LabU8ColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (channel->channelType() == KoChannelInfo::COLOR) { + if (channelIndex == ColorSpaceTraits::L_pos) { + ColorSpaceTraits::channels_type c = ColorSpaceTraits::parent::nativeArray((src + (pixelIndex * ColorSpaceTraits::pixelSize)))[selectedChannelIndex]; + switch (selectedChannelIndex) { + case ColorSpaceTraits::L_pos: + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + if (c <= ColorSpaceTraits::math_trait::halfValueAB) { + c = ColorSpaceTraits::math_trait::unitValueL * (((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB))); + } else { + c = ColorSpaceTraits::math_trait::unitValueL * (0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB))); + } + break; + // As per KoChannelInfo alpha channels are [0..1] + default: + c = ColorSpaceTraits::math_trait::unitValueL * (qreal)c / ColorSpaceTraits::math_trait::unitValue; + break; + } + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = c; + } else { + ColorSpaceTraits::parent::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = ColorSpaceTraits::math_trait::halfValueAB; + } + } else if (channel->channelType() == KoChannelInfo::ALPHA) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } + } + } +} + +void LabU8ColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const +{ + for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { + for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { + KoChannelInfo *channel = this->channels().at(channelIndex); + qint32 channelSize = channel->size(); + if (selectedChannels.testBit(channelIndex)) { + memcpy(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize), channelSize); + } else { + ColorSpaceTraits::channels_type v; + switch (channelIndex) { + case ColorSpaceTraits::L_pos: + v = ColorSpaceTraits::math_trait::halfValueL; + break; + case ColorSpaceTraits::a_pos: + case ColorSpaceTraits::b_pos: + v = ColorSpaceTraits::math_trait::halfValueAB; + break; + default: + v = ColorSpaceTraits::math_trait::zeroValue; + break; + } + reinterpret_cast(dst + (pixelIndex * ColorSpaceTraits::pixelSize) + (channelIndex * channelSize))[0] = v; + } + } + } +} diff --git a/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.h b/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.h index 5a7e98b1f6..fe2b62f62e 100644 --- a/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.h +++ b/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.h @@ -1,106 +1,109 @@ /* * 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 * Library 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 LabU8ColorSpace_H_ #define LabU8ColorSpace_H_ #include "LcmsColorSpace.h" #include "KoColorModelStandardIds.h" #define TYPE_LABA_8 (COLORSPACE_SH(PT_Lab) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1)) struct KoLabU8Traits; class LabU8ColorSpace : public LcmsColorSpace { public: LabU8ColorSpace(const QString &name, KoColorProfile *p); bool willDegrade(ColorSpaceIndependence independence) const override; static QString colorSpaceId() { return QString("LABAU8"); } KoID colorModelId() const override { return LABAColorModelID; } KoID colorDepthId() const override { return Integer8BitsColorDepthID; } virtual KoColorSpace* clone() const; void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const override; void colorFromXML(quint8* pixel, const QDomElement& elt) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; + quint8 scaleToU8(const quint8 * srcPixel, qint32 channelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const override; + void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const override; }; class LabU8ColorSpaceFactory : public LcmsColorSpaceFactory { public: LabU8ColorSpaceFactory() : LcmsColorSpaceFactory(TYPE_LABA_8, cmsSigLabData) {} bool userVisible() const override { return true; } QString id() const override { return LabU8ColorSpace::colorSpaceId(); } QString name() const override { return QString("%1 (%2)").arg(LABAColorModelID.name()).arg(Integer8BitsColorDepthID.name()); } KoID colorModelId() const override { return LABAColorModelID; } KoID colorDepthId() const override { return Integer8BitsColorDepthID; } int referenceDepth() const override { return 8; } KoColorSpace *createColorSpace(const KoColorProfile *p) const override { return new LabU8ColorSpace(name(), p->clone()); } QString defaultProfile() const override { return "Lab identity built-in"; } }; #endif