/* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Adrian Page * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include LCMS_HEADER #include #include #include #include "kis_lms_f32_colorspace.h" #include "kis_color_conversions.h" namespace { const TQ_INT32 MAX_CHANNEL_LMS = 3; const TQ_INT32 MAX_CHANNEL_LMSA = 4; } #include "kis_integer_maths.h" #define FLOAT_MAX 1.0f //temp #define EPSILON 1e-6 // FIXME: lcms doesn't support 32-bit float #define F32_LCMS_TYPE TYPE_BGRA_16 // disable the lcms handling by setting profile=0 KisLmsF32ColorSpace::KisLmsF32ColorSpace(KisColorSpaceFactoryRegistry * parent, KisProfile */*p*/) : KisF32BaseColorSpace(KisID("LMSAF32", i18n("LMS (32-bit float/channel)")), F32_LCMS_TYPE, icSig3colorData, parent, 0) { m_channels.push_back(new KisChannelInfo(i18n("Long"), i18n("L"), PIXEL_LONGWAVE * sizeof(float), KisChannelInfo::COLOR, KisChannelInfo::FLOAT32, sizeof(float))); m_channels.push_back(new KisChannelInfo(i18n("Middle"), i18n("M"), PIXEL_MIDDLEWAVE * sizeof(float), KisChannelInfo::COLOR, KisChannelInfo::FLOAT32, sizeof(float))); m_channels.push_back(new KisChannelInfo(i18n("Short"), i18n("S"), PIXEL_SHORTWAVE * sizeof(float), KisChannelInfo::COLOR, KisChannelInfo::FLOAT32, sizeof(float))); m_channels.push_back(new KisChannelInfo(i18n("Alpha"), i18n("A"), PIXEL_ALPHA * sizeof(float), KisChannelInfo::ALPHA, KisChannelInfo::FLOAT32, sizeof(float))); m_alphaPos = PIXEL_ALPHA * sizeof(float); } KisLmsF32ColorSpace::~KisLmsF32ColorSpace() { } void KisLmsF32ColorSpace::setPixel(TQ_UINT8 *dst, float longWave, float middleWave, float shortWave, float alpha) const { Pixel *dstPixel = reinterpret_cast(dst); dstPixel->longWave = longWave; dstPixel->middleWave = middleWave; dstPixel->shortWave = shortWave; dstPixel->alpha = alpha; } void KisLmsF32ColorSpace::getPixel(const TQ_UINT8 *src, float *longWave, float *middleWave, float *shortWave, float *alpha) const { const Pixel *srcPixel = reinterpret_cast(src); *longWave = srcPixel->longWave; *middleWave = srcPixel->middleWave; *shortWave = srcPixel->shortWave; *alpha = srcPixel->alpha; } void KisLmsF32ColorSpace::fromTQColor(const TQColor& c, TQ_UINT8 *dstU8, KisProfile * /*profile*/) { Pixel *dst = reinterpret_cast(dstU8); dst->longWave = computeLong(c.red(),c.green(),c.blue()); dst->middleWave = computeMiddle(c.red(),c.green(),c.blue()); dst->shortWave = computeShort(c.red(),c.green(),c.blue()); } void KisLmsF32ColorSpace::fromTQColor(const TQColor& c, TQ_UINT8 opacity, TQ_UINT8 *dstU8, KisProfile * /*profile*/) { Pixel *dst = reinterpret_cast(dstU8); dst->longWave = computeLong(c.red(),c.green(),c.blue()); dst->middleWave = computeMiddle(c.red(),c.green(),c.blue()); dst->shortWave = computeShort(c.red(),c.green(),c.blue()); dst->alpha = UINT8_TO_FLOAT(opacity); } void KisLmsF32ColorSpace::toTQColor(const TQ_UINT8 *srcU8, TQColor *c, KisProfile * /*profile*/) { const Pixel *src = reinterpret_cast(srcU8); c->setRgb(computeRed(src->longWave,src->middleWave,src->shortWave), computeGreen(src->longWave,src->middleWave,src->shortWave), computeBlue(src->longWave,src->middleWave,src->shortWave)); } void KisLmsF32ColorSpace::toTQColor(const TQ_UINT8 *srcU8, TQColor *c, TQ_UINT8 *opacity, KisProfile * /*profile*/) { const Pixel *src = reinterpret_cast(srcU8); c->setRgb(computeRed(src->longWave,src->middleWave,src->shortWave), computeGreen(src->longWave,src->middleWave,src->shortWave), computeBlue(src->longWave,src->middleWave,src->shortWave)); *opacity = FLOAT_TO_UINT8(src->alpha); } TQ_UINT8 KisLmsF32ColorSpace::difference(const TQ_UINT8 *src1U8, const TQ_UINT8 *src2U8) { const Pixel *src1 = reinterpret_cast(src1U8); const Pixel *src2 = reinterpret_cast(src2U8); return FLOAT_TO_UINT8(TQMAX(TQABS(src2->longWave - src1->longWave), TQMAX(TQABS(src2->middleWave - src1->middleWave), TQABS(src2->shortWave - src1->shortWave)))); } void KisLmsF32ColorSpace::mixColors(const TQ_UINT8 **colors, const TQ_UINT8 *weights, TQ_UINT32 nColors, TQ_UINT8 *dst) const { float totalLong = 0, totalMiddle = 0, totalShort = 0, newAlpha = 0; while (nColors--) { const Pixel *pixel = reinterpret_cast(*colors); float alpha = pixel->alpha; float alphaTimesWeight = alpha * UINT8_TO_FLOAT(*weights); totalLong += pixel->longWave * alphaTimesWeight; totalMiddle += pixel->middleWave * alphaTimesWeight; totalShort += pixel->shortWave * alphaTimesWeight; newAlpha += alphaTimesWeight; weights++; colors++; } Q_ASSERT(newAlpha <= F32_OPACITY_OPAQUE); Pixel *dstPixel = reinterpret_cast(dst); dstPixel->alpha = newAlpha; if (newAlpha > EPSILON) { totalLong = totalLong / newAlpha; totalMiddle = totalMiddle / newAlpha; totalShort = totalShort / newAlpha; } dstPixel->longWave = totalLong; dstPixel->middleWave = totalMiddle; dstPixel->shortWave = totalShort; } TQValueVector KisLmsF32ColorSpace::channels() const { return m_channels; } TQ_UINT32 KisLmsF32ColorSpace::nChannels() const { return MAX_CHANNEL_LMSA; } TQ_UINT32 KisLmsF32ColorSpace::nColorChannels() const { return MAX_CHANNEL_LMS; } TQ_UINT32 KisLmsF32ColorSpace::pixelSize() const { return MAX_CHANNEL_LMSA * sizeof(float); } TQImage KisLmsF32ColorSpace::convertToTQImage(const TQ_UINT8 *dataU8, TQ_INT32 width, TQ_INT32 height, KisProfile * /*dstProfile*/, TQ_INT32 /*renderingIntent*/, float /*exposure*/) { const float *data = reinterpret_cast(dataU8); TQImage img = TQImage(width, height, 32, 0, TQImage::LittleEndian); img.setAlphaBuffer(true); TQ_INT32 i = 0; uchar *j = img.bits(); while ( i < width * height * MAX_CHANNEL_LMSA) { double l = *( data + i + PIXEL_LONGWAVE ); double m = *( data + i + PIXEL_MIDDLEWAVE ); double s = *( data + i + PIXEL_SHORTWAVE ); *( j + 3) = FLOAT_TO_UINT8(*( data + i + PIXEL_ALPHA )); *( j + 2 ) = computeRed(l,m,s); *( j + 1 ) = computeGreen(l,m,s); *( j + 0 ) = computeBlue(l,m,s); i += MAX_CHANNEL_LMSA; j += MAX_CHANNEL_LMSA; } /* if (srcProfile != 0 && dstProfile != 0) { convertPixelsTo(img.bits(), srcProfile, img.bits(), this, dstProfile, width * height, renderingIntent); } */ return img; } void KisLmsF32ColorSpace::compositeOver(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, float opacity) { while (rows > 0) { const float *src = reinterpret_cast(srcRowStart); float *dst = reinterpret_cast(dstRowStart); const TQ_UINT8 *mask = maskRowStart; TQ_INT32 columns = numColumns; while (columns > 0) { float srcAlpha = src[PIXEL_ALPHA]; // apply the alphamask if (mask != 0) { TQ_UINT8 U8_mask = *mask; if (U8_mask != OPACITY_OPAQUE) { srcAlpha *= UINT8_TO_FLOAT(U8_mask); } mask++; } if (srcAlpha > F32_OPACITY_TRANSPARENT + EPSILON) { if (opacity < F32_OPACITY_OPAQUE - EPSILON) { srcAlpha *= opacity; } if (srcAlpha > F32_OPACITY_OPAQUE - EPSILON) { memcpy(dst, src, MAX_CHANNEL_LMSA * sizeof(float)); } else { float dstAlpha = dst[PIXEL_ALPHA]; float srcBlend; if (dstAlpha > F32_OPACITY_OPAQUE - EPSILON) { srcBlend = srcAlpha; } else { float newAlpha = dstAlpha + (F32_OPACITY_OPAQUE - dstAlpha) * srcAlpha; dst[PIXEL_ALPHA] = newAlpha; if (newAlpha > EPSILON) { srcBlend = srcAlpha / newAlpha; } else { srcBlend = srcAlpha; } } if (srcBlend > F32_OPACITY_OPAQUE - EPSILON) { memcpy(dst, src, MAX_CHANNEL_LMS * sizeof(float)); } else { dst[PIXEL_LONGWAVE] = FLOAT_BLEND(src[PIXEL_LONGWAVE], dst[PIXEL_LONGWAVE], srcBlend); dst[PIXEL_MIDDLEWAVE] = FLOAT_BLEND(src[PIXEL_MIDDLEWAVE], dst[PIXEL_MIDDLEWAVE], srcBlend); dst[PIXEL_SHORTWAVE] = FLOAT_BLEND(src[PIXEL_SHORTWAVE], dst[PIXEL_SHORTWAVE], srcBlend); } } } columns--; src += MAX_CHANNEL_LMSA; dst += MAX_CHANNEL_LMSA; } rows--; srcRowStart += srcRowStride; dstRowStart += dstRowStride; if(maskRowStart) { maskRowStart += maskRowStride; } } } void KisLmsF32ColorSpace::compositeErase(TQ_UINT8 *dst, TQ_INT32 dstRowSize, const TQ_UINT8 *src, TQ_INT32 srcRowSize, const TQ_UINT8 *srcAlphaMask, TQ_INT32 maskRowStride, TQ_INT32 rows, TQ_INT32 cols, float /*opacity*/) { while (rows-- > 0) { const Pixel *s = reinterpret_cast(src); Pixel *d = reinterpret_cast(dst); const TQ_UINT8 *mask = srcAlphaMask; for (TQ_INT32 i = cols; i > 0; i--, s++, d++) { float srcAlpha = s->alpha; // apply the alphamask if (mask != 0) { TQ_UINT8 U8_mask = *mask; if (U8_mask != OPACITY_OPAQUE) { srcAlpha = FLOAT_BLEND(srcAlpha, F32_OPACITY_OPAQUE, UINT8_TO_FLOAT(U8_mask)); } mask++; } d->alpha = srcAlpha * d->alpha; } dst += dstRowSize; src += srcRowSize; if(srcAlphaMask) { srcAlphaMask += maskRowStride; } } } void KisLmsF32ColorSpace::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, float /*opacity*/) { while (rows > 0) { memcpy(dstRowStart, srcRowStart, numColumns * sizeof(Pixel)); --rows; srcRowStart += srcRowStride; dstRowStart += dstRowStride; } } void KisLmsF32ColorSpace::bitBlt(TQ_UINT8 *dst, TQ_INT32 dstRowStride, const TQ_UINT8 *src, TQ_INT32 srcRowStride, const TQ_UINT8 *mask, TQ_INT32 maskRowStride, TQ_UINT8 U8_opacity, TQ_INT32 rows, TQ_INT32 cols, const KisCompositeOp& op) { float opacity = UINT8_TO_FLOAT(U8_opacity); switch (op.op()) { case COMPOSITE_UNDEF: // Undefined == no composition break; case COMPOSITE_OVER: compositeOver(dst, dstRowStride, src, srcRowStride, mask, maskRowStride, rows, cols, opacity); break; case COMPOSITE_COPY: compositeCopy(dst, dstRowStride, src, srcRowStride, mask, maskRowStride, rows, cols, opacity); break; case COMPOSITE_ERASE: compositeErase(dst, dstRowStride, src, srcRowStride, mask, maskRowStride, rows, cols, opacity); break; default: break; } } KisCompositeOpList KisLmsF32ColorSpace::userVisiblecompositeOps() const { KisCompositeOpList list; list.append(KisCompositeOp(COMPOSITE_OVER)); return list; }