|
|
|
/*
|
|
|
|
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
|
|
|
|
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
|
|
|
|
*
|
|
|
|
* 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 <tqimage.h>
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kconfig.h>
|
|
|
|
|
|
|
|
#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 * tqparent,
|
|
|
|
KisProfile *p)
|
|
|
|
: m_parent( tqparent )
|
|
|
|
, 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];
|
|
|
|
Q_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 <TQ_UINT8 *>(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 <TQ_UINT8 *>(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<TQ_UINT8 *>( 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<TQ_UINT8 *>( 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<TQ_UINT8 *>(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(register WORD In[], register WORD Out[], register 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<LPVOID>(&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<KisColorAdjustmentImpl*>(adjustment);
|
|
|
|
if (adj)
|
|
|
|
cmsDoTransform(adj->transform, const_cast<TQ_UINT8 *>(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<TQ_UINT8*>( src1 ), lab1, 1);
|
|
|
|
cmsDoTransform( m_defaultToLab, const_cast<TQ_UINT8*>( 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<KisAbstractColorSpace *>(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<KisAbstractColorSpace *>(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<KisAbstractColorSpace *>(this)->toTQColor(dst, &dstColor, &dstOpacity);
|
|
|
|
|
|
|
|
while (nColors--)
|
|
|
|
{
|
|
|
|
TQ_INT32 weight = *kernelValues;
|
|
|
|
|
|
|
|
if (weight != 0) {
|
|
|
|
TQColor c;
|
|
|
|
TQ_UINT8 opacity;
|
|
|
|
const_cast<KisAbstractColorSpace *>(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<KisAbstractColorSpace *>(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<KisAbstractColorSpace *>(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<TQ_UINT8*>( src ), reinterpret_cast<TQ_UINT8*>( labcache ), nPixels );
|
|
|
|
for ( int i = 0; i < nPixels * 4; ++i ) {
|
|
|
|
if ( compensate ) {
|
|
|
|
labcache[i] = static_cast<TQ_UINT16>( ( labcache[i] * shade ) / ( compensation * 255 ) );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
labcache[i] = static_cast<TQ_UINT16>( labcache[i] * shade / 255 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cmsDoTransform( m_defaultFromLab, reinterpret_cast<TQ_UINT8*>( 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<KisAbstractColorSpace *>(this)->toTQColor(src + (i * psize), &c);
|
|
|
|
TQ_INT32 r, g, b;
|
|
|
|
|
|
|
|
if (compensate) {
|
|
|
|
r = static_cast<TQ_INT32>( TQMIN(255, (c.red() * shade) / (compensation * 255)));
|
|
|
|
g = static_cast<TQ_INT32>( TQMIN(255, (c.green() * shade) / (compensation * 255)));
|
|
|
|
b = static_cast<TQ_INT32>( TQMIN(255, (c.blue() * shade) / (compensation * 255)));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
r = static_cast<TQ_INT32>( TQMIN(255, (c.red() * shade / 255)));
|
|
|
|
g = static_cast<TQ_INT32>( TQMIN(255, (c.green() * shade / 255)));
|
|
|
|
b = static_cast<TQ_INT32>( TQMIN(255, (c.blue() * shade / 255)));
|
|
|
|
}
|
|
|
|
c.setRgb(r, g, b);
|
|
|
|
|
|
|
|
const_cast<KisAbstractColorSpace *>(this)->fromTQColor( c, dst + (i * psize));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_UINT8 KisAbstractColorSpace::intensity8(const TQ_UINT8 * src) const
|
|
|
|
{
|
|
|
|
TQColor c;
|
|
|
|
TQ_UINT8 opacity;
|
|
|
|
const_cast<KisAbstractColorSpace *>(this)->toTQColor(src, &c, &opacity);
|
|
|
|
return static_cast<TQ_UINT8>((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<TQ_UINT8 *>(data), img.bits(), dstCS, width * height, renderingIntent);
|
|
|
|
|
|
|
|
return img;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cmsHTRANSFORM KisAbstractColorSpace::createTransform(KisColorSpace * dstColorSpace,
|
|
|
|
KisProfile * srcProfile,
|
|
|
|
KisProfile * dstProfile,
|
|
|
|
TQ_INT32 renderingIntent)
|
|
|
|
{
|
|
|
|
KConfig * cfg = KGlobal::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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|