summaryrefslogtreecommitdiff
path: root/Runtime/Graphics/Image.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Graphics/Image.cpp')
-rw-r--r--Runtime/Graphics/Image.cpp1657
1 files changed, 1657 insertions, 0 deletions
diff --git a/Runtime/Graphics/Image.cpp b/Runtime/Graphics/Image.cpp
new file mode 100644
index 0000000..b4ad02d
--- /dev/null
+++ b/Runtime/Graphics/Image.cpp
@@ -0,0 +1,1657 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+#include "Image.h"
+#include "S3Decompression.h"
+#include "Runtime/Allocator/MemoryMacros.h"
+#include "Runtime/Utilities/Utility.h"
+#include "Runtime/Utilities/FileUtilities.h"
+#include "Runtime/Utilities/LogAssert.h"
+#include "Runtime/Utilities/vector_utility.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "External/ProphecySDK/include/prcore/surface.hpp"
+
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+
+using namespace std;
+
+prcore::PixelFormat GetProphecyPixelFormat (TextureFormat format);
+
+void BlitCopyCompressedImage( TextureFormat format, const UInt8* src, int srcWidth, int srcHeight, UInt8* dst, int dstWidth, int dstHeight, bool fillRest )
+{
+ AssertIf( dstWidth < srcWidth );
+ AssertIf( dstHeight < srcHeight );
+
+ int blockBytes, srcBlocksX, srcBlocksY, dstBlocksX, dstBlocksY;
+
+ {
+ AssertIf( !IsCompressedDXTTextureFormat(format) && !IsCompressedETCTextureFormat(format)
+ && !IsCompressedATCTextureFormat(format) && !IsCompressedETC2TextureFormat(format)
+ && !IsCompressedEACTextureFormat(format));
+
+ blockBytes = (format == kTexFormatDXT1 || format == kTexFormatETC_RGB4 || format == kTexFormatATC_RGB4
+ || format == kTexFormatETC2_RGB || format == kTexFormatETC2_RGBA1
+ || format == kTexFormatEAC_R || format == kTexFormatEAC_R_SIGNED)
+ ? 8 : 16;
+
+ srcBlocksX = (srcWidth + 3) / 4;
+ srcBlocksY = (srcHeight + 3) / 4;
+ dstBlocksX = (dstWidth + 3) / 4;
+ dstBlocksY = (dstHeight + 3) / 4;
+ }
+
+ int srcRowBytes = srcBlocksX * blockBytes;
+ int dstRowBytes = dstBlocksX * blockBytes;
+
+ const UInt8* srcPtr = src;
+ UInt8* dstPtr = dst;
+ int y;
+ for( y = 0; y < srcBlocksY; ++y )
+ {
+ memcpy( dstPtr, srcPtr, srcRowBytes ); // copy DXT blocks
+ if( fillRest )
+ memset( dstPtr + srcRowBytes, 0, dstRowBytes - srcRowBytes ); // fill rest with black/transparent
+ srcPtr += srcRowBytes;
+ dstPtr += dstRowBytes;
+ }
+ if( fillRest )
+ memset( dstPtr, 0, dstRowBytes * (dstBlocksY-srcBlocksY) ); // fill rest with black/transparent
+}
+
+void BlitCopyCompressedDXT1ToDXT5( const UInt8* src, int srcWidth, int srcHeight, UInt8* dst, int dstWidth, int dstHeight )
+{
+ AssertIf( dstWidth < srcWidth );
+ AssertIf( dstHeight < srcHeight );
+
+ int blockBytesSrc = 8;
+ int blockBytesDst = 16;
+
+ int srcBlocksX = (srcWidth + 3) / 4;
+ int srcBlocksY = (srcHeight + 3) / 4;
+ int dstBlocksX = (dstWidth + 3) / 4;
+
+ int srcRowBytes = srcBlocksX * blockBytesSrc;
+ int dstRowBytes = dstBlocksX * blockBytesDst;
+
+ const UInt8* srcPtr = src;
+ UInt8* dstPtr = dst;
+ int y;
+ for( y = 0; y < srcBlocksY; ++y )
+ {
+ for( int x = 0; x < srcBlocksX; ++x )
+ {
+ // set alpha to opaque white
+ memset( dstPtr + x * blockBytesDst, 0xFF, blockBytesDst-blockBytesSrc );
+ // copy block
+ memcpy( dstPtr + x * blockBytesDst + blockBytesSrc, srcPtr + x * blockBytesSrc, blockBytesSrc );
+ }
+ srcPtr += srcRowBytes;
+ dstPtr += dstRowBytes;
+ }
+}
+
+
+void PadImageBorder( ImageReference& image, int sizeX, int sizeY )
+{
+ AssertIf( IsAnyCompressedTextureFormat(image.GetFormat()) );
+
+ int width = image.GetWidth();
+ int height = image.GetHeight();
+ AssertIf( sizeX < 1 || sizeY < 1 || sizeX > width || sizeY > height );
+
+ int y;
+ UInt8* rowptr = image.GetImageData();
+
+ int bpp = GetBytesFromTextureFormat(image.GetFormat());
+ AssertIf(bpp > 4); // we only support up to 4 bpp
+ UInt8 pixel[4];
+
+ // pad rightmost part by repeating last pixel in each row
+ for( y = 0; y < sizeY; ++y )
+ {
+ UInt8* ptr = rowptr + (sizeX-1) * bpp;
+
+ for( int b = 0; b < bpp; ++b )
+ pixel[b] = ptr[b];
+ ptr += bpp;
+ for( int x = sizeX; x < width; ++x )
+ {
+ for( int b = 0; b < bpp; ++b )
+ ptr[b] = pixel[b];
+ ptr += bpp;
+ }
+
+ rowptr += image.GetRowBytes();
+ }
+
+ // pad bottom part by repeating last row
+ UInt8* lastRowPtr = image.GetRowPtr( sizeY - 1 );
+ for( int b = 0; b < bpp; ++b )
+ pixel[b] = lastRowPtr[ (sizeX-1)*bpp + b ];
+
+ for( y = sizeY; y < height; ++y )
+ {
+ UInt8* ptr = rowptr;
+ memcpy( ptr, lastRowPtr, sizeX * bpp ); // copy
+ ptr += sizeX * bpp;
+ for( int x = sizeX; x < width; ++x ) // repeat last pixel
+ {
+ for( int b = 0; b < bpp; ++b )
+ ptr[b] = pixel[b];
+ ptr += bpp;
+ }
+
+ rowptr += image.GetRowBytes();
+ }
+}
+
+
+int CalculateMipMapCount3D (int width, int height, int depth)
+{
+ //AssertIf( !IsPowerOfTwo(width) || !IsPowerOfTwo(height) || !IsPowerOfTwo(depth) );
+
+ // Mip-levels for non-power-of-two textures follow OpenGL's NPOT texture rules: size is divided
+ // by two and floor'ed. This allows just to use same old code I think.
+
+ int minSizeLog2 = HighestBit (width);
+ minSizeLog2 = max (minSizeLog2, HighestBit (height));
+ minSizeLog2 = max (minSizeLog2, HighestBit (depth));
+
+ AssertIf( (width >> minSizeLog2) < 1 && (height >> minSizeLog2) < 1 && (depth >> minSizeLog2) < 1 );
+ AssertIf( (width >> minSizeLog2) > 1 && (height >> minSizeLog2) > 1 && (depth >> minSizeLog2) < 1 );
+
+ return minSizeLog2 + 1;
+}
+
+int CalculateImageSize (int width, int height, TextureFormat format)
+{
+ if (format == kTexFormatDXT1 || format == kTexFormatATC_RGB4)
+ return ((width + 3) / 4) * ((height + 3) / 4) * 8;
+ else if (format == kTexFormatDXT3 || format == kTexFormatDXT5 || format == kTexFormatATC_RGBA8)
+ return ((width + 3) / 4) * ((height + 3) / 4) * 16;
+ else if (format == kTexFormatPVRTC_RGB4 || format == kTexFormatPVRTC_RGBA4)
+ return (max(width, 8) * max(height, 8) * 4 + 7) / 8;
+ else if (format == kTexFormatPVRTC_RGB2 || format == kTexFormatPVRTC_RGBA2)
+ return (max(width, 16) * max(height, 8) * 2 + 7) / 8;
+ else if (format == kTexFormatETC_RGB4 || format == kTexFormatETC2_RGB || format == kTexFormatETC2_RGBA1
+ || format == kTexFormatEAC_R || format == kTexFormatEAC_R_SIGNED)
+ return (max(width, 4) * max(height, 4) * 4 + 7) / 8;
+ else if (format == kTexFormatETC2_RGBA8 || format == kTexFormatEAC_RG || format == kTexFormatEAC_RG_SIGNED)
+ return (max(width, 4) * max(height, 4) * 8 + 7) / 8;
+
+#define STR_(x) #x
+#define STR(x) STR_(x)
+ // The size of the ASTC block is always 128 bits = 16 bytes, width of image in blocks is ceiling(width/blockwidth)
+#define DO_ASTC(bx, by) else if( format == kTexFormatASTC_RGB_##bx##x##by || format == kTexFormatASTC_RGBA_##bx##x##by)\
+ return ( (CeilfToInt(((float)width) / ((float)bx))) * (CeilfToInt(((float)height) / ((float)by))) * 16 )
+
+ DO_ASTC(4, 4);
+ DO_ASTC(5, 5);
+ DO_ASTC(6, 6);
+ DO_ASTC(8, 8);
+ DO_ASTC(10, 10);
+ DO_ASTC(12, 12);
+
+#undef DO_ASTC
+#undef STR_
+#undef STR
+
+ // ATF Format is a container format and has no known size based on width & height
+ else if (format == kTexFormatFlashATF_RGB_DXT1 || format == kTexFormatFlashATF_RGBA_JPG || format == kTexFormatFlashATF_RGB_JPG)
+ return 0;
+ else
+ return GetRowBytesFromWidthAndFormat (width, format) * height;
+}
+
+int CalculateMipMapOffset (int width, int height, TextureFormat format, int miplevel)
+{
+ if (width == 0 || height == 0)
+ return 0;
+
+ // Allow NPOT textures as well.
+ //AssertIf (!IsPowerOfTwo (width) || !IsPowerOfTwo (height));
+ int completeSize = 0;
+ for (int i=0;i < miplevel;i++)
+ completeSize += CalculateImageSize (std::max (width >> i, 1), std::max (height >> i, 1), format);
+ return completeSize;
+}
+
+int CalculateImageMipMapSize (int width, int height, TextureFormat format)
+{
+ return CalculateMipMapOffset( width, height, format, CalculateMipMapCount3D(width, height, 1) );
+}
+
+
+
+void CreateMipMap (UInt8* inData, int width, int height, int depth, TextureFormat format)
+{
+ const int bpp = GetBytesFromTextureFormat (format);
+ int mipCount = CalculateMipMapCount3D (width, height, depth) - 1;
+ UInt8* srcPtr = inData;
+
+ UInt8* tempBuffer = NULL;
+
+ for (int mip = 0; mip < mipCount; ++mip)
+ {
+ int nextWidth = std::max(width/2,1);
+ int nextHeight = std::max(height/2,1);
+ int nextDepth = std::max(depth/2,1);
+
+ UInt8* nextMipPtr = srcPtr + width * height * depth * bpp;
+ UInt8* dstPtr = nextMipPtr;
+ if (depth > 1)
+ {
+ if (!tempBuffer)
+ tempBuffer = ALLOC_TEMP_MANUAL(UInt8, nextWidth*nextHeight*bpp+bpp);
+
+ for (int d = 0; d < nextDepth; ++d)
+ {
+ // blit two slices of this mip level into two 2x2 smaller images
+ ImageReference src1 (width, height, width * bpp, format, srcPtr);
+ srcPtr += width * height * bpp;
+ ImageReference src2 (width, height, width * bpp, format, srcPtr);
+ srcPtr += width * height * bpp;
+
+ ImageReference dst1 (nextWidth, nextHeight, nextWidth * bpp, format, dstPtr); // one directly into dst
+ dst1.BlitImage (src1, ImageReference::BLIT_BILINEAR_SCALE);
+ ImageReference dst2 (nextWidth, nextHeight, nextWidth * bpp, format, tempBuffer); // one into temp buffer
+ dst2.BlitImage (src2, ImageReference::BLIT_BILINEAR_SCALE);
+
+ // now average the two smaller images
+ for (int i = 0; i < nextWidth*nextHeight*bpp; ++i)
+ {
+ unsigned b1 = dstPtr[i];
+ unsigned b2 = tempBuffer[i];
+ dstPtr[i] = (b1 + b2) / 2;
+ }
+
+ dstPtr += nextWidth * nextHeight * bpp;
+ }
+ }
+ else
+ {
+ ImageReference src (width, height, width * bpp, format, srcPtr);
+ ImageReference dst (nextWidth, nextHeight, nextWidth * bpp, format, dstPtr);
+ dst.BlitImage (src, ImageReference::BLIT_BILINEAR_SCALE);
+ }
+
+ srcPtr = nextMipPtr;
+ width = nextWidth;
+ height = nextHeight;
+ depth = nextDepth;
+ }
+
+ FREE_TEMP_MANUAL(tempBuffer);
+}
+
+
+
+ImageReference::ImageReference (int width, int height, int rowbytes, TextureFormat format, void* image)
+{
+ m_Width = width;
+ m_Height = height;
+ m_Format = format;
+ m_RowBytes = rowbytes;
+
+ if (image && CheckImageFormatValid (width, height, format))
+ m_Image = reinterpret_cast<UInt8*> (image);
+ else
+ m_Image = NULL;
+}
+
+ImageReference::ImageReference (int width, int height, TextureFormat format)
+{
+ m_Width = width;
+ m_Height = height;
+ m_Format = format;
+ m_RowBytes = GetRowBytesFromWidthAndFormat (m_Width, format);
+ m_Image = NULL;
+}
+
+void ImageReference::FlipImageY ()
+{
+ if (m_Image)
+ {
+ prcore::Surface srcSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ srcSurface.FlipImageY ();
+ }
+}
+
+#if UNITY_EDITOR
+static inline void SwapPixels( UInt8* a, UInt8* b, int bpp ) {
+ for( int i = 0; i < bpp; ++i ) {
+ UInt8 t = a[i];
+ a[i] = b[i];
+ b[i] = t;
+ }
+}
+
+void ImageReference::FlipImageX ()
+{
+ if (!m_Image)
+ return;
+
+ const int width = GetWidth();
+ const int height = GetHeight();
+ const TextureFormat format = GetFormat();
+ if( IsAnyCompressedTextureFormat(format) ) {
+ AssertString("FlipImageX does not work on compressed formats");
+ return;
+ }
+ const int bpp = GetBytesFromTextureFormat(format);
+ for( int y = 0; y < height; ++y ) {
+ UInt8* rowFirst = GetRowPtr(y);
+ UInt8* rowLast = rowFirst + (width-1) * bpp;
+ for( int x = 0; x < width/2; ++x ) {
+ SwapPixels( rowFirst, rowLast, bpp );
+ rowFirst += bpp;
+ rowLast -= bpp;
+ }
+ }
+}
+#endif
+
+
+void ImageReference::BlitImage (const ImageReference& source, BlitMode mode)
+{
+ if (m_Image && source.m_Image)
+ {
+ prcore::Surface srcSurface (source.GetWidth (), source.GetHeight (), source.GetRowBytes (), GetProphecyPixelFormat (source.GetFormat ()), source.GetImageData ());
+ prcore::Surface dstSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ dstSurface.BlitImage (srcSurface, (prcore::Surface::BlitMode)mode);
+ }
+}
+
+void ImageReference::BlitImage (int x, int y, const ImageReference& source)
+{
+ if (source.m_Image && m_Image)
+ {
+ prcore::Surface srcSurface (source.GetWidth (), source.GetHeight (), source.GetRowBytes (), GetProphecyPixelFormat (source.GetFormat ()), source.GetImageData ());
+ prcore::Surface dstSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ dstSurface.BlitImage (x, y, srcSurface);
+ }
+}
+
+void ImageReference::ClearImage (const ColorRGBA32& color, ClearMode mode)
+{
+ if (m_Image)
+ {
+ prcore::color32 c;
+ c.r = color.r;
+ c.g = color.g;
+ c.b = color.b;
+ c.a = color.a;
+ prcore::Surface dstSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ dstSurface.ClearImage (c, (prcore::Surface::ClearMode)mode);
+ }
+}
+
+ImageReference ImageReference::ClipImage (int x, int y, int width, int height) const
+{
+ if (m_Image)
+ {
+ x = clamp<int> (x, 0, m_Width);
+ y = clamp<int> (y, 0, m_Height);
+ width = min<int> (m_Width, x + width) - x;
+ height = min<int> (m_Height, y + height) - y;
+ width = max<int> (0, width);
+ height = max<int> (0, height);
+
+ int bytesPerPixel = GetBytesFromTextureFormat( m_Format );
+ return ImageReference( width, height, m_RowBytes, m_Format, m_Image + bytesPerPixel * x + m_RowBytes * y );
+ }
+ else
+ return ImageReference( 0, 0, 0, m_Format, NULL );
+}
+
+bool ImageReference::NeedsReformat(int width, int height, TextureFormat format) const
+{
+ // TODO : it actually doesn't need to reallocate when downsizing!
+ return width != m_Width || height != m_Height || format != m_Format;
+}
+
+Image::Image (int width, int height, TextureFormat format)
+{
+ m_Height = height;
+ m_Width = width;
+ m_Format = format;
+ int bpp = GetBytesFromTextureFormat( m_Format );
+ m_RowBytes = m_Width * bpp;
+ if (CheckImageFormatValid (width, height, format))
+ m_Image = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, m_RowBytes * m_Height + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+ else
+ m_Image = NULL;
+}
+
+Image::Image (int width, int height, int rowbytes, TextureFormat format, void* image)
+{
+ m_Height = height;
+ m_Width = width;
+ m_Format = format;
+ int bpp = GetBytesFromTextureFormat( m_Format );
+ m_RowBytes = m_Width * bpp;
+ if( CheckImageFormatValid (width, height, format) )
+ m_Image = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, m_RowBytes * m_Height + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+ else
+ m_Image = NULL;
+
+ if (image && m_Image)
+ BlitImage (ImageReference (width, height, rowbytes, format, image), BLIT_COPY);
+}
+
+void Image::SetImage(SInt32 width, SInt32 height, UInt32 format, bool shrinkAllowed)
+{
+ // TODO : this size is wrong if it has been shrunk already, but we don't care at the moment,
+ // because we use it for calculation of mipmaps only
+ const int oldSize = m_RowBytes * m_Height + GetBytesFromTextureFormat( m_Format );
+
+ m_Width = width;
+ m_Height = height;
+ m_Format = format;
+ const int bpp = GetBytesFromTextureFormat( m_Format );
+ m_RowBytes = m_Width * bpp;
+
+ const int newSize = m_RowBytes * m_Height + bpp;
+
+ if ((!shrinkAllowed && (oldSize < newSize)) ||
+ (shrinkAllowed && (oldSize != newSize)))
+ {
+ UNITY_FREE(kMemNewDelete, m_Image);
+ m_Image = NULL;
+ if( m_Format != 0 && CheckImageFormatValid (m_Width, m_Height, m_Format) )
+ m_Image = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, m_RowBytes * m_Height + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+ }
+}
+
+void Image::SetImage(const ImageReference& src, bool shrinkAllowed)
+{
+ if (this == &src)
+ return;
+
+ SetImage (src.GetWidth(), src.GetHeight(), src.GetFormat(), shrinkAllowed);
+ BlitImage (src, BLIT_COPY);
+}
+
+void Image::ReformatImage (const ImageReference& image, int width, int height, TextureFormat format, BlitMode mode)
+{
+ AssertIf(!image.NeedsReformat(width, height, format));
+
+ int bpp = GetBytesFromTextureFormat(format);
+ int newRowBytes = width * bpp;
+ UInt8* newImageData = NULL;
+ if (CheckImageFormatValid (width, height, format))
+ newImageData = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, height * newRowBytes + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+
+ ImageReference newImage (width, height, newRowBytes, format, newImageData);
+ newImage.BlitImage (image, mode);
+ UNITY_FREE(kMemNewDelete, m_Image);
+
+ m_Height = height;
+ m_Width = width;
+ m_Format = format;
+ m_RowBytes = newRowBytes;
+ m_Image = newImageData;
+}
+
+void Image::ReformatImage (int width, int height, TextureFormat format, BlitMode mode)
+{
+ if (!NeedsReformat(width, height, format))
+ return;
+
+ ReformatImage (*this, width, height, format, mode);
+}
+
+bool ImageReference::IsValidImage () const
+{
+ return m_Image != NULL && CheckImageFormatValid (GetWidth(), GetHeight(), GetFormat());
+}
+
+bool CheckImageFormatValid (int width, int height, TextureFormat format)
+{
+ if (width > 0 && height > 0 && format > 0 && (format <= kTexFormatBGR24 || format == kTexFormatBGRA32 || format == kTexFormatRGBA4444))
+ return true;
+ else
+ {
+ if (width < 0) {
+ AssertString ("Image invalid width!");
+ }
+ if (height < 0) {
+ AssertString ("Image invalid height!");
+ }
+ if (format > kTexFormatBGR24 && format != kTexFormatRGBA4444 && format != kTexFormatBGRA32) {
+ AssertString ("Image invalid format!");
+ }
+ return false;
+ }
+}
+
+prcore::PixelFormat GetProphecyPixelFormat (TextureFormat format)
+{
+ switch (format)
+ {
+ case kTexFormatAlpha8:
+ return prcore::PixelFormat (8,0,0xff);
+ case kTexFormatARGB4444:
+ return prcore::PixelFormat (16,0x00000f00,0x000000f0,0x0000000f,0x0000f000);
+ case kTexFormatRGBA4444:
+ return prcore::PixelFormat (16,0x0000f000,0x00000f00,0x000000f0,0x0000000f);
+ case kTexFormatRGB24:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (24,0x00ff0000,0x0000ff00,0x000000ff,0x00000000);
+ #else
+ return prcore::PixelFormat (24,0x000000ff,0x0000ff00,0x00ff0000,0x00000000);
+ #endif
+ case kTexFormatRGBA32:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (32,0xff000000,0x00ff0000,0x0000ff00,0x000000ff);
+ #else
+ return prcore::PixelFormat (32,0x000000ff,0x0000ff00,0x00ff0000,0xff000000);
+ #endif
+ case kTexFormatBGRA32:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (32,0x0000ff00,0x00ff0000,0xff000000,0x000000ff);
+ #else
+ return prcore::PixelFormat (32,0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
+ #endif
+ case kTexFormatARGB32:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (32,0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
+ #else
+ return prcore::PixelFormat (32,0x0000ff00,0x00ff0000,0xff000000,0x000000ff);
+ #endif
+ case kTexFormatARGBFloat:
+ return prcore::PixelFormat (128, 2, 4, 8, 1, prcore::PIXELFORMAT_FLOAT32);
+ case kTexFormatRGB565:
+ return prcore::PixelFormat (16,0x0000f800,0x000007e0,0x0000001f,0x00000000);
+ case kTexFormatBGR24:
+#if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (24,0x00ff0000,0x0000ff00,0x000000ff,0x00000000);
+#else
+ return prcore::PixelFormat (24,0x000000ff,0x0000ff00,0x00ff0000,0x00000000);
+#endif
+ default:
+ DebugAssertIf( true );
+ return prcore::PixelFormat ();
+ }
+}
+
+bool operator == (const ImageReference& lhs, const ImageReference& rhs)
+{
+ if (lhs.GetWidth () != rhs.GetWidth ()) return false;
+ if (lhs.GetHeight () != rhs.GetHeight ()) return false;
+ if (lhs.GetRowBytes () != rhs.GetRowBytes ()) return false;
+ if (lhs.GetFormat () != rhs.GetFormat ()) return false;
+ UInt8* lhsData = lhs.GetImageData ();
+ UInt8* rhsData = rhs.GetImageData ();
+ if (lhsData == NULL || rhsData == NULL)
+ return lhsData == rhsData;
+
+ int size = lhs.GetRowBytes () * lhs.GetHeight ();
+ for (int i=0;i<size / 4;i++)
+ {
+ if (*reinterpret_cast<UInt32*> (lhsData) != *reinterpret_cast<UInt32*> (rhsData))
+ return false;
+ lhsData += 4; rhsData += 4;
+ }
+ for (int i=0;i<size % 4;i++)
+ {
+ if (*lhsData != *rhsData)
+ return false;
+ lhsData++; rhsData++;
+ }
+ return true;
+}
+
+void SwizzleARGB32ToBGRA32 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=4)
+ {
+ UInt8 swizzle[4];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[3];
+ dst[1] = swizzle[2];
+ dst[2] = swizzle[1];
+ dst[3] = swizzle[0];
+ }
+}
+
+void SwizzleRGBA32ToBGRA32 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=4)
+ {
+ UInt8 swizzle[4];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[2];
+ dst[1] = swizzle[1];
+ dst[2] = swizzle[0];
+ dst[3] = swizzle[3];
+ }
+}
+
+void SwizzleBGRAToRGBA32 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=4)
+ {
+ UInt8 swizzle[4];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[2];
+ dst[1] = swizzle[1];
+ dst[2] = swizzle[0];
+ dst[3] = swizzle[3];
+ }
+}
+
+void SwizzleRGB24ToBGR24 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=3)
+ {
+ UInt8 swizzle[3];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[2];
+ dst[1] = swizzle[1];
+ dst[2] = swizzle[0];
+ }
+}
+
+void Premultiply( ImageReference& image )
+{
+ if (image.GetFormat() == kTexFormatRGBA32)
+ {
+ UInt8* data = image.GetImageData();
+ int size = image.GetRowBytes() * image.GetHeight() / 4;
+ for (int i=0;i<size;i++)
+ {
+ UInt8* rgba = data + 4 * i;
+ rgba[0] = ((int)rgba[0] * (int)rgba[3]) / 255;
+ rgba[1] = ((int)rgba[1] * (int)rgba[3]) / 255;
+ rgba[2] = ((int)rgba[2] * (int)rgba[3]) / 255;
+ }
+ }
+ else if (image.GetFormat() == kTexFormatARGB32)
+ {
+ UInt8* data = image.GetImageData();
+ int size = image.GetRowBytes() * image.GetHeight() / 4;
+ for (int i=0;i<size;i++)
+ {
+ UInt8* rgba = data + 4 * i;
+ rgba[1] = ((int)rgba[1] * (int)rgba[0]) / 255;
+ rgba[2] = ((int)rgba[2] * (int)rgba[0]) / 255;
+ rgba[3] = ((int)rgba[3] * (int)rgba[0]) / 255;
+ }
+ }
+ else
+ {
+ ErrorString("Unsupported");
+ }
+}
+
+inline UInt32 DecodeRGBMChannel(UInt32 val, UInt32 mult, UInt32 mask, UInt8 offset)
+{
+ UInt32 channel = (val & mask) >> offset;
+ channel *= mult;
+ channel /= 255 * 2;
+ channel = channel > 255 ? 255 : channel;
+ channel <<= offset;
+
+ return channel;
+}
+
+// Decodes RGBM encoded lightmaps into doubleLDR in place.
+// Handles kTexFormatRGBA32 and kTexFormatARGB32 data.
+void DecodeRGBM(int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf)
+{
+ UInt8* rowData = data;
+ uint32 rmask = pf.GetRedMask();
+ uint32 gmask = pf.GetGreenMask();
+ uint32 bmask = pf.GetBlueMask();
+ uint32 amask = pf.GetAlphaMask();
+ uint8 roffset = (int)pf.GetRedOffset() - (int)pf.GetRedBits() + 1;
+ uint8 goffset = (int)pf.GetGreenOffset() - (int)pf.GetGreenBits() + 1;
+ uint8 boffset = (int)pf.GetBlueOffset() - (int)pf.GetBlueBits() + 1;
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+ uint32 alpha = ((val & amask) >> aoffset) * kRGBMMaxRange;
+ pixel[c] = DecodeRGBMChannel(val, alpha, rmask, roffset) +
+ DecodeRGBMChannel(val, alpha, gmask, goffset) +
+ DecodeRGBMChannel(val, alpha, bmask, boffset) +
+ (255 << aoffset);
+ }
+ rowData += pitch;
+ }
+}
+
+
+void SetAlphaChannel(int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf, UInt8 alpha)
+{
+ UInt8* rowData = data;
+ uint32 amask = pf.GetAlphaMask();
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+ uint32 properAlpha = (alpha << aoffset);
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+
+ // merge bits from two values according to a mask,
+ // equivalent to: (val & ~amask) | (properAlpha & amask)
+ pixel[c] = val ^ ((val ^ properAlpha) & amask);
+ }
+ rowData += pitch;
+ }
+}
+
+void SetAlphaToRedChannel (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf)
+{
+ UInt8* rowData = data;
+ uint32 rmask = pf.GetRedMask();
+ uint32 amask = pf.GetAlphaMask();
+ uint8 roffset = (int)pf.GetRedOffset() - (int)pf.GetRedBits() + 1;
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+ uint32 red = (val & rmask) >> roffset;
+ pixel[c] = (val & ~amask) | (red << aoffset);
+ }
+ rowData += pitch;
+ }
+}
+
+inline UInt8 XenonToNormalSRGBTexture (UInt32 value)
+{
+ value = UInt32(LinearToGammaSpace(GammaToLinearSpaceXenon (value / 255.0F)) * 255.0F);
+ return std::min<UInt32>(value, 255);
+}
+
+void XenonToNormalSRGBTexture (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf)
+{
+ UInt8* rowData = data;
+ uint32 rmask = pf.GetRedMask();
+ uint32 gmask = pf.GetGreenMask();
+ uint32 bmask = pf.GetBlueMask();
+ uint32 amask = pf.GetAlphaMask();
+ uint8 roffset = (int)pf.GetRedOffset() - (int)pf.GetRedBits() + 1;
+ uint8 goffset = (int)pf.GetGreenOffset() - (int)pf.GetGreenBits() + 1;
+ uint8 boffset = (int)pf.GetBlueOffset() - (int)pf.GetBlueBits() + 1;
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+ uint32 r = (val & rmask) >> roffset;
+ uint32 g = (val & gmask) >> goffset;
+ uint32 b = (val & bmask) >> boffset;
+ uint32 a = (val & amask) >> aoffset;
+
+ r = XenonToNormalSRGBTexture (r);
+ g = XenonToNormalSRGBTexture (g);
+ b = XenonToNormalSRGBTexture (b);
+
+ pixel[c] = (r << roffset) | (g << goffset) | (b << boffset) | (a << aoffset);
+ }
+ rowData += pitch;
+ }
+}
+
+
+
+
+// --------------------------------------------------------------------------
+
+
+const char* kUnsupportedGetPixelOpFormatMessage = "Unsupported texture format - needs to be ARGB32, RGBA32, BGRA32, RGB24, Alpha8 or DXT";
+const char* kUnsupportedSetPixelOpFormatMessage = "Unsupported texture format - needs to be ARGB32, RGBA32, RGB24 or Alpha8";
+
+inline int RepeatInt (int x, int width)
+{
+ if (width == 0) return 0;
+ if( x < 0 ) {
+ int times = -(x/width)+1;
+ x += width * times;
+ }
+ x = x % width;
+ return x;
+}
+
+inline int TextureWrap(int x, int max, TextureWrapMode wrapMode)
+{
+ if (wrapMode == kTexWrapRepeat)
+ {
+ return RepeatInt(x, max);
+ }
+ else
+ {
+ if (max <= 0)
+ return 0;
+ return clamp(x, 0, max-1);
+ }
+}
+
+void SetImagePixel (ImageReference& image, int x, int y, TextureWrapMode wrap, const ColorRGBAf& color)
+{
+ int width = image.GetWidth();
+ int height = image.GetHeight();
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ {
+ x = TextureWrap(x, width, wrap);
+ y = TextureWrap(y, height, wrap);
+ }
+
+ if (image.GetFormat() == kTexFormatARGB32)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x * 4;
+ pixel[1] = RoundfToIntPos(clamp01(color.r) * 255.0);
+ pixel[2] = RoundfToIntPos(clamp01(color.g) * 255.0);
+ pixel[3] = RoundfToIntPos(clamp01(color.b) * 255.0);
+ pixel[0] = RoundfToIntPos(clamp01(color.a) * 255.0);
+ }
+ else if (image.GetFormat() == kTexFormatRGBA32)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x * 4;
+ pixel[0] = RoundfToIntPos(clamp01(color.r) * 255.0);
+ pixel[1] = RoundfToIntPos(clamp01(color.g) * 255.0);
+ pixel[2] = RoundfToIntPos(clamp01(color.b) * 255.0);
+ pixel[3] = RoundfToIntPos(clamp01(color.a) * 255.0);
+ }
+ else if (image.GetFormat() == kTexFormatRGB24)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x * 3;
+ pixel[0] = RoundfToIntPos(clamp01(color.r) * 255.0);
+ pixel[1] = RoundfToIntPos(clamp01(color.g) * 255.0);
+ pixel[2] = RoundfToIntPos(clamp01(color.b) * 255.0);
+ }
+ else if (image.GetFormat() == kTexFormatRGB565)
+ {
+ UInt16* pixel = (UInt16 *)(image.GetRowPtr(y) + x * 2);
+ UInt16 r = (UInt16)(RoundfToIntPos( clamp01(color.r) * 31.0f));
+ UInt16 g = (UInt16)(RoundfToIntPos( clamp01(color.g) * 63.0f));
+ UInt16 b = (UInt16)(RoundfToIntPos( clamp01(color.b) * 31.0f));
+ pixel[0] = r<<11 | g<<5 | b;
+ }
+ else if (image.GetFormat() == kTexFormatAlpha8)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x;
+ pixel[0] = RoundfToIntPos(clamp01(color.a) * 255.0);
+ }
+ else
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ }
+}
+
+
+ColorRGBA32 GetImagePixel (UInt8* data, int width, int height, TextureFormat format, TextureWrapMode wrap, int x, int y)
+{
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ {
+ x = TextureWrap (x, width, wrap);
+ y = TextureWrap (y, height, wrap);
+ }
+
+ if( IsCompressedDXTTextureFormat(format) )
+ {
+ int texWidth = std::max (width, 4);
+
+ UInt32 uncompressed [16];
+ int blockBytes = (format == kTexFormatDXT1 ? 8 : 16);
+
+ const UInt8 *pos = data + ((x/4) + (y/4)*(texWidth/4)) * blockBytes;
+ DecompressNativeTextureFormat (format, 4, 4, (const UInt32*)pos, 4, 4, uncompressed);
+
+ const UInt8* pixel = (const UInt8*)(uncompressed + x%4 + 4* (y%4));
+ return ColorRGBA32(pixel[0], pixel[1], pixel[2], pixel[3]);
+ }
+ else if( IsAnyCompressedTextureFormat (format) )
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ }
+ else
+ {
+ ImageReference image (width, height, GetRowBytesFromWidthAndFormat(width, format), format, data);
+ const UInt8* pixel;
+ if (format == kTexFormatARGB32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ return ColorRGBA32 (pixel[1], pixel[2], pixel[3], pixel[0]);
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ return ColorRGBA32 (pixel[0], pixel[1], pixel[2], pixel[3]);
+ }
+ else if (format == kTexFormatBGRA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ return ColorRGBA32 (pixel[2], pixel[1], pixel[0], pixel[3]);
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ pixel = image.GetRowPtr(y) + x * 3;
+ return ColorRGBA32 (pixel[0], pixel[1], pixel[2], 255);
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ pixel = image.GetRowPtr(y) + x;
+ return ColorRGBA32 (255, 255, 255, pixel[0]);
+ }
+ else if (format == kTexFormatRGBA4444)
+ {
+ pixel = image.GetRowPtr(y) + x * 2;
+ return ColorRGBA32(
+ (pixel[1] & 0xF0) << 4 | (pixel[1] & 0xF0),
+ (pixel[1] & 0x0F) << 4 | (pixel[1] & 0x0F),
+ (pixel[0] & 0xF0) << 4 | (pixel[0] & 0xF0),
+ (pixel[0] & 0x0F) << 4 | (pixel[0] & 0x0F)
+ );
+ }
+ else if (format == kTexFormatARGB4444)
+ {
+ pixel = image.GetRowPtr(y) + x * 2;
+ return ColorRGBA32(
+ (pixel[1] & 0x0F) << 4 | (pixel[1] & 0x0F),
+ (pixel[0] & 0xF0) << 4 | (pixel[0] & 0xF0),
+ (pixel[0] & 0x0F) << 4 | (pixel[0] & 0x0F),
+ (pixel[1] & 0xF0) << 4 | (pixel[1] & 0xF0)
+ );
+ }
+ else if (format == kTexFormatRGB565)
+ {
+ pixel = image.GetRowPtr(y) + x * 2;
+ UInt16 c = *reinterpret_cast<const UInt16*>(pixel);
+ UInt8 r = (UInt8)((c >> 11)&0x1F);
+ UInt8 g = (UInt8)((c >> 5)&0x3F);
+ UInt8 b = (UInt8)((c >> 0)&0x1F);
+ return ColorRGBA32 (r<<3|r>>2, g<<2|g>>4, b<<3|b>>2, 255);
+ }
+ else
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ }
+ }
+ return ColorRGBA32(255,255,255,255);
+}
+
+ColorRGBAf GetImagePixelBilinear (UInt8* data, int width, int height, TextureFormat format, TextureWrapMode wrap, float u, float v)
+{
+ u *= width;
+ v *= height;
+
+ int xBase = FloorfToInt(u);
+ int yBase = FloorfToInt(v);
+
+ float s = u - xBase;
+ float t = v - yBase;
+
+ ColorRGBAf colors[4];
+
+ if (IsCompressedDXTTextureFormat(format))
+ {
+ if (xBase < 0 || xBase+1 >= width || yBase < 0 || yBase+1 >= height)
+ {
+ for (int i=0;i<4;i++)
+ {
+ int x = xBase;
+ int y = yBase;
+ if (i & 1)
+ x++;
+ if (i & 2)
+ y++;
+
+ x = TextureWrap(x, width, wrap);
+ y = TextureWrap(y, height, wrap);
+
+ colors[i] = GetImagePixel (data, width, height, format, wrap, x, y);
+ }
+ }
+ else
+ {
+ GetImagePixelBlock (data, width, height, format, xBase, yBase, 2, 2, colors);
+ }
+ }
+ else if( IsAnyCompressedTextureFormat(format) )
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+ }
+ else
+ {
+ ImageReference image (width, height, GetRowBytesFromWidthAndFormat(width, format), format, data);
+ for (int i=0;i<4;i++)
+ {
+ int x = xBase;
+ int y = yBase;
+ if (i & 1)
+ x++;
+ if (i & 2)
+ y++;
+
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ {
+ x = TextureWrap(x, width, wrap);
+ y = TextureWrap(y, height, wrap);
+ }
+
+ UInt8* pixel;
+ if (format == kTexFormatARGB32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ colors[i] = ColorRGBA32 (pixel[1], pixel[2], pixel[3], pixel[0]);
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ colors[i] = ColorRGBA32 (pixel[0], pixel[1], pixel[2], pixel[3]);
+ }
+ else if (format == kTexFormatBGRA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ colors[i] = ColorRGBA32 (pixel[2], pixel[1], pixel[0], pixel[3]);
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ pixel = image.GetRowPtr(y) + x * 3;
+ colors[i] = ColorRGBA32 (pixel[0], pixel[1], pixel[2], 255);
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ pixel = image.GetRowPtr(y) + x;
+ colors[i] = ColorRGBA32 (255, 255, 255, pixel[0]);
+ }
+ else
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+ }
+ }
+ }
+ ColorRGBAf a = Lerp(colors[0], colors[1], s);
+ ColorRGBAf b = Lerp(colors[2], colors[3], s);
+ return Lerp(a, b, t);
+}
+
+bool GetImagePixelBlock (UInt8* data, int dataWidth, int dataHeight, TextureFormat format, int x, int y, int blockWidth, int blockHeight, ColorRGBAf* outColors)
+{
+ // Checks
+ if (blockWidth <= 0 || blockHeight <= 0)
+ {
+ ErrorString ("Width and height must be positive");
+ return false;
+ }
+
+ // TODO: repeat/crop support
+ if (x < 0 || y < 0 || x + blockWidth < 0 || y + blockHeight < 0 || x + blockWidth > dataWidth || y + blockHeight > dataHeight)
+ {
+ char mad[255];
+
+ if (x < 0)
+ sprintf(mad, "Texture rectangle is out of bounds (%d < 0)", x);
+ if (y < 0)
+ sprintf(mad, "Texture rectangle is out of bounds (%d < 0)", y);
+ if (x + blockWidth > dataWidth)
+ sprintf(mad, "Texture rectangle is out of bounds (%d + %d > %d)", x, blockWidth, dataWidth);
+ if (y + blockHeight > dataHeight)
+ sprintf(mad, "Texture rectangle is out of bounds (%d + %d > %d)", y, blockHeight, dataHeight);
+
+ ErrorString(mad);
+ return false;
+ }
+
+ if (IsCompressedDXTTextureFormat(format))
+ {
+ int texWidth = std::max (dataWidth, 4);
+
+ int paddedWidth = x+blockWidth-(x&~3);
+ if(paddedWidth % 4)
+ paddedWidth = (paddedWidth&~3) + 4;
+ int paddedHeight = y+blockHeight-(y&~3);
+ if(paddedHeight % 4)
+ paddedHeight = (paddedHeight&~3) + 4;
+
+ UInt32* uncompressed;
+ ALLOC_TEMP(uncompressed, UInt32, paddedWidth * paddedHeight);
+
+ int blockBytes = (format == kTexFormatDXT1 ? 8 : 16);
+
+ for(int line = 0; line< paddedHeight; line+=4)
+ {
+ UInt8 *pos = data + ((x/4) + ((y+line)/4)*(texWidth/4)) * blockBytes;
+ DecompressNativeTextureFormat(format, paddedWidth, 4, (UInt32*)pos, paddedWidth, 4, uncompressed+(line*paddedWidth));
+ }
+
+ ColorRGBAf* dest = outColors;
+ const UInt8* pixelRow = (UInt8*)(uncompressed + x%4 + (y%4)*paddedWidth);
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[0], pixel[1], pixel[2], pixel[3]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += paddedWidth * 4;
+ }
+ }
+ else
+ {
+ ImageReference image (dataWidth, dataHeight, GetRowBytesFromWidthAndFormat(dataWidth, format), format, data);
+
+ ColorRGBAf* dest = outColors;
+
+ if (format == kTexFormatARGB32)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[1], pixel[2], pixel[3], pixel[0]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[0], pixel[1], pixel[2], pixel[3]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatBGRA32)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[2], pixel[1], pixel[0], pixel[3]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 3;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[0], pixel[1], pixel[2], 255);
+ pixel += 3;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32 (255, 255, 255, pixel[0]);
+ pixel += 1;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB565)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ UInt16 c = (UInt16)pixel[0] | ((UInt16)pixel[1])<<8;
+ UInt8 r = (UInt8)((c >> 11)&31);
+ UInt8 g = (UInt8)((c >> 5)&63);
+ UInt8 b = (UInt8)((c >> 0)&31);
+
+ *dest = ColorRGBA32 (r<<3|r>>2, g<<2|g>>4, b<<3|b>>2, 255);
+ pixel += 2;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+bool SetImagePixelBlock (UInt8* data, int dataWidth, int dataHeight, TextureFormat format, int x, int y, int blockWidth, int blockHeight, int pixelCount, const ColorRGBAf* pixels)
+{
+ if (IsAnyCompressedTextureFormat(format))
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ return false;
+ }
+ if (blockWidth <= 0 || blockHeight <= 0)
+ {
+ ErrorString ("Width and height must be positive");
+ return false;
+ }
+
+ int tmp = blockWidth * blockHeight;
+ if (blockHeight != tmp / blockWidth || blockWidth * blockHeight > pixelCount ) // check for overflow as well
+ {
+ ErrorString ("Array size must be at least width*height");
+ return false;
+ }
+
+ if (x < 0 || y < 0 || x + blockWidth < 0 || y + blockHeight < 0 || x + blockWidth > dataWidth || y + blockHeight > dataHeight)
+ {
+ ErrorString ("Texture rectangle is out of bounds");
+ return false;
+ }
+
+ ImageReference image (dataWidth, dataHeight, GetRowBytesFromWidthAndFormat(dataWidth, format), format, data);
+
+ if (format == kTexFormatARGB32)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[1] = NormalizedToByte( pixels->r);
+ pixel[2] = NormalizedToByte( pixels->g);
+ pixel[3] = NormalizedToByte( pixels->b);
+ pixel[0] = NormalizedToByte( pixels->a);
+ pixel += 4;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[0] = NormalizedToByte( pixels->r);
+ pixel[1] = NormalizedToByte( pixels->g);
+ pixel[2] = NormalizedToByte( pixels->b);
+ pixel[3] = NormalizedToByte( pixels->a);
+ pixel += 4;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x * 3;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[0] = NormalizedToByte(pixels->r);
+ pixel[1] = NormalizedToByte(pixels->g);
+ pixel[2] = NormalizedToByte(pixels->b);
+ pixel += 3;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[0] = RoundfToIntPos( clamp01(pixels->a) * 255.0f );
+ pixel += 1;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB565)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt16* pixel = (UInt16 *)pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ UInt16 r = (UInt16)(RoundfToIntPos(clamp01(pixels->r) * 31.0f));
+ UInt16 g = (UInt16)(RoundfToIntPos(clamp01(pixels->g) * 63.0f));
+ UInt16 b = (UInt16)(RoundfToIntPos(clamp01(pixels->b) * 31.0f));
+ pixel[0] = r<<11 | g<<5 | b;
+ pixel += 1;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ return false;
+ }
+
+ return true;
+}
+
+
+
+// --------------------------------------------------------------------------
+
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+
+SUITE (ImageOpsTests)
+{
+
+TEST (RepeatInt)
+{
+ // valdemar: seems like a CW compiler bug, error: (10139) division by 0
+#if !UNITY_WII
+ // handle zero width
+ CHECK_EQUAL (0, RepeatInt (7,0));
+#endif
+ // handle positive args
+ CHECK_EQUAL (7, RepeatInt (7,13));
+ CHECK_EQUAL (0, RepeatInt (13,13));
+ CHECK_EQUAL (1, RepeatInt (170,13));
+ // handle negative args
+ CHECK_EQUAL (12, RepeatInt (-1,13));
+ CHECK_EQUAL (0, RepeatInt (-13,13));
+}
+
+TEST (TextureWrap)
+{
+ // valdemar: seems like a CW compiler bug, error: (10139) division by 0
+#if !UNITY_WII
+ // handle zero width
+ CHECK_EQUAL (0, TextureWrap (7,0,kTexWrapClamp));
+ CHECK_EQUAL (0, TextureWrap (7,0,kTexWrapRepeat));
+#endif
+ // repeat mode
+ CHECK_EQUAL (7, TextureWrap (7,13,kTexWrapRepeat));
+ CHECK_EQUAL (1, TextureWrap (170,13,kTexWrapRepeat));
+ CHECK_EQUAL (12, TextureWrap (-1,13,kTexWrapRepeat));
+ // clamp mode
+ CHECK_EQUAL (7, TextureWrap (7,13,kTexWrapClamp));
+ CHECK_EQUAL (0, TextureWrap (-1,13,kTexWrapClamp));
+ CHECK_EQUAL (12, TextureWrap (13,13,kTexWrapClamp));
+}
+
+TEST (SetGetImagePixelARGB)
+{
+ UInt8 data[4][4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 8, kTexFormatARGB32, data);
+ SetImagePixel (image, 0, 0, kTexWrapRepeat, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0]
+ CHECK (data[0][1]==255 && data[0][2]==128 && data[0][3]==77 && data[0][0]==51);
+ SetImagePixel (image, 3, 8, kTexWrapRepeat, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1] due to repeat
+ CHECK (data[1][1]==26 && data[1][2]==51 && data[1][3]==77 && data[1][0]==102);
+ SetImagePixel (image, -3, 1, kTexWrapClamp, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2] due to clamp
+ CHECK (data[2][1]==77 && data[2][2]==102 && data[2][3]==128 && data[2][0]==153);
+
+ CHECK (data[3][1]==13 && data[3][2]==13 && data[3][3]==13 && data[3][0]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.5f,0.3f,0.2f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.1f,0.2f,0.3f,0.4f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.3f,0.4f,0.5f,0.6f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelRGBA)
+{
+ UInt8 data[4][4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 8, kTexFormatRGBA32, data);
+ SetImagePixel (image, 0, 0, kTexWrapRepeat, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0]
+ CHECK (data[0][0]==255 && data[0][1]==128 && data[0][2]==77 && data[0][3]==51);
+ SetImagePixel (image, 3, 8, kTexWrapRepeat, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1] due to repeat
+ CHECK (data[1][0]==26 && data[1][1]==51 && data[1][2]==77 && data[1][3]==102);
+ SetImagePixel (image, -3, 1, kTexWrapClamp, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2] due to clamp
+ CHECK (data[2][0]==77 && data[2][1]==102 && data[2][2]==128 && data[2][3]==153);
+
+ CHECK (data[3][1]==13 && data[3][2]==13 && data[3][3]==13 && data[3][0]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.5f,0.3f,0.2f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.1f,0.2f,0.3f,0.4f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.3f,0.4f,0.5f,0.6f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelRGB)
+{
+ UInt8 data[4][3];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 6, kTexFormatRGB24, data);
+ SetImagePixel (image, 0, 0, kTexWrapClamp, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0]
+ CHECK (data[0][0]==255 && data[0][1]==128 && data[0][2]==77);
+ SetImagePixel (image, 1, 0, kTexWrapClamp, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1]
+ CHECK (data[1][0]==26 && data[1][1]==51 && data[1][2]==77);
+ SetImagePixel (image, 0, 1, kTexWrapClamp, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2]
+ CHECK (data[2][0]==77 && data[2][1]==102 && data[2][2]==128);
+
+ CHECK (data[3][0]==13 && data[3][1]==13 && data[3][2]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.5f,0.3f,1)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.1f,0.2f,0.3f,1)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.3f,0.4f,0.5f,1)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelAlpha)
+{
+ UInt8 data[4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 2, kTexFormatAlpha8, data);
+ SetImagePixel (image, -3, -2, kTexWrapClamp, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0] due to clamp
+ CHECK (data[0]==51);
+ SetImagePixel (image, 1, -4, kTexWrapRepeat, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1] due to repeat
+ CHECK (data[1]==102);
+ SetImagePixel (image, -4, 7, kTexWrapRepeat, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2] due to repeat
+ CHECK (data[2]==153);
+
+ CHECK (data[3]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1,1,1,0.2f)) == GetImagePixel (&data[0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(1,1,1,0.4f)) == GetImagePixel (&data[0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(1,1,1,0.6f)) == GetImagePixel (&data[0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelRGB565)
+{
+ UInt16 data[4];
+ memset (data, 0xab, sizeof(data));
+ ImageReference image (2, 2, 4, kTexFormatRGB565, data);
+ SetImagePixel (image, 0, 0, kTexWrapClamp, ColorRGBAf(1.0f,0.0f,0.0f,0.2f)); // sets [0]
+ CHECK (data[0]==0xf800);
+ SetImagePixel (image, 1, 0, kTexWrapClamp, ColorRGBAf(0.0f,1.0f,0.0f,0.4f)); // sets [1]
+ CHECK (data[1]==0x07e0);
+ SetImagePixel (image, 0, 1, kTexWrapClamp, ColorRGBAf(0.0f,0.0f,1.0f,0.6f)); // sets [2]
+ CHECK (data[2]==0x001f);
+ CHECK (data[3]==0xabab); // [3] still left untouched
+
+ ColorRGBAf gray(14.0f/31.0f, 31.0f/63.0f, 16.0f/31.0f, 1.0f);
+ SetImagePixel (image, 1, 1, kTexWrapClamp, gray); // sets [3]
+ CHECK (data[3]==0x73f0);
+
+ UInt8* srcData = reinterpret_cast<UInt8*>(&data[0]);
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.0f,0.0f,1)) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.0f,1.0f,0.0f,1)) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.0f,0.0f,1.0f,1)) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+ CHECK(ColorRGBA32(gray) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapClamp, 2, 2)); // gets [3] due to clamp
+}
+
+TEST (SetImagePixelBlockARGB)
+{
+ UInt8 data[16][16][4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (16, 16, 16*4, kTexFormatARGB32, data);
+
+ ColorRGBAf color (1,0,1,0);
+ SetImagePixelBlock (&data[0][0][0], 16, 16, kTexFormatARGB32, 15,15,1,1, 1, &color);
+ CHECK (data[15][15][1]==255 && data[15][15][2]==0 && data[15][15][3]==255 && data[15][15][0]==0);
+}
+
+#if UNITY_EDITOR
+TEST (BlitBilinearARGBToFloat)
+{
+ Image src (128, 256, kTexFormatARGB32);
+ Image dst (16, 16, kTexFormatARGB32);
+ // ProphecySDK's bilinear blitter from integer source to floating point destination
+ // when sizes are different allocates temporary intermediate image, and blit there
+ // can result in access violation for the last pixel. This unit test would crash then, especially
+ // on Macs. ProphecySDK modified to allocate one pixel more for temporary images, just like
+ // we do for ours.
+ dst.ReformatImage (src, 128, 128, kTexFormatARGBFloat, Image::BLIT_BILINEAR_SCALE);
+}
+#endif
+
+TEST (CreateMipMap2x2)
+{
+ ColorRGBA32 data[4+1+1]; // 2x2, 1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ CreateMipMap ((UInt8*)data, 2, 2, 1, kTexFormatARGB32);
+
+ // next mip level of 1x1 size
+ CHECK(ColorRGBA32(255,191,127,63) == data[4]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[5]);
+}
+TEST (CreateMipMap4x1)
+{
+ ColorRGBA32 data[4+2+1+1]; // 4x1, 2x1, 1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ CreateMipMap ((UInt8*)data, 4, 1, 1, kTexFormatARGB32);
+
+ // next mip level of 2x1 size
+ CHECK(ColorRGBA32(255,255,255,127) == data[4]);
+ CHECK(ColorRGBA32(255,127,0,0) == data[5]);
+
+ // next mip level of 1x1 size
+ CHECK(ColorRGBA32(255,191,127,63) == data[6]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[7]);
+}
+TEST (CreateMipMap4x1x2)
+{
+ ColorRGBA32 data[8+2+1+1]; // 4x1x2, 2x1x1, 1x1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ data[4] = ColorRGBA32(128,128,128,128);
+ data[5] = ColorRGBA32(128,128,128,0);
+ data[6] = ColorRGBA32(128,128,0,0);
+ data[7] = ColorRGBA32(128,0,0,0);
+ CreateMipMap ((UInt8*)data, 4, 1, 2, kTexFormatARGB32);
+
+ // next mip level, 2x1x1 size
+ CHECK(ColorRGBA32(191,191,191,95) == data[8]);
+ CHECK(ColorRGBA32(191,95,0,0) == data[9]);
+
+ // next mip level, 1x1x1 size
+ CHECK(ColorRGBA32(191,143,95,47) == data[10]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[11]);
+}
+TEST (CreateMipMap4x1x3)
+{
+ ColorRGBA32 data[12+2+1+1]; // 4x1x2, 2x1x1, 1x1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ data[4] = ColorRGBA32(128,128,128,128);
+ data[5] = ColorRGBA32(128,128,128,0);
+ data[6] = ColorRGBA32(128,128,0,0);
+ data[7] = ColorRGBA32(128,0,0,0);
+ data[8] = ColorRGBA32(64,64,64,64);
+ data[9] = ColorRGBA32(64,64,64,0);
+ data[10] = ColorRGBA32(64,64,0,0);
+ data[11] = ColorRGBA32(64,0,0,0);
+ CreateMipMap ((UInt8*)data, 4, 1, 3, kTexFormatARGB32);
+
+ // next mip level, 2x1x1 size
+ CHECK(ColorRGBA32(191,191,191,95) == data[12]);
+ CHECK(ColorRGBA32(191,95,0,0) == data[13]);
+
+ // next mip level, 1x1x1 size
+ CHECK(ColorRGBA32(191,143,95,47) == data[14]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[15]);
+}
+
+
+} // SUITE
+
+#endif // ENABLE_UNIT_TESTS