Merge PVRTC compressor into split library.

This commit is contained in:
Pavel Krajcevski 2013-09-27 17:30:16 -04:00
commit dcf389d346
29 changed files with 1228 additions and 205 deletions

View file

@ -150,4 +150,4 @@ ADD_LIBRARY( BPTCEncoder
${SOURCES} ${SOURCES}
) )
TARGET_LINK_LIBRARIES( BPTCEncoder FasTCBase ) TARGET_LINK_LIBRARIES( BPTCEncoder FasTCBase )

View file

@ -49,17 +49,29 @@
class Image { class Image {
public: public:
Image(uint32 width, uint32 height, const uint32 *pixels); Image(uint32 width, uint32 height,
const uint32 *pixels,
bool bBlockStreamOrder = false);
Image(const Image &); Image(const Image &);
Image &operator=(const Image &); Image &operator=(const Image &);
virtual ~Image(); virtual ~Image();
virtual Image *Clone() const {
return new Image(*this);
};
const uint8 *RawData() const { return m_Data; } const uint8 *RawData() const { return m_Data; }
uint32 GetWidth() const { return m_Width; } uint32 GetWidth() const { return m_Width; }
uint32 GetHeight() const { return m_Height; } 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; } bool GetBlockStreamOrder() const { return m_bBlockStreamOrder; }
double ComputePSNR(Image *other); double ComputePSNR(Image *other);
@ -77,6 +89,9 @@ class Image {
protected: protected:
uint8 *m_Data; uint8 *m_Data;
void ConvertToBlockStreamOrder();
void ConvertFromBlockStreamOrder();
}; };
#endif // __TEXCOMP_IMAGE_H__ #endif // __TEXCOMP_IMAGE_H__

View file

