diff -r 39e5f73667ba -r c2ef9095503a hostsupport/hostopenvg/src/riImage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hostsupport/hostopenvg/src/riImage.cpp Wed Oct 06 17:59:01 2010 +0100 @@ -0,0 +1,2835 @@ +/*------------------------------------------------------------------------ + * + * OpenVG 1.1 Reference Implementation + * ----------------------------------- + * + * Copyright (c) 2007 The Khronos Group Inc. + * Portions copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and /or associated documentation files + * (the "Materials "), to deal in the Materials without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Materials, + * and to permit persons to whom the Materials are furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR + * THE USE OR OTHER DEALINGS IN THE MATERIALS. + * + *//** + * \file + * \brief Implementation of Color and Image functions. + * \note + *//*-------------------------------------------------------------------*/ + +#include "riImage.h" +#include "riRasterizer.h" +#include "riContext.h" + +#ifndef __SFDYNAMICBLITTER_H +# include "sfDynamicBlitter.h" +#endif + +//============================================================================================== + +namespace OpenVGRI +{ + +/*-------------------------------------------------------------------*//*! +* \brief Converts from numBits into a shifted mask +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +static RI_INLINE unsigned int bitsToMask(unsigned int bits, unsigned int shift) +{ + return ((1<= 0) ? (int)x : (int)(x-1); +} + +//static const float FLOAT_0 = 0.0f; +static const float FLOAT_0_5 = 0.5f; + +/* \note Rewrite this if time. */ +static unsigned int colorToInt(RIfloat c, int maxc) +{ +#if defined RI_USE_SSE + /* + Registers mapping: + c <-> xmm0, + maxc <-> xmm1 + 0 <-> xmm2 + */ + _asm + { + xorps xmm2, xmm2 ; xmm2 = 0 + + ;--------------------------------------------- + ; Computing: xmm0 = (c * (RIfloat)maxc + 0.5f) + ;--------------------------------------------- + movss xmm0, dword ptr [c] ; xmm0 = c + cvtsi2ss xmm1, dword ptr [maxc] ; xmm1 = (float)maxc + mulss xmm0, xmm1 ; xmm0 = xmm0 * xmm1 = c * (float)maxc + addss xmm0, FLOAT_0_5 ; xmm0 = xmm0 + 0.5f = c * (float)maxc + 0.5f + + ;--------------------------------------------- + ; Computing: xmm0 = floor(xmm0) = floor(c * (RIfloat)maxc + 0.5f) + ;--------------------------------------------- + cvttss2si ebx, xmm0 ; ebx = (int)xmm0 + mov eax, ebx ; eax = ebx = (int)xmm0 + shr eax, 31 ; eax = sign(eax) = sign((int)xmm0) + sub ebx, eax ; ebx = ebx - sign((int)xmm0) = (int)xmm0 - sign((int)xmm0) = (int)floor((int)xmm0) + cvtsi2ss xmm0, ebx ; xmm0 = floor(xmm0) + + pmaxsw xmm0, xmm2; ; xmm0 = MAX(xmm0, 0) + pminsw xmm0, xmm1 ; xmm0 = MIN(xmm0, maxc) + cvttss2si eax, xmm0 ; return value = eax = (int)xmm0 + } +#else + return RI_INT_MIN(RI_INT_MAX((int)ffloor(c * (RIfloat)maxc + 0.5f), 0), maxc); +#endif +} + +/*-------------------------------------------------------------------*//*! +* \brief Converts from int to color (RIfloat) with the given maximum +* mapped to 1.0f. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +static RI_INLINE RIfloat intToColor(unsigned int i, unsigned int maxi) +{ + return (RIfloat)(i & maxi) / (RIfloat)maxi; +} + +void Color::Descriptor::toSmallDescriptor(Color::SmallDescriptor& smallDesc) const +{ + switch (bitsPerPixel) + { + case 32: + smallDesc.size = SIZE_32; + break; + case 24: + smallDesc.size = SIZE_24; + break; + case 16: + smallDesc.size = SIZE_16; + break; + case 8: + smallDesc.size = SIZE_8; + break; + case 4: + smallDesc.size = SIZE_4; + break; + default: + RI_ASSERT(bitsPerPixel == 1); + smallDesc.size = SIZE_1; + break; + } + smallDesc.shape = shape; + smallDesc.internalFormat = internalFormat; +} + +Color::Descriptor Color::Descriptor::getDummyDescriptor() +{ + static const Descriptor dummy = Color::Descriptor(8,0,8,8,8,16,8,24,0,0,sRGBA,32,SHAPE_ABGR); + return dummy; +} + +/** + * \brief Determine the shape of the color format from other data. + * \todo The naming is poor because it may be interpreted as returning the member + * "shape". + */ +Color::Shape Color::Descriptor::getShape() const +{ + // \todo There should be some easier way to define the shape so that it does + // not need to be determined with so many conditions. + + if (isAlphaOnly()) + { + return SHAPE_A; + } + else if (isLuminance()) + { + if (alphaBits) + { + if (alphaShift == 0) + return SHAPE_LA; + return SHAPE_AL; + } + return SHAPE_L; + } + else if (!alphaBits) + { + if (bitsPerPixel == 32) + { + switch(redShift) + { + case 0: + return SHAPE_XBGR; + case 8: + return SHAPE_BGRX; + case 16: + return SHAPE_XRGB; + default: + RI_ASSERT(redShift == 24); + return SHAPE_RGBX; + } + } else if (bitsPerPixel == 24) + { + if (!redShift) + return SHAPE_BGR; + else + { + RI_ASSERT(redShift == 16); + return SHAPE_RGB; + } + } else + { + RI_ASSERT(redBits == 5 && greenBits == 6 && blueBits == 5); + if(redShift) + return SHAPE_RGB; + else + return SHAPE_BGR; + } + } + else + { + if (bitsPerPixel == 32) + { + switch(redShift) + { + case 0: + return SHAPE_ABGR; + case 8: + return SHAPE_BGRA; + case 16: + return SHAPE_ARGB; + default: + RI_ASSERT(redShift == 24); + return SHAPE_RGBA; + } + } else + { + RI_ASSERT(bitsPerPixel == 16); + if (redBits == 5) + { + RI_ASSERT(greenBits == 5 && blueBits == 5 && alphaBits == 1); + switch(redShift) + { + case 0: + return SHAPE_ABGR; + case 1: + return SHAPE_BGRA; + case 10: + return SHAPE_ARGB; + default: + RI_ASSERT(redShift == 11); + return SHAPE_RGBA; + } + } else + { + RI_ASSERT(redBits == 4 && greenBits == 4 && alphaBits == 4); + switch(redShift) + { + case 0: + return SHAPE_ABGR; + case 4: + return SHAPE_BGRA; + case 8: + return SHAPE_ARGB; + default: + RI_ASSERT(redShift == 12); + return SHAPE_RGBA; + } + } + } + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Converts from packed integer in a given format to a Color. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Color::unpack(unsigned int inputData, const Color::Descriptor& inputDesc) +{ + int rb = inputDesc.redBits; + int gb = inputDesc.greenBits; + int bb = inputDesc.blueBits; + int ab = inputDesc.alphaBits; + int lb = inputDesc.luminanceBits; + int rs = inputDesc.redShift; + int gs = inputDesc.greenShift; + int bs = inputDesc.blueShift; + int as = inputDesc.alphaShift; + int ls = inputDesc.luminanceShift; + + m_format = inputDesc.internalFormat; + if(lb) + { //luminance + r = g = b = intToColor(inputData >> ls, (1<> rs, (1<> gs, (1<> bs, (1<> as, (1<= 0 && baseFormat < numBaseFormats); + int swizzleBits = ((int)format >> 6) & 3; + + /* base formats + VG_sRGBX_8888 = 0, + VG_sRGBA_8888 = 1, + VG_sRGBA_8888_PRE = 2, + VG_sRGB_565 = 3, + VG_sRGBA_5551 = 4, + VG_sRGBA_4444 = 5, + VG_sL_8 = 6, + VG_lRGBX_8888 = 7, + VG_lRGBA_8888 = 8, + VG_lRGBA_8888_PRE = 9, + VG_lL_8 = 10, + VG_A_8 = 11, + VG_BW_1 = 12, + VG_A_1 = 13, + VG_A_4 = 14, + */ + + static const int redBits[numBaseFormats] = {8, 8, 8, 5, 5, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0}; + static const int greenBits[numBaseFormats] = {8, 8, 8, 6, 5, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0}; + static const int blueBits[numBaseFormats] = {8, 8, 8, 5, 5, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0}; + static const int alphaBits[numBaseFormats] = {0, 8, 8, 0, 1, 4, 0, 0, 8, 8, 0, 8, 0, 1, 4}; + static const int luminanceBits[numBaseFormats] = {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 1, 0, 0}; + + static const int redShifts[4*numBaseFormats] = {24, 24, 24, 11, 11, 12, 0, 24, 24, 24, 0, 0, 0, 0, 0, //RGBA + 16, 16, 16, 11, 10, 8, 0, 16, 16, 16, 0, 0, 0, 0, 0, //ARGB + 8, 8, 8, 0, 1, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0, //BGRA + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; //ABGR + + static const int greenShifts[4*numBaseFormats] = {16, 16, 16, 5, 6, 8, 0, 16, 16, 16, 0, 0, 0, 0, 0, //RGBA + 8, 8, 8, 5, 5, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0, //ARGB + 16, 16, 16, 5, 6, 8, 0, 16, 16, 16, 0, 0, 0, 0, 0, //BGRA + 8, 8, 8, 5, 5, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0};//ABGR + + static const int blueShifts[4*numBaseFormats] = {8, 8, 8, 0, 1, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0, //RGBA + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //ARGB + 24, 24, 24, 11, 11, 12, 0, 24, 24, 24, 0, 0, 0, 0, 0, //BGRA + 16, 16, 16, 11, 10, 8, 0, 16, 16, 16, 0, 0, 0, 0, 0};//ABGR + + static const int alphaShifts[4*numBaseFormats] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //RGBA + 0, 24, 24, 0, 15, 12, 0, 0, 24, 24, 0, 0, 0, 0, 0, //ARGB + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //BGRA + 0, 24, 24, 0, 15, 12, 0, 0, 24, 24, 0, 0, 0, 0, 0};//ABGR + + static const int bpps[numBaseFormats] = {32, 32, 32, 16, 16, 16, 8, 32, 32, 32, 8, 8, 1, 1, 4}; + + static const InternalFormat internalFormats[numBaseFormats] = {sRGBA, sRGBA, sRGBA_PRE, sRGBA, sRGBA, sRGBA, sLA, lRGBA, lRGBA, lRGBA_PRE, lLA, lRGBA, lLA, lRGBA, lRGBA}; + + desc.redBits = redBits[baseFormat]; + desc.greenBits = greenBits[baseFormat]; + desc.blueBits = blueBits[baseFormat]; + desc.alphaBits = alphaBits[baseFormat]; + desc.luminanceBits = luminanceBits[baseFormat]; + + desc.redShift = redShifts[swizzleBits * numBaseFormats + baseFormat]; + desc.greenShift = greenShifts[swizzleBits * numBaseFormats + baseFormat]; + desc.blueShift = blueShifts[swizzleBits * numBaseFormats + baseFormat]; + desc.alphaShift = alphaShifts[swizzleBits * numBaseFormats + baseFormat]; + desc.luminanceShift = 0; //always zero + + desc.vgFormat = format; + desc.bitsPerPixel = bpps[baseFormat]; + desc.bytesPerPixel = desc.bitsPerPixel / 8; + desc.internalFormat = internalFormats[baseFormat]; + desc.shape = desc.getShape(); + + if (desc.alphaBits) + { + desc.maskBits = desc.alphaBits; + desc.maskShift = desc.alphaShift; + } + else if (!desc.isLuminance()) + { + desc.maskBits = desc.redBits; + desc.maskShift = desc.redShift; + } + else + { + desc.maskBits = desc.luminanceBits; + desc.maskShift = desc.luminanceShift; + } + + return desc; +} + + +struct DescToFormatMapping +{ + Color::Descriptor desc; + VGImageFormat format; +}; + +RI_INLINE static bool isDescEqualToMapping(const Color::Descriptor& desc, const DescToFormatMapping &mapping) +{ + if ((desc.redBits == mapping.desc.redBits) && + (desc.redShift == mapping.desc.redShift) && + (desc.greenBits == mapping.desc.greenBits) && + (desc.greenShift == mapping.desc.greenShift) && + (desc.blueBits == mapping.desc.blueBits) && + (desc.blueShift == mapping.desc.blueShift) && + (desc.alphaBits == mapping.desc.alphaBits) && + (desc.alphaShift == mapping.desc.alphaShift) && + (desc.luminanceBits == mapping.desc.luminanceBits) && + (desc.luminanceShift == mapping.desc.luminanceShift) && + (desc.internalFormat == mapping.desc.internalFormat) && + (desc.bitsPerPixel == mapping.desc.bitsPerPixel)) + return true; + + return false; +} + +VGImageFormat Color::descriptorToVGImageFormat(const Descriptor& desc) +{ +//Color::Descriptor::Descriptor(int dredBits, int dredShift, int dgreenBits, int dgreenShift, int dblueBits, int dblueShift, int dalphaBits, int dalphaShift, int dluminanceBits, int dluminanceShift, InternalFormat dinternalFormat, int dbpp) : + // \todo These are hardcoded here only to allow constant initialization, they should be generated + // using formatToDescriptor! + static const DescToFormatMapping map[] = { + /* RGB{A,X} channel ordering */ + { formatToDescriptorConst(VG_sRGBX_8888), VG_sRGBX_8888 }, + { formatToDescriptorConst(VG_sRGBA_8888), VG_sRGBA_8888 }, + { formatToDescriptorConst(VG_sRGBA_8888_PRE), VG_sRGBA_8888_PRE }, + { formatToDescriptorConst(VG_sRGB_565), VG_sRGB_565 }, + { formatToDescriptorConst(VG_sRGBA_5551), VG_sRGBA_5551 }, + { formatToDescriptorConst(VG_sRGBA_4444), VG_sRGBA_4444 }, + { formatToDescriptorConst(VG_sL_8), VG_sL_8 }, + { formatToDescriptorConst(VG_lRGBX_8888), VG_lRGBX_8888 }, + { formatToDescriptorConst(VG_lRGBA_8888), VG_lRGBA_8888 }, + { formatToDescriptorConst(VG_lRGBA_8888_PRE), VG_lRGBA_8888_PRE }, + { formatToDescriptorConst(VG_lL_8), VG_lL_8 }, + { formatToDescriptorConst(VG_A_8), VG_A_8 }, + { formatToDescriptorConst(VG_BW_1), VG_BW_1 }, + { formatToDescriptorConst(VG_A_1), VG_A_1 }, + { formatToDescriptorConst(VG_A_4), VG_A_4 }, + + /* {A,X}RGB channel ordering */ + { formatToDescriptorConst(VG_sXRGB_8888), VG_sXRGB_8888 }, + { formatToDescriptorConst(VG_sARGB_8888), VG_sARGB_8888 }, + { formatToDescriptorConst(VG_sARGB_8888_PRE), VG_sARGB_8888_PRE }, + { formatToDescriptorConst(VG_sARGB_1555), VG_sARGB_1555 }, + { formatToDescriptorConst(VG_sARGB_4444), VG_sARGB_4444 }, + { formatToDescriptorConst(VG_lXRGB_8888), VG_lXRGB_8888 }, + { formatToDescriptorConst(VG_lARGB_8888), VG_lARGB_8888 }, + { formatToDescriptorConst(VG_lARGB_8888_PRE), VG_lARGB_8888_PRE }, + + /* BGR{A,X} channel ordering */ + { formatToDescriptorConst(VG_sBGRX_8888), VG_sBGRX_8888 }, + { formatToDescriptorConst(VG_sBGRA_8888), VG_sBGRA_8888 }, + { formatToDescriptorConst(VG_sBGRA_8888_PRE), VG_sBGRA_8888_PRE }, + { formatToDescriptorConst(VG_sBGR_565), VG_sBGR_565 }, + { formatToDescriptorConst(VG_sBGRA_5551), VG_sBGRA_5551 }, + { formatToDescriptorConst(VG_sBGRA_4444), VG_sBGRA_4444 }, + { formatToDescriptorConst(VG_lBGRX_8888), VG_lBGRX_8888 }, + { formatToDescriptorConst(VG_lBGRA_8888), VG_lBGRA_8888 }, + { formatToDescriptorConst(VG_lBGRA_8888_PRE), VG_lBGRA_8888_PRE }, + + /* {A,X}BGR channel ordering */ + { formatToDescriptorConst(VG_sXBGR_8888), VG_sXBGR_8888 }, + { formatToDescriptorConst(VG_sABGR_8888), VG_sABGR_8888 }, + { formatToDescriptorConst(VG_sABGR_8888_PRE), VG_sABGR_8888_PRE }, + { formatToDescriptorConst(VG_sABGR_1555), VG_sABGR_1555 }, + { formatToDescriptorConst(VG_sABGR_4444), VG_sABGR_4444 }, + { formatToDescriptorConst(VG_lXBGR_8888), VG_lXBGR_8888 }, + { formatToDescriptorConst(VG_lABGR_8888), VG_lABGR_8888 }, + { formatToDescriptorConst(VG_lABGR_8888_PRE), VG_lABGR_8888_PRE }, + }; + + for (size_t i = 0; i < sizeof(map)/sizeof(map[0]); i++) + { + if (isDescEqualToMapping(desc, map[i])) + return map[i].format; + } + RI_ASSERT(false); + return (VGImageFormat)-1; +} + +/*-------------------------------------------------------------------*//*! +* \brief Checks if the pixel format descriptor is valid (i.e. all the +* values are supported by the RI) +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +bool Color::isValidDescriptor(const Color::Descriptor& desc) +{ + //A valid descriptor has 1, 2, 4, 8, 16, or 32 bits per pixel, and either luminance or rgba channels, but not both. + //Any of the rgba channels can be missing, and not all bits need to be used. Maximum channel bit depth is 8. + int rb = desc.redBits; + int gb = desc.greenBits; + int bb = desc.blueBits; + int ab = desc.alphaBits; + int lb = desc.luminanceBits; + int rs = desc.redShift; + int gs = desc.greenShift; + int bs = desc.blueShift; + int as = desc.alphaShift; + int ls = desc.luminanceShift; + int bpp = desc.bitsPerPixel; + + int rgbaBits = rb + gb + bb + ab; + if(rb < 0 || rb > 8 || rs < 0 || rs + rb > bpp || !(rb || !rs)) + return false; //invalid channel description + if(gb < 0 || gb > 8 || gs < 0 || gs + gb > bpp || !(gb || !gs)) + return false; //invalid channel description + if(bb < 0 || bb > 8 || bs < 0 || bs + bb > bpp || !(bb || !bs)) + return false; //invalid channel description + if(ab < 0 || ab > 8 || as < 0 || as + ab > bpp || !(ab || !as)) + return false; //invalid channel description + if(lb < 0 || lb > 8 || ls < 0 || ls + lb > bpp || !(lb || !ls)) + return false; //invalid channel description + +#if 0 + if(rgbaBits && lb) + return false; //can't have both rgba and luminance +#endif + if(!rgbaBits && !lb) + return false; //must have either rgba or luminance + if(rgbaBits) + { //rgba + if(rb+gb+bb == 0) + { //alpha only + if(rs || gs || bs || as || ls) + return false; //wrong shifts (even alpha shift must be zero) + if((ab != 1 && ab != 2 && ab != 4 && ab != 8) || bpp != ab) + return false; //alpha size must be 1, 2, 4, or, 8, bpp must match + } + else + { //rgba + if(rgbaBits > bpp) + return false; //bpp must be greater than or equal to the sum of rgba bits + if(!(bpp == 32 || bpp == 16 || bpp == 8)) + return false; //only 1, 2, and 4 byte formats are supported for rgba + + unsigned int rm = bitsToMask((unsigned int)rb, (unsigned int)rs); + unsigned int gm = bitsToMask((unsigned int)gb, (unsigned int)gs); + unsigned int bm = bitsToMask((unsigned int)bb, (unsigned int)bs); + unsigned int am = bitsToMask((unsigned int)ab, (unsigned int)as); + if((rm & gm) || (rm & bm) || (rm & am) || (gm & bm) || (gm & am) || (bm & am)) + return false; //channels overlap + } + } + else + { //luminance + if(rs || gs || bs || as || ls) + return false; //wrong shifts (even luminance shift must be zero) + if(!(lb == 1 || lb == 8) || bpp != lb) + return false; //luminance size must be either 1 or 8, bpp must match + } + + if(desc.vgFormat != -1) + { + if(!isValidImageFormat(desc.vgFormat)) + return false; //invalid image format + + Descriptor d = formatToDescriptor(desc.vgFormat); + if(d.redBits != rb || d.greenBits != gb || d.blueBits != bb || d.alphaBits != ab || d.luminanceBits != lb || + d.redShift != rs || d.greenShift != gs || d.blueShift != bs || d.alphaShift != as || d.luminanceShift != ls || + d.bitsPerPixel != bpp) + return false; //if the descriptor has a VGImageFormat, it must match the bits, shifts, and bpp + } + + if((unsigned int)desc.internalFormat & ~(Color::PREMULTIPLIED | Color::NONLINEAR | Color::LUMINANCE)) + return false; //invalid internal format + + return true; +} + +//============================================================================================== + +//============================================================================================== + +IntegerColor::IntegerColor(const Color& color) +{ + r = (RIuint32)(color.r * 255.0f + 0.5f); + g = (RIuint32)(color.g * 255.0f + 0.5f); + b = (RIuint32)(color.b * 255.0f + 0.5f); + a = (RIuint32)(color.a * 255.0f + 0.5f); +} + +//============================================================================================== + +//============================================================================================== + +/*-------------------------------------------------------------------*//*! +* \brief Constructs a blank image. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Image::Image(const Color::Descriptor& desc, int width, int height, VGbitfield allowedQuality) : + m_desc(desc), + m_width(width), + m_height(height), + m_allowedQuality(allowedQuality), + m_inUse(0), + m_stride(0), + m_data(NULL), + m_referenceCount(0), + m_ownsData(true), + m_parent(NULL), + m_storageOffsetX(0), + m_storageOffsetY(0), + m_unsafeData(false) +{ + RI_ASSERT(Color::isValidDescriptor(m_desc)); + RI_ASSERT(width > 0 && height > 0); + + m_stride = (m_width*m_desc.bitsPerPixel+7)/8; + + m_data = RI_NEW_ARRAY(RIuint8, m_stride*m_height); //throws bad_alloc + memset(m_data, 0, m_stride*m_height); //clear image +} + +/*-------------------------------------------------------------------*//*! +* \brief Constructs an image that uses an external array for its data +* storage. +* \param +* \return +* \note This is meant for internal use to make blitting easier +* \note Now this is "tagged" into m_unsafeData if necessary. +* Using this constructor may then affect performance. +*//*-------------------------------------------------------------------*/ + +Image::Image(const Color::Descriptor& desc, int width, int height, int stride, RIuint8* data) : + m_desc(desc), + m_width(width), + m_height(height), + m_allowedQuality(0), + m_inUse(0), + m_stride(stride), + m_data(data), + m_referenceCount(0), + m_ownsData(false), + m_parent(NULL), + m_storageOffsetX(0), + m_storageOffsetY(0), + m_unsafeData(false) +{ + RI_ASSERT(Color::isValidDescriptor(m_desc)); + RI_ASSERT(width > 0 && height > 0); + RI_ASSERT(data); + setUnsafe(true); // External data always potentially unsafe, see note above. +} + +/*-------------------------------------------------------------------*//*! +* \brief Construcs a child image. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Image::Image(Image* parent, int x, int y, int width, int height) : + m_desc(Color::formatToDescriptor(VG_sRGBA_8888)), //dummy initialization, will be overwritten below (can't read from parent->m_desc before knowing the pointer is valid) + m_width(width), + m_height(height), + m_allowedQuality(0), + m_inUse(0), + m_stride(0), + m_data(NULL), + m_referenceCount(0), + m_ownsData(false), + m_parent(parent), + m_storageOffsetX(0), + m_storageOffsetY(0), + m_unsafeData(false) +{ + RI_ASSERT(parent); + RI_ASSERT(x >= 0 && y >= 0 && width > 0 && height > 0); + RI_ASSERT(RI_INT_ADDSATURATE(x,width) <= parent->m_width && RI_INT_ADDSATURATE(y,height) <= parent->m_height); //child image must be contained in parent + + m_desc = parent->m_desc; + RI_ASSERT(Color::isValidDescriptor(m_desc)); + m_allowedQuality = parent->m_allowedQuality; + m_stride = parent->m_stride; + m_data = parent->m_data; + m_storageOffsetX = parent->m_storageOffsetX + x; + m_storageOffsetY = parent->m_storageOffsetY + y; + + //increase the reference and use count of the parent + addInUse(); + parent->addInUse(); + parent->addReference(); + m_unsafeData = parent->m_unsafeData; +} + +/*-------------------------------------------------------------------*//*! +* \brief Image destructor. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Image::~Image() +{ + RI_ASSERT(m_referenceCount == 0); + + if(m_parent) + { + //decrease the reference and use count of the parent + removeInUse(); + m_parent->removeInUse(); + if(!m_parent->removeReference()) + RI_DELETE(m_parent); + } + RI_ASSERT(m_inUse == 0); + + + if(m_ownsData) + { + RI_ASSERT(!m_parent); //can't have parent if owns the data + RI_DELETE_ARRAY(m_data); //delete image data if we own it + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Returns true if the two images share pixels. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +bool Image::overlaps(const Image* src) const +{ + RI_ASSERT(src); + + if(m_data != src->m_data) + return false; //images don't share data + + //check if the image storage regions overlap + Rectangle r(m_storageOffsetX, m_storageOffsetY, m_width, m_height); + r.intersect(Rectangle(src->m_storageOffsetX, src->m_storageOffsetY, src->m_width, src->m_height)); + if(!r.width || !r.height) + return false; //intersection is empty, images don't overlap + + return true; +} + +/** + * \brief Expand log2 bpp packed pixel (single value) to 8 bits. This will + * Result in 8, 4, or 2 same pixel values to be packed into the return value. + */ +RI_INLINE static RIuint32 logExpand8(RIuint32 packedColor, int srcBits) +{ + RI_ASSERT(srcBits == 4 || srcBits == 2 || srcBits == 1); + RIuint32 ret = packedColor; + int n = srcBits; + while (n < 8) + { + ret |= ret << n; + n += n; + } + return ret; +} + +RI_INLINE void Image::fillPacked(RIuint32 packedColor) +{ + RIuint32 pc = packedColor; + int Bpp = m_desc.bitsPerPixel / 8; + int nSetsPerScanline = m_width; + + RI_ASSERT(nSetsPerScanline); + // \todo 1bpp and 4bpp mask formats must be supported. fillPackedPixels should + // automatically work, but riMemSet32 path needs a bit more logic. + // \note < 8bpp formats are always rounded to 8-bit boundaries at scanline end. + // It is assumed that the "padding bits" may be filled. + + if (m_desc.bitsPerPixel < 8) + { + pc = logExpand8(packedColor, m_desc.bitsPerPixel); + Bpp = 1; + nSetsPerScanline = (m_width * m_desc.bitsPerPixel + 7) / 8; + //nSetsPerScanline /= (8/m_desc.bitsPerPixel); + } + + RI_ASSERT(Bpp <= 4 && Bpp >= 1); + + if (m_stride == ((m_desc.bitsPerPixel*m_width+7)/8)) + { + const int nPixels = nSetsPerScanline * m_height; + riMemSet32(m_data, pc, nPixels, Bpp); + } else + { + RIuint8 *ptr = (RIuint8*)m_data; + // set per-scanline + for (int y = 0; y < m_height; y++) + { + riMemSet32(ptr, pc, nSetsPerScanline, Bpp); + ptr += m_stride; + } + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Clears a rectangular portion of an image with the given clear color. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Image::clear(const Color& clearColor, int x, int y, int w, int h) +{ + RI_ASSERT(m_data); + RI_ASSERT(m_referenceCount > 0); + + + //intersect clear region with image bounds + Rectangle r(0,0,m_width,m_height); + r.intersect(Rectangle(x,y,w,h)); + if(!r.width || !r.height) + return; //intersection is empty or one of the rectangles is invalid + + Color col = clearColor; + col.clamp(); + col.convert(getDescriptor().internalFormat); + + IntegerColor ic = IntegerColor(col); + ic.truncateColor(getDescriptor()); + const RIuint32 c = ic.getPackedColor(getDescriptor()); + + if (r.width == getWidth() && r.height == getHeight() && !m_parent) + fillPacked(c); + else + { + fillPackedRectangle(r.x, r.y, r.width, r.height, c); + } +} + +#if 0 +static RIfloat ditherChannel(RIfloat c, int bits, RIfloat m) +{ + RIfloat fc = c * (RIfloat)((1< m) ic += 1.0f; + return RI_MIN(ic / (RIfloat)((1< 0 && h > 0); + sx = RI_INT_MIN(RI_INT_MAX(sx, (int)(RI_INT32_MIN>>2)), (int)(RI_INT32_MAX>>2)); + sy = RI_INT_MIN(RI_INT_MAX(sy, (int)(RI_INT32_MIN>>2)), (int)(RI_INT32_MAX>>2)); + dx = RI_INT_MIN(RI_INT_MAX(dx, (int)(RI_INT32_MIN>>2)), (int)(RI_INT32_MAX>>2)); + dy = RI_INT_MIN(RI_INT_MAX(dy, (int)(RI_INT32_MIN>>2)), (int)(RI_INT32_MAX>>2)); + w = RI_INT_MIN(w, (int)(RI_INT32_MAX>>2)); + h = RI_INT_MIN(h, (int)(RI_INT32_MAX>>2)); + int srcsx = sx, srcex = sx + w, dstsx = dx, dstex = dx + w; + if(srcsx < 0) + { + dstsx -= srcsx; + srcsx = 0; + } + if(srcex > srcWidth) + { + dstex -= srcex - srcWidth; + srcex = srcWidth; + } + if(dstsx < 0) + { + srcsx -= dstsx; + dstsx = 0; + } + if(dstex > dstWidth) + { + srcex -= dstex - dstWidth; + dstex = dstWidth; + } + RI_ASSERT(srcsx >= 0 && dstsx >= 0 && srcex <= srcWidth && dstex <= dstWidth); + w = srcex - srcsx; + RI_ASSERT(w == dstex - dstsx); + + int srcsy = sy, srcey = sy + h, dstsy = dy, dstey = dy + h; + if(srcsy < 0) + { + dstsy -= srcsy; + srcsy = 0; + } + if(srcey > srcHeight) + { + dstey -= srcey - srcHeight; + srcey = srcHeight; + } + if(dstsy < 0) + { + srcsy -= dstsy; + dstsy = 0; + } + if(dstey > dstHeight) + { + srcey -= dstey - dstHeight; + dstey = dstHeight; + } + RI_ASSERT(srcsy >= 0 && dstsy >= 0 && srcey <= srcHeight && dstey <= dstHeight); + h = srcey - srcsy; + RI_ASSERT(h == dstey - dstsy); + sx = srcsx; + sy = srcsy; + dx = dstsx; + dy = dstsy; +} + +/*-------------------------------------------------------------------*//*! +* \brief Blits a source region to destination. Source and destination +* can overlap. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +// \todo Extract dithering kernel and put it into the blitter +#if 0 +void Image::blit(VGContext* context, const Image* src, int sx, int sy, int dx, int dy, int w, int h, bool dither) +{ + //img=>img: vgCopyImage + //img=>user: vgGetImageSubData + //user=>img: vgImageSubData + + // \todo Implement dither to blitter. + this->blit(context, src, sx, sy, dx, dy, w, h, NULL, dither); + RI_ASSERT(src.m_data); //source exists + RI_ASSERT(m_data); //destination exists + RI_ASSERT(m_referenceCount > 0 && src.m_referenceCount > 0); + + computeBlitRegion(sx, sy, dx, dy, w, h, src.m_width, src.m_height, m_width, m_height); + if(w <= 0 || h <= 0) + return; //zero area + + Array tmp; + tmp.resize(w*h); //throws bad_alloc + + //copy source region to tmp + for(int j=0;jgetCurrentDrawable(); + if(!drawable) + return false; //no EGL surface is current at the moment + + Image* img = (Image*)image; + //transform image corners into the surface space + Vector3 p0(0, 0, 1); + Vector3 p1(0, (RIfloat)img->getHeight(), 1); + Vector3 p2((RIfloat)img->getWidth(), (RIfloat)img->getHeight(), 1); + Vector3 p3((RIfloat)img->getWidth(), 0, 1); + + p0 = userToSurfaceMatrix * p0; + p1 = userToSurfaceMatrix * p1; + p2 = userToSurfaceMatrix * p2; + p3 = userToSurfaceMatrix * p3; + if(p0.z <= 0.0f || p1.z <= 0.0f || p2.z <= 0.0f || p3.z <= 0.0f) + return false; + + //projection + p0 *= 1.0f/p0.z; + p1 *= 1.0f/p1.z; + p2 *= 1.0f/p2.z; + p3 *= 1.0f/p3.z; + + Rasterizer& rasterizer = context->m_rasterizer; + rasterizer.clear(); + + if(context->m_scissoring) + rasterizer.setScissor(context->m_scissor); //throws bad_alloc + + PixelPipe& pixelPipe = context->m_pixelPipe; + pixelPipe.setTileFillColor(context->m_tileFillColor); + pixelPipe.setPaint((Paint*)context->m_fillPaint); + const bool aa = imageQuality == VG_IMAGE_QUALITY_NONANTIALIASED ? false : true; + rasterizer.setAntiAliasing(aa); + pixelPipe.setImageQuality(imageQuality); + pixelPipe.setBlendMode(blendMode); + pixelPipe.setRenderToMask(false); + pixelPipe.setDrawable(drawable); + pixelPipe.setMask(hasMasking); + pixelPipe.setColorTransform(hasColorTransform, context->m_colorTransformValues); + + Matrix3x3 surfaceToImageMatrix = userToSurfaceMatrix; + Matrix3x3 surfaceToPaintMatrix = userToSurfaceMatrix * context->m_fillPaintToUser; + if(surfaceToImageMatrix.invert() && surfaceToPaintMatrix.invert()) + { + VGImageMode imode = imageMode; + + if(!surfaceToPaintMatrix.isAffine()) + imode = VG_DRAW_IMAGE_NORMAL; //if paint matrix is not affine, always use normal image mode + + surfaceToPaintMatrix[2].set(0,0,1); //force affine + + pixelPipe.setImage(img, imode); + pixelPipe.setSurfaceToPaintMatrix(surfaceToPaintMatrix); + pixelPipe.setSurfaceToImageMatrix(surfaceToImageMatrix); + pixelPipe.prepareSpanUniforms(aa); + rasterizer.setup(0, 0, drawable->getWidth(), drawable->getHeight(), VG_EVEN_ODD, &pixelPipe); + + rasterizer.addEdge(Vector2(p0.x,p0.y), Vector2(p1.x,p1.y)); //throws bad_alloc + rasterizer.addEdge(Vector2(p1.x,p1.y), Vector2(p2.x,p2.y)); //throws bad_alloc + rasterizer.addEdge(Vector2(p2.x,p2.y), Vector2(p3.x,p3.y)); //throws bad_alloc + rasterizer.addEdge(Vector2(p3.x,p3.y), Vector2(p0.x,p0.y)); //throws bad_alloc + + rasterizer.fill(); //throws bad_alloc + } + + return true; +} + +/*-------------------------------------------------------------------*//*! +* \brief Converts from multisampled format to display format. +* \param unsafeInput Data may contain incorrect values (user data) +* \return +* \note May throw std::bad_alloc on cases where blitting within the +* same buffer and overlapping regions (this may change in the +* future). +*//*-------------------------------------------------------------------*/ + +void Image::blit(VGContext* context, const Image* src, + int sx, int sy, int dx, int dy, int w, int h, + Array* scissors, + bool dither) +{ + bool overlap = false; + (void)dither; + DynamicBlitter& blitter = context->getBlitter(); + + //RI_ASSERT(!src->isInUse(this)); + //int isx = sx, isy = sy, idx = dx, idy = dy, iw = w, ih = h; + + computeBlitRegion(sx, sy, dx, dy, w, h, src->getWidth(), src->getHeight(), m_width, m_height); + + if(w <= 0 || h <= 0) + return; //zero area + + if (this->m_data == src->m_data) + { + // The images may overlap. + int minsx = RI_INT_MIN(sx, dx); + int minsy = RI_INT_MIN(sy, dy); + int maxsx = RI_INT_MAX(sx, dx); + int maxsy = RI_INT_MAX(sy, dy); + + if ((maxsx < (minsx + w)) && (maxsy < (minsy + h))) + { + overlap = true; + } + } + + if (!scissors) + { + // Currently the blitter does not support scissors + if (!overlap) + { + blitter.prepareBlit(this, src, sx+src->m_storageOffsetX, sy+src->m_storageOffsetY, + dx+m_storageOffsetX, dy+m_storageOffsetY, w, h); + blitter.blit(); + } else + { + Image temp(src->getDescriptor(), w, h, VG_IMAGE_QUALITY_NONANTIALIASED); + blitter.prepareBlit(&temp, src, sx+src->m_storageOffsetX, sy+src->m_storageOffsetY, 0, 0, w, h); + blitter.blit(); + blitter.prepareBlit(this, &temp, 0, 0, dx+m_storageOffsetX, dy+m_storageOffsetY, w, h); + blitter.blit(); + } + } else + { + // For the moment, use the generic poly-rasterizer for scissored images. + if (!overlap) + { + // Create a child image + Image blitImage((Image*)src, sx, sy, w, h); + Matrix3x3 tx; + tx.set(1, 0, dx, 0, 1, dy, 0, 0, 1); + + drawImageBody(context, &blitImage, tx, + VG_IMAGE_QUALITY_NONANTIALIASED, + VG_BLEND_SRC, + false, + false, + VG_DRAW_IMAGE_NORMAL); + } else + { + // Create a copy of the source region + Image temp(src->getDescriptor(), w, h, VG_IMAGE_QUALITY_NONANTIALIASED); + blitter.prepareBlit(&temp, src, sx, sy, 0, 0, w, h); + blitter.blit(); + + Image blitImage((Image*)src, sx, sy, w, h); + Matrix3x3 tx; + tx.set(1, 0, dx, 0, 1, dy, 0, 0, 1); + + drawImageBody(context, &blitImage, tx, + VG_IMAGE_QUALITY_NONANTIALIASED, + VG_BLEND_SRC, + false, + false, + VG_DRAW_IMAGE_NORMAL); + } + } + +} + +/*-------------------------------------------------------------------*//*! +* \brief Returns the color at pixel (x,y). +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Color Image::readPixel(int x, int y) const +{ + const RIuint32 p = readPackedPixel(x, y); + + Color c; + c.unpack(p, m_desc); + return c; +} + +/*-------------------------------------------------------------------*//*! +* \brief Writes the color to pixel (x,y). Internal color formats must +* match. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Image::writePixel(int x, int y, const Color& c) +{ + RI_ASSERT(c.getInternalFormat() == m_desc.internalFormat); + + RIuint32 p = c.pack(m_desc); + writePackedPixel(x, y, p); +} + +/*-------------------------------------------------------------------*//*! +* \brief Writes a filtered color to destination surface +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Image::writeFilteredPixel(int i, int j, const Color& color, VGbitfield channelMask) +{ + //section 3.4.4: before color space conversion, premultiplied colors are + //clamped to alpha, and the color is converted to nonpremultiplied format + //section 11.2: how to deal with channel mask + //step 1 + Color f = color; + f.clamp(); //vgColorMatrix and vgLookups can produce colors that exceed alpha or [0,1] range + + //step 2: color space conversion + f.convert((Color::InternalFormat)(m_desc.internalFormat & (Color::NONLINEAR | Color::LUMINANCE))); + + //step 3: read the destination color and convert it to nonpremultiplied + Color d = readPixel(i,j); + d.unpremultiply(); + RI_ASSERT(d.getInternalFormat() == f.getInternalFormat()); + + //step 4: replace the destination channels specified by the channelMask (channelmask is ignored for luminance formats) + if(!m_desc.isLuminance()) + { //rgba format => use channelmask + if(channelMask & VG_RED) + d.r = f.r; + if(channelMask & VG_GREEN) + d.g = f.g; + if(channelMask & VG_BLUE) + d.b = f.b; + if(channelMask & VG_ALPHA) + d.a = f.a; + } + else d = f; + + //step 5: if destination is premultiplied, convert to premultiplied format + if(m_desc.isPremultiplied()) + d.premultiply(); + //write the color to destination + writePixel(i,j,d); +} + +/*-------------------------------------------------------------------*//*! +* \brief Reads the pixel (x,y) and converts it into an alpha mask value. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +RIfloat Image::readMaskPixel(int x, int y) const +{ + RI_ASSERT(m_data); + RI_ASSERT(x >= 0 && x < m_width); + RI_ASSERT(y >= 0 && y < m_height); + RI_ASSERT(m_referenceCount > 0); + + Color c = readPixel(x,y); + if(m_desc.isLuminance()) + { + return c.r; + } + else + { //rgba + if(m_desc.alphaBits) + return c.a; + return c.r; + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Writes the alpha mask to pixel (x,y). +* \param +* \return +* \note Overwrites color. +*//*-------------------------------------------------------------------*/ + +void Image::writeMaskPixel(int x, int y, RIfloat m) +{ + RI_ASSERT(m_data); + RI_ASSERT(x >= 0 && x < m_width); + RI_ASSERT(y >= 0 && y < m_height); + RI_ASSERT(m_referenceCount > 0); + + //if luminance or no alpha, red channel will be used, otherwise alpha channel will be used + writePixel(x, y, Color(m,m,m,m,m_desc.internalFormat)); +} + +/*-------------------------------------------------------------------*//*! +* \brief Reads a texel (u,v) at the given mipmap level. Tiling modes and +* color space conversion are applied. Outputs color in premultiplied +* format. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Color Image::readTexel(int u, int v, int level, VGTilingMode tilingMode, const Color& tileFillColor) const +{ + const Image* image = this; + if( level > 0 ) + { + RI_ASSERT(false); + } + RI_ASSERT(image); + + Color p; + if(tilingMode == VG_TILE_FILL) + { + if(u < 0 || v < 0 || u >= image->m_width || v >= image->m_height) + p = tileFillColor; + else + p = image->readPixel(u, v); + } + else if(tilingMode == VG_TILE_PAD) + { + u = RI_INT_MIN(RI_INT_MAX(u,0),image->m_width-1); + v = RI_INT_MIN(RI_INT_MAX(v,0),image->m_height-1); + p = image->readPixel(u, v); + } + else if(tilingMode == VG_TILE_REPEAT) + { + u = RI_INT_MOD(u, image->m_width); + v = RI_INT_MOD(v, image->m_height); + p = image->readPixel(u, v); + } + else + { + RI_ASSERT(tilingMode == VG_TILE_REFLECT); + + u = RI_INT_MOD(u, image->m_width*2); + v = RI_INT_MOD(v, image->m_height*2); + if( u >= image->m_width ) u = image->m_width*2-1 - u; + if( v >= image->m_height ) v = image->m_height*2-1 - v; + p = image->readPixel(u, v); + } + + p.premultiply(); //interpolate in premultiplied format + return p; +} + +/*-------------------------------------------------------------------*//*! +* \brief Maps point (x,y) to an image and returns a filtered, +* premultiplied color value. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Color Image::resample(RIfloat x, RIfloat y, const Matrix3x3& surfaceToImage, VGImageQuality quality, VGTilingMode tilingMode, const Color& tileFillColor) //throws bad_alloc +{ + RI_ASSERT(m_referenceCount > 0); + + VGbitfield aq = getAllowedQuality(); + aq &= (VGbitfield)quality; + + Vector3 uvw(x,y,1.0f); + uvw = surfaceToImage * uvw; + RIfloat oow = 1.0f / uvw.z; + uvw *= oow; + +#if 0 + if(aq & VG_IMAGE_QUALITY_BETTER) + { //EWA on mipmaps + makeMipMaps(); //throws bad_alloc + + Color::InternalFormat procFormat = (Color::InternalFormat)(m_desc.internalFormat | Color::PREMULTIPLIED); + + RIfloat m_pixelFilterRadius = 1.25f; + RIfloat m_resamplingFilterRadius = 1.25f; + + RIfloat Ux = (surfaceToImage[0][0] - uvw.x * surfaceToImage[2][0]) * oow * m_pixelFilterRadius; + RIfloat Vx = (surfaceToImage[1][0] - uvw.y * surfaceToImage[2][0]) * oow * m_pixelFilterRadius; + RIfloat Uy = (surfaceToImage[0][1] - uvw.x * surfaceToImage[2][1]) * oow * m_pixelFilterRadius; + RIfloat Vy = (surfaceToImage[1][1] - uvw.y * surfaceToImage[2][1]) * oow * m_pixelFilterRadius; + RIfloat U0 = uvw.x; + RIfloat V0 = uvw.y; + + //calculate mip level + int level = 0; + RIfloat axis1sq = Ux*Ux + Vx*Vx; + RIfloat axis2sq = Uy*Uy + Vy*Vy; + RIfloat minorAxissq = RI_MIN(axis1sq,axis2sq); + while(minorAxissq > 9.0f && level < m_mipmaps.size()) //half the minor axis must be at least three texels + { + level++; + minorAxissq *= 0.25f; + } + + RIfloat sx = 1.0f; + RIfloat sy = 1.0f; + if(level > 0) + { + sx = (RIfloat)m_mipmaps[level-1]->m_width / (RIfloat)m_width; + sy = (RIfloat)m_mipmaps[level-1]->m_height / (RIfloat)m_height; + } + Ux *= sx; + Vx *= sx; + U0 *= sx; + Uy *= sy; + Vy *= sy; + V0 *= sy; + + //clamp filter size so that filtering doesn't take excessive amount of time (clamping results in aliasing) + RIfloat lim = 100.0f; + axis1sq = Ux*Ux + Vx*Vx; + axis2sq = Uy*Uy + Vy*Vy; + if( axis1sq > lim*lim ) + { + RIfloat s = lim / (RIfloat)sqrt(axis1sq); + Ux *= s; + Vx *= s; + } + if( axis2sq > lim*lim ) + { + RIfloat s = lim / (RIfloat)sqrt(axis2sq); + Uy *= s; + Vy *= s; + } + + + //form elliptic filter by combining texel and pixel filters + RIfloat A = Vx*Vx + Vy*Vy + 1.0f; + RIfloat B = -2.0f*(Ux*Vx + Uy*Vy); + RIfloat C = Ux*Ux + Uy*Uy + 1.0f; + //scale by the user-defined size of the kernel + A *= m_resamplingFilterRadius; + B *= m_resamplingFilterRadius; + C *= m_resamplingFilterRadius; + + //calculate bounding box in texture space + RIfloat usize = (RIfloat)sqrt(C); + RIfloat vsize = (RIfloat)sqrt(A); + int u1 = (int)floor(U0 - usize + 0.5f); + int u2 = (int)floor(U0 + usize + 0.5f); + int v1 = (int)floor(V0 - vsize + 0.5f); + int v2 = (int)floor(V0 + vsize + 0.5f); + if( u1 == u2 || v1 == v2 ) + return Color(0,0,0,0,procFormat); + + //scale the filter so that Q = 1 at the cutoff radius + RIfloat F = A*C - 0.25f * B*B; + if( F <= 0.0f ) + return Color(0,0,0,0,procFormat); //invalid filter shape due to numerical inaccuracies => return black + RIfloat ooF = 1.0f / F; + A *= ooF; + B *= ooF; + C *= ooF; + + //evaluate filter by using forward differences to calculate Q = A*U^2 + B*U*V + C*V^2 + Color color(0,0,0,0,procFormat); + RIfloat sumweight = 0.0f; + RIfloat DDQ = 2.0f * A; + RIfloat U = (RIfloat)u1 - U0 + 0.5f; + for(int v=v1;v= 0.0f && Q < 1.0f ) + { //Q = r^2, fit gaussian to the range [0,1] + RIfloat weight = (RIfloat)exp(-0.5f * 10.0f * Q); //gaussian at radius 10 equals 0.0067 + color += weight * readTexel(u, v, level, tilingMode, tileFillColor); + sumweight += weight; + } + Q += DQ; + DQ += DDQ; + } + } + if( sumweight == 0.0f ) + return Color(0,0,0,0,procFormat); + RI_ASSERT(sumweight > 0.0f); + sumweight = 1.0f / sumweight; + return color * sumweight; + } + else +#endif + //if(aq & VG_IMAGE_QUALITY_FASTER) + if(aq & VG_IMAGE_QUALITY_BETTER) + { //bilinear + uvw.x -= 0.5f; + uvw.y -= 0.5f; + int u = (int)floor(uvw.x); + int v = (int)floor(uvw.y); + Color c00 = readTexel(u,v, 0, tilingMode, tileFillColor); + Color c10 = readTexel(u+1,v, 0, tilingMode, tileFillColor); + Color c01 = readTexel(u,v+1, 0, tilingMode, tileFillColor); + Color c11 = readTexel(u+1,v+1, 0, tilingMode, tileFillColor); + RIfloat fu = uvw.x - (RIfloat)u; + RIfloat fv = uvw.y - (RIfloat)v; + Color c0 = c00 * (1.0f - fu) + c10 * fu; + Color c1 = c01 * (1.0f - fu) + c11 * fu; + return c0 * (1.0f - fv) + c1 * fv; + } + else //VG_IMAGE_QUALITY_FASTER and VG_IMAGE_QUALITY_NONANTIALIASED + { //point sampling + return readTexel((int)floor(uvw.x), (int)floor(uvw.y), 0, tilingMode, tileFillColor); + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Applies color matrix filter. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Image::colorMatrix(const Image& src, const RIfloat* matrix, bool filterFormatLinear, bool filterFormatPremultiplied, VGbitfield channelMask) +{ + RI_ASSERT(src.m_data); //source exists + RI_ASSERT(m_data); //destination exists + RI_ASSERT(matrix); + RI_ASSERT(m_referenceCount > 0 && src.m_referenceCount > 0); + + int w = RI_INT_MIN(m_width, src.m_width); + int h = RI_INT_MIN(m_height, src.m_height); + RI_ASSERT(w > 0 && h > 0); + + Color::InternalFormat srcFormat = src.m_desc.internalFormat; + Color::InternalFormat procFormat = (Color::InternalFormat)(srcFormat & ~Color::LUMINANCE); //process in RGB, not luminance + if(filterFormatLinear) + procFormat = (Color::InternalFormat)(procFormat & ~Color::NONLINEAR); + else + procFormat = (Color::InternalFormat)(procFormat | Color::NONLINEAR); + + if(filterFormatPremultiplied) + procFormat = (Color::InternalFormat)(procFormat | Color::PREMULTIPLIED); + else + procFormat = (Color::InternalFormat)(procFormat & ~Color::PREMULTIPLIED); + + for(int j=0;j& image, const Color& edge) +{ + Color s; + if(x < 0 || x >= w || y < 0 || y >= h) + { //apply tiling mode + switch(tilingMode) + { + case VG_TILE_FILL: + s = edge; + break; + case VG_TILE_PAD: + x = RI_INT_MIN(RI_INT_MAX(x, 0), w-1); + y = RI_INT_MIN(RI_INT_MAX(y, 0), h-1); + RI_ASSERT(x >= 0 && x < w && y >= 0 && y < h); + s = image[y*w+x]; + break; + case VG_TILE_REPEAT: + x = RI_INT_MOD(x, w); + y = RI_INT_MOD(y, h); + RI_ASSERT(x >= 0 && x < w && y >= 0 && y < h); + s = image[y*w+x]; + break; + default: + RI_ASSERT(tilingMode == VG_TILE_REFLECT); + x = RI_INT_MOD(x, w*2); + y = RI_INT_MOD(y, h*2); + if(x >= w) x = w*2-1-x; + if(y >= h) y = h*2-1-y; + RI_ASSERT(x >= 0 && x < w && y >= 0 && y < h); + s = image[y*w+x]; + break; + } + } + else + { + RI_ASSERT(x >= 0 && x < w && y >= 0 && y < h); + s = image[y*w+x]; + } + return s; +} + +/*-------------------------------------------------------------------*//*! +* \brief Returns processing format for filtering. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +static Color::InternalFormat getProcessingFormat(Color::InternalFormat srcFormat, bool filterFormatLinear, bool filterFormatPremultiplied) +{ + Color::InternalFormat procFormat = (Color::InternalFormat)(srcFormat & ~Color::LUMINANCE); //process in RGB, not luminance + if(filterFormatLinear) + procFormat = (Color::InternalFormat)(procFormat & ~Color::NONLINEAR); + else + procFormat = (Color::InternalFormat)(procFormat | Color::NONLINEAR); + + if(filterFormatPremultiplied) + procFormat = (Color::InternalFormat)(procFormat | Color::PREMULTIPLIED); + else + procFormat = (Color::InternalFormat)(procFormat & ~Color::PREMULTIPLIED); + return procFormat; +} + +/*-------------------------------------------------------------------*//*! +* \brief Applies convolution filter. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Image::convolve(const Image& src, int kernelWidth, int kernelHeight, int shiftX, int shiftY, const RIint16* kernel, RIfloat scale, RIfloat bias, VGTilingMode tilingMode, const Color& edgeFillColor, bool filterFormatLinear, bool filterFormatPremultiplied, VGbitfield channelMask) +{ + RI_ASSERT(src.m_data); //source exists + RI_ASSERT(m_data); //destination exists + RI_ASSERT(kernel && kernelWidth > 0 && kernelHeight > 0); + RI_ASSERT(m_referenceCount > 0 && src.m_referenceCount > 0); + + //the area to be written is an intersection of source and destination image areas. + //lower-left corners of the images are aligned. + int w = RI_INT_MIN(m_width, src.m_width); + int h = RI_INT_MIN(m_height, src.m_height); + RI_ASSERT(w > 0 && h > 0); + + Color::InternalFormat procFormat = getProcessingFormat(src.m_desc.internalFormat, filterFormatLinear, filterFormatPremultiplied); + + Color edge = edgeFillColor; + edge.clamp(); + edge.convert(procFormat); + + Array tmp; + tmp.resize(src.m_width*src.m_height); //throws bad_alloc + + //copy source region to tmp and do conversion + for(int j=0;j= 0 && kx < kernelWidth && ky >= 0 && ky < kernelHeight); + + sum += (RIfloat)kernel[kx*kernelHeight+ky] * s; + } + } + + sum *= scale; + sum.r += bias; + sum.g += bias; + sum.b += bias; + sum.a += bias; + + writeFilteredPixel(i, j, sum, channelMask); + } + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Applies separable convolution filter. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Image::separableConvolve(const Image& src, int kernelWidth, int kernelHeight, int shiftX, int shiftY, const RIint16* kernelX, const RIint16* kernelY, RIfloat scale, RIfloat bias, VGTilingMode tilingMode, const Color& edgeFillColor, bool filterFormatLinear, bool filterFormatPremultiplied, VGbitfield channelMask) +{ + RI_ASSERT(src.m_data); //source exists + RI_ASSERT(m_data); //destination exists + RI_ASSERT(kernelX && kernelY && kernelWidth > 0 && kernelHeight > 0); + RI_ASSERT(m_referenceCount > 0 && src.m_referenceCount > 0); + + //the area to be written is an intersection of source and destination image areas. + //lower-left corners of the images are aligned. + int w = RI_INT_MIN(m_width, src.m_width); + int h = RI_INT_MIN(m_height, src.m_height); + RI_ASSERT(w > 0 && h > 0); + + Color::InternalFormat procFormat = getProcessingFormat(src.m_desc.internalFormat, filterFormatLinear, filterFormatPremultiplied); + + Color edge = edgeFillColor; + edge.clamp(); + edge.convert(procFormat); + + Array tmp; + tmp.resize(src.m_width*src.m_height); //throws bad_alloc + + //copy source region to tmp and do conversion + for(int j=0;j tmp2; + tmp2.resize(w*src.m_height); //throws bad_alloc + for(int j=0;j= 0 && kx < kernelWidth); + + sum += (RIfloat)kernelX[kx] * s; + } + tmp2[j*w+i] = sum; + } + } + + if(tilingMode == VG_TILE_FILL) + { //convolve the edge color + Color sum(0,0,0,0,procFormat); + for(int ki=0;ki= 0 && ky < kernelHeight); + + sum += (RIfloat)kernelY[ky] * s; + } + + sum *= scale; + sum.r += bias; + sum.g += bias; + sum.b += bias; + sum.a += bias; + + writeFilteredPixel(i, j, sum, channelMask); + } + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Applies Gaussian blur filter. +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Image::gaussianBlur(const Image& src, RIfloat stdDeviationX, RIfloat stdDeviationY, VGTilingMode tilingMode, const Color& edgeFillColor, bool filterFormatLinear, bool filterFormatPremultiplied, VGbitfield channelMask) +{ + RI_ASSERT(src.m_data); //source exists + RI_ASSERT(m_data); //destination exists + RI_ASSERT(stdDeviationX > 0.0f && stdDeviationY > 0.0f); + RI_ASSERT(stdDeviationX <= RI_MAX_GAUSSIAN_STD_DEVIATION && stdDeviationY <= RI_MAX_GAUSSIAN_STD_DEVIATION); + RI_ASSERT(m_referenceCount > 0 && src.m_referenceCount > 0); + + //the area to be written is an intersection of source and destination image areas. + //lower-left corners of the images are aligned. + int w = RI_INT_MIN(m_width, src.m_width); + int h = RI_INT_MIN(m_height, src.m_height); + RI_ASSERT(w > 0 && h > 0); + + Color::InternalFormat procFormat = getProcessingFormat(src.m_desc.internalFormat, filterFormatLinear, filterFormatPremultiplied); + + Color edge = edgeFillColor; + edge.clamp(); + edge.convert(procFormat); + + Array tmp; + tmp.resize(src.m_width*src.m_height); //throws bad_alloc + + //copy source region to tmp and do conversion + for(int j=0;j kernelX; + kernelX.resize(kernelWidth*2+1); + int shiftX = kernelWidth; + RIfloat scaleX = 0.0f; + for(int i=0;i kernelY; + kernelY.resize(kernelHeight*2+1); + int shiftY = kernelHeight; + RIfloat scaleY = 0.0f; + for(int i=0;i tmp2; + tmp2.resize(w*src.m_height); //throws bad_alloc + //horizontal pass + for(int j=0;j 0 && src.m_referenceCount > 0); + + //the area to be written is an intersection of source and destination image areas. + //lower-left corners of the images are aligned. + int w = RI_INT_MIN(m_width, src.m_width); + int h = RI_INT_MIN(m_height, src.m_height); + RI_ASSERT(w > 0 && h > 0); + + Color::InternalFormat procFormat = getProcessingFormat(src.m_desc.internalFormat, filterFormatLinear, filterFormatPremultiplied); + Color::InternalFormat lutFormat = getLUTFormat(outputLinear, outputPremultiplied); + + for(int j=0;j 0 && src.m_referenceCount > 0); + + //the area to be written is an intersection of source and destination image areas. + //lower-left corners of the images are aligned. + int w = RI_INT_MIN(m_width, src.m_width); + int h = RI_INT_MIN(m_height, src.m_height); + RI_ASSERT(w > 0 && h > 0); + + if(src.m_desc.isLuminance()) + sourceChannel = VG_RED; + else if(src.m_desc.redBits + src.m_desc.greenBits + src.m_desc.blueBits == 0) + { + RI_ASSERT(src.m_desc.alphaBits); + sourceChannel = VG_ALPHA; + } + + Color::InternalFormat procFormat = getProcessingFormat(src.m_desc.internalFormat, filterFormatLinear, filterFormatPremultiplied); + Color::InternalFormat lutFormat = getLUTFormat(outputLinear, outputPremultiplied); + + for(int j=0;j>24), 255); + d.g = intToColor((l>>16), 255); + d.b = intToColor((l>> 8), 255); + d.a = intToColor((l ), 255); + + writeFilteredPixel(i, j, d, channelMask); + } + } +} + + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Surface::Surface(const Color::Descriptor& desc, int width, int height, int numSamples) : + m_width(width), + m_height(height), + m_numSamples(numSamples), + m_referenceCount(0), + m_image(NULL) +{ + RI_ASSERT(width > 0 && height > 0 && numSamples > 0 && numSamples <= 32); + m_image = RI_NEW(Image, (desc, width*numSamples, height, 0)); //throws bad_alloc + m_image->addReference(); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Surface::Surface(Image* image) : + m_width(0), + m_height(0), + m_numSamples(1), + m_referenceCount(0), + m_image(image) +{ + RI_ASSERT(image); + m_width = image->getWidth(); + m_height = image->getHeight(); + m_image->addReference(); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Surface::Surface(const Color::Descriptor& desc, int width, int height, int stride, RIuint8* data) : + m_width(width), + m_height(height), + m_numSamples(1), + m_referenceCount(0), + m_image(NULL) +{ + RI_ASSERT(width > 0 && height > 0); + m_image = RI_NEW(Image, (desc, width, height, stride, data)); //throws bad_alloc + m_image->addReference(); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Surface::~Surface() +{ + RI_ASSERT(m_referenceCount == 0); + if(!m_image->removeReference()) + RI_DELETE(m_image); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Surface::clear(const Color& clearColor, int x, int y, int w, int h, const Array* scissors) +{ + RI_ASSERT(m_numSamples == 1); + + Image* image = m_image; + + Color col = clearColor; + col.clamp(); + col.convert(m_image->getDescriptor().internalFormat); + + IntegerColor ic = IntegerColor(col); + ic.truncateColor(m_image->getDescriptor()); + const RIuint32 c = ic.getPackedColor(m_image->getDescriptor()); + + if (x != 0 || y != 0 || w != image->getWidth() || h != image->getHeight() || scissors) + { + // Simple implementation: intersect with surface and clip rects -> may clear the + // same area several times. Best if scissors are non-overlapping + Rectangle r(0,0,getWidth(),getHeight()); + r.intersect(Rectangle(x,y,w,h)); + + if (r.isEmpty() || (scissors && scissors->size() == 0)) + return; + + if (scissors && scissors->size()) + { + for (int i = 0; i < scissors->size(); i++) + { + Rectangle s = (*scissors)[i]; + s.intersect(r); + + if (s.isEmpty()) + continue; + + image->fillPackedRectangle(s.x, s.y, s.width, s.height, c); + } + } + else + { + image->fillPackedRectangle(r.x, r.y, r.width, r.height, c); + } + } + else + { + // Clear the whole buffer + + m_image->fillPacked(c); + } +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +#if 0 +void Surface::blit(const Image& src, int sx, int sy, int dx, int dy, int w, int h) +{ + Rectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = getWidth(); + rect.height = getHeight(); + Array scissors; + scissors.push_back(rect); + blit(src, sx, sy, dx, dy, w, h, scissors); +} +#endif + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note no overlap is possible. Single sample to single or multisample (replicate) +*//*-------------------------------------------------------------------*/ + +#if 0 +void Surface::blit(const Image& src, int sx, int sy, int dx, int dy, int w, int h, const Array& scissors) +{ + //img=>fb: vgSetPixels + //user=>fb: vgWritePixels + computeBlitRegion(sx, sy, dx, dy, w, h, src.getWidth(), src.getHeight(), getWidth(), getHeight()); + if(w <= 0 || h <= 0) + return; //zero area + + Array scissorEdges; + for(int i=0;i 0 && scissors[i].height > 0) + { + ScissorEdge e; + e.miny = scissors[i].y; + e.maxy = RI_INT_ADDSATURATE(scissors[i].y, scissors[i].height); + + e.x = scissors[i].x; + e.direction = 1; + scissorEdges.push_back(e); //throws bad_alloc + e.x = RI_INT_ADDSATURATE(scissors[i].x, scissors[i].width); + e.direction = -1; + scissorEdges.push_back(e); //throws bad_alloc + } + } + if(!scissorEdges.size()) + return; //there are no scissor rectangles => nothing is visible + + //sort scissor edges by edge x + scissorEdges.sort(); + + Array scissorAet; + for(int j=0;j= se.miny && dy + j < se.maxy) + scissorAet.push_back(scissorEdges[e]); //throws bad_alloc + } + if(!scissorAet.size()) + continue; //scissoring is on, but there are no scissor rectangles on this scanline + + //blit a scanline + int scissorWinding = 0; + int scissorIndex = 0; + for(int i=0;i= 0); + + if(scissorWinding) + { + Color c = src.readPixel(sx + i, sy + j); + c.convert(getDescriptor().internalFormat); + for(int s=0;s scissors; + scissors.push_back(rect); + blit(src, sx, sy, dx, dy, w, h, scissors); +} +#endif + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +#if 0 +void Surface::blit(const Surface* src, int sx, int sy, int dx, int dy, int w, int h, const Array& scissors) +{ + RI_ASSERT(m_numSamples == src->m_numSamples); + + //fb=>fb: vgCopyPixels + computeBlitRegion(sx, sy, dx, dy, w, h, src->getWidth(), src->getHeight(), getWidth(), getHeight()); + if(w <= 0 || h <= 0) + return; //zero area + + Array scissorEdges; + for(int i=0;i 0 && scissors[i].height > 0) + { + ScissorEdge e; + e.miny = scissors[i].y; + e.maxy = RI_INT_ADDSATURATE(scissors[i].y, scissors[i].height); + + e.x = scissors[i].x; + e.direction = 1; + scissorEdges.push_back(e); //throws bad_alloc + e.x = RI_INT_ADDSATURATE(scissors[i].x, scissors[i].width); + e.direction = -1; + scissorEdges.push_back(e); //throws bad_alloc + } + } + if(!scissorEdges.size()) + return; //there are no scissor rectangles => nothing is visible + + //sort scissor edges by edge x + scissorEdges.sort(); + + Array tmp; + tmp.resize(w*m_numSamples*h); //throws bad_alloc + + //copy source region to tmp + for(int j=0;jm_image->readPixel((sx + i)*m_numSamples+s, sy + j); + c.convert(m_image->getDescriptor().internalFormat); + tmp[(j*w+i)*m_numSamples+s] = c; + } + } + } + + Array scissorAet; + for(int j=0;j= se.miny && dy + j < se.maxy) + scissorAet.push_back(scissorEdges[e]); //throws bad_alloc + } + if(!scissorAet.size()) + continue; //scissoring is on, but there are no scissor rectangles on this scanline + + //blit a scanline + int scissorWinding = 0; + int scissorIndex = 0; + for(int i=0;i= 0); + + if(scissorWinding) + { + int numSamples = m_numSamples; + for(int s=0;swritePixel((dx + i)*m_numSamples+s, dy + j, tmp[(j*w+i)*m_numSamples+s]); + } + } + } + } +} +#endif + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Surface::mask(DynamicBlitter& blitter, const Image* src, VGMaskOperation operation, int x, int y, int w, int h) +{ + RI_ASSERT(w > 0 && h > 0); + RI_ASSERT(m_numSamples == 1); + + if(operation == VG_CLEAR_MASK || operation == VG_FILL_MASK) + { + //intersect clear region with image bounds + Rectangle r(0,0,getWidth(),getHeight()); + r.intersect(Rectangle(x,y,w,h)); + + if(!r.width || !r.height) + return; //intersection is empty or one of the rectangles is invalid + + { + Color mcolor(1.0f, 1.0f, 1.0f, 1.0f, Color::sRGBA_PRE); + if (operation == VG_CLEAR_MASK) + mcolor = Color(0,0,0,0, Color::sRGBA_PRE); + IntegerColor ic = IntegerColor(mcolor); + RIuint32 p = ic.getPackedMaskColor(m_image->getDescriptor()); + m_image->fillPackedRectangle(r.x, r.y, r.width, r.height, p); + } + } + else + { + int sx = 0, sy = 0, dx = x, dy = y; + + computeBlitRegion(sx, sy, dx, dy, w, h, src->getWidth(), src->getHeight(), getWidth(), getHeight()); + + if(w <= 0 || h <= 0) + return; //zero area + + blitter.enableMaskOperation(true); + blitter.setMaskOperation(operation); + blitter.prepareBlit(this->m_image, src, sx, sy, dx, dy, w, h); + blitter.blit(); + blitter.enableMaskOperation(false); +#if 0 + RI_ASSERT(src); + + int sx = 0, sy = 0, dx = x, dy = y; + computeBlitRegion(sx, sy, dx, dy, w, h, src->getWidth(), src->getHeight(), getWidth(), getHeight()); + if(w <= 0 || h <= 0) + return; //zero area + + { + for(int j=0;jreadMaskPixel(sx + i, sy + j); + if(operation == VG_SET_MASK) + writeMaskCoverage(dx + i, dy + j, amask); + else + { + RIfloat aprev = readMaskCoverage(dx + i, dy + j); + RIfloat anew = 0.0f; + switch(operation) + { + case VG_UNION_MASK: anew = 1.0f - (1.0f - amask)*(1.0f - aprev); break; + case VG_INTERSECT_MASK: anew = amask * aprev; break; + default: anew = aprev * (1.0f - amask); RI_ASSERT(operation == VG_SUBTRACT_MASK); break; + } + writeMaskCoverage(dx + i, dy + j, anew); + } + } + } + } +#endif + } +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +RIfloat Surface::readMaskCoverage(int x, int y) const +{ + RI_ASSERT(x >= 0 && x < m_width && y >= 0 && y < m_height); + RI_ASSERT(m_numSamples == 1); + return m_image->readMaskPixel(x, y); +} + +void Surface::writeMaskCoverage(int x, int y, RIfloat m) +{ + RI_ASSERT(x >= 0 && x < m_width && y >= 0 && y < m_height); + RI_ASSERT(m_numSamples == 1); + m_image->writeMaskPixel(x, y, m); //TODO support other than alpha formats but don't write to color channels? +} + +unsigned int Surface::readMaskMSAA(int x, int y) const +{ + RI_ASSERT(x >= 0 && x < m_width && y >= 0 && y < m_height); + RI_ASSERT(m_numSamples > 1); + unsigned int m = 0; + for(int i=0;ireadMaskPixel(x*m_numSamples+i, y) > 0.5f) //TODO is this the right formula for converting alpha to bit mask? does it matter? + m |= 1<= 0 && x < m_width && y >= 0 && y < m_height); + RI_ASSERT(m_numSamples > 1); + for(int i=0;iwriteMaskPixel(x*m_numSamples+i, y, a); //TODO support other than alpha formats but don't write to color channels? + } +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Color Surface::FSAAResolve(int x, int y) const +{ + if(m_numSamples == 1) + return readSample(x, y, 0); + + Color::InternalFormat aaFormat = getDescriptor().isLuminance() ? Color::lLA_PRE : Color::lRGBA_PRE; //antialias in linear color space + Color r(0.0f, 0.0f, 0.0f, 0.0f, aaFormat); + for(int i=0;i 0 && height > 0 && numSamples > 0 && numSamples <= 32); + RI_ASSERT(maskBits == 0 || maskBits == 1 || maskBits == 4 || maskBits == 8); + m_color = RI_NEW(Surface, (desc, width, height, numSamples)); //throws bad_alloc + m_color->addReference(); + if(maskBits) + { + VGImageFormat mf = VG_A_1; + if(maskBits == 4) + mf = VG_A_4; + else if(maskBits == 8) + mf = VG_A_8; + m_mask = RI_NEW(Surface, (Color::formatToDescriptor(mf), width, height, numSamples)); + m_mask->addReference(); + m_mask->clear(Color(1,1,1,1,Color::sRGBA), 0, 0, width, height); + } +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Drawable::Drawable(Image* image, int maskBits) : + m_referenceCount(0), + m_color(NULL), + m_mask(NULL) +{ + RI_ASSERT(maskBits == 0 || maskBits == 1 || maskBits == 4 || maskBits == 8); + RI_ASSERT(image); + m_color = RI_NEW(Surface, (image)); + m_color->addReference(); + if(maskBits) + { + VGImageFormat mf = VG_A_1; + if(maskBits == 4) + mf = VG_A_4; + else if(maskBits == 8) + mf = VG_A_8; + m_mask = RI_NEW(Surface, (Color::formatToDescriptor(mf), image->getWidth(), image->getHeight(), 1)); + m_mask->addReference(); + m_mask->clear(Color(1,1,1,1,Color::sRGBA), 0, 0, image->getWidth(), image->getHeight()); + } +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Drawable::Drawable(const Color::Descriptor& desc, int width, int height, int stride, RIuint8* data, int maskBits) : + m_referenceCount(0), + m_color(NULL), + m_mask(NULL) +{ + RI_ASSERT(width > 0 && height > 0); + RI_ASSERT(maskBits == 0 || maskBits == 1 || maskBits == 4 || maskBits == 8); + m_color = RI_NEW(Surface, (desc, width, height, stride, data)); //throws bad_alloc + m_color->addReference(); + if(maskBits) + { + VGImageFormat mf = VG_A_1; + if(maskBits == 4) + mf = VG_A_4; + else if(maskBits == 8) + mf = VG_A_8; + m_mask = RI_NEW(Surface, (Color::formatToDescriptor(mf), width, height, 1)); + m_mask->addReference(); + m_mask->clear(Color(1,1,1,1,Color::sRGBA), 0, 0, width, height); + } +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Drawable::~Drawable() +{ + RI_ASSERT(m_referenceCount == 0); + if(!m_color->removeReference()) + RI_DELETE(m_color); + if(m_mask) + if(!m_mask->removeReference()) + RI_DELETE(m_mask); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Drawable::resize(VGContext* context, int newWidth, int newHeight) +{ + Surface* oldcolor = m_color; + Surface* oldmask = m_mask; + int oldWidth = m_color->getWidth(); + int oldHeight = m_color->getHeight(); + + //TODO check that image is not a proxy + m_color = RI_NEW(Surface, (m_color->getDescriptor(), newWidth, newHeight, m_color->getNumSamples())); + m_color->addReference(); + if(m_mask) + { + m_mask = RI_NEW(Surface, (m_mask->getDescriptor(), newWidth, newHeight, m_mask->getNumSamples())); + m_mask->addReference(); + } + + int wmin = RI_INT_MIN(newWidth,oldWidth); + int hmin = RI_INT_MIN(newHeight,oldHeight); + m_color->clear(Color(0.0f, 0.0f, 0.0f, 0.0f, getDescriptor().internalFormat), 0, 0, m_color->getWidth(), m_color->getHeight()); + m_color->m_image->blit(context, oldcolor->m_image, 0, 0, 0, 0, wmin, hmin); + if(m_mask) + { + m_mask->clear(Color(1.0f, 1.0f, 1.0f, 1.0f, getDescriptor().internalFormat), 0, 0, m_mask->getWidth(), m_mask->getHeight()); + m_mask->m_image->blit(context, oldmask->m_image, 0, 0, 0, 0, wmin, hmin); + } + + if(!oldcolor->removeReference()) + RI_DELETE(oldcolor); + if(oldmask) + if(!oldmask->removeReference()) + RI_DELETE(oldmask); +} + +#ifndef RI_COMPILE_LLVM_BYTECODE + +#endif /* RI_COMPILE_LLVM_BYTECODE */ + +//============================================================================================== + +} //namespace OpenVGRI + +//==============================================================================================