/* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "kis_abstract_colorspace.h" #include "kis_global.h" #include "kis_profile.h" #include "kis_id.h" #include "kis_integer_maths.h" #include "kis_color_conversions.h" #include "kis_colorspace_factory_registry.h" #include "kis_channelinfo.h" class KisColorAdjustmentImpl : public KisColorAdjustment { public: KisColorAdjustmentImpl() : KisColorAdjustment() { csProfile = 0; transform = 0; profiles[0] = 0; profiles[1] = 0; profiles[2] = 0; }; ~KisColorAdjustmentImpl() { if (transform) cmsDeleteTransform(transform); if (profiles[0] && profiles[0] != csProfile) cmsCloseProfile(profiles[0]); if(profiles[1] && profiles[1] != csProfile) cmsCloseProfile(profiles[1]); if(profiles[2] && profiles[2] != csProfile) cmsCloseProfile(profiles[2]); } cmsHPROFILE csProfile; cmsHPROFILE profiles[3]; cmsHTRANSFORM transform; }; KisAbstractColorSpace::KisAbstractColorSpace(const KisID& id, DWORD cmType, icColorSpaceSignature colorSpaceSignature, KisColorSpaceFactoryRegistry * parent, KisProfile *p) : m_parent( parent ) , m_profile( p ) , m_id( id ) , m_cmType( cmType ) , m_colorSpaceSignature( colorSpaceSignature ) { m_alphaPos = -1; m_alphaSize = -1; m_qcolordata = 0; m_lastUsedDstColorSpace = 0; m_lastUsedTransform = 0; m_lastRGBProfile = 0; m_lastToRGB = 0; m_lastFromRGB = 0; m_defaultFromRGB = 0; m_defaultToRGB = 0; m_defaultFromLab = 0; m_defaultToLab = 0; } void KisAbstractColorSpace::init() { // Default pixel buffer for TQColor conversion m_qcolordata = new TQ_UINT8[3]; TQ_CHECK_PTR(m_qcolordata); if (m_profile == 0) return; // For conversions from default rgb m_lastFromRGB = cmsCreate_sRGBProfile(); m_defaultFromRGB = cmsCreateTransform(m_lastFromRGB, TYPE_BGR_8, m_profile->profile(), m_cmType, INTENT_PERCEPTUAL, 0); m_defaultToRGB = cmsCreateTransform(m_profile->profile(), m_cmType, m_lastFromRGB, TYPE_BGR_8, INTENT_PERCEPTUAL, 0); cmsHPROFILE hLab = cmsCreateLabProfile(NULL); m_defaultFromLab = cmsCreateTransform(hLab, TYPE_Lab_16, m_profile->profile(), m_cmType, INTENT_PERCEPTUAL, 0); m_defaultToLab = cmsCreateTransform(m_profile->profile(), m_cmType, hLab, TYPE_Lab_16, INTENT_PERCEPTUAL, 0); } KisAbstractColorSpace::~KisAbstractColorSpace() { } void KisAbstractColorSpace::fromTQColor(const TQColor& color, TQ_UINT8 *dst, KisProfile * profile) { m_qcolordata[2] = color.red(); m_qcolordata[1] = color.green(); m_qcolordata[0] = color.blue(); if (profile == 0) { // Default sRGB if (!m_defaultFromRGB) return; cmsDoTransform(m_defaultFromRGB, m_qcolordata, dst, 1); } else { if (m_lastFromRGB == 0 || (m_lastFromRGB != 0 && m_lastRGBProfile != profile->profile())) { m_lastFromRGB = cmsCreateTransform(profile->profile(), TYPE_BGR_8, m_profile->profile(), m_cmType, INTENT_PERCEPTUAL, 0); m_lastRGBProfile = profile->profile(); } cmsDoTransform(m_lastFromRGB, m_qcolordata, dst, 1); } setAlpha(dst, OPACITY_OPAQUE, 1); } void KisAbstractColorSpace::fromTQColor(const TQColor& color, TQ_UINT8 opacity, TQ_UINT8 *dst, KisProfile * profile) { fromTQColor(color, dst, profile); setAlpha(dst, opacity, 1); } void KisAbstractColorSpace::toTQColor(const TQ_UINT8 *src, TQColor *c, KisProfile * profile) { if (profile == 0) { // Default sRGB transform if (!m_defaultToRGB) return; cmsDoTransform(m_defaultToRGB, const_cast (src), m_qcolordata, 1); } else { if (m_lastToRGB == 0 || (m_lastToRGB != 0 && m_lastRGBProfile != profile->profile())) { m_lastToRGB = cmsCreateTransform(m_profile->profile(), m_cmType, profile->profile(), TYPE_BGR_8, INTENT_PERCEPTUAL, 0); m_lastRGBProfile = profile->profile(); } cmsDoTransform(m_lastToRGB, const_cast (src), m_qcolordata, 1); } c->setRgb(m_qcolordata[2], m_qcolordata[1], m_qcolordata[0]); } void KisAbstractColorSpace::toTQColor(const TQ_UINT8 *src, TQColor *c, TQ_UINT8 *opacity, KisProfile * profile) { toTQColor(src, c, profile); *opacity = getAlpha(src); } void KisAbstractColorSpace::toLabA16(const TQ_UINT8 * src, TQ_UINT8 * dst, const TQ_UINT32 nPixels) const { if ( m_defaultToLab == 0 ) return; cmsDoTransform( m_defaultToLab, const_cast( src ), dst, nPixels ); } void KisAbstractColorSpace::fromLabA16(const TQ_UINT8 * src, TQ_UINT8 * dst, const TQ_UINT32 nPixels) const { if ( m_defaultFromLab == 0 ) return; cmsDoTransform( m_defaultFromLab, const_cast( src ), dst, nPixels ); } void KisAbstractColorSpace::getSingleChannelPixel(TQ_UINT8 *dstPixel, const TQ_UINT8 *srcPixel, TQ_UINT32 channelIndex) { if (channelIndex < m_channels.count()) { fromTQColor(TQt::black, OPACITY_TRANSPARENT, dstPixel); const KisChannelInfo *channelInfo = m_channels[channelIndex]; memcpy(dstPixel + channelInfo->pos(), srcPixel + channelInfo->pos(), channelInfo->size()); } } bool KisAbstractColorSpace::convertPixelsTo(const TQ_UINT8 * src, TQ_UINT8 * dst, KisColorSpace * dstColorSpace, TQ_UINT32 numPixels, TQ_INT32 renderingIntent) { if (dstColorSpace->colorSpaceType() == colorSpaceType() && dstColorSpace->getProfile() == getProfile()) { if (src!= dst) memcpy (dst, src, numPixels * pixelSize()); return true; } cmsHTRANSFORM tf = 0; TQ_INT32 srcPixelSize = pixelSize(); TQ_INT32 dstPixelSize = dstColorSpace->pixelSize(); if (m_lastUsedTransform != 0 && m_lastUsedDstColorSpace != 0) { if (dstColorSpace->colorSpaceType() == m_lastUsedDstColorSpace->colorSpaceType() && dstColorSpace->getProfile() == m_lastUsedDstColorSpace->getProfile()) { tf = m_lastUsedTransform; } } if (!tf && m_profile && dstColorSpace->getProfile()) { if (!m_transforms.contains(dstColorSpace)) { tf = createTransform(dstColorSpace, m_profile, dstColorSpace->getProfile(), renderingIntent); if (tf) { // XXX: Should we clear the transform cache if it gets too big? m_transforms[dstColorSpace] = tf; } } else { tf = m_transforms[dstColorSpace]; } if ( tf ) { m_lastUsedTransform = tf; m_lastUsedDstColorSpace = dstColorSpace; } } if (tf) { cmsDoTransform(tf, const_cast(src), dst, numPixels); // Lcms does nothing to the destination alpha channel so we must convert that manually. while (numPixels > 0) { TQ_UINT8 alpha = getAlpha(src); dstColorSpace->setAlpha(dst, alpha, 1); src += srcPixelSize; dst += dstPixelSize; numPixels--; } return true; } // Last resort fallback. This will be removed when this class is renamed KisLCMSColorSpace after 1.5. while (numPixels > 0) { TQColor color; TQ_UINT8 opacity; toTQColor(src, &color, &opacity); dstColorSpace->fromTQColor(color, opacity, dst); src += srcPixelSize; dst += dstPixelSize; numPixels--; } return true; } KisColorAdjustment *KisAbstractColorSpace::createBrightnessContrastAdjustment(TQ_UINT16 *transferValues) { if (!m_profile) return 0; LPGAMMATABLE transferFunctions[3]; transferFunctions[0] = cmsBuildGamma(256, 1.0); transferFunctions[1] = cmsBuildGamma(256, 1.0); transferFunctions[2] = cmsBuildGamma(256, 1.0); for(int i =0; i < 256; i++) transferFunctions[0]->GammaTable[i] = transferValues[i]; KisColorAdjustmentImpl *adj = new KisColorAdjustmentImpl; adj->profiles[1] = cmsCreateLinearizationDeviceLink(icSigLabData, transferFunctions); cmsSetDeviceClass(adj->profiles[1], icSigAbstractClass); adj->profiles[0] = m_profile->profile(); adj->profiles[2] = m_profile->profile(); adj->transform = cmsCreateMultiprofileTransform(adj->profiles, 3, m_cmType, m_cmType, INTENT_PERCEPTUAL, 0); adj->csProfile = m_profile->profile(); return adj; } typedef struct { double Saturation; } BCHSWADJUSTS, *LPBCHSWADJUSTS; static int desaturateSampler(WORD In[], WORD Out[], LPVOID /*Cargo*/) { cmsCIELab LabIn, LabOut; cmsCIELCh LChIn, LChOut; //LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo; cmsLabEncoded2Float(&LabIn, In); cmsLab2LCh(&LChIn, &LabIn); // Do some adjusts on LCh LChOut.L = LChIn.L; LChOut.C = 0;//LChIn.C + bchsw->Saturation; LChOut.h = LChIn.h; cmsLCh2Lab(&LabOut, &LChOut); // Back to encoded cmsFloat2LabEncoded(Out, &LabOut); return TRUE; } KisColorAdjustment *KisAbstractColorSpace::createDesaturateAdjustment() { if (!m_profile) return 0; KisColorAdjustmentImpl *adj = new KisColorAdjustmentImpl; adj->profiles[0] = m_profile->profile(); adj->profiles[2] = m_profile->profile(); adj->csProfile = m_profile->profile(); LPLUT Lut; BCHSWADJUSTS bchsw; bchsw.Saturation = -25; adj->profiles[1] = _cmsCreateProfilePlaceholder(); if (!adj->profiles[1]) // can't allocate return NULL; cmsSetDeviceClass(adj->profiles[1], icSigAbstractClass); cmsSetColorSpace(adj->profiles[1], icSigLabData); cmsSetPCS(adj->profiles[1], icSigLabData); cmsSetRenderingIntent(adj->profiles[1], INTENT_PERCEPTUAL); // Creates a LUT with 3D grid only Lut = cmsAllocLUT(); cmsAlloc3DGrid(Lut, 32, 3, 3); if (!cmsSample3DGrid(Lut, desaturateSampler, static_cast(&bchsw), 0)) { // Shouldn't reach here cmsFreeLUT(Lut); cmsCloseProfile(adj->profiles[1]); return NULL; } // Create tags cmsAddTag(adj->profiles[1], icSigDeviceMfgDescTag, (LPVOID) "(chalk internal)"); cmsAddTag(adj->profiles[1], icSigProfileDescriptionTag, (LPVOID) "chalk saturation abstract profile"); cmsAddTag(adj->profiles[1], icSigDeviceModelDescTag, (LPVOID) "saturation built-in"); cmsAddTag(adj->profiles[1], icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ()); cmsAddTag(adj->profiles[1], icSigAToB0Tag, (LPVOID) Lut); // LUT is already on virtual profile cmsFreeLUT(Lut); adj->transform = cmsCreateMultiprofileTransform(adj->profiles, 3, m_cmType, m_cmType, INTENT_PERCEPTUAL, 0); return adj; } KisColorAdjustment *KisAbstractColorSpace::createPerChannelAdjustment(TQ_UINT16 **transferValues) { if (!m_profile) return 0; LPGAMMATABLE *transferFunctions = new LPGAMMATABLE[nColorChannels()+1]; for(uint ch=0; ch < nColorChannels(); ch++) { transferFunctions[ch] = cmsBuildGamma(256, 1.0); for(uint i =0; i < 256; i++) { transferFunctions[ch]->GammaTable[i] = transferValues[ch][i]; } } KisColorAdjustmentImpl *adj = new KisColorAdjustmentImpl; adj->profiles[0] = cmsCreateLinearizationDeviceLink(colorSpaceSignature(), transferFunctions); adj->profiles[1] = NULL; adj->profiles[2] = NULL; adj->csProfile = m_profile->profile(); adj->transform = cmsCreateTransform(adj->profiles[0], m_cmType, NULL, m_cmType, INTENT_PERCEPTUAL, 0); delete [] transferFunctions; return adj; } void KisAbstractColorSpace::applyAdjustment(const TQ_UINT8 *src, TQ_UINT8 *dst, KisColorAdjustment *adjustment, TQ_INT32 nPixels) { KisColorAdjustmentImpl * adj = dynamic_cast(adjustment); if (adj) cmsDoTransform(adj->transform, const_cast(src), dst, nPixels); } void KisAbstractColorSpace::invertColor(TQ_UINT8 * src, TQ_INT32 nPixels) { TQColor c; TQ_UINT8 opacity; TQ_UINT32 psize = pixelSize(); while (nPixels--) { toTQColor(src, &c, &opacity); c.setRgb(TQ_UINT8_MAX - c.red(), TQ_UINT8_MAX - c.green(), TQ_UINT8_MAX - c.blue()); fromTQColor( c, opacity, src); src += psize; } } TQ_UINT8 KisAbstractColorSpace::difference(const TQ_UINT8* src1, const TQ_UINT8* src2) { if (m_defaultToLab) { TQ_UINT8 lab1[8], lab2[8]; cmsCIELab labF1, labF2; if (getAlpha(src1) == OPACITY_TRANSPARENT || getAlpha(src2) == OPACITY_TRANSPARENT) return (getAlpha(src1) == getAlpha(src2) ? 0 : 255); cmsDoTransform( m_defaultToLab, const_cast( src1 ), lab1, 1); cmsDoTransform( m_defaultToLab, const_cast( src2 ), lab2, 1); cmsLabEncoded2Float(&labF1, (WORD *)lab1); cmsLabEncoded2Float(&labF2, (WORD *)lab2); double diff = cmsDeltaE(&labF1, &labF2); if(diff>255) return 255; else return TQ_INT8(diff); } else { TQColor c1; TQ_UINT8 opacity1; toTQColor(src1, &c1, &opacity1); TQColor c2; TQ_UINT8 opacity2; toTQColor(src2, &c2, &opacity2); TQ_UINT8 red = abs(c1.red() - c2.red()); TQ_UINT8 green = abs(c1.green() - c2.green()); TQ_UINT8 blue = abs(c1.blue() - c2.blue()); return TQMAX(red, TQMAX(green, blue)); } } void KisAbstractColorSpace::mixColors(const TQ_UINT8 **colors, const TQ_UINT8 *weights, TQ_UINT32 nColors, TQ_UINT8 *dst) const { TQ_UINT32 totalRed = 0, totalGreen = 0, totalBlue = 0, newAlpha = 0; TQColor c; TQ_UINT8 opacity; while (nColors--) { // Ugly hack to get around the current constness mess of the colour strategy... const_cast(this)->toTQColor(*colors, &c, &opacity); TQ_UINT32 alphaTimesWeight = UINT8_MULT(opacity, *weights); totalRed += c.red() * alphaTimesWeight; totalGreen += c.green() * alphaTimesWeight; totalBlue += c.blue() * alphaTimesWeight; newAlpha += alphaTimesWeight; weights++; colors++; } Q_ASSERT(newAlpha <= 255); if (newAlpha > 0) { totalRed = UINT8_DIVIDE(totalRed, newAlpha); totalGreen = UINT8_DIVIDE(totalGreen, newAlpha); totalBlue = UINT8_DIVIDE(totalBlue, newAlpha); } // Divide by 255. totalRed += 0x80; TQ_UINT32 dstRed = ((totalRed >> 8) + totalRed) >> 8; Q_ASSERT(dstRed <= 255); totalGreen += 0x80; TQ_UINT32 dstGreen = ((totalGreen >> 8) + totalGreen) >> 8; Q_ASSERT(dstGreen <= 255); totalBlue += 0x80; TQ_UINT32 dstBlue = ((totalBlue >> 8) + totalBlue) >> 8; Q_ASSERT(dstBlue <= 255); const_cast(this)->fromTQColor(TQColor(dstRed, dstGreen, dstBlue), newAlpha, dst); } void KisAbstractColorSpace::convolveColors(TQ_UINT8** colors, TQ_INT32 * kernelValues, KisChannelInfo::enumChannelFlags channelFlags, TQ_UINT8 *dst, TQ_INT32 factor, TQ_INT32 offset, TQ_INT32 nColors) const { TQ_INT32 totalRed = 0, totalGreen = 0, totalBlue = 0, totalAlpha = 0; TQColor dstColor; TQ_UINT8 dstOpacity; const_cast(this)->toTQColor(dst, &dstColor, &dstOpacity); while (nColors--) { TQ_INT32 weight = *kernelValues; if (weight != 0) { TQColor c; TQ_UINT8 opacity; const_cast(this)->toTQColor( *colors, &c, &opacity ); totalRed += c.red() * weight; totalGreen += c.green() * weight; totalBlue += c.blue() * weight; totalAlpha += opacity * weight; } colors++; kernelValues++; } if (channelFlags & KisChannelInfo::FLAG_COLOR) { const_cast(this)->fromTQColor(TQColor(CLAMP((totalRed / factor) + offset, 0, TQ_UINT8_MAX), CLAMP((totalGreen / factor) + offset, 0, TQ_UINT8_MAX), CLAMP((totalBlue / factor) + offset, 0, TQ_UINT8_MAX)), dstOpacity, dst); } if (channelFlags & KisChannelInfo::FLAG_ALPHA) { const_cast(this)->fromTQColor(dstColor, CLAMP((totalAlpha/ factor) + offset, 0, TQ_UINT8_MAX), dst); } } void KisAbstractColorSpace::darken(const TQ_UINT8 * src, TQ_UINT8 * dst, TQ_INT32 shade, bool compensate, double compensation, TQ_INT32 nPixels) const { if (m_defaultToLab) { TQ_UINT16 * labcache = new TQ_UINT16[nPixels * 4]; cmsDoTransform( m_defaultToLab, const_cast( src ), reinterpret_cast( labcache ), nPixels ); for ( int i = 0; i < nPixels * 4; ++i ) { if ( compensate ) { labcache[i] = static_cast( ( labcache[i] * shade ) / ( compensation * 255 ) ); } else { labcache[i] = static_cast( labcache[i] * shade / 255 ); } } cmsDoTransform( m_defaultFromLab, reinterpret_cast( labcache ), dst, nPixels ); // Copy alpha for ( int i = 0; i < nPixels; ++i ) { TQ_UINT8 alpha = getAlpha( src ); setAlpha( dst, alpha, 1 ); } delete [] labcache; } else { TQColor c; TQ_INT32 psize = pixelSize(); for (int i = 0; i < nPixels; ++i) { const_cast(this)->toTQColor(src + (i * psize), &c); TQ_INT32 r, g, b; if (compensate) { r = static_cast( TQMIN(255, (c.red() * shade) / (compensation * 255))); g = static_cast( TQMIN(255, (c.green() * shade) / (compensation * 255))); b = static_cast( TQMIN(255, (c.blue() * shade) / (compensation * 255))); } else { r = static_cast( TQMIN(255, (c.red() * shade / 255))); g = static_cast( TQMIN(255, (c.green() * shade / 255))); b = static_cast( TQMIN(255, (c.blue() * shade / 255))); } c.setRgb(r, g, b); const_cast(this)->fromTQColor( c, dst + (i * psize)); } } } TQ_UINT8 KisAbstractColorSpace::intensity8(const TQ_UINT8 * src) const { TQColor c; TQ_UINT8 opacity; const_cast(this)->toTQColor(src, &c, &opacity); return static_cast((c.red() * 0.30 + c.green() * 0.59 + c.blue() * 0.11) + 0.5); } KisID KisAbstractColorSpace::mathToolboxID() const { return KisID("Basic"); } void KisAbstractColorSpace::bitBlt(TQ_UINT8 *dst, TQ_INT32 dststride, KisColorSpace * srcSpace, const TQ_UINT8 *src, TQ_INT32 srcRowStride, const TQ_UINT8 *srcAlphaMask, TQ_INT32 maskRowStride, TQ_UINT8 opacity, TQ_INT32 rows, TQ_INT32 cols, const KisCompositeOp& op) { if (rows <= 0 || cols <= 0) return; if (this != srcSpace) { TQ_UINT32 len = pixelSize() * rows * cols; // If our conversion cache is too small, extend it. if (!m_conversionCache.resize( len, TQGArray::SpeedOptim )) { kdWarning() << "Could not allocate enough memory for the conversion!\n"; // XXX: We should do a slow, pixel by pixel bitblt here... abort(); } for (TQ_INT32 row = 0; row < rows; row++) { srcSpace->convertPixelsTo(src + row * srcRowStride, m_conversionCache.data() + row * cols * pixelSize(), this, cols); } // The old srcRowStride is no longer valid because we converted to the current cs srcRowStride = cols * pixelSize(); bitBlt(dst, dststride, m_conversionCache.data(), srcRowStride, srcAlphaMask, maskRowStride, opacity, rows, cols, op); } else { bitBlt(dst, dststride, src, srcRowStride, srcAlphaMask, maskRowStride, opacity, rows, cols, op); } } TQImage KisAbstractColorSpace::convertToTQImage(const TQ_UINT8 *data, TQ_INT32 width, TQ_INT32 height, KisProfile *dstProfile, TQ_INT32 renderingIntent, float /*exposure*/) { TQImage img = TQImage(width, height, 32, 0, TQImage::LittleEndian); img.setAlphaBuffer( true ); KisColorSpace * dstCS; if (dstProfile) dstCS = m_parent->getColorSpace(KisID("RGBA",""),dstProfile->productName()); else dstCS = m_parent->getRGB8(); if (data) convertPixelsTo(const_cast(data), img.bits(), dstCS, width * height, renderingIntent); return img; } cmsHTRANSFORM KisAbstractColorSpace::createTransform(KisColorSpace * dstColorSpace, KisProfile * srcProfile, KisProfile * dstProfile, TQ_INT32 renderingIntent) { TDEConfig * cfg = TDEGlobal::config(); bool bpCompensation = cfg->readBoolEntry("useBlackPointCompensation", false); int flags = 0; if (bpCompensation) { flags = cmsFLAGS_BLACKPOINTCOMPENSATION; } if (dstColorSpace && dstProfile && srcProfile ) { cmsHTRANSFORM tf = cmsCreateTransform(srcProfile->profile(), colorSpaceType(), dstProfile->profile(), dstColorSpace->colorSpaceType(), renderingIntent, flags); return tf; } return 0; } void KisAbstractColorSpace::compositeCopy(TQ_UINT8 *dstRowStart, TQ_INT32 dstRowStride, const TQ_UINT8 *srcRowStart, TQ_INT32 srcRowStride, const TQ_UINT8 * /*maskRowStart*/, TQ_INT32 /*maskRowStride*/, TQ_INT32 rows, TQ_INT32 numColumns, TQ_UINT8 opacity) { TQ_UINT8 *dst = dstRowStart; const TQ_UINT8 *src = srcRowStart; TQ_INT32 bytesPerPixel = pixelSize(); while (rows > 0) { memcpy(dst, src, numColumns * bytesPerPixel); if (opacity != OPACITY_OPAQUE) { multiplyAlpha(dst, opacity, numColumns); } dst += dstRowStride; src += srcRowStride; --rows; } }