//------------------------------------------------------------------------------------- // DirectXTexMisc.cpp // // DirectX Texture Library - Misc image operations // // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248926 //------------------------------------------------------------------------------------- #include "DirectXTexP.h" using namespace DirectX; namespace { const XMVECTORF32 g_Gamma22 = { { { 2.2f, 2.2f, 2.2f, 1.f } } }; //------------------------------------------------------------------------------------- HRESULT ComputeMSE_( const Image& image1, const Image& image2, float& mse, _Out_writes_opt_(4) float* mseV, DWORD flags) { if (!image1.pixels || !image2.pixels) return E_POINTER; assert(image1.width == image2.width && image1.height == image2.height); assert(!IsCompressed(image1.format) && !IsCompressed(image2.format)); const size_t width = image1.width; ScopedAlignedArrayXMVECTOR scanline(static_cast(_aligned_malloc((sizeof(XMVECTOR)*width) * 2, 16))); if (!scanline) return E_OUTOFMEMORY; // Flags implied from image formats switch (image1.format) { case DXGI_FORMAT_B8G8R8X8_UNORM: flags |= CMSE_IGNORE_ALPHA; break; case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: flags |= CMSE_IMAGE1_SRGB | CMSE_IGNORE_ALPHA; break; case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: case DXGI_FORMAT_BC1_UNORM_SRGB: case DXGI_FORMAT_BC2_UNORM_SRGB: case DXGI_FORMAT_BC3_UNORM_SRGB: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: case DXGI_FORMAT_BC7_UNORM_SRGB: flags |= CMSE_IMAGE1_SRGB; break; default: break; } switch (image2.format) { case DXGI_FORMAT_B8G8R8X8_UNORM: flags |= CMSE_IGNORE_ALPHA; break; case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: flags |= CMSE_IMAGE2_SRGB | CMSE_IGNORE_ALPHA; break; case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: case DXGI_FORMAT_BC1_UNORM_SRGB: case DXGI_FORMAT_BC2_UNORM_SRGB: case DXGI_FORMAT_BC3_UNORM_SRGB: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: case DXGI_FORMAT_BC7_UNORM_SRGB: flags |= CMSE_IMAGE2_SRGB; break; default: break; } const uint8_t *pSrc1 = image1.pixels; const size_t rowPitch1 = image1.rowPitch; const uint8_t *pSrc2 = image2.pixels; const size_t rowPitch2 = image2.rowPitch; XMVECTOR acc = g_XMZero; static XMVECTORF32 two = { { { 2.0f, 2.0f, 2.0f, 2.0f } } }; for (size_t h = 0; h < image1.height; ++h) { XMVECTOR* ptr1 = scanline.get(); if (!_LoadScanline(ptr1, width, pSrc1, rowPitch1, image1.format)) return E_FAIL; XMVECTOR* ptr2 = scanline.get() + width; if (!_LoadScanline(ptr2, width, pSrc2, rowPitch2, image2.format)) return E_FAIL; for (size_t i = 0; i < width; ++i) { XMVECTOR v1 = *(ptr1++); if (flags & CMSE_IMAGE1_SRGB) { v1 = XMVectorPow(v1, g_Gamma22); } if (flags & CMSE_IMAGE1_X2_BIAS) { v1 = XMVectorMultiplyAdd(v1, two, g_XMNegativeOne); } XMVECTOR v2 = *(ptr2++); if (flags & CMSE_IMAGE2_SRGB) { v2 = XMVectorPow(v2, g_Gamma22); } if (flags & CMSE_IMAGE2_X2_BIAS) { v2 = XMVectorMultiplyAdd(v2, two, g_XMNegativeOne); } // sum[ (I1 - I2)^2 ] XMVECTOR v = XMVectorSubtract(v1, v2); if (flags & CMSE_IGNORE_RED) { v = XMVectorSelect(v, g_XMZero, g_XMMaskX); } if (flags & CMSE_IGNORE_GREEN) { v = XMVectorSelect(v, g_XMZero, g_XMMaskY); } if (flags & CMSE_IGNORE_BLUE) { v = XMVectorSelect(v, g_XMZero, g_XMMaskZ); } if (flags & CMSE_IGNORE_ALPHA) { v = XMVectorSelect(v, g_XMZero, g_XMMaskW); } acc = XMVectorMultiplyAdd(v, v, acc); } pSrc1 += rowPitch1; pSrc2 += rowPitch2; } // MSE = sum[ (I1 - I2)^2 ] / w*h XMVECTOR d = XMVectorReplicate(float(image1.width * image1.height)); XMVECTOR v = XMVectorDivide(acc, d); if (mseV) { XMStoreFloat4(reinterpret_cast(mseV), v); mse = mseV[0] + mseV[1] + mseV[2] + mseV[3]; } else { XMFLOAT4 _mseV; XMStoreFloat4(&_mseV, v); mse = _mseV.x + _mseV.y + _mseV.z + _mseV.w; } return S_OK; } //------------------------------------------------------------------------------------- HRESULT EvaluateImage_( const Image& image, std::function& pixelFunc) { if (!pixelFunc) return E_INVALIDARG; if (!image.pixels) return E_POINTER; assert(!IsCompressed(image.format)); const size_t width = image.width; ScopedAlignedArrayXMVECTOR scanline(static_cast(_aligned_malloc((sizeof(XMVECTOR)*width), 16))); if (!scanline) return E_OUTOFMEMORY; const uint8_t *pSrc = image.pixels; const size_t rowPitch = image.rowPitch; for (size_t h = 0; h < image.height; ++h) { if (!_LoadScanline(scanline.get(), width, pSrc, rowPitch, image.format)) return E_FAIL; pixelFunc(scanline.get(), width, h); pSrc += rowPitch; } return S_OK; } //------------------------------------------------------------------------------------- HRESULT TransformImage_( const Image& srcImage, std::function& pixelFunc, const Image& destImage) { if (!pixelFunc) return E_INVALIDARG; if (!srcImage.pixels || !destImage.pixels) return E_POINTER; if (srcImage.width != destImage.width || srcImage.height != destImage.height || srcImage.format != destImage.format) return E_FAIL; const size_t width = srcImage.width; ScopedAlignedArrayXMVECTOR scanlines(static_cast(_aligned_malloc((sizeof(XMVECTOR)*width*2), 16))); if (!scanlines) return E_OUTOFMEMORY; XMVECTOR* sScanline = scanlines.get(); XMVECTOR* dScanline = scanlines.get() + width; const uint8_t *pSrc = srcImage.pixels; const size_t spitch = srcImage.rowPitch; uint8_t *pDest = destImage.pixels; const size_t dpitch = destImage.rowPitch; for (size_t h = 0; h < srcImage.height; ++h) { if (!_LoadScanline(sScanline, width, pSrc, spitch, srcImage.format)) return E_FAIL; #ifdef _DEBUG memset(dScanline, 0xCD, sizeof(XMVECTOR)*width); #endif pixelFunc(dScanline, sScanline, width, h); if (!_StoreScanline(pDest, destImage.rowPitch, destImage.format, dScanline, width)) return E_FAIL; pSrc += spitch; pDest += dpitch; } return S_OK; } }; //===================================================================================== // Entry points //===================================================================================== //------------------------------------------------------------------------------------- // Copies a rectangle from one image into another //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::CopyRectangle( const Image& srcImage, const Rect& srcRect, const Image& dstImage, DWORD filter, size_t xOffset, size_t yOffset) { if (!srcImage.pixels || !dstImage.pixels) return E_POINTER; if (IsCompressed(srcImage.format) || IsCompressed(dstImage.format) || IsPlanar(srcImage.format) || IsPlanar(dstImage.format) || IsPalettized(srcImage.format) || IsPalettized(dstImage.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); // Validate rectangle/offset if (!srcRect.w || !srcRect.h || ((srcRect.x + srcRect.w) > srcImage.width) || ((srcRect.y + srcRect.h) > srcImage.height)) { return E_INVALIDARG; } if (((xOffset + srcRect.w) > dstImage.width) || ((yOffset + srcRect.h) > dstImage.height)) { return E_INVALIDARG; } // Compute source bytes-per-pixel size_t sbpp = BitsPerPixel(srcImage.format); if (!sbpp) return E_FAIL; if (sbpp < 8) { // We don't support monochrome (DXGI_FORMAT_R1_UNORM) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); } const uint8_t* pEndSrc = srcImage.pixels + srcImage.rowPitch*srcImage.height; const uint8_t* pEndDest = dstImage.pixels + dstImage.rowPitch*dstImage.height; // Round to bytes sbpp = (sbpp + 7) / 8; const uint8_t* pSrc = srcImage.pixels + (srcRect.y * srcImage.rowPitch) + (srcRect.x * sbpp); if (srcImage.format == dstImage.format) { // Direct copy case (avoid intermediate conversions) uint8_t* pDest = dstImage.pixels + (yOffset * dstImage.rowPitch) + (xOffset * sbpp); const size_t copyW = srcRect.w * sbpp; for (size_t h = 0; h < srcRect.h; ++h) { if (((pSrc + copyW) > pEndSrc) || (pDest > pEndDest)) return E_FAIL; memcpy_s(pDest, size_t(pEndDest - pDest), pSrc, copyW); pSrc += srcImage.rowPitch; pDest += dstImage.rowPitch; } return S_OK; } // Compute destination bytes-per-pixel (not the same format as source) size_t dbpp = BitsPerPixel(dstImage.format); if (!dbpp) return E_FAIL; if (dbpp < 8) { // We don't support monochrome (DXGI_FORMAT_R1_UNORM) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); } // Round to bytes dbpp = (dbpp + 7) / 8; uint8_t* pDest = dstImage.pixels + (yOffset * dstImage.rowPitch) + (xOffset * dbpp); ScopedAlignedArrayXMVECTOR scanline(static_cast(_aligned_malloc((sizeof(XMVECTOR)*srcRect.w), 16))); if (!scanline) return E_OUTOFMEMORY; const size_t copyS = srcRect.w * sbpp; const size_t copyD = srcRect.w * dbpp; for (size_t h = 0; h < srcRect.h; ++h) { if (((pSrc + copyS) > pEndSrc) || ((pDest + copyD) > pEndDest)) return E_FAIL; if (!_LoadScanline(scanline.get(), srcRect.w, pSrc, copyS, srcImage.format)) return E_FAIL; _ConvertScanline(scanline.get(), srcRect.w, dstImage.format, srcImage.format, filter); if (!_StoreScanline(pDest, copyD, dstImage.format, scanline.get(), srcRect.w)) return E_FAIL; pSrc += srcImage.rowPitch; pDest += dstImage.rowPitch; } return S_OK; } //------------------------------------------------------------------------------------- // Computes the Mean-Squared-Error (MSE) between two images //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::ComputeMSE( const Image& image1, const Image& image2, float& mse, float* mseV, DWORD flags) { if (!image1.pixels || !image2.pixels) return E_POINTER; if (image1.width != image2.width || image1.height != image2.height) return E_INVALIDARG; if (!IsValid(image1.format) || !IsValid(image2.format)) return E_INVALIDARG; if (IsPlanar(image1.format) || IsPlanar(image2.format) || IsPalettized(image1.format) || IsPalettized(image2.format) || IsTypeless(image1.format) || IsTypeless(image2.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (IsCompressed(image1.format)) { if (IsCompressed(image2.format)) { // Case 1: both images are compressed, expand to RGBA32F ScratchImage temp1; HRESULT hr = Decompress(image1, DXGI_FORMAT_R32G32B32A32_FLOAT, temp1); if (FAILED(hr)) return hr; ScratchImage temp2; hr = Decompress(image2, DXGI_FORMAT_R32G32B32A32_FLOAT, temp2); if (FAILED(hr)) return hr; const Image* img1 = temp1.GetImage(0, 0, 0); const Image* img2 = temp2.GetImage(0, 0, 0); if (!img1 || !img2) return E_POINTER; return ComputeMSE_(*img1, *img2, mse, mseV, flags); } else { // Case 2: image1 is compressed, expand to RGBA32F ScratchImage temp; HRESULT hr = Decompress(image1, DXGI_FORMAT_R32G32B32A32_FLOAT, temp); if (FAILED(hr)) return hr; const Image* img = temp.GetImage(0, 0, 0); if (!img) return E_POINTER; return ComputeMSE_(*img, image2, mse, mseV, flags); } } else { if (IsCompressed(image2.format)) { // Case 3: image2 is compressed, expand to RGBA32F ScratchImage temp; HRESULT hr = Decompress(image2, DXGI_FORMAT_R32G32B32A32_FLOAT, temp); if (FAILED(hr)) return hr; const Image* img = temp.GetImage(0, 0, 0); if (!img) return E_POINTER; return ComputeMSE_(image1, *img, mse, mseV, flags); } else { // Case 4: neither image is compressed return ComputeMSE_(image1, image2, mse, mseV, flags); } } } //------------------------------------------------------------------------------------- // Evaluates a user-supplied function for all the pixels in the image //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::EvaluateImage( const Image& image, std::function pixelFunc) { if (image.width > UINT32_MAX || image.height > UINT32_MAX) return E_INVALIDARG; if (!IsValid(image.format)) return E_INVALIDARG; if (IsPlanar(image.format) || IsPalettized(image.format) || IsTypeless(image.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (IsCompressed(image.format)) { ScratchImage temp; HRESULT hr = Decompress(image, DXGI_FORMAT_R32G32B32A32_FLOAT, temp); if (FAILED(hr)) return hr; const Image* img = temp.GetImage(0, 0, 0); if (!img) return E_POINTER; return EvaluateImage_(*img, pixelFunc); } else { return EvaluateImage_(image, pixelFunc); } } _Use_decl_annotations_ HRESULT DirectX::EvaluateImage( const Image* images, size_t nimages, const TexMetadata& metadata, std::function pixelFunc) { if (!images || !nimages) return E_INVALIDARG; if (!IsValid(metadata.format)) return E_INVALIDARG; if (IsPlanar(metadata.format) || IsPalettized(metadata.format) || IsTypeless(metadata.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (metadata.width > UINT32_MAX || metadata.height > UINT32_MAX) return E_INVALIDARG; if (metadata.IsVolumemap() && metadata.depth > UINT16_MAX) return E_INVALIDARG; ScratchImage temp; DXGI_FORMAT format = metadata.format; if (IsCompressed(format)) { HRESULT hr = Decompress(images, nimages, metadata, DXGI_FORMAT_R32G32B32A32_FLOAT, temp); if (FAILED(hr)) return hr; if (nimages != temp.GetImageCount()) return E_UNEXPECTED; images = temp.GetImages(); format = DXGI_FORMAT_R32G32B32A32_FLOAT; } switch (metadata.dimension) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: for (size_t index = 0; index < nimages; ++index) { const Image& img = images[index]; if (img.format != format) return E_FAIL; if ((img.width > UINT32_MAX) || (img.height > UINT32_MAX)) return E_FAIL; HRESULT hr = EvaluateImage_(img, pixelFunc); if (FAILED(hr)) return hr; } break; case TEX_DIMENSION_TEXTURE3D: { size_t index = 0; size_t d = metadata.depth; for (size_t level = 0; level < metadata.mipLevels; ++level) { for (size_t slice = 0; slice < d; ++slice, ++index) { if (index >= nimages) return E_FAIL; const Image& img = images[index]; if (img.format != format) return E_FAIL; if ((img.width > UINT32_MAX) || (img.height > UINT32_MAX)) return E_FAIL; HRESULT hr = EvaluateImage_(img, pixelFunc); if (FAILED(hr)) return hr; } if (d > 1) d >>= 1; } } break; default: return E_FAIL; } return S_OK; } //------------------------------------------------------------------------------------- // Use a user-supplied function to compute a new image from an input image //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::TransformImage( const Image& image, std::function pixelFunc, ScratchImage& result) { if (image.width > UINT32_MAX || image.height > UINT32_MAX) return E_INVALIDARG; if (IsPlanar(image.format) || IsPalettized(image.format) || IsCompressed(image.format) || IsTypeless(image.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); HRESULT hr = result.Initialize2D(image.format, image.width, image.height, 1, 1); if (FAILED(hr)) return hr; const Image* dimg = result.GetImage(0, 0, 0); if (!dimg) { result.Release(); return E_POINTER; } hr = TransformImage_(image, pixelFunc, *dimg); if (FAILED(hr)) { result.Release(); return hr; } return S_OK; } _Use_decl_annotations_ HRESULT DirectX::TransformImage( const Image* srcImages, size_t nimages, const TexMetadata& metadata, std::function pixelFunc, ScratchImage& result) { if (!srcImages || !nimages) return E_INVALIDARG; if (IsPlanar(metadata.format) || IsPalettized(metadata.format) || IsCompressed(metadata.format) || IsTypeless(metadata.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (metadata.width > UINT32_MAX || metadata.height > UINT32_MAX) return E_INVALIDARG; if (metadata.IsVolumemap() && metadata.depth > UINT16_MAX) return E_INVALIDARG; HRESULT hr = result.Initialize(metadata); if (FAILED(hr)) return hr; if (nimages != result.GetImageCount()) { result.Release(); return E_FAIL; } const Image* dest = result.GetImages(); if (!dest) { result.Release(); return E_POINTER; } switch (metadata.dimension) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: for (size_t index = 0; index < nimages; ++index) { const Image& src = srcImages[index]; if (src.format != metadata.format) { result.Release(); return E_FAIL; } if ((src.width > UINT32_MAX) || (src.height > UINT32_MAX)) { result.Release(); return E_FAIL; } const Image& dst = dest[index]; if (src.width != dst.width || src.height != dst.height) { result.Release(); return E_FAIL; } hr = TransformImage_(src, pixelFunc, dst); if (FAILED(hr)) { result.Release(); return hr; } } break; case TEX_DIMENSION_TEXTURE3D: { size_t index = 0; size_t d = metadata.depth; for (size_t level = 0; level < metadata.mipLevels; ++level) { for (size_t slice = 0; slice < d; ++slice, ++index) { if (index >= nimages) { result.Release(); return E_FAIL; } const Image& src = srcImages[index]; if (src.format != metadata.format) { result.Release(); return E_FAIL; } if ((src.width > UINT32_MAX) || (src.height > UINT32_MAX)) { result.Release(); return E_FAIL; } const Image& dst = dest[index]; if (src.width != dst.width || src.height != dst.height) { result.Release(); return E_FAIL; } hr = TransformImage_(src, pixelFunc, dst); if (FAILED(hr)) { result.Release(); return hr; } } if (d > 1) d >>= 1; } } break; default: result.Release(); return E_FAIL; } return S_OK; }