diff -r 0be82064630b -r 2bf8a359aa2f egl/sfopenvg/riImage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/egl/sfopenvg/riImage.cpp Wed May 12 11:20:41 2010 +0100 @@ -0,0 +1,2673 @@ +/*------------------------------------------------------------------------ + * + * OpenVG 1.1 Reference Implementation + * ----------------------------------- + * + * Copyright (c) 2007 The Khronos Group Inc. + * + * 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" +//============================================================================================== + +namespace OpenVGRI +{ + +/*-------------------------------------------------------------------*//*! +* \brief Converts from numBits into a shifted mask +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +static unsigned int bitsToMask(unsigned int bits, unsigned int shift) +{ + return ((1<> 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.format = format; + desc.bitsPerPixel = bpps[baseFormat]; + desc.internalFormat = internalFormats[baseFormat]; + + return desc; +} + +/*-------------------------------------------------------------------*//*! +* \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(rgbaBits && lb) + return false; //can't have both rgba and luminance + 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.format != -1) + { + if(!isValidImageFormat(desc.format)) + return false; //invalid image format + + Descriptor d = formatToDescriptor(desc.format); + 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; +} + +//============================================================================================== + + + + +//============================================================================================== + +/*-------------------------------------------------------------------*//*! +* \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_mipmapsValid(false), + m_mipmaps() +{ + 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 +*//*-------------------------------------------------------------------*/ + +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_mipmapsValid(false), + m_mipmaps() +{ + RI_ASSERT(Color::isValidDescriptor(m_desc)); + RI_ASSERT(width > 0 && height > 0); + RI_ASSERT(data); +} + +/*-------------------------------------------------------------------*//*! +* \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_mipmapsValid(false), + m_mipmaps() +{ + 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(); +} + +/*-------------------------------------------------------------------*//*! +* \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); + + for(int i=0;iremoveReference()) + RI_DELETE(m_mipmaps[i]); + else + { + RI_ASSERT(0); //there can't be any other references to the mipmap levels + } + } + m_mipmaps.clear(); + + 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 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(m_desc.internalFormat); + + for(int j=r.y;j 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; +} + +void Image::blit(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 + 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;jimg: vgGetPixels + //fb=>user: vgReadPixels + RI_ASSERT(!src->isInUse(this)); + + computeBlitRegion(sx, sy, dx, dy, w, h, src->getWidth(), src->getHeight(), m_width, m_height); + if(w <= 0 || h <= 0) + return; //zero area + + for(int y=0;yFSAAResolve(sx + x, sy + y); + r.convert(getDescriptor().internalFormat); + writePixel(dx + x, dy + y, r); + } + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Returns the color at pixel (x,y). +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +Color Image::readPixel(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); + x += m_storageOffsetX; + y += m_storageOffsetY; + + unsigned int p = 0; + RIuint8* scanline = m_data + y * m_stride; + switch(m_desc.bitsPerPixel) + { + case 32: + { + RIuint32* s = (((RIuint32*)scanline) + x); + p = (unsigned int)*s; + break; + } + + case 16: + { + RIuint16* s = ((RIuint16*)scanline) + x; + p = (unsigned int)*s; + break; + } + + case 8: + { + RIuint8* s = ((RIuint8*)scanline) + x; + p = (unsigned int)*s; + break; + } + + case 4: + { + RIuint8* s = ((RIuint8*)scanline) + (x>>1); + p = (unsigned int)(*s >> ((x&1)<<2)) & 0xf; + break; + } + + case 2: + { + RIuint8* s = ((RIuint8*)scanline) + (x>>2); + p = (unsigned int)(*s >> ((x&3)<<1)) & 0x3; + break; + } + + default: + { + RI_ASSERT(m_desc.bitsPerPixel == 1); + RIuint8* s = ((RIuint8*)scanline) + (x>>3); + p = (unsigned int)(*s >> (x&7)) & 0x1; + break; + } + } + 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(m_data); + RI_ASSERT(x >= 0 && x < m_width); + RI_ASSERT(y >= 0 && y < m_height); + RI_ASSERT(m_referenceCount > 0); + RI_ASSERT(c.getInternalFormat() == m_desc.internalFormat); + x += m_storageOffsetX; + y += m_storageOffsetY; + + unsigned int p = c.pack(m_desc); + RIuint8* scanline = m_data + y * m_stride; + switch(m_desc.bitsPerPixel) + { + case 32: + { + RIuint32* s = ((RIuint32*)scanline) + x; + *s = (RIuint32)p; + break; + } + + case 16: + { + RIuint16* s = ((RIuint16*)scanline) + x; + *s = (RIuint16)p; + break; + } + + case 8: + { + RIuint8* s = ((RIuint8*)scanline) + x; + *s = (RIuint8)p; + break; + } + case 4: + { + RIuint8* s = ((RIuint8*)scanline) + (x>>1); + *s = (RIuint8)((p << ((x&1)<<2)) | ((unsigned int)*s & ~(0xf << ((x&1)<<2)))); + break; + } + + case 2: + { + RIuint8* s = ((RIuint8*)scanline) + (x>>2); + *s = (RIuint8)((p << ((x&3)<<1)) | ((unsigned int)*s & ~(0x3 << ((x&3)<<1)))); + break; + } + + default: + { + RI_ASSERT(m_desc.bitsPerPixel == 1); + RIuint8* s = ((RIuint8*)scanline) + (x>>3); + *s = (RIuint8)((p << (x&7)) | ((unsigned int)*s & ~(0x1 << (x&7)))); + break; + } + } + m_mipmapsValid = false; +} + +/*-------------------------------------------------------------------*//*! +* \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(level <= m_mipmaps.size()); + image = m_mipmaps[level-1]; + } + 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(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 if(aq & VG_IMAGE_QUALITY_FASTER) + { //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 + { //point sampling + return readTexel((int)floor(uvw.x), (int)floor(uvw.y), 0, tilingMode, tileFillColor); + } +} + +/*-------------------------------------------------------------------*//*! +* \brief Generates mip maps for an image. +* \param +* \return +* \note Downsampling is done in the input color space. We use a box +* filter for downsampling. +*//*-------------------------------------------------------------------*/ + +void Image::makeMipMaps() +{ + RI_ASSERT(m_data); + RI_ASSERT(m_referenceCount > 0); + + if(m_mipmapsValid) + return; + + //delete existing mipmaps + for(int i=0;iremoveReference()) + RI_DELETE(m_mipmaps[i]); + else + { + RI_ASSERT(0); //there can't be any other references to the mipmap levels + } + } + m_mipmaps.clear(); + + try + { + Color::InternalFormat procFormat = m_desc.internalFormat; + procFormat = (Color::InternalFormat)(procFormat | Color::PREMULTIPLIED); //premultiplied + + //generate mipmaps until width and height are one + Image* prev = this; + while( prev->m_width > 1 || prev->m_height > 1 ) + { + int nextw = (int)ceil(prev->m_width*0.5f); + int nexth = (int)ceil(prev->m_height*0.5f); + RI_ASSERT(nextw >= 1 && nexth >= 1); + RI_ASSERT(nextw < prev->m_width || nexth < prev->m_height); + + m_mipmaps.resize(m_mipmaps.size()+1); //throws bad_alloc + m_mipmaps[m_mipmaps.size()-1] = NULL; + + Image* next = RI_NEW(Image, (m_desc, nextw, nexth, m_allowedQuality)); //throws bad_alloc + next->addReference(); + for(int j=0;jm_height;j++) + { + for(int i=0;im_width;i++) + { + RIfloat u0 = (RIfloat)i / (RIfloat)next->m_width; + RIfloat u1 = (RIfloat)(i+1) / (RIfloat)next->m_width; + RIfloat v0 = (RIfloat)j / (RIfloat)next->m_height; + RIfloat v1 = (RIfloat)(j+1) / (RIfloat)next->m_height; + + u0 *= prev->m_width; + u1 *= prev->m_width; + v0 *= prev->m_height; + v1 *= prev->m_height; + + int su = (int)floor(u0); + int eu = (int)ceil(u1); + int sv = (int)floor(v0); + int ev = (int)ceil(v1); + + Color c(0,0,0,0,procFormat); + int samples = 0; + for(int y=sv;yreadPixel(x, y); + p.convert(procFormat); + c += p; + samples++; + } + } + c *= (1.0f/samples); + c.convert(m_desc.internalFormat); + next->writePixel(i,j,c); + } + } + m_mipmaps[m_mipmaps.size()-1] = next; + prev = next; + } + RI_ASSERT(prev->m_width == 1 && prev->m_height == 1); + m_mipmapsValid = true; + } + catch(std::bad_alloc) + { + //delete existing mipmaps + for(int i=0;iremoveReference()) + RI_DELETE(m_mipmaps[i]); + else + { + RI_ASSERT(0); //there can't be any other references to the mipmap levels + } + } + } + m_mipmaps.clear(); + m_mipmapsValid = false; + throw; + } +} + +/*-------------------------------------------------------------------*//*! +* \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) +{ + Rectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = getWidth(); + rect.height = getHeight(); + Array scissors; + scissors.push_back(rect); + clear(clearColor, x, y, w, h, scissors); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Surface::clear(const Color& clearColor, int x, int y, int w, int h, const Array& scissors) +{ + RI_ASSERT(w > 0 && h > 0); + + //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 + + 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(); + + //clear the image + Color col = clearColor; + col.clamp(); + col.convert(m_image->getDescriptor().internalFormat); + + Array scissorAet; + for(int j=r.y;j= se.miny && 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 + + //clear a scanline + int scissorWinding = 0; + int scissorIndex = 0; + for(int i=r.x;i= 0); + + if(scissorWinding) + { + for(int s=0;s scissors; + scissors.push_back(rect); + blit(src, sx, sy, dx, dy, w, h, scissors); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note no overlap is possible. Single sample to single or multisample (replicate) +*//*-------------------------------------------------------------------*/ + +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); +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +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]); + } + } + } + } +} + +/*-------------------------------------------------------------------*//*! +* \brief +* \param +* \return +* \note +*//*-------------------------------------------------------------------*/ + +void Surface::mask(const Image* src, VGMaskOperation operation, int x, int y, int w, int h) +{ + RI_ASSERT(w > 0 && h > 0); + + 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 + + if(m_numSamples == 1) + { + RIfloat m = 0.0f; + if(operation == VG_FILL_MASK) + m = 1.0f; + for(int j=r.y;jgetWidth(), src->getHeight(), getWidth(), getHeight()); + if(w <= 0 || h <= 0) + return; //zero area + + if(m_numSamples == 1) + { + 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); + } + } + } + } + else + { + for(int j=0;jreadMaskPixel(sx + i, sy + j); + //TODO implement dithering? + unsigned int amask = fmask > 0.5f ? (1< 0 && h > 0); + + 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 + + if(m_numSamples == 1) + { + RIfloat m = 0.0f; + if(operation == VG_FILL_MASK) + m = 1.0f; + for(int j=r.y;jm_numSamples); + + 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 + + if(m_numSamples == 1) + { + for(int j=0;jreadMaskCoverage(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); + } + } + } + } + else + { + for(int j=0;jreadMaskMSAA(sx + i, sy + j); + if(operation == VG_SET_MASK) + writeMaskMSAA(dx + i, dy + j, amask); + else + { + unsigned int aprev = readMaskMSAA(dx + i, dy + j); + unsigned int anew = 0; + switch(operation) + { + case VG_UNION_MASK: anew = amask | aprev; break; + case VG_INTERSECT_MASK: anew = amask & aprev; break; + default: anew = ~amask & aprev; RI_ASSERT(operation == VG_SUBTRACT_MASK); break; + } + writeMaskMSAA(dx + i, dy + j, anew); + } + } + } + } + } +} + +/*-------------------------------------------------------------------*//*! +* \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(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->blit(oldcolor, 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->blit(oldmask, 0, 0, 0, 0, wmin, hmin); + } + + if(!oldcolor->removeReference()) + RI_DELETE(oldcolor); + if(oldmask) + if(!oldmask->removeReference()) + RI_DELETE(oldmask); +} + +//============================================================================================== + +} //namespace OpenVGRI + +//==============================================================================================