diff --git a/BPTCEncoder/CMakeLists.txt b/BPTCEncoder/CMakeLists.txt index 03cda9b..e841b89 100644 --- a/BPTCEncoder/CMakeLists.txt +++ b/BPTCEncoder/CMakeLists.txt @@ -150,4 +150,4 @@ ADD_LIBRARY( BPTCEncoder ${SOURCES} ) -TARGET_LINK_LIBRARIES( BPTCEncoder FasTCBase ) \ No newline at end of file +TARGET_LINK_LIBRARIES( BPTCEncoder FasTCBase ) diff --git a/Base/include/Image.h b/Base/include/Image.h index 74a42e5..07cfe27 100644 --- a/Base/include/Image.h +++ b/Base/include/Image.h @@ -49,17 +49,29 @@ class Image { public: - Image(uint32 width, uint32 height, const uint32 *pixels); + Image(uint32 width, uint32 height, + const uint32 *pixels, + bool bBlockStreamOrder = false); Image(const Image &); Image &operator=(const Image &); virtual ~Image(); - + + virtual Image *Clone() const { + return new Image(*this); + }; + const uint8 *RawData() const { return m_Data; } uint32 GetWidth() const { return m_Width; } uint32 GetHeight() const { return m_Height; } - void SetBlockStreamOrder(bool flag) { m_bBlockStreamOrder = flag; } + void SetBlockStreamOrder(bool flag) { + if(flag) { + ConvertToBlockStreamOrder(); + } else { + ConvertFromBlockStreamOrder(); + } + } bool GetBlockStreamOrder() const { return m_bBlockStreamOrder; } double ComputePSNR(Image *other); @@ -77,6 +89,9 @@ class Image { protected: uint8 *m_Data; + + void ConvertToBlockStreamOrder(); + void ConvertFromBlockStreamOrder(); }; #endif // __TEXCOMP_IMAGE_H__ diff --git a/Base/src/Image.cpp b/Base/src/Image.cpp index c082c77..4e75758 100644 --- a/Base/src/Image.cpp +++ b/Base/src/Image.cpp @@ -67,10 +67,10 @@ Image::Image(const Image &other) } } -Image::Image(uint32 width, uint32 height, const uint32 *pixels) +Image::Image(uint32 width, uint32 height, const uint32 *pixels, bool bBlockStreamOrder) : m_Width(width) , m_Height(height) - , m_bBlockStreamOrder(false) + , m_bBlockStreamOrder(bBlockStreamOrder) { if(pixels) { m_Data = new uint8[4 * m_Width * m_Height]; @@ -80,6 +80,12 @@ Image::Image(uint32 width, uint32 height, const uint32 *pixels) } } +Image::~Image() { + if(m_Data) { + delete [] m_Data; + m_Data = 0; + } +} Image &Image::operator=(const Image &other) { @@ -105,13 +111,6 @@ Image &Image::operator=(const Image &other) { return *this; } -Image::~Image() { - if(m_Data) { - delete [] m_Data; - m_Data = 0; - } -} - double Image::ComputePSNR(Image *other) { if(!other) return -1.0; @@ -130,42 +129,88 @@ double Image::ComputePSNR(Image *other) { const uint8 *otherData = reinterpret_cast(other->GetRGBA()); - const double wr = 1.0; - const double wg = 1.0; - const double wb = 1.0; + // const double w[3] = { 0.2126, 0.7152, 0.0722 }; + const double w[3] = { 1.0, 1.0, 1.0 }; - double MSE = 0.0; + double mse = 0.0; const uint32 imageSz = GetWidth() * GetHeight() * 4; for(uint32 i = 0; i < imageSz; i+=4) { - const unsigned char *ourPixel = ourData + i; - const unsigned char *otherPixel = otherData + i; + const unsigned char *pixelDataRaw = ourData + i; + const unsigned char *pixelDataUncomp = otherData + i; - double ourAlphaScale = double(ourPixel[3]) / 255.0; - double otherAlphaScale = double(otherPixel[3]) / 255.0; - double dr = double(sad(ourAlphaScale * ourPixel[0], - otherAlphaScale * otherPixel[0])) * wr; - double dg = double(sad(ourAlphaScale * ourPixel[1], - otherAlphaScale * otherPixel[1])) * wg; - double db = double(sad(ourAlphaScale * ourPixel[2], - otherAlphaScale * otherPixel[2])) * wb; - - const double pixelMSE = - (double(dr) * double(dr)) + - (double(dg) * double(dg)) + - (double(db) * double(db)); - - //fprintf(stderr, "Pixel MSE: %f\n", pixelMSE); - MSE += pixelMSE; + float r[4], u[4]; + for(uint32 c = 0; c < 4; c++) { + if(c == 3) { + r[c] = pixelDataRaw[c] / 255.0; + u[c] = pixelDataUncomp[c] / 255.0; + } else { + r[c] = static_cast(pixelDataRaw[c]) * w[c]; + u[c] = static_cast(pixelDataUncomp[c]) * w[c]; + } + } + + for(uint32 c = 0; c < 3; c++) { + double diff = (r[3] * r[c] - u[3] * u[c]); + mse += diff * diff; + } } - MSE /= (double(GetWidth()) * double(GetHeight())); + mse /= GetWidth() * GetHeight(); - double MAXI = - (255.0 * wr) * (255.0 * wr) + - (255.0 * wg) * (255.0 * wg) + - (255.0 * wb) * (255.0 * wb); - - double PSNR = 10 * log10(MAXI/MSE); - return PSNR; + const double C = 255.0 * 255.0; + double maxi = (w[0]*w[0] + w[1]*w[1] + w[2]*w[2]) * C; + return 10 * log10(maxi/mse); +} + +void Image::ConvertToBlockStreamOrder() { + if(m_bBlockStreamOrder || !m_Data) + return; + + uint32 *newPixelData = new uint32[GetWidth() * GetHeight() * 4]; + for(uint32 j = 0; j < GetHeight(); j+=4) { + for(uint32 i = 0; i < GetWidth(); i+=4) { + uint32 blockX = i / 4; + uint32 blockY = j / 4; + uint32 blockIdx = blockY * (GetWidth() / 4) + blockX; + + uint32 offset = blockIdx * 4 * 4; + for(uint32 t = 0; t < 16; t++) { + uint32 x = i + t % 4; + uint32 y = j + t / 4; + newPixelData[offset + t] = + reinterpret_cast(m_Data)[y*GetWidth() + x]; + } + } + } + + delete m_Data; + m_Data = reinterpret_cast(newPixelData); + m_bBlockStreamOrder = true; +} + +void Image::ConvertFromBlockStreamOrder() { + if(!m_bBlockStreamOrder || !m_Data) + return; + + uint32 *newPixelData = new uint32[GetWidth() * GetHeight() * 4]; + for(uint32 j = 0; j < GetHeight(); j+=4) { + for(uint32 i = 0; i < GetWidth(); i+=4) { + uint32 blockX = i / 4; + uint32 blockY = j / 4; + uint32 blockIdx = blockY * (GetWidth() / 4) + blockX; + + uint32 offset = blockIdx * 4 * 4; + for(uint32 t = 0; t < 16; t++) { + uint32 x = i + t % 4; + uint32 y = j + t / 4; + newPixelData[y*GetWidth() + x] = + reinterpret_cast(m_Data)[offset + t]; + } + } + } + + delete m_Data; + m_Data = reinterpret_cast(newPixelData); + m_bBlockStreamOrder = false; } diff --git a/CLTool/src/clunix.cpp b/CLTool/src/clunix.cpp index 2ed560f..e9593e8 100644 --- a/CLTool/src/clunix.cpp +++ b/CLTool/src/clunix.cpp @@ -53,6 +53,7 @@ void PrintUsage() { fprintf(stderr, "Usage: tc [OPTIONS] imagefile\n"); fprintf(stderr, "\n"); + fprintf(stderr, "\t-f\t\tFormat to use. Either \"BPTC\" or \"PVRTC\". Default: BPTC\n"); fprintf(stderr, "\t-l\t\tSave an output log.\n"); fprintf(stderr, "\t-q \tSet compression quality level. Default: 50\n"); fprintf(stderr, "\t-n \tCompress the image num times and give the average time and PSNR. Default: 1\n"); @@ -92,7 +93,8 @@ int main(int argc, char **argv) { bool bUseSIMD = false; bool bSaveLog = false; bool bUseAtomics = false; - + ECompressionFormat format = eCompressionFormat_BPTC; + bool knowArg = false; do { knowArg = false; @@ -110,6 +112,23 @@ int main(int argc, char **argv) { continue; } + if(strcmp(argv[fileArg], "-f") == 0) { + fileArg++; + + if(fileArg == argc) { + PrintUsage(); + exit(1); + } else { + if(!strcmp(argv[fileArg], "PVRTC")) { + format = eCompressionFormat_PVRTC; + } + } + + fileArg++; + knowArg = true; + continue; + } + if(strcmp(argv[fileArg], "-l") == 0) { fileArg++; bSaveLog = true; @@ -187,6 +206,9 @@ int main(int argc, char **argv) { } Image img = Image(*file.GetImage()); + if(format == eCompressionFormat_PVRTC) { + img.SetBlockStreamOrder(false); + } int numBlocks = (img.GetWidth() * img.GetHeight())/16; BlockStatManager *statManager = NULL; @@ -195,6 +217,7 @@ int main(int argc, char **argv) { } SCompressionSettings settings; + settings.format = format; settings.bUseSIMD = bUseSIMD; settings.bUseAtomics = bUseAtomics; settings.iNumThreads = numThreads; @@ -223,8 +246,13 @@ int main(int argc, char **argv) { statManager->ToFile(logname); } - Image cImg(*ci); - ImageFile cImgFile (strcat(basename, "-bc7.png"), eFileFormat_PNG, cImg); + if(format == eCompressionFormat_BPTC) { + strcat(basename, "-bc7.png"); + } else if(format == eCompressionFormat_PVRTC) { + strcat(basename, "-pvrtc.png"); + } + + ImageFile cImgFile (basename, eFileFormat_PNG, *ci); cImgFile.Write(); // Cleanup diff --git a/CLTool/src/clwin32.cpp b/CLTool/src/clwin32.cpp index d746a22..2f37c24 100644 --- a/CLTool/src/clwin32.cpp +++ b/CLTool/src/clwin32.cpp @@ -54,6 +54,7 @@ void PrintUsage() { fprintf(stderr, "Usage: tc [OPTIONS] imagefile\n"); fprintf(stderr, "\n"); + fprintf(stderr, "\t-f\t\tFormat to use. Either \"BPTC\" or \"PVRTC\". Default: BPTC\n"); fprintf(stderr, "\t-l\t\tSave an output log.\n"); fprintf(stderr, "\t-q \tSet compression quality level. Default: 50\n"); fprintf(stderr, "\t-n \tCompress the image num times and give the average time and PSNR. Default: 1\n"); @@ -93,11 +94,29 @@ int _tmain(int argc, _TCHAR* argv[]) bool bUseSIMD = false; bool bSaveLog = false; bool bUseAtomics = false; + ECompressionFormat format = eCompressionFormat_BPTC; bool knowArg = false; do { knowArg = false; + if(strcmp(argv[fileArg], "-f") == 0) { + fileArg++; + + if(fileArg == argc) { + PrintUsage(); + exit(1); + } else { + if(!strcmp(argv[fileArg], "PVRTC")) { + format = eCompressionFormat_PVRTC; + } + } + + fileArg++; + knowArg = true; + continue; + } + if(strcmp(argv[fileArg], "-n") == 0) { fileArg++; @@ -192,15 +211,19 @@ int _tmain(int argc, _TCHAR* argv[]) return 1; } - const Image *img = file.GetImage(); + Image img (*file.GetImage()) + if(format == eCompressionFormat_PVRTC) { + img.SetBlockStreamOrder(false); + } - int numBlocks = (img->GetWidth() * img->GetHeight())/16; + int numBlocks = (img.GetWidth() * img.GetHeight())/16; BlockStatManager *statManager = NULL; if(bSaveLog) { statManager = new BlockStatManager(numBlocks); } SCompressionSettings settings; + settings.format = format; settings.bUseSIMD = bUseSIMD; settings.bUseAtomics = bUseAtomics; settings.iNumThreads = numThreads; @@ -226,11 +249,16 @@ int _tmain(int argc, _TCHAR* argv[]) if(bSaveLog) { strcat_s(basename, ".log"); statManager->ToFile(basename); - basename[strlen(basename) - 4] = '\0'; + basename[strlen(basename) - 4] = '\0'; } - strcat_s(basename, "-bc7.png"); - Image cImg (*ci); - ImageFile cImgFile (basename, eFileFormat_PNG, cImg); + + if(format == eCompressionFormat_BPTC) { + strcat_s(basename, "-bc7.png"); + } else if(format == eCompressionFormat_PVRTC) { + strcat_s(basename, "-pvrtc.png"); + } + + ImageFile cImgFile (basename, eFileFormat_PNG, *ci); cImgFile.Write(); // Cleanup diff --git a/CMakeModules/FindPVRTexLib.cmake b/CMakeModules/FindPVRTexLib.cmake index b837860..f2da742 100644 --- a/CMakeModules/FindPVRTexLib.cmake +++ b/CMakeModules/FindPVRTexLib.cmake @@ -56,30 +56,51 @@ # PVRTEXLIB_LIBRARIES - The libraries needed to use PVRTexLib IF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + SET( PVRTEXLIB_ROOT "/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library" ) find_path( PVRTEXLIB_INCLUDE_DIR PVRTexture.h - PATHS "/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Include" + PATHS ${PVRTEXLIB_ROOT}/Include ) find_library(PVRTEXLIB_LIB PVRTexLib - PATHS "/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/OSX_x86/Static" - "/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/OSX_x86/Dynamic" + PATHS ${PVRTEXLIB_ROOT}/OSX_x86/Static + ${PVRTEXLIB_ROOT}/OSX_x86/Dynamic ) -ELSEIF(MSVC) +ELSEIF (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + SET( PVRTEXLIB_ROOT "/opt/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library" ) find_path( PVRTEXLIB_INCLUDE_DIR PVRTexture.h - PATHS "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Include" + PATHS ${PVRTEXLIB_ROOT}/Include + ) + + IF(${CMAKE_SIZEOF_VOID_P} EQUAL 8) + find_library(PVRTEXLIB_LIB PVRTexLib + PATHS ${PVRTEXLIB_ROOT}/Linux_x86_64/Static + ${PVRTEXLIB_ROOT}/Linux_x86_64/Dynamic + ) + ELSE() + find_library(PVRTEXLIB_LIB PVRTexLib + PATHS ${PVRTEXLIB_ROOT}/Linux_x86_32/Static + ${PVRTEXLIB_ROOT}/Linux_x86_32/Dynamic + ) + ENDIF() + +ELSEIF(MSVC) + SET( PVRTEXLIB_ROOT "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library" ) + find_path( + PVRTEXLIB_INCLUDE_DIR PVRTexture.h + PATHS ${PVRTEXLIB_ROOT}/Include ) IF(${CMAKE_GENERATOR} MATCHES Win64) find_library(PVRTEXLIB_LIB PVRTexLib - PATHS "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_64/Static" - "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_64/Dynamic" + PATHS ${PVRTEXLIB_ROOT}/Windows_x86_64/Static + ${PVRTEXLIB_ROOT}/Windows_x86_64/Dynamic ) ELSE() find_library(PVRTEXLIB_LIB PVRTexLib - PATHS "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_32/Static" - "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_32/Dynamic" + PATHS ${PVRTEXLIB_ROOT}/Windows_x86_32/Static + ${PVRTEXLIB_ROOT}/Windows_x86_32/Dynamic ) ENDIF() ENDIF() @@ -93,4 +114,4 @@ include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PVRTexLib DEFAULT_MSG PVRTEXLIB_LIB PVRTEXLIB_INCLUDE_DIR) -mark_as_advanced(PVRTEXLIB_INCLUDE_DIR PVRTEXLIB_LIB ) +mark_as_advanced( PVRTEXLIB_ROOT PVRTEXLIB_INCLUDE_DIR PVRTEXLIB_LIB ) diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index a532b4c..c948adc 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -77,6 +77,8 @@ ELSE() ENDIF() INCLUDE_DIRECTORIES( ${FasTC_SOURCE_DIR}/Base/include ) +INCLUDE_DIRECTORIES( ${FasTC_SOURCE_DIR} ) +INCLUDE_DIRECTORIES( ${FasTC_SOURCE_DIR}/PVRTCEncoder/include ) INCLUDE_DIRECTORIES( ${FasTC_SOURCE_DIR}/BPTCEncoder/include ) INCLUDE_DIRECTORIES( ${FasTC_BINARY_DIR}/BPTCEncoder/include ) @@ -157,6 +159,7 @@ ADD_LIBRARY( FasTCCore TARGET_LINK_LIBRARIES( FasTCCore FasTCBase ) TARGET_LINK_LIBRARIES( FasTCCore FasTCIO ) TARGET_LINK_LIBRARIES( FasTCCore BPTCEncoder ) +TARGET_LINK_LIBRARIES( FasTCCore PVRTCEncoder ) IF( THREAD_API MATCHES "Boost" ) TARGET_LINK_LIBRARIES( FasTCCore ${Boost_LIBRARIES} ) diff --git a/Core/include/CompressedImage.h b/Core/include/CompressedImage.h index 58d0bb8..04fb561 100644 --- a/Core/include/CompressedImage.h +++ b/Core/include/CompressedImage.h @@ -44,10 +44,13 @@ #ifndef _COMPRESSED_IMAGE_H_ #define _COMPRESSED_IMAGE_H_ +#include "TexCompTypes.h" + enum ECompressionFormat { eCompressionFormat_DXT1, eCompressionFormat_DXT5, eCompressionFormat_BPTC, + eCompressionFormat_PVRTC, kNumCompressionFormats }; @@ -55,37 +58,47 @@ enum ECompressionFormat { #include "Image.h" class CompressedImage : public Image { - private: ECompressionFormat m_Format; uint32 *m_RGBAData; uint32 m_DataSz; - void InitData(const unsigned char *withData); public: - CompressedImage(); + CompressedImage(const CompressedImage &); + CompressedImage &operator=(const CompressedImage &); // Create a compressed image from the given data according to // the passed format. The size of the data is expected to conform // to the width, height, and format specified. CompressedImage( - const unsigned int width, - const unsigned int height, + const uint32 width, + const uint32 height, const ECompressionFormat format, - const unsigned char *data + const uint8 *data ); - CompressedImage( const CompressedImage &other ); virtual ~CompressedImage(); + virtual Image *Clone() const { + return new CompressedImage(*this); + } + virtual void ComputeRGBA(); virtual const uint32 *GetRGBA() const { return m_RGBAData; } + static uint32 GetCompressedSize(uint32 uncompressedSize, ECompressionFormat format); + static uint32 GetUncompressedSize(uint32 compressedSize, ECompressionFormat format) { + uint32 cmp = GetCompressedSize(compressedSize, format); + return compressedSize * (compressedSize / cmp); + } + // Decompress the compressed image data into outBuf. outBufSz is expected // to be the proper size determined by the width, height, and format. // !FIXME! We should have a function to explicitly return the in/out buf // size for a given compressed image. - bool DecompressImage(unsigned char *outBuf, unsigned int outBufSz) const; + bool DecompressImage(uint8 *outBuf, uint32 outBufSz) const; + + ECompressionFormat GetFormat() const { return m_Format; } }; #endif // _COMPRESSED_IMAGE_H_ diff --git a/Core/include/TexComp.h b/Core/include/TexComp.h index b2c7a87..bba3f1f 100644 --- a/Core/include/TexComp.h +++ b/Core/include/TexComp.h @@ -96,7 +96,8 @@ extern CompressedImage *CompressImage(Image *img, const SCompressionSettings &se extern bool CompressImageData( const unsigned char *data, - const unsigned int dataSz, + const unsigned int width, + const unsigned int height, unsigned char *cmpData, const unsigned int cmpDataSz, const SCompressionSettings &settings diff --git a/Core/src/CompressedImage.cpp b/Core/src/CompressedImage.cpp index a0f250d..f048d4d 100644 --- a/Core/src/CompressedImage.cpp +++ b/Core/src/CompressedImage.cpp @@ -50,14 +50,18 @@ #include "TexCompTypes.h" #include "BC7Compressor.h" +#include "PVRTCCompressor.h" CompressedImage::CompressedImage( const CompressedImage &other ) : Image(other) , m_Format(other.m_Format) , m_RGBAData(0) - , m_DataSz(0) + , m_DataSz(other.m_DataSz) { - InitData(other.m_Data); + if(other.m_RGBAData) { + m_RGBAData = new uint32[GetWidth() * GetHeight()]; + memcpy(m_RGBAData, other.m_RGBAData, sizeof(uint32) * GetWidth() * GetHeight()); + } } CompressedImage::CompressedImage( @@ -69,46 +73,36 @@ CompressedImage::CompressedImage( : Image(width, height, NULL) , m_Format(format) , m_RGBAData(0) - , m_DataSz(0) + , m_DataSz(GetCompressedSize(GetWidth() * GetHeight() * 4, m_Format)) { - InitData(data); + if(m_DataSz > 0) { + assert(!m_Data); + m_Data = new unsigned char[m_DataSz]; + memcpy(m_Data, data, m_DataSz); + } } -void CompressedImage::InitData(const unsigned char *withData) { - m_DataSz = 0; - int uncompDataSz = GetWidth() * GetHeight() * 4; - - switch(m_Format) { - default: assert(!"Not implemented!"); // Fall through V - case eCompressionFormat_DXT1: m_DataSz = uncompDataSz / 8; break; - case eCompressionFormat_DXT5: m_DataSz = uncompDataSz / 4; break; - case eCompressionFormat_BPTC: m_DataSz = uncompDataSz / 4; break; - } - - if(m_DataSz > 0) { - m_Data = new unsigned char[m_DataSz]; - memcpy(m_Data, withData, m_DataSz); +CompressedImage &CompressedImage::operator=(const CompressedImage &other) { + Image::operator=(other); + m_Format = other.m_Format; + m_DataSz = other.m_DataSz; + if(other.m_RGBAData) { + m_RGBAData = new uint32[GetWidth() * GetHeight()]; + memcpy(m_RGBAData, other.m_RGBAData, sizeof(uint32) * GetWidth() * GetHeight()); } } CompressedImage::~CompressedImage() { - if(m_Data) { - delete [] m_Data; - m_Data = NULL; + if(m_RGBAData) { + delete m_RGBAData; + m_RGBAData = NULL; } } bool CompressedImage::DecompressImage(unsigned char *outBuf, unsigned int outBufSz) const { // First make sure that we have enough data - uint32 dataSz = 0; - switch(m_Format) { - default: assert(!"Not implemented!"); // Fall through V - case eCompressionFormat_DXT1: dataSz = m_DataSz * 8; break; - case eCompressionFormat_DXT5: dataSz = m_DataSz * 4; break; - case eCompressionFormat_BPTC: dataSz = m_DataSz * 4; break; - } - + uint32 dataSz = GetUncompressedSize(m_DataSz, m_Format); if(dataSz > outBufSz) { fprintf(stderr, "Not enough space to store entire decompressed image! " "Got %d bytes, but need %d!\n", outBufSz, dataSz); @@ -118,6 +112,12 @@ bool CompressedImage::DecompressImage(unsigned char *outBuf, unsigned int outBuf DecompressionJob dj (m_Data, outBuf, GetWidth(), GetHeight()); switch(m_Format) { + case eCompressionFormat_PVRTC: + { + PVRTCC::Decompress(dj); + } + break; + case eCompressionFormat_BPTC: { BC7C::Decompress(dj); @@ -146,3 +146,26 @@ void CompressedImage::ComputeRGBA() { uint8 *pixelData = reinterpret_cast(m_RGBAData); DecompressImage(pixelData, GetWidth() * GetHeight() * 4); } + +uint32 CompressedImage::GetCompressedSize(uint32 uncompressedSize, ECompressionFormat format) { + assert(uncompressedSize % 8 == 0); + + uint32 cmpDataSzNeeded = 0; + switch(format) { + default: + assert(!"Not implemented!"); + // Fall through V + case eCompressionFormat_DXT1: + case eCompressionFormat_PVRTC: + cmpDataSzNeeded = uncompressedSize / 8; + break; + + case eCompressionFormat_DXT5: + case eCompressionFormat_BPTC: + cmpDataSzNeeded = uncompressedSize / 4; + break; + } + + return cmpDataSzNeeded; +} + diff --git a/Core/src/TexComp.cpp b/Core/src/TexComp.cpp index c91afe0..47f566e 100644 --- a/Core/src/TexComp.cpp +++ b/Core/src/TexComp.cpp @@ -50,14 +50,14 @@ #include #include -#include "CompressionFuncs.h" #include "BC7Compressor.h" -#include "Thread.h" -#include "WorkerQueue.h" -#include "ThreadGroup.h" - -#include "ImageFile.h" +#include "CompressionFuncs.h" #include "Image.h" +#include "ImageFile.h" +#include "PVRTCCompressor.h" +#include "Thread.h" +#include "ThreadGroup.h" +#include "WorkerQueue.h" template static void clamp(T &x, const T &minX, const T &maxX) { @@ -69,6 +69,10 @@ static inline T sad(const T &a, const T &b) { return (a > b)? a - b : b - a; } +static void CompressPVRTC(const CompressionJob &cj) { + PVRTCC::Compress(cj); +} + SCompressionSettings:: SCompressionSettings() : format(eCompressionFormat_BPTC) , bUseSIMD(false) @@ -87,6 +91,12 @@ static CompressionFuncWithStats ChooseFuncFromSettingsWithStats(const SCompress } break; + case eCompressionFormat_PVRTC: + { + // !FIXME! actually implement one of these methods... + return NULL; + } + default: { assert(!"Not implemented!"); @@ -110,6 +120,11 @@ static CompressionFunc ChooseFuncFromSettings(const SCompressionSettings &s) { } break; + case eCompressionFormat_PVRTC: + { + return CompressPVRTC; + } + default: { assert(!"Not implemented!"); @@ -124,8 +139,9 @@ static void ReportError(const char *msg) { } static double CompressImageInSerial( - const unsigned char *imgData, - const unsigned int imgDataSz, + const uint8 *imgData, + const uint32 imgWidth, + const uint32 imgHeight, const SCompressionSettings &settings, unsigned char *outBuf ) { @@ -134,14 +150,14 @@ static double CompressImageInSerial( double cmpTimeTotal = 0.0; + StopWatch stopWatch = StopWatch(); for(int i = 0; i < settings.iNumCompressions; i++) { - StopWatch stopWatch = StopWatch(); stopWatch.Reset(); stopWatch.Start(); // !FIXME! We're assuming that we have 4x4 blocks here... - CompressionJob cj (imgData, outBuf, imgDataSz / 16, 4); + CompressionJob cj (imgData, outBuf, imgWidth, imgHeight); if(fStats && settings.pStatManager) { // !FIXME! Actually use the stat manager... //(*fStats)(cj, *(settings.pStatManager)); @@ -347,20 +363,14 @@ CompressedImage *CompressImage( assert(dataSz > 0); // Allocate data based on the compression method - int cmpDataSz = 0; - switch(settings.format) { - default: assert(!"Not implemented!"); // Fall Through V - case eCompressionFormat_DXT1: cmpDataSz = dataSz / 8; break; - case eCompressionFormat_DXT5: cmpDataSz = dataSz / 4; break; - case eCompressionFormat_BPTC: cmpDataSz = dataSz / 4; break; - } + uint32 cmpDataSz = CompressedImage::GetCompressedSize(dataSz, settings.format); // Make sure that we have RGBA data... img->ComputeRGBA(); unsigned char *cmpData = new unsigned char[cmpDataSz]; const uint8 *pixelData = reinterpret_cast(img->GetRGBA()); - CompressImageData(pixelData, dataSz, cmpData, cmpDataSz, settings); + CompressImageData(pixelData, w, h, cmpData, cmpDataSz, settings); outImg = new CompressedImage(w, h, settings.format, cmpData); @@ -370,12 +380,15 @@ CompressedImage *CompressImage( bool CompressImageData( const unsigned char *data, - const unsigned int dataSz, + const unsigned int width, + const unsigned int height, unsigned char *cmpData, const unsigned int cmpDataSz, const SCompressionSettings &settings ) { + uint32 dataSz = width * height * 4; + // Make sure that platform supports SSE if they chose this // option... #ifndef HAS_SSE_41 @@ -390,15 +403,23 @@ bool CompressImageData( return false; } - // Allocate data based on the compression method - uint32 cmpDataSzNeeded = 0; - switch(settings.format) { - default: assert(!"Not implemented!"); // Fall through V - case eCompressionFormat_DXT1: cmpDataSzNeeded = dataSz / 8; break; - case eCompressionFormat_DXT5: cmpDataSzNeeded = dataSz / 4; break; - case eCompressionFormat_BPTC: cmpDataSzNeeded = dataSz / 4; break; + uint32 numThreads = settings.iNumThreads; + if(settings.format == eCompressionFormat_PVRTC && + (settings.iNumThreads > 1 || settings.pStatManager)) { + if(settings.iNumThreads > 1) { + ReportError("WARNING - PVRTC compressor does not support multithreading."); + numThreads = 1; + } + + if(settings.pStatManager) { + ReportError("WARNING - PVRTC compressor does not support stat collection."); + } } + // Allocate data based on the compression method + uint32 cmpDataSzNeeded = + CompressedImage::GetCompressedSize(dataSz, settings.format); + if(cmpDataSzNeeded == 0) { ReportError("Unknown compression format"); return false; @@ -413,21 +434,17 @@ bool CompressImageData( double cmpMSTime = 0.0; - if(settings.iNumThreads > 1) { + if(numThreads > 1) { if(settings.bUseAtomics) { - //!KLUDGE! - unsigned int height = 4; - unsigned int width = dataSz / 16; - cmpMSTime = CompressImageWithAtomics(data, width, height, settings, cmpData); - } - else if(settings.iJobSize > 0) + } else if(settings.iJobSize > 0) { cmpMSTime = CompressImageWithWorkerQueue(data, dataSz, settings, cmpData); - else + } else { cmpMSTime = CompressImageWithThreads(data, dataSz, settings, cmpData); + } } else { - cmpMSTime = CompressImageInSerial(data, dataSz, settings, cmpData); + cmpMSTime = CompressImageInSerial(data, width, height, settings, cmpData); } // Report compression time diff --git a/IO/src/ImageFile.cpp b/IO/src/ImageFile.cpp index e6f8d84..293bc9b 100644 --- a/IO/src/ImageFile.cpp +++ b/IO/src/ImageFile.cpp @@ -107,7 +107,7 @@ ImageFile::ImageFile(const CHAR *filename, EImageFileFormat format) ImageFile::ImageFile(const char *filename, EImageFileFormat format, const Image &image) : m_FileFormat(format) - , m_Image(new Image(image)) + , m_Image(image.Clone()) { strncpy(m_Filename, filename, kMaxFilenameSz); } @@ -207,7 +207,7 @@ Image *ImageFile::LoadImage(const unsigned char *rawImageData) const { } uint32 *pixels = reinterpret_cast(pixelData); - Image *i = new Image(loader->GetWidth(), loader->GetHeight(), pixels); + Image *i = new Image(loader->GetWidth(), loader->GetHeight(), pixels, true); // Cleanup delete loader; diff --git a/IO/src/ImageWriterPNG.cpp b/IO/src/ImageWriterPNG.cpp index 79474af..c8bef79 100644 --- a/IO/src/ImageWriterPNG.cpp +++ b/IO/src/ImageWriterPNG.cpp @@ -84,11 +84,13 @@ public: }; -ImageWriterPNG::ImageWriterPNG(const Image &im) +ImageWriterPNG::ImageWriterPNG(Image &im) : ImageWriter(im.GetWidth(), im.GetHeight(), im.RawData()) , m_bBlockStreamOrder(im.GetBlockStreamOrder()) , m_StreamPosition(0) { + im.ComputeRGBA(); + m_PixelData = reinterpret_cast(im.GetRGBA()); } bool ImageWriterPNG::WriteImage() { @@ -111,7 +113,6 @@ bool ImageWriterPNG::WriteImage() { } /* Set image attributes. */ - png_set_IHDR (png_ptr, info_ptr, m_Width, @@ -123,7 +124,6 @@ bool ImageWriterPNG::WriteImage() { PNG_FILTER_TYPE_DEFAULT); /* Initialize rows of PNG. */ - row_pointers = (png_byte **)png_malloc (png_ptr, m_Height * sizeof (png_byte *)); for (uint32 y = 0; y < m_Height; ++y) { png_byte *row = (png_byte *)png_malloc (png_ptr, sizeof (uint8) * m_Width * pixel_size); @@ -136,8 +136,8 @@ bool ImageWriterPNG::WriteImage() { *row++ = GetChannelForPixel(x, y, ch); } } else { - *(reinterpret_cast(row) + x) = - (reinterpret_cast(m_PixelData))[y * m_Width + x]; + reinterpret_cast(row)[x] = + reinterpret_cast(m_PixelData)[y * m_Width + x]; } } } diff --git a/IO/src/ImageWriterPNG.h b/IO/src/ImageWriterPNG.h index d15b32e..3b4c515 100644 --- a/IO/src/ImageWriterPNG.h +++ b/IO/src/ImageWriterPNG.h @@ -50,7 +50,7 @@ class Image; class ImageWriterPNG : public ImageWriter { public: - ImageWriterPNG(const Image &); + ImageWriterPNG(Image &); virtual ~ImageWriterPNG() { } virtual bool WriteImage(); diff --git a/PVRTCEncoder/CMakeLists.txt b/PVRTCEncoder/CMakeLists.txt index e5bc214..80ef77c 100644 --- a/PVRTCEncoder/CMakeLists.txt +++ b/PVRTCEncoder/CMakeLists.txt @@ -62,6 +62,7 @@ SET( HEADERS ) SET( SOURCES + src/Compressor.cpp src/Decompressor.cpp src/Pixel.cpp src/Block.cpp diff --git a/PVRTCEncoder/include/PVRTCCompressor.h b/PVRTCEncoder/include/PVRTCCompressor.h index 50363fc..cfc327a 100644 --- a/PVRTCEncoder/include/PVRTCCompressor.h +++ b/PVRTCEncoder/include/PVRTCCompressor.h @@ -70,9 +70,18 @@ namespace PVRTCC { // decompress the data. void Decompress(const DecompressionJob &, bool bTwoBitMode = false, - const EWrapMode wrapMode = eWrapMode_Clamp, + const EWrapMode wrapMode = eWrapMode_Wrap, bool bDebugImages = false); + // Takes a stream of uncompressed RGBA8 data and compresses it into PVRTC + // version one. The width and height must be specified in order to properly + // decompress the data. + void Compress(const CompressionJob &, + bool bTwoBitMode = false, + const EWrapMode wrapMode = eWrapMode_Wrap); + + static const uint32 kBlockSize = sizeof(uint64); + } // namespace PVRTCC #endif // PVRTCENCODER_INCLUDE_PVRTCCOMPRESSOR_H_ diff --git a/PVRTCEncoder/src/Block.cpp b/PVRTCEncoder/src/Block.cpp index 8197953..602aab5 100644 --- a/PVRTCEncoder/src/Block.cpp +++ b/PVRTCEncoder/src/Block.cpp @@ -83,6 +83,42 @@ namespace PVRTCC { return m_ColorA; } + Pixel Block::SetColor(const Pixel &c, bool transparent, + const uint8 (&tbd)[4], const uint8 (&obd)[4]) { + uint8 cDepth[4]; + c.GetBitDepth(cDepth); + + Pixel final = c; + if(transparent) { + final.ChangeBitDepth(tbd); + + // If we went effectively transparent, then just switch over to opaque... + if(final.A() == 0x7) { + return SetColor(c, false, tbd, obd); + } + + } else { + final.A() = 255; + final.ChangeBitDepth(obd); + } + + return final; + } + + void Block::SetColorA(const Pixel &c, bool transparent) { + const uint8 transparentBitDepth[4] = { 3, 4, 4, 4 }; + const uint8 opaqueBitDepth[4] = { 0, 5, 5, 5 }; + m_ColorA = SetColor(c, transparent, transparentBitDepth, opaqueBitDepth); + m_ColorACached = true; + } + + void Block::SetColorB(const Pixel &c, bool transparent) { + const uint8 transparentBitDepth[4] = { 3, 4, 4, 3 }; + const uint8 opaqueBitDepth[4] = { 0, 5, 5, 4 }; + m_ColorB = SetColor(c, transparent, transparentBitDepth, opaqueBitDepth); + m_ColorBCached = true; + } + Pixel Block::GetColorB() { if(m_ColorBCached) { return m_ColorB; @@ -109,6 +145,17 @@ namespace PVRTCC { return (m_LongData >> (texelIdx * 2)) & 0x3; } + void Block::SetLerpValue(uint32 texelIdx, uint8 lerpVal) { + assert(texelIdx >= 0); + assert(texelIdx <= 15); + + assert(lerpVal >= 0); + assert(lerpVal < 4); + + m_LongData &= ~(static_cast(0x3) << (texelIdx * 2)); + m_LongData |= static_cast(lerpVal & 0x3) << (texelIdx * 2); + } + Block::E2BPPSubMode Block::Get2BPPSubMode() const { uint8 first = GetLerpValue(0); if(!(first & 0x1)) { @@ -145,4 +192,65 @@ namespace PVRTCC { return ret; } + + uint64 Block::Pack() { + assert(m_ColorACached); + assert(m_ColorBCached); + +#ifndef NDEBUG + uint8 bitDepthA[4]; + m_ColorA.GetBitDepth(bitDepthA); + + uint32 sumA = 0; + for(int i = 0; i < 4; i++) { + sumA += bitDepthA[i]; + } + assert(sumA == 15); +#endif + +#ifndef NDEBUG + uint8 bitDepthB[4]; + m_ColorB.GetBitDepth(bitDepthB); + + uint32 sumB = 0; + for(int i = 0; i < 4; i++) { + sumB += bitDepthB[i]; + } + assert(sumB == 14); +#endif + + uint8 aBits[2], bBits[2]; + memset(aBits, 0, sizeof(aBits)); + memset(bBits, 0, sizeof(bBits)); + + m_ColorA.ToBits(aBits, 2); + m_ColorB.ToBits(bBits, 2, 1); + + if(m_ColorA.A() == 0xFF) { + m_ByteData[7] |= 0x80; + } else { + m_ByteData[7] &= 0x7f; + } + m_ByteData[7] = aBits[1]; + m_ByteData[6] = aBits[0]; + + bool modeBit = GetModeBit(); + m_ByteData[5] = bBits[1]; + m_ByteData[4] = bBits[0]; + if(m_ColorB.A() == 0xFF) { + m_ByteData[5] |= 0x80; + } else { + m_ByteData[5] &= 0x7f; + } + + if(modeBit) { + m_ByteData[4] |= 0x1; + } else { + m_ByteData[4] &= 0xFE; + } + + // Modulation data should have already been set... + return m_LongData; + } + } // namespace PVRTCC diff --git a/PVRTCEncoder/src/Block.h b/PVRTCEncoder/src/Block.h index 08c4b3e..7404f40 100644 --- a/PVRTCEncoder/src/Block.h +++ b/PVRTCEncoder/src/Block.h @@ -60,15 +60,29 @@ namespace PVRTCC { class Block { public: + Block(): m_LongData(0) { } explicit Block(const uint8 *data); + // Accessors for the A and B colors of the block. Pixel GetColorA(); + void SetColorA(const Pixel &, bool transparent=false); + Pixel GetColorB(); + void SetColorB(const Pixel &, bool transparent=false); bool GetModeBit() const { return static_cast((m_LongData >> 32) & 0x1); } + void SetModeBit(bool flag) { + const uint64 bit = 0x100000000L; + if(flag) { + m_LongData |= bit; + } else { + m_LongData &= ~bit; + } + } + // For 2BPP PVRTC, if the mode bit is set, then we use the modulation data // as 2 bits for every other texel in the 8x4 block in a checkerboard pattern. // The interleaved texel data is decided by averaging nearby texel modulation @@ -100,6 +114,11 @@ class Block { // 12 13 14 15 uint8 GetLerpValue(uint32 texelIdx) const; + // Sets the values in the data for this block according to the texel and + // modulation value passed. This happens immediately (i.e. a call to Pack() + // will reflect these changes). + void SetLerpValue(uint32 texelIdx, uint8 lerpVal); + // This returns the modulation value for the texel in the block interpreted as // 2BPP. If the modulation bit is not set, then it expects a number from 0-31 // and does the same operation as GetLerpValue. If the modulation bit is set, @@ -109,6 +128,12 @@ class Block { // global information. uint8 Get2BPPLerpValue(uint32 texelIdx) const; + // Returns the 64-bit word that represents this block. This function packs the + // A and B colors based on their bit depths and preserves the corresponding mode + // bits. The color modes are determined by whether or not the alpha channel of + // each block is fully opaque or not. + uint64 Pack(); + private: union { uint8 m_ByteData[8]; @@ -120,6 +145,11 @@ class Block { bool m_ColorBCached; Pixel m_ColorB; + + // tbd -- transparent bit depth + // obd -- opaque bit depth + static Pixel SetColor(const Pixel &c, bool transparent, + const uint8 (&tbd)[4], const uint8 (&obd)[4]); }; } // namespace PVRTCC diff --git a/PVRTCEncoder/src/Compressor.cpp b/PVRTCEncoder/src/Compressor.cpp new file mode 100644 index 0000000..11b8340 --- /dev/null +++ b/PVRTCEncoder/src/Compressor.cpp @@ -0,0 +1,290 @@ +/* FasTC + * Copyright (c) 2013 University of North Carolina at Chapel Hill. + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for educational, research, and non-profit purposes, without + * fee, and without a written agreement is hereby granted, provided that the + * above copyright notice, this paragraph, and the following four paragraphs + * appear in all copies. + * + * Permission to incorporate this software into commercial products may be + * obtained by contacting the authors or the Office of Technology Development + * at the University of North Carolina at Chapel Hill . + * + * This software program and documentation are copyrighted by the University of + * North Carolina at Chapel Hill. The software program and documentation are + * supplied "as is," without any accompanying services from the University of + * North Carolina at Chapel Hill or the authors. The University of North + * Carolina at Chapel Hill and the authors do not warrant that the operation of + * the program will be uninterrupted or error-free. The end-user understands + * that the program was developed for research purposes and is advised not to + * rely exclusively on the program for any reason. + * + * IN NO EVENT SHALL THE UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL OR THE + * AUTHORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, + * OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF + * THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF NORTH CAROLINA + * AT CHAPEL HILL OR THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL AND THE AUTHORS SPECIFICALLY + * DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND ANY + * STATUTORY WARRANTY OF NON-INFRINGEMENT. THE SOFTWARE PROVIDED HEREUNDER IS ON + * AN "AS IS" BASIS, AND THE UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL AND + * THE AUTHORS HAVE NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + * + * Please send all BUG REPORTS to . + * + * The authors may be contacted via: + * + * Pavel Krajcevski + * Dept of Computer Science + * 201 S Columbia St + * Frederick P. Brooks, Jr. Computer Science Bldg + * Chapel Hill, NC 27599-3175 + * USA + * + * + */ + +#include "PVRTCCompressor.h" + +#include +#include +#include +#include + +#include "Pixel.h" +#include "Image.h" +#include "Block.h" + +namespace PVRTCC { + + static uint32 Interleave(uint16 inx, uint16 iny) { + // Taken from: + // http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN + + static const uint32 B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF}; + static const uint32 S[] = {1, 2, 4, 8}; + + uint32 x = static_cast(inx); + uint32 y = static_cast(iny); + + x = (x | (x << S[3])) & B[3]; + x = (x | (x << S[2])) & B[2]; + x = (x | (x << S[1])) & B[1]; + x = (x | (x << S[0])) & B[0]; + + y = (y | (y << S[3])) & B[3]; + y = (y | (y << S[2])) & B[2]; + y = (y | (y << S[1])) & B[1]; + y = (y | (y << S[0])) & B[0]; + + return x | (y << 1); + } + + template + static T Clamp(const T &v, const T &low, const T &high) { + return ::std::min(::std::max(low, v), high); + } + + static const Pixel &Lookup(const Image &img, + int32 x, int32 y, + uint32 width, uint32 height, + const EWrapMode wrapMode) { + while(x >= width) { + if(wrapMode == eWrapMode_Wrap) { + x -= width; + } else { + x = width - 1; + } + } + + while(x < 0) { + if(wrapMode == eWrapMode_Wrap) { + x += width; + } else { + x = 0; + } + } + + while(y >= height) { + if(wrapMode == eWrapMode_Wrap) { + y -= height; + } else { + y = height - 1; + } + } + + while(y < 0) { + if(wrapMode == eWrapMode_Wrap) { + y += height; + } else { + y = 0; + } + } + + return img(x, y); + } + + void Compress(const CompressionJob &dcj, + bool bTwoBitMode, + const EWrapMode wrapMode) { + Image img(dcj.height, dcj.width); + uint32 nPixels = dcj.height * dcj.width; + for(uint32 i = 0; i < nPixels; i++) { + uint32 x = i % dcj.width; + uint32 y = i / dcj.width; + + const uint32 *pixels = reinterpret_cast(dcj.inBuf); + img(x, y).UnpackRGBA(pixels[i]); + } + + Image original = img; + img.DebugOutput("Original"); + + // Downscale it using anisotropic diffusion based scheme in order to preserve + // image features, then reupscale and compute deltas. Use deltas to generate + // initial A & B images followed by modulation data. + img.ContentAwareDownscale(1, 1, eWrapMode_Wrap, true); + img.ContentAwareDownscale(1, 1, eWrapMode_Wrap, false); + + Image downscaled = img; + + // Upscale it again + img.BilinearUpscale(2, 2, eWrapMode_Wrap); + + img.DebugOutput("Reconstruction"); + + // Compute difference... + ::std::vector difference; + difference.resize(dcj.height * dcj.width * 4); + for(uint32 j = 0; j < dcj.height; j++) { + for(uint32 i = 0; i < dcj.width; i++) { + for(uint32 c = 0; c < 4; c++) { + int16 o = original(i, j).Component(c); + int16 n = img(i, j).Component(c); + difference[j*dcj.width*4 + i*4 + c] = o - n; + } + } + } + + const uint32 blocksW = dcj.width / 4; + const uint32 blocksH = dcj.height / 4; + + // Go over the 7x7 texel blocks and extract bounding box diagonals for each + // block. We should be able to choose which diagonal we want... + const uint32 kKernelSz = 7; + + Image imgA = downscaled; + Image imgB = downscaled; + for(uint32 j = 0; j < blocksH; j++) { + for(uint32 i = 0; i < blocksW; i++) { + int32 startX = i*4 + 2 - (kKernelSz / 2); + int32 startY = j*4 + 2 - (kKernelSz / 2); + for(int32 y = startY; y < startY + kKernelSz; y++) { + for(int32 x = startX; x < startX + kKernelSz; x++) { + const Pixel &po = Lookup(original, x, y, dcj.width, dcj.height, wrapMode); + Pixel &pa = imgA(i, j); + Pixel &pb = imgB(i, j); + for(uint32 c = 0; c < 4; c++) { + pa.Component(c) = ::std::max(po.Component(c), pa.Component(c)); + pb.Component(c) = ::std::min(po.Component(c), pb.Component(c)); + } + } + } + } + } + + imgA.DebugOutput("ImageA"); + imgB.DebugOutput("ImageB"); + + // Determine modulation values... + Image upA = imgA; + Image upB = imgB; + + upA.BilinearUpscale(2, 2, wrapMode); + upB.BilinearUpscale(2, 2, wrapMode); + + assert(upA.GetHeight() == dcj.height && upA.GetWidth() == dcj.width); + assert(upB.GetHeight() == dcj.height && upB.GetWidth() == dcj.width); + + upA.DebugOutput("UpscaledA"); + upB.DebugOutput("UpscaledB"); + + // Choose the most appropriate modulation values for the two images... + ::std::vector modValues; + modValues.resize(dcj.width * dcj.height); + for(uint32 j = 0; j < dcj.height; j++) { + for(uint32 i = 0; i < dcj.width; i++) { + uint8 &mv = modValues[j * dcj.width + i]; + + const Pixel pa = upA(i, j); + const Pixel pb = upB(i, j); + const Pixel po = original(i, j); + + // !FIXME! there are two modulation modes... we're only using one. + uint8 modSteps[4] = { 8, 5, 3, 0 }; + uint8 bestMod = 0; + uint32 bestError = 0xFFFFFFFF; + for(uint32 s = 0; s < 4; s++) { + uint32 error = 0; + for(uint32 c = 0; c < 4; c++) { + uint16 va = static_cast(pa.Component(c)); + uint16 vb = static_cast(pb.Component(c)); + uint16 vo = static_cast(po.Component(c)); + + uint16 lerpVal = modSteps[s]; + uint16 res = (va * (8 - lerpVal) + vb * lerpVal) / 8; + uint16 e = (res > vo)? res - vo : vo - res; + error += e * e; + } + + if(error < bestError) { + bestError = error; + bestMod = s; + } + } + + mv = bestMod; + } + } + + // Pack everything into a PVRTC blocks. + assert(imgA.GetHeight() == blocksH); + assert(imgA.GetWidth() == blocksW); + + std::vector blocks; + blocks.reserve(blocksW * blocksH); + for(uint32 j = 0; j < blocksH; j++) { + for(uint32 i = 0; i < blocksW; i++) { + Block b; + b.SetColorA(imgA(i, j), true); + b.SetColorB(imgB(i, j), true); + for(uint32 t = 0; t < 16; t++) { + uint32 x = i*4 + (t%4); + uint32 y = j*4 + (t/4); + b.SetLerpValue(t, modValues[y*dcj.width + x]); + } + blocks.push_back(b.Pack()); + } + } + + // Spit out the blocks... + for(uint32 j = 0; j < blocksH; j++) { + for(uint32 i = 0; i < blocksW; i++) { + + // The blocks are initially arranged in morton order. Let's + // linearize them... + uint32 idx = Interleave(j, i); + + uint64 *outPtr = reinterpret_cast(dcj.outBuf); + outPtr[idx] = blocks[j*blocksW + i]; + } + } + } + +} // namespace PVRTCC diff --git a/PVRTCEncoder/src/Decompressor.cpp b/PVRTCEncoder/src/Decompressor.cpp index 1308cd5..4365aac 100644 --- a/PVRTCEncoder/src/Decompressor.cpp +++ b/PVRTCEncoder/src/Decompressor.cpp @@ -304,11 +304,10 @@ namespace PVRTCC { // First, extract all of the block information... std::vector blocks; - blocks.reserve(w * h); const uint32 blocksW = bTwoBitMode? (w / 8) : (w / 4); const uint32 blocksH = h / 4; - const uint32 blockSz = 8; + blocks.reserve(blocksW * blocksH); for(uint32 j = 0; j < blocksH; j++) { for(uint32 i = 0; i < blocksW; i++) { @@ -317,7 +316,7 @@ namespace PVRTCC { // linearize them... uint32 idx = Interleave(j, i); - uint32 offset = idx * blockSz; + uint32 offset = idx * kBlockSize; blocks.push_back( Block(dcj.inBuf + offset) ); } } diff --git a/PVRTCEncoder/src/Image.cpp b/PVRTCEncoder/src/Image.cpp index 57e1091..e91176c 100644 --- a/PVRTCEncoder/src/Image.cpp +++ b/PVRTCEncoder/src/Image.cpp @@ -52,15 +52,32 @@ #include "Image.h" +#include #include #include #include +#include + +#if _MSC_VER +# define _CRT_SECURE_NO_WARNINGS +# define snprintf _snprintf +#endif #include "Pixel.h" #include "../../Base/include/Image.h" #include "../../IO/include/ImageFile.h" +template +inline T Clamp(const T &v, const T &a, const T &b) { + return ::std::min(::std::max(a, v), b); +} + +static float ConvertChannelToFloat(uint8 channel, uint8 bitDepth) { + float denominator = static_cast((1 << bitDepth) - 1); + return static_cast(channel) / denominator; +} + namespace PVRTCC { Image::Image(uint32 height, uint32 width) @@ -83,27 +100,27 @@ Image::Image(uint32 height, uint32 width, const Pixel *pixels) } Image::Image(const Image &other) - : m_Width(other.m_Width) - , m_Height(other.m_Height) - , m_Pixels(new Pixel[other.m_Width * other.m_Height]) - , m_FractionalPixels(new Pixel[other.m_Width * other.m_Height]) { - memcpy(m_Pixels, other.m_Pixels, m_Width * m_Height * sizeof(Pixel)); + : m_Width(other.GetWidth()) + , m_Height(other.GetHeight()) + , m_Pixels(new Pixel[other.GetWidth() * other.GetHeight()]) + , m_FractionalPixels(new Pixel[other.GetWidth() * other.GetHeight()]) { + memcpy(m_Pixels, other.m_Pixels, GetWidth() * GetHeight() * sizeof(Pixel)); } Image &Image::operator=(const Image &other) { - m_Width = other.m_Width; - m_Height = other.m_Height; + m_Width = other.GetWidth(); + m_Height = other.GetHeight(); assert(m_Pixels); delete m_Pixels; - m_Pixels = new Pixel[other.m_Width * other.m_Height]; - memcpy(m_Pixels, other.m_Pixels, m_Width * m_Height * sizeof(Pixel)); + m_Pixels = new Pixel[other.GetWidth() * other.GetHeight()]; + memcpy(m_Pixels, other.m_Pixels, GetWidth() * GetHeight() * sizeof(Pixel)); assert(m_FractionalPixels); delete m_FractionalPixels; - m_FractionalPixels = new Pixel[other.m_Width * other.m_Height]; + m_FractionalPixels = new Pixel[other.GetWidth() * other.GetHeight()]; memcpy(m_FractionalPixels, other.m_FractionalPixels, - m_Width * m_Height * sizeof(Pixel)); + GetWidth() * GetHeight() * sizeof(Pixel)); return *this; } @@ -129,8 +146,8 @@ static bool CompareBitDepths(const uint8 (&depth1)[4], void Image::BilinearUpscale(uint32 xtimes, uint32 ytimes, EWrapMode wrapMode) { - const uint32 newWidth = m_Width << xtimes; - const uint32 newHeight = m_Height << ytimes; + const uint32 newWidth = GetWidth() << xtimes; + const uint32 newHeight = GetHeight() << ytimes; const uint32 xscale = 1 << xtimes; const uint32 xoffset = xscale >> 1; @@ -213,10 +230,152 @@ void Image::BilinearUpscale(uint32 xtimes, uint32 ytimes, m_Height = newHeight; } +void Image::ContentAwareDownscale(uint32 xtimes, uint32 ytimes, + EWrapMode wrapMode, bool bOffsetNewPixels) { + const uint32 w = GetWidth(); + const uint32 h = GetHeight(); + + const uint32 newWidth = w >> xtimes; + const uint32 newHeight = h >> ytimes; + + Pixel *downscaledPixels = new Pixel[newWidth * newHeight]; + const uint32 numDownscaledPixels = newWidth * newHeight; + + uint8 bitDepth[4]; + m_Pixels[0].GetBitDepth(bitDepth); + + for(uint32 i = 0; i < numDownscaledPixels; i++) { + downscaledPixels[i].ChangeBitDepth(bitDepth); + } + + // Allocate memory + float *imgData = new float[19 * w * h]; + float *I = imgData; + float *Ix[5] = { + imgData + (w * h), + imgData + (2 * w * h), + imgData + (3 * w * h), + imgData + (4 * w * h), + imgData + (18 * w * h), + }; + float *Iy = imgData + (5 * w * h); + float *Ixx[4] = { + imgData + (6 * w * h), + imgData + (7 * w * h), + imgData + (8 * w * h), + imgData + (9 * w * h) + }; + float *Iyy[4] = { + imgData + (10 * w * h), + imgData + (11 * w * h), + imgData + (12 * w * h), + imgData + (13 * w * h) + }; + float *Ixy[4] = { + imgData + (14 * w * h), + imgData + (15 * w * h), + imgData + (16 * w * h), + imgData + (17 * w * h) + }; + + // Then, compute the intensity of the image + for(uint32 i = 0; i < w * h; i++) { + // First convert the pixel values to floats using + // premultiplied alpha... + float a = ConvertChannelToFloat(m_Pixels[i].A(), bitDepth[0]); + float r = a * ConvertChannelToFloat(m_Pixels[i].R(), bitDepth[1]); + float g = a * ConvertChannelToFloat(m_Pixels[i].G(), bitDepth[2]); + float b = a * ConvertChannelToFloat(m_Pixels[i].B(), bitDepth[3]); + + I[i] = r * 0.21 + g * 0.71 + b * 0.07; + } + + // Use central differences to calculate Ix, Iy, Ixx, Iyy... + for(uint32 j = 0; j < h; j++) { + for(uint32 i = 0; i < w; i++) { + uint32 xmhidx = GetPixelIndex(i-1, j); + uint32 xphidx = GetPixelIndex(i+1, j); + + uint32 ymhidx = GetPixelIndex(i, j-1); + uint32 yphidx = GetPixelIndex(i, j+1); + + uint32 idx = GetPixelIndex(i, j); + + uint32 upidx = GetPixelIndex(i + 1, j + 1); + uint32 downidx = GetPixelIndex(i - 1, j - 1); + + Ix[4][idx] = (I[xphidx] - I[xmhidx]) / 2.0f; + Iy[idx] = (I[yphidx] - I[ymhidx]) / 2.0f; + + for(uint32 c = 0; c <= 3; c++) { + #define CPNT(dx) ConvertChannelToFloat(m_Pixels[dx].Component(c), bitDepth[c]) + Ix[c][idx] = (CPNT(xphidx) - CPNT(xmhidx)) / 2.0f; + Ixx[c][idx] = (CPNT(xphidx) - 2.0f*CPNT(idx) + CPNT(xmhidx)) / 2.0f; + Iyy[c][idx] = (CPNT(yphidx) - 2.0f*CPNT(idx) + CPNT(ymhidx)) / 2.0f; + Ixy[c][idx] = (CPNT(upidx) - CPNT(xphidx) - CPNT(yphidx) + 2.0f*CPNT(idx) - + CPNT(xmhidx) - CPNT(ymhidx) + CPNT(downidx)) / 2.0f; + #undef CPNT + } + } + } + + // Now, for each pixel that we take into consideration, use + // a smoothing step that is taken from the anisotropic diffusion + // equation: + // I_t = (I_x^2I_yy - 2I_xyI_xI_y + I_y^2I_xx)(I_x^2 + I_y^2) + for(uint32 j = 0; j < newHeight; j++) { + for(uint32 i = 0; i < newWidth; i++) { + + // Map this new pixel back into the original space... + uint32 scalex = 1 << xtimes; + uint32 scaley = 1 << ytimes; + + uint32 x = scalex * i; + uint32 y = scaley * j; + + if(bOffsetNewPixels) { + x += scalex >> 1; + y += scaley >> 1; + } + + uint32 idx = GetPixelIndex(x, y); + Pixel current = m_Pixels[idx]; + + Pixel result; + result.ChangeBitDepth(bitDepth); + + float Ixsq = Ix[4][idx] * Ix[4][idx]; + float Iysq = Iy[idx] * Iy[idx]; + float denom = Ixsq + Iysq; + + for(uint32 c = 0; c < 4; c++) { + float I0 = ConvertChannelToFloat(current.Component(c), bitDepth[c]); + float It = Ixx[c][idx] + Iyy[c][idx]; + if(fabs(denom) > 1e-6) { + It -= (Ixsq * Ixx[c][idx] + + 2 * Ix[4][idx] * Iy[idx] * Ixy[c][idx] + + Iysq * Iyy[c][idx]) / denom; + } + float scale = static_cast((1 << bitDepth[c]) - 1); + result.Component(c) = static_cast(Clamp(I0 + 0.25f*It, 0.0f, 1.0f) * scale + 0.5f); + } + + downscaledPixels[j * newHeight + i] = result; + } + } + + delete m_Pixels; + m_Pixels = downscaledPixels; + m_Width = newWidth; + m_Height = newHeight; + + delete [] imgData; +} + void Image::ChangeBitDepth(const uint8 (&depths)[4]) { - for(uint32 j = 0; j < m_Height; j++) { - for(uint32 i = 0; i < m_Width; i++) { - uint32 pidx = j * m_Width + i; + for(uint32 j = 0; j < GetHeight(); j++) { + for(uint32 i = 0; i < GetWidth(); i++) { + uint32 pidx = j * GetWidth() + i; m_Pixels[pidx].ChangeBitDepth(depths); } } @@ -229,10 +388,10 @@ void Image::ExpandTo8888() { uint8 fractionDepth[4]; const uint8 fullDepth[4] = { 8, 8, 8, 8 }; - for(uint32 j = 0; j < m_Height; j++) { - for(uint32 i = 0; i < m_Width; i++) { + for(uint32 j = 0; j < GetHeight(); j++) { + for(uint32 i = 0; i < GetWidth(); i++) { - uint32 pidx = j * m_Width + i; + uint32 pidx = j * GetWidth() + i; m_Pixels[pidx].ChangeBitDepth(fullDepth); m_FractionalPixels[pidx].GetBitDepth(fractionDepth); @@ -252,20 +411,24 @@ void Image::ExpandTo8888() { } } -const Pixel &Image::GetPixel(int32 i, int32 j, EWrapMode wrapMode) { +const Pixel &Image::GetPixel(int32 i, int32 j, EWrapMode wrapMode) const { + return m_Pixels[GetPixelIndex(i, j, wrapMode)]; +} + +const uint32 Image::GetPixelIndex(int32 i, int32 j, EWrapMode wrapMode) const { while(i < 0) { if(wrapMode == eWrapMode_Clamp) { i = 0; } else { - i += m_Width; + i += GetWidth(); } } - while(i >= static_cast(m_Width)) { + while(i >= static_cast(GetWidth())) { if(wrapMode == eWrapMode_Clamp) { - i = m_Width - 1; + i = GetWidth() - 1; } else { - i -= m_Width; + i -= GetWidth(); } } @@ -273,46 +436,51 @@ const Pixel &Image::GetPixel(int32 i, int32 j, EWrapMode wrapMode) { if(wrapMode == eWrapMode_Clamp) { j = 0; } else { - j += m_Height; + j += GetHeight(); } } - while(j >= static_cast(m_Height)) { + while(j >= static_cast(GetHeight())) { if(wrapMode == eWrapMode_Clamp) { - j = m_Height - 1; + j = GetHeight() - 1; } else { - j -= m_Height; + j -= GetHeight(); } } - return m_Pixels[j * m_Width + i]; + uint32 idx = j * GetWidth() + i; + assert(idx >= 0); + assert(idx < GetWidth() * GetHeight()); + return idx; } Pixel & Image::operator()(uint32 i, uint32 j) { - assert(i < m_Width); - assert(j < m_Height); - return m_Pixels[j * m_Width + i]; + assert(i < GetWidth()); + assert(j < GetHeight()); + return m_Pixels[j * GetWidth() + i]; } const Pixel & Image::operator()(uint32 i, uint32 j) const { - assert(i < m_Width); - assert(j < m_Height); - return m_Pixels[j * m_Width + i]; + assert(i < GetWidth()); + assert(j < GetHeight()); + return m_Pixels[j * GetWidth() + i]; } void Image::DebugOutput(const char *filename) const { - uint32 *outPixels = new uint32[m_Width * m_Height]; + uint32 *outPixels = new uint32[GetWidth() * GetHeight()]; const uint8 fullDepth[4] = { 8, 8, 8, 8 }; - for(uint32 j = 0; j < m_Height; j++) { - for(uint32 i = 0; i < m_Width; i++) { - uint32 idx = j * m_Width + i; + for(uint32 j = 0; j < GetHeight(); j++) { + for(uint32 i = 0; i < GetWidth(); i++) { + uint32 idx = j * GetWidth() + i; Pixel p = m_Pixels[idx]; p.ChangeBitDepth(fullDepth); + p.A() = 255; + outPixels[idx] = p.PackRGBA(); } } - ::Image img(m_Width, m_Height, outPixels); + ::Image img(GetWidth(), GetHeight(), outPixels); char debugFilename[256]; snprintf(debugFilename, sizeof(debugFilename), "%s.png", filename); diff --git a/PVRTCEncoder/src/Image.h b/PVRTCEncoder/src/Image.h index fc7c751..959ba4c 100644 --- a/PVRTCEncoder/src/Image.h +++ b/PVRTCEncoder/src/Image.h @@ -69,7 +69,17 @@ class Image { ~Image(); void BilinearUpscale(uint32 xtimes, uint32 ytimes, - EWrapMode wrapMode = eWrapMode_Clamp); + EWrapMode wrapMode = eWrapMode_Wrap); + + // Downscales the image by taking an anisotropic diffusion approach + // with respect to the gradient of the intensity. In this way, we can + // preserve the most important image structures by not blurring across + // edge boundaries, which when upscaled will retain the structural + // image quality... + void ContentAwareDownscale(uint32 xtimes, uint32 ytimes, + EWrapMode wrapMode = eWrapMode_Wrap, + bool bOffsetNewPixels = false); + void ChangeBitDepth(const uint8 (&depths)[4]); void ExpandTo8888(); @@ -87,7 +97,8 @@ class Image { Pixel *m_Pixels; Pixel *m_FractionalPixels; - const Pixel &GetPixel(int32 i, int32 j, EWrapMode wrapMode = eWrapMode_Clamp); + const uint32 GetPixelIndex(int32 i, int32 j, EWrapMode wrapMode = eWrapMode_Clamp) const; + const Pixel &GetPixel(int32 i, int32 j, EWrapMode wrapMode = eWrapMode_Clamp) const; }; } // namespace PVRTCC diff --git a/PVRTCEncoder/src/Pixel.cpp b/PVRTCEncoder/src/Pixel.cpp index aa3d3a1..c4bfe86 100644 --- a/PVRTCEncoder/src/Pixel.cpp +++ b/PVRTCEncoder/src/Pixel.cpp @@ -108,6 +108,44 @@ namespace PVRTCC { } } + void Pixel::ToBits(uint8 *bits, uint32 numBytes, uint32 bitOffset) const { +#ifndef NDEBUG + uint32 bitDepthSum = bitOffset; + for(int i = 0; i < 4; i++) { + bitDepthSum += m_BitDepth[i]; + } + assert((bitDepthSum / 8) < numBytes); +#endif + + uint8 byteIdx = 0; + while(bitOffset > 8) { + byteIdx++; + bitOffset -= 8; + } + + uint8 bitIdx = bitOffset; + for(int i = 3; i >= 0; i--) { + uint8 val = Component(i); + uint8 depth = m_BitDepth[i]; + + if(depth + bitIdx > 8) { + uint8 nextBitIdx = depth - (8 - bitIdx); + uint16 v = static_cast(val); + bits[byteIdx++] |= (v << bitIdx) & 0xFF; + bitIdx = nextBitIdx; + bits[byteIdx] = (v >> (depth - bitIdx)) & 0xFF; + } else { + bits[byteIdx] |= (val << bitIdx) & 0xFF; + bitIdx += depth; + } + + if(bitIdx == 8) { + bitIdx = 0; + byteIdx++; + } + } + } + uint8 Pixel::ChangeBitDepth(uint8 val, uint8 oldDepth, uint8 newDepth) { assert(newDepth <= 8); assert(oldDepth <= 8); @@ -134,7 +172,10 @@ namespace PVRTCC { return 0xFF; } else { uint8 bitsWasted = oldDepth - newDepth; - return val >> bitsWasted; + uint16 v = static_cast(val); + v = (v + (1 << (bitsWasted - 1))) >> bitsWasted; + v = ::std::min(::std::max(0, v), (1 << newDepth) - 1); + return v; } } diff --git a/PVRTCEncoder/src/Pixel.h b/PVRTCEncoder/src/Pixel.h index dc5a5b1..f8821f4 100644 --- a/PVRTCEncoder/src/Pixel.h +++ b/PVRTCEncoder/src/Pixel.h @@ -63,9 +63,14 @@ class Pixel { for(int i = 0; i < 4; i++) m_BitDepth[i] = 8; } - explicit Pixel(const uint8 *bits, - const uint8 channelDepth[4] = static_cast(0), - uint8 bitOffset = 0) { + explicit Pixel(uint32 rgba) { + for(int i = 0; i < 4; i++) m_BitDepth[i] = 8; + UnpackRGBA(rgba); + } + + Pixel(const uint8 *bits, + const uint8 channelDepth[4] = static_cast(0), + uint8 bitOffset = 0) { FromBits(bits, channelDepth, bitOffset); } @@ -77,6 +82,13 @@ class Pixel { const uint8 channelDepth[4] = static_cast(0), uint8 bitOffset = 0); + // This function is the converse of FromBits. It will pack a pixel + // into a specified buffer based on the bit depth of the pixel. The + // bitOffset determines at which bit to start from. The bits are written + // starting from the LSB of bits[0]. numBytes is a sanity check and isn't + // used in release mode. + void ToBits(uint8 *bits, uint32 numBytes, uint32 bitOffset = 0) const; + // Changes the depth of each pixel. This scales the values to // the appropriate bit depth by either truncating the least // significant bits when going from larger to smaller bit depth diff --git a/PVRTCEncoder/test/BlockTest.cpp b/PVRTCEncoder/test/BlockTest.cpp index ca23353..fc9712e 100644 --- a/PVRTCEncoder/test/BlockTest.cpp +++ b/PVRTCEncoder/test/BlockTest.cpp @@ -241,3 +241,94 @@ TEST(Block, Get2BPPSubMode) { b = PVRTCC::Block(data); EXPECT_EQ(b.Get2BPPSubMode(), PVRTCC::Block::e2BPPSubMode_Vertical); } + +TEST(Block, SetColorAandB) { + PVRTCC::Block b; + PVRTCC::Pixel color; + color.A() = 212; + color.R() = 200; + color.G() = 100; + color.B() = -120; + b.SetColorA(color); + PVRTCC::Pixel cA = b.GetColorA(); + + uint8 bitDepth[4] = { 0, 5, 5, 5 }; + color.ChangeBitDepth(bitDepth); + + EXPECT_FALSE(memcmp(&color, &cA, sizeof(color))); + + memset(bitDepth, 8, sizeof(bitDepth)); + color.ChangeBitDepth(bitDepth); + + color.A() = 212; + color.R() = 200; + color.G() = 100; + color.B() = -120; + b.SetColorB(color, true); + PVRTCC::Pixel cB = b.GetColorB(); + + uint8 tBitDepth[4] = { 0, 5, 5, 4 }; + color.ChangeBitDepth(tBitDepth); + + EXPECT_FALSE(memcmp(&color, &cB, sizeof(color))); + + memset(bitDepth, 8, sizeof(bitDepth)); + color.ChangeBitDepth(bitDepth); + + color.A() = 100; + color.R() = 200; + color.G() = 100; + color.B() = -120; + b.SetColorB(color, true); + PVRTCC::Pixel cC = b.GetColorB(); + + uint8 uBitDepth[4] = { 3, 4, 4, 3 }; + color.ChangeBitDepth(uBitDepth); + + EXPECT_FALSE(memcmp(&color, &cC, sizeof(color))); +} + +TEST(Block, SetLerpValue) { + PVRTCC::Block b; + + for(int i = 0; i < 16; i++) { + b.SetLerpValue(i, i%4); + } + + for(int i = 0; i < 16; i++) { + EXPECT_EQ(b.GetLerpValue(i), i % 4); + } +} + +TEST(Block, PackBlock) { + PVRTCC::Block b; + + PVRTCC::Pixel cA, cB; + + cA.A() = 0xFF; + cA.R() = 0xFF; + cA.G() = 0x80; + cA.B() = 0x00; + + cB.A() = 0x80; + cB.R() = 0x7F; + cB.G() = 0x00; + cB.B() = 0xFF; + + b.SetColorA(cA); + b.SetColorB(cB, true); + + for(int i = 0; i < 16; i++) { + b.SetLerpValue(i, i%4); + } + + b.SetModeBit(false); + EXPECT_EQ(b.Pack(), 0xFE00480EE4E4E4E4UL); + + b.SetModeBit(true); + EXPECT_EQ(b.Pack(), 0xFE00480FE4E4E4E4UL); + + b.SetColorB(cB); + b.SetModeBit(false); + EXPECT_EQ(b.Pack(), 0xFE00C01EE4E4E4E4UL); +} diff --git a/PVRTCEncoder/test/CMakeLists.txt b/PVRTCEncoder/test/CMakeLists.txt index a027222..67a5b20 100644 --- a/PVRTCEncoder/test/CMakeLists.txt +++ b/PVRTCEncoder/test/CMakeLists.txt @@ -101,8 +101,16 @@ IF(PVRTEXLIB_FOUND) TARGET_LINK_LIBRARIES(${TEST_NAME} FasTCIO) TARGET_LINK_LIBRARIES(${TEST_NAME} FasTCCore) - ADD_TEST(${TEST_NAME} - ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} - ) + IF(MSVC) + ADD_TEST(${TEST_NAME} + ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/Debug/${TEST_NAME} + ) + ELSE() + ADD_TEST(${TEST_NAME} + ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} + ) + ENDIF() + ENDIF(PVRTEXLIB_FOUND) diff --git a/PVRTCEncoder/test/DecompTestPVR.cpp b/PVRTCEncoder/test/DecompTestPVR.cpp index e5a64f2..2d29aa1 100644 --- a/PVRTCEncoder/test/DecompTestPVR.cpp +++ b/PVRTCEncoder/test/DecompTestPVR.cpp @@ -73,7 +73,7 @@ class ImageTester { pvrtexture::CPVRTexture pvrTex(filename); const uint8 *data = static_cast(pvrTex.getDataPtr()); - ASSERT_TRUE(data); + assert(data); const pvrtexture::CPVRTextureHeader &hdr = pvrTex.getHeader(); const uint32 w = hdr.getWidth(); @@ -96,7 +96,7 @@ class ImageTester { uint32 *libPixels = static_cast(pvrTex.getDataPtr()); - for(int i = 0; i < w*h; i++) { + for(uint32 i = 0; i < w*h; i++) { EXPECT_EQ(PixelPrinter(libPixels[i]), PixelPrinter(outPixels[i])); } diff --git a/PVRTCEncoder/test/ImageTest.cpp b/PVRTCEncoder/test/ImageTest.cpp index 7b71635..527e530 100644 --- a/PVRTCEncoder/test/ImageTest.cpp +++ b/PVRTCEncoder/test/ImageTest.cpp @@ -131,7 +131,7 @@ TEST(Image, BilinearUpscale) { } PVRTCC::Image img(4, 4, pxs); - img.BilinearUpscale(1, 1); + img.BilinearUpscale(1, 1, PVRTCC::eWrapMode_Clamp); EXPECT_EQ(img.GetWidth(), static_cast(8)); EXPECT_EQ(img.GetHeight(), static_cast(8)); @@ -171,7 +171,7 @@ TEST(Image, BilinearUpscaleMaintainsPixels) { } PVRTCC::Image img(w, h, pxs); - img.BilinearUpscale(2, 2); + img.BilinearUpscale(2, 2, PVRTCC::eWrapMode_Clamp); EXPECT_EQ(img.GetWidth(), w << 2); EXPECT_EQ(img.GetHeight(), h << 2); @@ -199,7 +199,7 @@ TEST(Image, NonuniformBilinearUpscale) { } PVRTCC::Image img(kHeight, kWidth, pxs); - img.BilinearUpscale(2, 1); + img.BilinearUpscale(2, 1, PVRTCC::eWrapMode_Clamp); EXPECT_EQ(img.GetWidth(), static_cast(kWidth << 2)); EXPECT_EQ(img.GetHeight(), static_cast(kHeight << 1)); @@ -279,6 +279,33 @@ TEST(Image, BilinearUpscaleWrapped) { } } +TEST(Image, ContentAwareDownscale) { + PVRTCC::Image img(8, 8); + for(uint32 j = 0; j < img.GetHeight(); j++) { + for(uint32 i = 0; i < img.GetWidth(); i++) { + if(j < 4) { + img(i, j) = PVRTCC::Pixel( 0xFF000000 ); + } else { + img(i, j) = PVRTCC::Pixel( 0xFF0000FF ); + } + } + } + + img.ContentAwareDownscale(1, 1); + EXPECT_EQ(img.GetWidth(), static_cast(4)); + EXPECT_EQ(img.GetHeight(), static_cast(4)); + + for(uint32 j = 0; j < img.GetHeight(); j++) { + for(uint32 i = 0; i < img.GetWidth(); i++) { + if(j < 2) { + EXPECT_EQ(img(i, j).R(), 0); + } else { + EXPECT_EQ(img(i, j).R(), 255); + } + } + } +} + TEST(Image, ChangeBitDepth) { PVRTCC::Image img(4, 4); diff --git a/PVRTCEncoder/test/PixelTest.cpp b/PVRTCEncoder/test/PixelTest.cpp index b8b4d2a..decab5b 100644 --- a/PVRTCEncoder/test/PixelTest.cpp +++ b/PVRTCEncoder/test/PixelTest.cpp @@ -133,13 +133,39 @@ TEST(Pixel, FromBitsAndAssociatedConstructor) { } } +TEST(Pixel, ToBits) { + PVRTCC::Pixel p; + + uint8 bitDepth[4] = { 2, 8, 1, 7 }; + p.ChangeBitDepth(bitDepth); + + p.A() = 0x2; + p.R() = 0x56; + p.G() = 0; + p.B() = 0x4F; + + uint8 bits[3]; + memset(bits, 0, sizeof(bits)); + p.ToBits(bits, sizeof(bits)); + + EXPECT_EQ(bits[0], 0x4F); + EXPECT_EQ(bits[1], 0x56); + EXPECT_EQ(bits[2], 0x2); + + memset(bits, 0, sizeof(bits)); + p.ToBits(bits, 3, 2); + EXPECT_EQ(bits[0], 0x3C); + EXPECT_EQ(bits[1], 0x59); + EXPECT_EQ(bits[2], 0x09); +} + TEST(Pixel, ChangeChannelBitDepth) { uint8 val = 0x43; uint8 depth = 7; EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 8), 0x87); EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 7), 0x43); - EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 6), 0x21); + EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 6), 0x22); EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 2), 0x2); EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 0), 0xFF); @@ -149,7 +175,7 @@ TEST(Pixel, ChangeChannelBitDepth) { EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 8), 0x6D); EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 6), 0x1B); EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 3), 0x03); - EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 2), 0x01); + EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 2), 0x02); EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 0), 0xFF); } @@ -206,11 +232,19 @@ TEST(Pixel, UnpackRGBA) { EXPECT_EQ(p.G(), 0xB3); EXPECT_EQ(p.R(), 0xFE); + p = PVRTCC::Pixel(rgba); + EXPECT_EQ(p.A(), 0x46); + EXPECT_EQ(p.B(), 0x19); + EXPECT_EQ(p.G(), 0xB3); + EXPECT_EQ(p.R(), 0xFE); + + p = PVRTCC::Pixel(); uint8 newBitDepth[4] = { 3, 5, 2, 1 }; // A R G B p.ChangeBitDepth(newBitDepth); + p.UnpackRGBA(rgba); EXPECT_EQ(p.A(), 0x2); EXPECT_EQ(p.B(), 0x0); - EXPECT_EQ(p.G(), 0x2); + EXPECT_EQ(p.G(), 0x3); EXPECT_EQ(p.R(), 0x1f); }