@ -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_Width(width)
, m_Height(height) , m_Height(height)
, m_bBlockStreamOrder(false) , m_bBlockStreamOrder(bBlockStreamOrder)
{ {
if(pixels) { if(pixels) {
m_Data = new uint8[4 * m_Width * m_Height]; 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) { Image &Image::operator=(const Image &other) {
@ -105,13 +111,6 @@ Image &Image::operator=(const Image &other) {
return *this; return *this;
} }
Image::~Image() {
if(m_Data) {
delete [] m_Data;
m_Data = 0;
}
}
double Image::ComputePSNR(Image *other) { double Image::ComputePSNR(Image *other) {
if(!other) if(!other)
return -1.0; return -1.0;
@ -130,42 +129,88 @@ double Image::ComputePSNR(Image *other) {
const uint8 *otherData = const uint8 *otherData =
reinterpret_cast<const uint8 *>(other->GetRGBA()); reinterpret_cast<const uint8 *>(other->GetRGBA());
const double wr = 1.0; // const double w[3] = { 0.2126, 0.7152, 0.0722 };
const double wg = 1.0; const double w[3] = { 1.0, 1.0, 1.0 };
const double wb = 1.0;
double MSE = 0.0; double mse = 0.0;
const uint32 imageSz = GetWidth() * GetHeight() * 4; const uint32 imageSz = GetWidth() * GetHeight() * 4;
for(uint32 i = 0; i < imageSz; i+=4) { for(uint32 i = 0; i < imageSz; i+=4) {
const unsigned char *ourPixel = ourData + i; const unsigned char *pixelDataRaw = ourData + i;
const unsigned char *otherPixel = otherData + i; const unsigned char *pixelDataUncomp = otherData + i;
double ourAlphaScale = double(ourPixel[3]) / 255.0; float r[4], u[4];
double otherAlphaScale = double(otherPixel[3]) / 255.0; for(uint32 c = 0; c < 4; c++) {
double dr = double(sad(ourAlphaScale * ourPixel[0], if(c == 3) {
otherAlphaScale * otherPixel[0])) * wr; r[c] = pixelDataRaw[c] / 255.0;
double dg = double(sad(ourAlphaScale * ourPixel[1], u[c] = pixelDataUncomp[c] / 255.0;
otherAlphaScale * otherPixel[1])) * wg; } else {
double db = double(sad(ourAlphaScale * ourPixel[2], r[c] = static_cast<double>(pixelDataRaw[c]) * w[c];
otherAlphaScale * otherPixel[2])) * wb; u[c] = static_cast<double>(pixelDataUncomp[c]) * w[c];
}
const double pixelMSE = }
(double(dr) * double(dr)) +
(double(dg) * double(dg)) + for(uint32 c = 0; c < 3; c++) {
(double(db) * double(db)); double diff = (r[3] * r[c] - u[3] * u[c]);
mse += diff * diff;
//fprintf(stderr, "Pixel MSE: %f\n", pixelMSE); }
MSE += pixelMSE;
} }
MSE /= (double(GetWidth()) * double(GetHeight())); mse /= GetWidth() * GetHeight();
double MAXI = const double C = 255.0 * 255.0;
(255.0 * wr) * (255.0 * wr) + double maxi = (w[0]*w[0] + w[1]*w[1] + w[2]*w[2]) * C;
(255.0 * wg) * (255.0 * wg) + return 10 * log10(maxi/mse);
(255.0 * wb) * (255.0 * wb); }
double PSNR = 10 * log10(MAXI/MSE); void Image::ConvertToBlockStreamOrder() {
return PSNR; 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<uint32 *>(m_Data)[y*GetWidth() + x];
}
}
}
delete m_Data;
m_Data = reinterpret_cast<uint8 *>(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<uint32 *>(m_Data)[offset + t];
}
}
}
delete m_Data;
m_Data = reinterpret_cast<uint8 *>(newPixelData);
m_bBlockStreamOrder = false;
} }

View file

@ -53,6 +53,7 @@
void PrintUsage() { void PrintUsage() {
fprintf(stderr, "Usage: tc [OPTIONS] imagefile\n"); fprintf(stderr, "Usage: tc [OPTIONS] imagefile\n");
fprintf(stderr, "\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-l\t\tSave an output log.\n");
fprintf(stderr, "\t-q <quality>\tSet compression quality level. Default: 50\n"); fprintf(stderr, "\t-q <quality>\tSet compression quality level. Default: 50\n");
fprintf(stderr, "\t-n <num>\tCompress the image num times and give the average time and PSNR. Default: 1\n"); fprintf(stderr, "\t-n <num>\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 bUseSIMD = false;
bool bSaveLog = false; bool bSaveLog = false;
bool bUseAtomics = false; bool bUseAtomics = false;
ECompressionFormat format = eCompressionFormat_BPTC;
bool knowArg = false; bool knowArg = false;
do { do {
knowArg = false; knowArg = false;
@ -110,6 +112,23 @@ int main(int argc, char **argv) {
continue; 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) { if(strcmp(argv[fileArg], "-l") == 0) {
fileArg++; fileArg++;
bSaveLog = true; bSaveLog = true;
@ -187,6 +206,9 @@ int main(int argc, char **argv) {
} }
Image img = Image(*file.GetImage()); Image img = Image(*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; BlockStatManager *statManager = NULL;
@ -195,6 +217,7 @@ int main(int argc, char **argv) {
} }
SCompressionSettings settings; SCompressionSettings settings;
settings.format = format;
settings.bUseSIMD = bUseSIMD; settings.bUseSIMD = bUseSIMD;
settings.bUseAtomics = bUseAtomics; settings.bUseAtomics = bUseAtomics;
settings.iNumThreads = numThreads; settings.iNumThreads = numThreads;
@ -223,8 +246,13 @@ int main(int argc, char **argv) {
statManager->ToFile(logname); statManager->ToFile(logname);
} }
Image cImg(*ci); if(format == eCompressionFormat_BPTC) {
ImageFile cImgFile (strcat(basename, "-bc7.png"), eFileFormat_PNG, cImg); strcat(basename, "-bc7.png");
} else if(format == eCompressionFormat_PVRTC) {
strcat(basename, "-pvrtc.png");
}
ImageFile cImgFile (basename, eFileFormat_PNG, *ci);
cImgFile.Write(); cImgFile.Write();
// Cleanup // Cleanup

View file

@ -54,6 +54,7 @@
void PrintUsage() { void PrintUsage() {
fprintf(stderr, "Usage: tc [OPTIONS] imagefile\n"); fprintf(stderr, "Usage: tc [OPTIONS] imagefile\n");
fprintf(stderr, "\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-l\t\tSave an output log.\n");
fprintf(stderr, "\t-q <quality>\tSet compression quality level. Default: 50\n"); fprintf(stderr, "\t-q <quality>\tSet compression quality level. Default: 50\n");
fprintf(stderr, "\t-n <num>\tCompress the image num times and give the average time and PSNR. Default: 1\n"); fprintf(stderr, "\t-n <num>\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 bUseSIMD = false;
bool bSaveLog = false; bool bSaveLog = false;
bool bUseAtomics = false; bool bUseAtomics = false;
ECompressionFormat format = eCompressionFormat_BPTC;
bool knowArg = false; bool knowArg = false;
do { do {
knowArg = false; 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) { if(strcmp(argv[fileArg], "-n") == 0) {
fileArg++; fileArg++;
@ -192,15 +211,19 @@ int _tmain(int argc, _TCHAR* argv[])
return 1; 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; BlockStatManager *statManager = NULL;
if(bSaveLog) { if(bSaveLog) {
statManager = new BlockStatManager(numBlocks); statManager = new BlockStatManager(numBlocks);
} }
SCompressionSettings settings; SCompressionSettings settings;
settings.format = format;
settings.bUseSIMD = bUseSIMD; settings.bUseSIMD = bUseSIMD;
settings.bUseAtomics = bUseAtomics; settings.bUseAtomics = bUseAtomics;
settings.iNumThreads = numThreads; settings.iNumThreads = numThreads;
@ -226,11 +249,16 @@ int _tmain(int argc, _TCHAR* argv[])
if(bSaveLog) { if(bSaveLog) {
strcat_s(basename, ".log"); strcat_s(basename, ".log");
statManager->ToFile(basename); statManager->ToFile(basename);
basename[strlen(basename) - 4] = '\0'; basename[strlen(basename) - 4] = '\0';
} }
strcat_s(basename, "-bc7.png");
Image cImg (*ci); if(format == eCompressionFormat_BPTC) {
ImageFile cImgFile (basename, eFileFormat_PNG, cImg); strcat_s(basename, "-bc7.png");
} else if(format == eCompressionFormat_PVRTC) {
strcat_s(basename, "-pvrtc.png");
}
ImageFile cImgFile (basename, eFileFormat_PNG, *ci);
cImgFile.Write(); cImgFile.Write();
// Cleanup // Cleanup

View file

@ -56,30 +56,51 @@
# PVRTEXLIB_LIBRARIES - The libraries needed to use PVRTexLib # PVRTEXLIB_LIBRARIES - The libraries needed to use PVRTexLib
IF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") IF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
SET( PVRTEXLIB_ROOT "/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library" )
find_path( find_path(
PVRTEXLIB_INCLUDE_DIR PVRTexture.h PVRTEXLIB_INCLUDE_DIR PVRTexture.h
PATHS "/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Include" PATHS ${PVRTEXLIB_ROOT}/Include
) )
find_library(PVRTEXLIB_LIB PVRTexLib find_library(PVRTEXLIB_LIB PVRTexLib
PATHS "/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/OSX_x86/Static" PATHS ${PVRTEXLIB_ROOT}/OSX_x86/Static
"/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/OSX_x86/Dynamic" ${PVRTEXLIB_ROOT}/OSX_x86/Dynamic
) )
ELSEIF(MSVC) ELSEIF (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
SET( PVRTEXLIB_ROOT "/opt/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library" )
find_path( find_path(
PVRTEXLIB_INCLUDE_DIR PVRTexture.h 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) IF(${CMAKE_GENERATOR} MATCHES Win64)
find_library(PVRTEXLIB_LIB PVRTexLib find_library(PVRTEXLIB_LIB PVRTexLib
PATHS "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_64/Static" PATHS ${PVRTEXLIB_ROOT}/Windows_x86_64/Static
"C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_64/Dynamic" ${PVRTEXLIB_ROOT}/Windows_x86_64/Dynamic
) )
ELSE() ELSE()
find_library(PVRTEXLIB_LIB PVRTexLib find_library(PVRTEXLIB_LIB PVRTexLib
PATHS "C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_32/Static" PATHS ${PVRTEXLIB_ROOT}/Windows_x86_32/Static
"C:/Imagination/PowerVR/GraphicsSDK/PVRTexTool/Library/Windows_x86_32/Dynamic" ${PVRTEXLIB_ROOT}/Windows_x86_32/Dynamic
) )
ENDIF() ENDIF()
ENDIF() ENDIF()
@ -93,4 +114,4 @@ include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PVRTexLib DEFAULT_MSG find_package_handle_standard_args(PVRTexLib DEFAULT_MSG
PVRTEXLIB_LIB PVRTEXLIB_INCLUDE_DIR) PVRTEXLIB_LIB PVRTEXLIB_INCLUDE_DIR)
mark_as_advanced(PVRTEXLIB_INCLUDE_DIR PVRTEXLIB_LIB ) mark_as_advanced( PVRTEXLIB_ROOT PVRTEXLIB_INCLUDE_DIR PVRTEXLIB_LIB )

View file

@ -77,6 +77,8 @@ ELSE()
ENDIF() ENDIF()
INCLUDE_DIRECTORIES( ${FasTC_SOURCE_DIR}/Base/include ) 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_SOURCE_DIR}/BPTCEncoder/include )
INCLUDE_DIRECTORIES( ${FasTC_BINARY_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 FasTCBase )
TARGET_LINK_LIBRARIES( FasTCCore FasTCIO ) TARGET_LINK_LIBRARIES( FasTCCore FasTCIO )
TARGET_LINK_LIBRARIES( FasTCCore BPTCEncoder ) TARGET_LINK_LIBRARIES( FasTCCore BPTCEncoder )
TARGET_LINK_LIBRARIES( FasTCCore PVRTCEncoder )
IF( THREAD_API MATCHES "Boost" ) IF( THREAD_API MATCHES "Boost" )
TARGET_LINK_LIBRARIES( FasTCCore ${Boost_LIBRARIES} ) TARGET_LINK_LIBRARIES( FasTCCore ${Boost_LIBRARIES} )

View file

@ -44,10 +44,13 @@
#ifndef _COMPRESSED_IMAGE_H_ #ifndef _COMPRESSED_IMAGE_H_
#define _COMPRESSED_IMAGE_H_ #define _COMPRESSED_IMAGE_H_
#include "TexCompTypes.h"
enum ECompressionFormat { enum ECompressionFormat {
eCompressionFormat_DXT1, eCompressionFormat_DXT1,
eCompressionFormat_DXT5, eCompressionFormat_DXT5,
eCompressionFormat_BPTC, eCompressionFormat_BPTC,
eCompressionFormat_PVRTC,
kNumCompressionFormats kNumCompressionFormats
}; };
@ -55,37 +58,47 @@ enum ECompressionFormat {
#include "Image.h" #include "Image.h"
class CompressedImage : public Image { class CompressedImage : public Image {
private: private:
ECompressionFormat m_Format; ECompressionFormat m_Format;
uint32 *m_RGBAData; uint32 *m_RGBAData;
uint32 m_DataSz; uint32 m_DataSz;
void InitData(const unsigned char *withData);
public: public:
CompressedImage(); CompressedImage(const CompressedImage &);
CompressedImage &operator=(const CompressedImage &);
// Create a compressed image from the given data according to // Create a compressed image from the given data according to
// the passed format. The size of the data is expected to conform // the passed format. The size of the data is expected to conform
// to the width, height, and format specified. // to the width, height, and format specified.
CompressedImage( CompressedImage(
const unsigned int width, const uint32 width,
const unsigned int height, const uint32 height,
const ECompressionFormat format, const ECompressionFormat format,
const unsigned char *data const uint8 *data
); );
CompressedImage( const CompressedImage &other );
virtual ~CompressedImage(); virtual ~CompressedImage();
virtual Image *Clone() const {
return new CompressedImage(*this);
}
virtual void ComputeRGBA(); virtual void ComputeRGBA();
virtual const uint32 *GetRGBA() const { return m_RGBAData; } 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 // Decompress the compressed image data into outBuf. outBufSz is expected
// to be the proper size determined by the width, height, and format. // 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 // !FIXME! We should have a function to explicitly return the in/out buf
// size for a given compressed image. // 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_ #endif // _COMPRESSED_IMAGE_H_

View file

@ -96,7 +96,8 @@ extern CompressedImage *CompressImage(Image *img, const SCompressionSettings &se
extern bool CompressImageData( extern bool CompressImageData(
const unsigned char *data, const unsigned char *data,
const unsigned int dataSz, const unsigned int width,
const unsigned int height,
unsigned char *cmpData, unsigned char *cmpData,
const unsigned int cmpDataSz, const unsigned int cmpDataSz,
const SCompressionSettings &settings const SCompressionSettings &settings

View file

@ -50,14 +50,18 @@
#include "TexCompTypes.h" #include "TexCompTypes.h"
#include "BC7Compressor.h" #include "BC7Compressor.h"
#include "PVRTCCompressor.h"
CompressedImage::CompressedImage( const CompressedImage &other ) CompressedImage::CompressedImage( const CompressedImage &other )
: Image(other) : Image(other)
, m_Format(other.m_Format) , m_Format(other.m_Format)
, m_RGBAData(0) , 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( CompressedImage::CompressedImage(
@ -69,46 +73,36 @@ CompressedImage::CompressedImage(
: Image(width, height, NULL) : Image(width, height, NULL)
, m_Format(format) , m_Format(format)
, m_RGBAData(0) , 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) { CompressedImage &CompressedImage::operator=(const CompressedImage &other) {
m_DataSz = 0; Image::operator=(other);
int uncompDataSz = GetWidth() * GetHeight() * 4; m_Format = other.m_Format;
m_DataSz = other.m_DataSz;
switch(m_Format) { if(other.m_RGBAData) {
default: assert(!"Not implemented!"); // Fall through V m_RGBAData = new uint32[GetWidth() * GetHeight()];
case eCompressionFormat_DXT1: m_DataSz = uncompDataSz / 8; break; memcpy(m_RGBAData, other.m_RGBAData, sizeof(uint32) * GetWidth() * GetHeight());
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() { CompressedImage::~CompressedImage() {
if(m_Data) { if(m_RGBAData) {
delete [] m_Data; delete m_RGBAData;
m_Data = NULL; m_RGBAData = NULL;
} }
} }
bool CompressedImage::DecompressImage(unsigned char *outBuf, unsigned int outBufSz) const { bool CompressedImage::DecompressImage(unsigned char *outBuf, unsigned int outBufSz) const {
// First make sure that we have enough data // First make sure that we have enough data
uint32 dataSz = 0; uint32 dataSz = GetUncompressedSize(m_DataSz, m_Format);
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;
}
if(dataSz > outBufSz) { if(dataSz > outBufSz) {
fprintf(stderr, "Not enough space to store entire decompressed image! " fprintf(stderr, "Not enough space to store entire decompressed image! "
"Got %d bytes, but need %d!\n", outBufSz, dataSz); "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()); DecompressionJob dj (m_Data, outBuf, GetWidth(), GetHeight());
switch(m_Format) { switch(m_Format) {
case eCompressionFormat_PVRTC:
{
PVRTCC::Decompress(dj);
}
break;
case eCompressionFormat_BPTC: case eCompressionFormat_BPTC:
{ {
BC7C::Decompress(dj); BC7C::Decompress(dj);
@ -146,3 +146,26 @@ void CompressedImage::ComputeRGBA() {
uint8 *pixelData = reinterpret_cast<uint8 *>(m_RGBAData); uint8 *pixelData = reinterpret_cast<uint8 *>(m_RGBAData);
DecompressImage(pixelData, GetWidth() * GetHeight() * 4); 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;
}

View file

@ -50,14 +50,14 @@
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
#include "CompressionFuncs.h"
#include "BC7Compressor.h" #include "BC7Compressor.h"
#include "Thread.h" #include "CompressionFuncs.h"
#include "WorkerQueue.h"
#include "ThreadGroup.h"
#include "ImageFile.h"
#include "Image.h" #include "Image.h"
#include "ImageFile.h"
#include "PVRTCCompressor.h"
#include "Thread.h"
#include "ThreadGroup.h"
#include "WorkerQueue.h"
template <typename T> template <typename T>
static void clamp(T &x, const T &minX, const T &maxX) { 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; return (a > b)? a - b : b - a;
} }
static void CompressPVRTC(const CompressionJob &cj) {
PVRTCC::Compress(cj);
}
SCompressionSettings:: SCompressionSettings() SCompressionSettings:: SCompressionSettings()
: format(eCompressionFormat_BPTC) : format(eCompressionFormat_BPTC)
, bUseSIMD(false) , bUseSIMD(false)
@ -87,6 +91,12 @@ static CompressionFuncWithStats ChooseFuncFromSettingsWithStats(const SCompress
} }
break; break;
case eCompressionFormat_PVRTC:
{
// !FIXME! actually implement one of these methods...
return NULL;
}
default: default:
{ {
assert(!"Not implemented!"); assert(!"Not implemented!");
@ -110,6 +120,11 @@ static CompressionFunc ChooseFuncFromSettings(const SCompressionSettings &s) {
} }
break; break;
case eCompressionFormat_PVRTC:
{
return CompressPVRTC;
}
default: default:
{ {
assert(!"Not implemented!"); assert(!"Not implemented!");
@ -124,8 +139,9 @@ static void ReportError(const char *msg) {
} }
static double CompressImageInSerial( static double CompressImageInSerial(
const unsigned char *imgData, const uint8 *imgData,
const unsigned int imgDataSz, const uint32 imgWidth,
const uint32 imgHeight,
const SCompressionSettings &settings, const SCompressionSettings &settings,
unsigned char *outBuf unsigned char *outBuf
) { ) {
@ -134,14 +150,14 @@ static double CompressImageInSerial(
double cmpTimeTotal = 0.0; double cmpTimeTotal = 0.0;
StopWatch stopWatch = StopWatch();
for(int i = 0; i < settings.iNumCompressions; i++) { for(int i = 0; i < settings.iNumCompressions; i++) {
StopWatch stopWatch = StopWatch();
stopWatch.Reset(); stopWatch.Reset();
stopWatch.Start(); stopWatch.Start();
// !FIXME! We're assuming that we have 4x4 blocks here... // !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) { if(fStats && settings.pStatManager) {
// !FIXME! Actually use the stat manager... // !FIXME! Actually use the stat manager...
//(*fStats)(cj, *(settings.pStatManager)); //(*fStats)(cj, *(settings.pStatManager));
@ -347,20 +363,14 @@ CompressedImage *CompressImage(
assert(dataSz > 0); assert(dataSz > 0);
// Allocate data based on the compression method // Allocate data based on the compression method
int cmpDataSz = 0; uint32 cmpDataSz = CompressedImage::GetCompressedSize(dataSz, settings.format);
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;
}
// Make sure that we have RGBA data... // Make sure that we have RGBA data...
img->ComputeRGBA(); img->ComputeRGBA();
unsigned char *cmpData = new unsigned char[cmpDataSz]; unsigned char *cmpData = new unsigned char[cmpDataSz];
const uint8 *pixelData = reinterpret_cast<const uint8 *>(img->GetRGBA()); const uint8 *pixelData = reinterpret_cast<const uint8 *>(img->GetRGBA());
CompressImageData(pixelData, dataSz, cmpData, cmpDataSz, settings); CompressImageData(pixelData, w, h, cmpData, cmpDataSz, settings);
outImg = new CompressedImage(w, h, settings.format, cmpData); outImg = new CompressedImage(w, h, settings.format, cmpData);
@ -370,12 +380,15 @@ CompressedImage *CompressImage(
bool CompressImageData( bool CompressImageData(
const unsigned char *data, const unsigned char *data,
const unsigned int dataSz, const unsigned int width,
const unsigned int height,
unsigned char *cmpData, unsigned char *cmpData,
const unsigned int cmpDataSz, const unsigned int cmpDataSz,
const SCompressionSettings &settings const SCompressionSettings &settings
) { ) {
uint32 dataSz = width * height * 4;
// Make sure that platform supports SSE if they chose this // Make sure that platform supports SSE if they chose this
// option... // option...
#ifndef HAS_SSE_41 #ifndef HAS_SSE_41
@ -390,15 +403,23 @@ bool CompressImageData(
return false; return false;
} }
// Allocate data based on the compression method uint32 numThreads = settings.iNumThreads;
uint32 cmpDataSzNeeded = 0; if(settings.format == eCompressionFormat_PVRTC &&
switch(settings.format) { (settings.iNumThreads > 1 || settings.pStatManager)) {
default: assert(!"Not implemented!"); // Fall through V if(settings.iNumThreads > 1) {
case eCompressionFormat_DXT1: cmpDataSzNeeded = dataSz / 8; break; ReportError("WARNING - PVRTC compressor does not support multithreading.");
case eCompressionFormat_DXT5: cmpDataSzNeeded = dataSz / 4; break; numThreads = 1;
case eCompressionFormat_BPTC: cmpDataSzNeeded = dataSz / 4; break; }
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) { if(cmpDataSzNeeded == 0) {
ReportError("Unknown compression format"); ReportError("Unknown compression format");
return false; return false;
@ -413,21 +434,17 @@ bool CompressImageData(
double cmpMSTime = 0.0; double cmpMSTime = 0.0;
if(settings.iNumThreads > 1) { if(numThreads > 1) {
if(settings.bUseAtomics) { if(settings.bUseAtomics) {
//!KLUDGE!
unsigned int height = 4;
unsigned int width = dataSz / 16;
cmpMSTime = CompressImageWithAtomics(data, width, height, settings, cmpData); cmpMSTime = CompressImageWithAtomics(data, width, height, settings, cmpData);
} } else if(settings.iJobSize > 0) {
else if(settings.iJobSize > 0)
cmpMSTime = CompressImageWithWorkerQueue(data, dataSz, settings, cmpData); cmpMSTime = CompressImageWithWorkerQueue(data, dataSz, settings, cmpData);
else } else {
cmpMSTime = CompressImageWithThreads(data, dataSz, settings, cmpData); cmpMSTime = CompressImageWithThreads(data, dataSz, settings, cmpData);
}
} }
else { else {
cmpMSTime = CompressImageInSerial(data, dataSz, settings, cmpData); cmpMSTime = CompressImageInSerial(data, width, height, settings, cmpData);
} }
// Report compression time // Report compression time

View file

@ -107,7 +107,7 @@ ImageFile::ImageFile(const CHAR *filename, EImageFileFormat format)
ImageFile::ImageFile(const char *filename, EImageFileFormat format, const Image &image) ImageFile::ImageFile(const char *filename, EImageFileFormat format, const Image &image)
: m_FileFormat(format) : m_FileFormat(format)
, m_Image(new Image(image)) , m_Image(image.Clone())
{ {
strncpy(m_Filename, filename, kMaxFilenameSz); strncpy(m_Filename, filename, kMaxFilenameSz);
} }
@ -207,7 +207,7 @@ Image *ImageFile::LoadImage(const unsigned char *rawImageData) const {
} }
uint32 *pixels = reinterpret_cast<uint32 *>(pixelData); uint32 *pixels = reinterpret_cast<uint32 *>(pixelData);
Image *i = new Image(loader->GetWidth(), loader->GetHeight(), pixels); Image *i = new Image(loader->GetWidth(), loader->GetHeight(), pixels, true);
// Cleanup // Cleanup
delete loader; delete loader;

View file

@ -84,11 +84,13 @@ public:
}; };
ImageWriterPNG::ImageWriterPNG(const Image &im) ImageWriterPNG::ImageWriterPNG(Image &im)
: ImageWriter(im.GetWidth(), im.GetHeight(), im.RawData()) : ImageWriter(im.GetWidth(), im.GetHeight(), im.RawData())
, m_bBlockStreamOrder(im.GetBlockStreamOrder()) , m_bBlockStreamOrder(im.GetBlockStreamOrder())
, m_StreamPosition(0) , m_StreamPosition(0)
{ {
im.ComputeRGBA();
m_PixelData = reinterpret_cast<const uint8 *>(im.GetRGBA());
} }
bool ImageWriterPNG::WriteImage() { bool ImageWriterPNG::WriteImage() {
@ -111,7 +113,6 @@ bool ImageWriterPNG::WriteImage() {
} }
/* Set image attributes. */ /* Set image attributes. */
png_set_IHDR (png_ptr, png_set_IHDR (png_ptr,
info_ptr, info_ptr,
m_Width, m_Width,
@ -123,7 +124,6 @@ bool ImageWriterPNG::WriteImage() {
PNG_FILTER_TYPE_DEFAULT); PNG_FILTER_TYPE_DEFAULT);
/* Initialize rows of PNG. */ /* Initialize rows of PNG. */
row_pointers = (png_byte **)png_malloc (png_ptr, m_Height * sizeof (png_byte *)); row_pointers = (png_byte **)png_malloc (png_ptr, m_Height * sizeof (png_byte *));
for (uint32 y = 0; y < m_Height; ++y) { for (uint32 y = 0; y < m_Height; ++y) {
png_byte *row = (png_byte *)png_malloc (png_ptr, sizeof (uint8) * m_Width * pixel_size); 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); *row++ = GetChannelForPixel(x, y, ch);
} }
} else { } else {
*(reinterpret_cast<uint32 *>(row) + x) = reinterpret_cast<uint32 *>(row)[x] =
(reinterpret_cast<const uint32 *>(m_PixelData))[y * m_Width + x]; reinterpret_cast<const uint32 *>(m_PixelData)[y * m_Width + x];
} }
} }
} }

View file

@ -50,7 +50,7 @@
class Image; class Image;
class ImageWriterPNG : public ImageWriter { class ImageWriterPNG : public ImageWriter {
public: public:
ImageWriterPNG(const Image &); ImageWriterPNG(Image &);
virtual ~ImageWriterPNG() { } virtual ~ImageWriterPNG() { }
virtual bool WriteImage(); virtual bool WriteImage();

View file

@ -62,6 +62,7 @@ SET( HEADERS
) )
SET( SOURCES SET( SOURCES
src/Compressor.cpp
src/Decompressor.cpp src/Decompressor.cpp
src/Pixel.cpp src/Pixel.cpp
src/Block.cpp src/Block.cpp

View file

@ -70,9 +70,18 @@ namespace PVRTCC {
// decompress the data. // decompress the data.
void Decompress(const DecompressionJob &, void Decompress(const DecompressionJob &,
bool bTwoBitMode = false, bool bTwoBitMode = false,
const EWrapMode wrapMode = eWrapMode_Clamp, const EWrapMode wrapMode = eWrapMode_Wrap,
bool bDebugImages = false); 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 } // namespace PVRTCC
#endif // PVRTCENCODER_INCLUDE_PVRTCCOMPRESSOR_H_ #endif // PVRTCENCODER_INCLUDE_PVRTCCOMPRESSOR_H_

View file

@ -83,6 +83,42 @@ namespace PVRTCC {
return m_ColorA; 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() { Pixel Block::GetColorB() {
if(m_ColorBCached) { if(m_ColorBCached) {
return m_ColorB; return m_ColorB;
@ -109,6 +145,17 @@ namespace PVRTCC {
return (m_LongData >> (texelIdx * 2)) & 0x3; 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<uint64>(0x3) << (texelIdx * 2));
m_LongData |= static_cast<uint64>(lerpVal & 0x3) << (texelIdx * 2);
}
Block::E2BPPSubMode Block::Get2BPPSubMode() const { Block::E2BPPSubMode Block::Get2BPPSubMode() const {
uint8 first = GetLerpValue(0); uint8 first = GetLerpValue(0);
if(!(first & 0x1)) { if(!(first & 0x1)) {
@ -145,4 +192,65 @@ namespace PVRTCC {
return ret; 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 } // namespace PVRTCC

View file

@ -60,15 +60,29 @@ namespace PVRTCC {
class Block { class Block {
public: public:
Block(): m_LongData(0) { }
explicit Block(const uint8 *data); explicit Block(const uint8 *data);
// Accessors for the A and B colors of the block.
Pixel GetColorA(); Pixel GetColorA();
void SetColorA(const Pixel &, bool transparent=false);
Pixel GetColorB(); Pixel GetColorB();
void SetColorB(const Pixel &, bool transparent=false);
bool GetModeBit() const { bool GetModeBit() const {
return static_cast<bool>((m_LongData >> 32) & 0x1); return static_cast<bool>((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 // 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. // 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 // The interleaved texel data is decided by averaging nearby texel modulation
@ -100,6 +114,11 @@ class Block {
// 12 13 14 15 // 12 13 14 15
uint8 GetLerpValue(uint32 texelIdx) const; 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 // 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 // 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, // and does the same operation as GetLerpValue. If the modulation bit is set,
@ -109,6 +128,12 @@ class Block {
// global information. // global information.
uint8 Get2BPPLerpValue(uint32 texelIdx) const; 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: private:
union { union {
uint8 m_ByteData[8]; uint8 m_ByteData[8];
@ -120,6 +145,11 @@ class Block {
bool m_ColorBCached; bool m_ColorBCached;
Pixel m_ColorB; 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 } // namespace PVRTCC

View file

@ -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 <otd@unc.edu>.
*
* 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 <pavel@cs.unc.edu>.
*
* 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
*
* <http://gamma.cs.unc.edu/FasTC/>
*/
#include "PVRTCCompressor.h"
#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
#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<uint32>(inx);
uint32 y = static_cast<uint32>(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 <typename T>
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<const uint32 *>(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<int16> 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<uint8> 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<uint16>(pa.Component(c));
uint16 vb = static_cast<uint16>(pb.Component(c));
uint16 vo = static_cast<uint16>(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<uint64> 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<uint64 *>(dcj.outBuf);
outPtr[idx] = blocks[j*blocksW + i];
}
}
}
} // namespace PVRTCC

View file

@ -304,11 +304,10 @@ namespace PVRTCC {
// First, extract all of the block information... // First, extract all of the block information...
std::vector<Block> blocks; std::vector<Block> blocks;
blocks.reserve(w * h);
const uint32 blocksW = bTwoBitMode? (w / 8) : (w / 4); const uint32 blocksW = bTwoBitMode? (w / 8) : (w / 4);
const uint32 blocksH = h / 4; const uint32 blocksH = h / 4;
const uint32 blockSz = 8; blocks.reserve(blocksW * blocksH);
for(uint32 j = 0; j < blocksH; j++) { for(uint32 j = 0; j < blocksH; j++) {
for(uint32 i = 0; i < blocksW; i++) { for(uint32 i = 0; i < blocksW; i++) {
@ -317,7 +316,7 @@ namespace PVRTCC {
// linearize them... // linearize them...
uint32 idx = Interleave(j, i); uint32 idx = Interleave(j, i);
uint32 offset = idx * blockSz; uint32 offset = idx * kBlockSize;
blocks.push_back( Block(dcj.inBuf + offset) ); blocks.push_back( Block(dcj.inBuf + offset) );
} }
} }

View file

@ -52,15 +52,32 @@
#include "Image.h" #include "Image.h"
#include <algorithm>
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <cmath>
#if _MSC_VER
# define _CRT_SECURE_NO_WARNINGS
# define snprintf _snprintf
#endif
#include "Pixel.h" #include "Pixel.h"
#include "../../Base/include/Image.h" #include "../../Base/include/Image.h"
#include "../../IO/include/ImageFile.h" #include "../../IO/include/ImageFile.h"
template <typename T>
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<float>((1 << bitDepth) - 1);
return static_cast<float>(channel) / denominator;
}
namespace PVRTCC { namespace PVRTCC {
Image::Image(uint32 height, uint32 width) Image::Image(uint32 height, uint32 width)
@ -83,27 +100,27 @@ Image::Image(uint32 height, uint32 width, const Pixel *pixels)
} }
Image::Image(const Image &other) Image::Image(const Image &other)
: m_Width(other.m_Width) : m_Width(other.GetWidth())
, m_Height(other.m_Height) , m_Height(other.GetHeight())
, m_Pixels(new Pixel[other.m_Width * other.m_Height]) , m_Pixels(new Pixel[other.GetWidth() * other.GetHeight()])
, m_FractionalPixels(new Pixel[other.m_Width * other.m_Height]) { , m_FractionalPixels(new Pixel[other.GetWidth() * other.GetHeight()]) {
memcpy(m_Pixels, other.m_Pixels, m_Width * m_Height * sizeof(Pixel)); memcpy(m_Pixels, other.m_Pixels, GetWidth() * GetHeight() * sizeof(Pixel));
} }
Image &Image::operator=(const Image &other) { Image &Image::operator=(const Image &other) {
m_Width = other.m_Width; m_Width = other.GetWidth();
m_Height = other.m_Height; m_Height = other.GetHeight();
assert(m_Pixels); assert(m_Pixels);
delete m_Pixels; delete m_Pixels;
m_Pixels = new Pixel[other.m_Width * other.m_Height]; m_Pixels = new Pixel[other.GetWidth() * other.GetHeight()];
memcpy(m_Pixels, other.m_Pixels, m_Width * m_Height * sizeof(Pixel)); memcpy(m_Pixels, other.m_Pixels, GetWidth() * GetHeight() * sizeof(Pixel));
assert(m_FractionalPixels); assert(m_FractionalPixels);
delete 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, memcpy(m_FractionalPixels, other.m_FractionalPixels,
m_Width * m_Height * sizeof(Pixel)); GetWidth() * GetHeight() * sizeof(Pixel));
return *this; return *this;
} }
@ -129,8 +146,8 @@ static bool CompareBitDepths(const uint8 (&depth1)[4],
void Image::BilinearUpscale(uint32 xtimes, uint32 ytimes, void Image::BilinearUpscale(uint32 xtimes, uint32 ytimes,
EWrapMode wrapMode) { EWrapMode wrapMode) {
const uint32 newWidth = m_Width << xtimes; const uint32 newWidth = GetWidth() << xtimes;
const uint32 newHeight = m_Height << ytimes; const uint32 newHeight = GetHeight() << ytimes;
const uint32 xscale = 1 << xtimes; const uint32 xscale = 1 << xtimes;
const uint32 xoffset = xscale >> 1; const uint32 xoffset = xscale >> 1;
@ -213,10 +230,152 @@ void Image::BilinearUpscale(uint32 xtimes, uint32 ytimes,
m_Height = newHeight; 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<float>((1 << bitDepth[c]) - 1);
result.Component(c) = static_cast<uint8>(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]) { void Image::ChangeBitDepth(const uint8 (&depths)[4]) {
for(uint32 j = 0; j < m_Height; j++) { for(uint32 j = 0; j < GetHeight(); j++) {
for(uint32 i = 0; i < m_Width; i++) { for(uint32 i = 0; i < GetWidth(); i++) {
uint32 pidx = j * m_Width + i; uint32 pidx = j * GetWidth() + i;
m_Pixels[pidx].ChangeBitDepth(depths); m_Pixels[pidx].ChangeBitDepth(depths);
} }
} }
@ -229,10 +388,10 @@ void Image::ExpandTo8888() {
uint8 fractionDepth[4]; uint8 fractionDepth[4];
const uint8 fullDepth[4] = { 8, 8, 8, 8 }; const uint8 fullDepth[4] = { 8, 8, 8, 8 };
for(uint32 j = 0; j < m_Height; j++) { for(uint32 j = 0; j < GetHeight(); j++) {
for(uint32 i = 0; i < m_Width; i++) { for(uint32 i = 0; i < GetWidth(); i++) {
uint32 pidx = j * m_Width + i; uint32 pidx = j * GetWidth() + i;
m_Pixels[pidx].ChangeBitDepth(fullDepth); m_Pixels[pidx].ChangeBitDepth(fullDepth);
m_FractionalPixels[pidx].GetBitDepth(fractionDepth); 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) { while(i < 0) {
if(wrapMode == eWrapMode_Clamp) { if(wrapMode == eWrapMode_Clamp) {
i = 0; i = 0;
} else { } else {
i += m_Width; i += GetWidth();
} }
} }
while(i >= static_cast<int32>(m_Width)) { while(i >= static_cast<int32>(GetWidth())) {
if(wrapMode == eWrapMode_Clamp) { if(wrapMode == eWrapMode_Clamp) {
i = m_Width - 1; i = GetWidth() - 1;
} else { } else {
i -= m_Width; i -= GetWidth();
} }
} }
@ -273,46 +436,51 @@ const Pixel &Image::GetPixel(int32 i, int32 j, EWrapMode wrapMode) {
if(wrapMode == eWrapMode_Clamp) { if(wrapMode == eWrapMode_Clamp) {
j = 0; j = 0;
} else { } else {
j += m_Height; j += GetHeight();
} }
} }
while(j >= static_cast<int32>(m_Height)) { while(j >= static_cast<int32>(GetHeight())) {
if(wrapMode == eWrapMode_Clamp) { if(wrapMode == eWrapMode_Clamp) {
j = m_Height - 1; j = GetHeight() - 1;
} else { } 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) { Pixel & Image::operator()(uint32 i, uint32 j) {
assert(i < m_Width); assert(i < GetWidth());
assert(j < m_Height); assert(j < GetHeight());
return m_Pixels[j * m_Width + i]; return m_Pixels[j * GetWidth() + i];
} }
const Pixel & Image::operator()(uint32 i, uint32 j) const { const Pixel & Image::operator()(uint32 i, uint32 j) const {
assert(i < m_Width); assert(i < GetWidth());
assert(j < m_Height); assert(j < GetHeight());
return m_Pixels[j * m_Width + i]; return m_Pixels[j * GetWidth() + i];
} }
void Image::DebugOutput(const char *filename) const { 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 }; const uint8 fullDepth[4] = { 8, 8, 8, 8 };
for(uint32 j = 0; j < m_Height; j++) { for(uint32 j = 0; j < GetHeight(); j++) {
for(uint32 i = 0; i < m_Width; i++) { for(uint32 i = 0; i < GetWidth(); i++) {
uint32 idx = j * m_Width + i; uint32 idx = j * GetWidth() + i;
Pixel p = m_Pixels[idx]; Pixel p = m_Pixels[idx];
p.ChangeBitDepth(fullDepth); p.ChangeBitDepth(fullDepth);
p.A() = 255;
outPixels[idx] = p.PackRGBA(); outPixels[idx] = p.PackRGBA();
} }
} }
::Image img(m_Width, m_Height, outPixels); ::Image img(GetWidth(), GetHeight(), outPixels);
char debugFilename[256]; char debugFilename[256];
snprintf(debugFilename, sizeof(debugFilename), "%s.png", filename); snprintf(debugFilename, sizeof(debugFilename), "%s.png", filename);

View file

@ -69,7 +69,17 @@ class Image {
~Image(); ~Image();
void BilinearUpscale(uint32 xtimes, uint32 ytimes, 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 ChangeBitDepth(const uint8 (&depths)[4]);
void ExpandTo8888(); void ExpandTo8888();
@ -87,7 +97,8 @@ class Image {
Pixel *m_Pixels; Pixel *m_Pixels;
Pixel *m_FractionalPixels; 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 } // namespace PVRTCC

View file

@ -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<uint16>(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) { uint8 Pixel::ChangeBitDepth(uint8 val, uint8 oldDepth, uint8 newDepth) {
assert(newDepth <= 8); assert(newDepth <= 8);
assert(oldDepth <= 8); assert(oldDepth <= 8);
@ -134,7 +172,10 @@ namespace PVRTCC {
return 0xFF; return 0xFF;
} else { } else {
uint8 bitsWasted = oldDepth - newDepth; uint8 bitsWasted = oldDepth - newDepth;
return val >> bitsWasted; uint16 v = static_cast<uint16>(val);
v = (v + (1 << (bitsWasted - 1))) >> bitsWasted;
v = ::std::min<uint16>(::std::max<uint16>(0, v), (1 << newDepth) - 1);
return v;
} }
} }

View file

@ -63,9 +63,14 @@ class Pixel {
for(int i = 0; i < 4; i++) m_BitDepth[i] = 8; for(int i = 0; i < 4; i++) m_BitDepth[i] = 8;
} }
explicit Pixel(const uint8 *bits, explicit Pixel(uint32 rgba) {
const uint8 channelDepth[4] = static_cast<uint8 *>(0), for(int i = 0; i < 4; i++) m_BitDepth[i] = 8;
uint8 bitOffset = 0) { UnpackRGBA(rgba);
}
Pixel(const uint8 *bits,
const uint8 channelDepth[4] = static_cast<uint8 *>(0),
uint8 bitOffset = 0) {
FromBits(bits, channelDepth, bitOffset); FromBits(bits, channelDepth, bitOffset);
} }
@ -77,6 +82,13 @@ class Pixel {
const uint8 channelDepth[4] = static_cast<uint8 *>(0), const uint8 channelDepth[4] = static_cast<uint8 *>(0),
uint8 bitOffset = 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 // Changes the depth of each pixel. This scales the values to
// the appropriate bit depth by either truncating the least // the appropriate bit depth by either truncating the least
// significant bits when going from larger to smaller bit depth // significant bits when going from larger to smaller bit depth

View file

@ -241,3 +241,94 @@ TEST(Block, Get2BPPSubMode) {
b = PVRTCC::Block(data); b = PVRTCC::Block(data);
EXPECT_EQ(b.Get2BPPSubMode(), PVRTCC::Block::e2BPPSubMode_Vertical); 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);
}

View file

@ -101,8 +101,16 @@ IF(PVRTEXLIB_FOUND)
TARGET_LINK_LIBRARIES(${TEST_NAME} FasTCIO) TARGET_LINK_LIBRARIES(${TEST_NAME} FasTCIO)
TARGET_LINK_LIBRARIES(${TEST_NAME} FasTCCore) TARGET_LINK_LIBRARIES(${TEST_NAME} FasTCCore)
ADD_TEST(${TEST_NAME} IF(MSVC)
${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} ADD_TEST(${TEST_NAME}
${CMAKE_CURRENT_BINARY_DIR}/${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) ENDIF(PVRTEXLIB_FOUND)

View file

@ -73,7 +73,7 @@ class ImageTester {
pvrtexture::CPVRTexture pvrTex(filename); pvrtexture::CPVRTexture pvrTex(filename);
const uint8 *data = static_cast<const uint8 *>(pvrTex.getDataPtr()); const uint8 *data = static_cast<const uint8 *>(pvrTex.getDataPtr());
ASSERT_TRUE(data); assert(data);
const pvrtexture::CPVRTextureHeader &hdr = pvrTex.getHeader(); const pvrtexture::CPVRTextureHeader &hdr = pvrTex.getHeader();
const uint32 w = hdr.getWidth(); const uint32 w = hdr.getWidth();
@ -96,7 +96,7 @@ class ImageTester {
uint32 *libPixels = static_cast<uint32 *>(pvrTex.getDataPtr()); uint32 *libPixels = static_cast<uint32 *>(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])); EXPECT_EQ(PixelPrinter(libPixels[i]), PixelPrinter(outPixels[i]));
} }

View file

@ -131,7 +131,7 @@ TEST(Image, BilinearUpscale) {
} }
PVRTCC::Image img(4, 4, pxs); PVRTCC::Image img(4, 4, pxs);
img.BilinearUpscale(1, 1); img.BilinearUpscale(1, 1, PVRTCC::eWrapMode_Clamp);
EXPECT_EQ(img.GetWidth(), static_cast<uint32>(8)); EXPECT_EQ(img.GetWidth(), static_cast<uint32>(8));
EXPECT_EQ(img.GetHeight(), static_cast<uint32>(8)); EXPECT_EQ(img.GetHeight(), static_cast<uint32>(8));
@ -171,7 +171,7 @@ TEST(Image, BilinearUpscaleMaintainsPixels) {
} }
PVRTCC::Image img(w, h, pxs); 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.GetWidth(), w << 2);
EXPECT_EQ(img.GetHeight(), h << 2); EXPECT_EQ(img.GetHeight(), h << 2);
@ -199,7 +199,7 @@ TEST(Image, NonuniformBilinearUpscale) {
} }
PVRTCC::Image img(kHeight, kWidth, pxs); PVRTCC::Image img(kHeight, kWidth, pxs);
img.BilinearUpscale(2, 1); img.BilinearUpscale(2, 1, PVRTCC::eWrapMode_Clamp);
EXPECT_EQ(img.GetWidth(), static_cast<uint32>(kWidth << 2)); EXPECT_EQ(img.GetWidth(), static_cast<uint32>(kWidth << 2));
EXPECT_EQ(img.GetHeight(), static_cast<uint32>(kHeight << 1)); EXPECT_EQ(img.GetHeight(), static_cast<uint32>(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<uint32>(4));
EXPECT_EQ(img.GetHeight(), static_cast<uint32>(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) { TEST(Image, ChangeBitDepth) {
PVRTCC::Image img(4, 4); PVRTCC::Image img(4, 4);

View file

@ -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) { TEST(Pixel, ChangeChannelBitDepth) {
uint8 val = 0x43; uint8 val = 0x43;
uint8 depth = 7; uint8 depth = 7;
EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 8), 0x87); 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, 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, 2), 0x2);
EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 0), 0xFF); 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, 8), 0x6D);
EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 6), 0x1B); 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, 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); EXPECT_EQ(PVRTCC::Pixel::ChangeBitDepth(val, depth, 0), 0xFF);
} }
@ -206,11 +232,19 @@ TEST(Pixel, UnpackRGBA) {
EXPECT_EQ(p.G(), 0xB3); EXPECT_EQ(p.G(), 0xB3);
EXPECT_EQ(p.R(), 0xFE); 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 uint8 newBitDepth[4] = { 3, 5, 2, 1 }; // A R G B
p.ChangeBitDepth(newBitDepth); p.ChangeBitDepth(newBitDepth);
p.UnpackRGBA(rgba);
EXPECT_EQ(p.A(), 0x2); EXPECT_EQ(p.A(), 0x2);
EXPECT_EQ(p.B(), 0x0); EXPECT_EQ(p.B(), 0x0);
EXPECT_EQ(p.G(), 0x2); EXPECT_EQ(p.G(), 0x3);
EXPECT_EQ(p.R(), 0x1f); EXPECT_EQ(p.R(), 0x1f);
} }