FasTC/DXTEncoder/src/Compressor.cpp
Pavel Krajcevski fb7805d875 Bring CMake integration up to date.
- Add a way to generate a FasTCConfig.cmake file so that you can
use cmake without having to install it.
- Add install paths for users that want to install it.
- Hide all public headers in FasTC/ qualified include path, this way we
know what files are public directly from the source. Also, it lets us
define build-tree and install-tree include directories a lot easier.
2014-11-18 17:07:26 -05:00

338 lines
14 KiB
C++
Executable file

/*
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/
// Refer to "Real-Time DXT Compression" by J.M.P. van Waveren for a more thorough discussion of the
// algorithms used in this code.
#include "FasTC/DXTCompressor.h"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
#define INSET_SHIFT 4 // Inset the bounding box with (range >> shift).
#define C565_5_MASK 0xF8 // 0xFF minus last three bits
#define C565_6_MASK 0xFC // 0xFF minus last two bits
namespace DXTC
{
// Function prototypes
void ExtractBlock(const uint32* inPtr, uint32 width, uint8* colorBlock);
void GetMinMaxColors(const uint8* colorBlock, uint8* minColor, uint8* maxColor);
void GetMinMaxColorsWithAlpha(const uint8* colorBlock, uint8* minColor, uint8* maxColor);
void EmitColorIndices(const uint8* colorBlock, uint8*& outBuf, const uint8* minColor, const uint8* maxColor);
void EmitAlphaIndices(const uint8* colorBlock, uint8*& outBuf, const uint8 minAlpha, const uint8 maxAlpha);
// Compress an image using DXT1 compression. Use the inBuf parameter to point to an image in
// 4-byte RGBA format. The width and height parameters specify the size of the image in pixels.
// The buffer pointed to by outBuf should be large enough to store the compressed image. This
// implementation has an 8:1 compression ratio.
void CompressImageDXT1(const FasTC::CompressionJob &cj) {
uint8 block[64];
uint8 minColor[4];
uint8 maxColor[4];
const uint32 kBlockSz = GetBlockSize(FasTC::eCompressionFormat_DXT1);
const uint32 startBlock = cj.CoordsToBlockIdx(cj.XStart(), cj.YStart());
uint8 *outBuf = cj.OutBuf() + startBlock * kBlockSz;
const uint32 *inPixels = reinterpret_cast<const uint32 *>(cj.InBuf());
uint32 endY = std::min(cj.YEnd(), cj.Height() - 4);
uint32 startX = cj.XStart();
for(uint32 j = cj.YStart(); j <= endY; j += 4) {
const uint32 endX = j == cj.YEnd()? cj.XEnd() : cj.Width();
for(uint32 i = startX; i < endX; i += 4) {
const uint32 kOffset = j*cj.Width() + i;
ExtractBlock(inPixels + kOffset, cj.Width(), block);
GetMinMaxColors(block, minColor, maxColor);
EmitWord(outBuf, ColorTo565(maxColor));
EmitWord(outBuf, ColorTo565(minColor));
EmitColorIndices(block, outBuf, minColor, maxColor);
}
startX = 0;
}
}
// Compress an image using DXT5 compression. Use the inBuf parameter to point to an image in
// 4-byte RGBA format. The width and height parameters specify the size of the image in pixels.
// The buffer pointed to by outBuf should be large enough to store the compressed image. This
// implementation has an 4:1 compression ratio.
void CompressImageDXT5(const FasTC::CompressionJob &cj) {
uint8 block[64];
uint8 minColor[4];
uint8 maxColor[4];
const uint32 kBlockSz = GetBlockSize(FasTC::eCompressionFormat_DXT5);
const uint32 startBlock = cj.CoordsToBlockIdx(cj.XStart(), cj.YStart());
uint8 *outBuf = cj.OutBuf() + startBlock * kBlockSz;
const uint32 *inPixels = reinterpret_cast<const uint32 *>(cj.InBuf());
uint32 endY = std::min(cj.YEnd(), cj.Height() - 4);
uint32 startX = cj.XStart();
for(uint32 j = cj.YStart(); j <= endY; j += 4) {
const uint32 endX = j == cj.YEnd()? cj.XEnd() : cj.Width();
for(uint32 i = startX; i < endX; i += 4) {
const uint32 kOffset = j*cj.Width() + i;
ExtractBlock(inPixels + kOffset, cj.Width(), block);
GetMinMaxColorsWithAlpha(block, minColor, maxColor);
EmitByte(outBuf, maxColor[3]);
EmitByte(outBuf, minColor[3]);
EmitAlphaIndices(block, outBuf, minColor[3], maxColor[3]);
EmitWord(outBuf, ColorTo565(maxColor));
EmitWord(outBuf, ColorTo565(minColor));
EmitColorIndices(block, outBuf, minColor, maxColor);
}
startX = 0;
}
}
// Convert a color in 24-bit RGB888 format to 16-bit RGB565 format.
uint16 ColorTo565(const uint8* color)
{
return ((color[0] >> 3) << 11) | ((color[1] >> 2) << 5) | (color[2] >> 3);
}
// Write a single byte to dest.
void EmitByte(uint8*& dest, uint8 b)
{
dest[0] = b;
dest += 1;
}
// Write a word to dest.
void EmitWord(uint8*& dest, uint16 s)
{
dest[0] = (s >> 0) & 255;
dest[1] = (s >> 8) & 255;
dest += 2;
}
// Write a double word to dest.
void EmitDoubleWord(uint8*& dest, uint32 i)
{
dest[0] = (i >> 0) & 255;
dest[1] = (i >> 8) & 255;
dest[2] = (i >> 16) & 255;
dest[3] = (i >> 24) & 255;
dest += 4;
}
// Extract a 4 by 4 block of pixels from inPtr and store it in colorBlock. The width parameter
// specifies the size of the image in pixels.
void ExtractBlock(const uint32* inPtr, uint32 width, uint8* colorBlock)
{
for(int j = 0; j < 4; j++)
{
memcpy(&colorBlock[j * 4 * 4], inPtr, 4 * 4);
inPtr += width;
}
}
// Find a line of best fit through the color space of colorBlock. The line is approximated using
// the extents of the bounding box of the color space. This function does not include the alpha
// channel.
void GetMinMaxColors(const uint8* colorBlock, uint8* minColor, uint8* maxColor)
{
uint32 i;
uint8 inset[3];
minColor[0] = minColor[1] = minColor[2] = 255;
maxColor[0] = maxColor[1] = maxColor[2] = 0;
// Find the bounding box (defined by minimum and maximum color).
for(i = 0; i < 16; i++) {
if(colorBlock[i * 4 + 0] < minColor[0]) {
minColor[0] = colorBlock[i * 4 + 0];
}
if(colorBlock[i * 4 + 1] < minColor[1]) {
minColor[1] = colorBlock[i * 4 + 1];
}
if(colorBlock[i * 4 + 2] < minColor[2]) {
minColor[2] = colorBlock[i * 4 + 2];
}
if(colorBlock[i * 4 + 0] > maxColor[0]) {
maxColor[0] = colorBlock[i * 4 + 0];
}
if(colorBlock[i * 4 + 1] > maxColor[1]) {
maxColor[1] = colorBlock[i * 4 + 1];
}
if(colorBlock[i * 4 + 2] > maxColor[2]) {
maxColor[2] = colorBlock[i * 4 + 2];
}
}
// Inset the bounding box by 1/16 of it's size. (i.e. shift right by 4).
inset[0] = (maxColor[0] - minColor[0]) >> INSET_SHIFT;
inset[1] = (maxColor[1] - minColor[1]) >> INSET_SHIFT;
inset[2] = (maxColor[2] - minColor[2]) >> INSET_SHIFT;
// Clamp the inset bounding box to 255.
minColor[0] = (minColor[0] + inset[0] <= 255) ? minColor[0] + inset[0] : 255;
minColor[1] = (minColor[1] + inset[1] <= 255) ? minColor[1] + inset[1] : 255;
minColor[2] = (minColor[2] + inset[2] <= 255) ? minColor[2] + inset[2] : 255;
// Clamp the inset bounding box to 0.
maxColor[0] = (maxColor[0] >= inset[0]) ? maxColor[0] - inset[0] : 0;
maxColor[1] = (maxColor[1] >= inset[1]) ? maxColor[1] - inset[1] : 0;
maxColor[2] = (maxColor[2] >= inset[2]) ? maxColor[2] - inset[2] : 0;
}
// Find a line of best fit through the color space of colorBlock. The line is approximated using
// the extents of the bounding box of the color space. This function includes the alpha channel.
void GetMinMaxColorsWithAlpha(const uint8* colorBlock, uint8* minColor, uint8* maxColor)
{
uint32 i;
uint8 inset[4];
minColor[0] = minColor[1] = minColor[2] = minColor[3] = 255;
maxColor[0] = maxColor[1] = maxColor[2] = maxColor[3] = 0;
// Find the bounding box (defined by minimum and maximum color).
for(i = 0; i < 16; i++) {
if(colorBlock[i * 4 + 0] < minColor[0]) {
minColor[0] = colorBlock[i * 4 + 0];
}
if(colorBlock[i * 4 + 1] < minColor[1]) {
minColor[1] = colorBlock[i * 4 + 1];
}
if(colorBlock[i * 4 + 2] < minColor[2]) {
minColor[2] = colorBlock[i * 4 + 2];
}
if(colorBlock[i * 4 + 3] < minColor[3]) {
minColor[3] = colorBlock[i * 4 + 3];
}
if(colorBlock[i * 4 + 0] > maxColor[0]) {
maxColor[0] = colorBlock[i * 4 + 0];
}
if(colorBlock[i * 4 + 1] > maxColor[1]) {
maxColor[1] = colorBlock[i * 4 + 1];
}
if(colorBlock[i * 4 + 2] > maxColor[2]) {
maxColor[2] = colorBlock[i * 4 + 2];
}
if(colorBlock[i * 4 + 3] > maxColor[3]) {
maxColor[3] = colorBlock[i * 4 + 3];
}
}
// Inset the bounding box by 1/16 of it's size. (i.e. shift right by 4).
inset[0] = (maxColor[0] - minColor[0]) >> INSET_SHIFT;
inset[1] = (maxColor[1] - minColor[1]) >> INSET_SHIFT;
inset[2] = (maxColor[2] - minColor[2]) >> INSET_SHIFT;
inset[3] = (maxColor[3] - minColor[3]) >> INSET_SHIFT;
// Clamp the inset bounding box to 255.
minColor[0] = (minColor[0] + inset[0] <= 255) ? minColor[0] + inset[0] : 255;
minColor[1] = (minColor[1] + inset[1] <= 255) ? minColor[1] + inset[1] : 255;
minColor[2] = (minColor[2] + inset[2] <= 255) ? minColor[2] + inset[2] : 255;
minColor[3] = (minColor[3] + inset[3] <= 255) ? minColor[3] + inset[3] : 255;
// Clamp the inset bounding box to 0.
maxColor[0] = (maxColor[0] >= inset[0]) ? maxColor[0] - inset[0] : 0;
maxColor[1] = (maxColor[1] >= inset[1]) ? maxColor[1] - inset[1] : 0;
maxColor[2] = (maxColor[2] >= inset[2]) ? maxColor[2] - inset[2] : 0;
maxColor[3] = (maxColor[3] >= inset[3]) ? maxColor[3] - inset[3] : 0;
}
// Quantize the pixels of the colorBlock to 4 colors that lie on the line through the color space
// of colorBlock. The paramaters minColor and maxColor approximate the line through the color
// space. 32 bits (2 bits per pixel) are written to outBuf, which represent the indices of the 4
// colors. This function does not include the alpha channel.
void EmitColorIndices(const uint8* colorBlock, uint8*& outBuf, const uint8* minColor, const uint8* maxColor)
{
uint16 colors[4][4];
uint32 result = 0;
colors[0][0] = (maxColor[0] & C565_5_MASK) | (maxColor[0] >> 5);
colors[0][1] = (maxColor[1] & C565_6_MASK) | (maxColor[1] >> 6);
colors[0][2] = (maxColor[2] & C565_5_MASK) | (maxColor[2] >> 5);
colors[1][0] = (minColor[0] & C565_5_MASK) | (minColor[0] >> 5);
colors[1][1] = (minColor[1] & C565_6_MASK) | (minColor[1] >> 6);
colors[1][2] = (minColor[2] & C565_5_MASK) | (minColor[2] >> 5);
colors[2][0] = (2 * colors[0][0] + 1 * colors[1][0]) / 3;
colors[2][1] = (2 * colors[0][1] + 1 * colors[1][1]) / 3;
colors[2][2] = (2 * colors[0][2] + 1 * colors[1][2]) / 3;
colors[3][0] = (1 * colors[0][0] + 2 * colors[1][0]) / 3;
colors[3][1] = (1 * colors[0][1] + 2 * colors[1][1]) / 3;
colors[3][2] = (1 * colors[0][2] + 2 * colors[1][2]) / 3;
for(int i = 15; i >= 0; i--) {
int32 c0 = colorBlock[i * 4 + 0];
int32 c1 = colorBlock[i * 4 + 1];
int32 c2 = colorBlock[i * 4 + 2];
int32 d0 = abs(colors[0][0] - c0) + abs(colors[0][1] - c1) + abs(colors[0][2] - c2);
int32 d1 = abs(colors[1][0] - c0) + abs(colors[1][1] - c1) + abs(colors[1][2] - c2);
int32 d2 = abs(colors[2][0] - c0) + abs(colors[2][1] - c1) + abs(colors[2][2] - c2);
int32 d3 = abs(colors[3][0] - c0) + abs(colors[3][1] - c1) + abs(colors[3][2] - c2);
int32 b0 = d0 > d3;
int32 b1 = d1 > d2;
int32 b2 = d0 > d2;
int32 b3 = d1 > d3;
int32 b4 = d2 > d3;
int32 x0 = b1 & b2;
int32 x1 = b0 & b3;
int32 x2 = b0 & b4;
result |= (x2 | ((x0 | x1) << 1)) << (i << 1);
}
EmitDoubleWord(outBuf, result);
}
// Quantize the alpha channel of the pixels in colorBlock to 8 alpha values that are equally
// spaced along the interval defined by minAlpha and maxAlpha. 48 bits (3 bits per alpha) are
// written to outBuf, which represent the indices of the 8 alpha values.
void EmitAlphaIndices(const uint8* colorBlock, uint8*& outBuf, const uint8 minAlpha, const uint8 maxAlpha)
{
assert(maxAlpha >= minAlpha);
uint8 indices[16];
uint8 mid = (maxAlpha - minAlpha) / (2 * 7);
uint8 ab1 = minAlpha + mid;
uint8 ab2 = (6 * maxAlpha + 1 * minAlpha) / 7 + mid;
uint8 ab3 = (5 * maxAlpha + 2 * minAlpha) / 7 + mid;
uint8 ab4 = (4 * maxAlpha + 3 * minAlpha) / 7 + mid;
uint8 ab5 = (3 * maxAlpha + 4 * minAlpha) / 7 + mid;
uint8 ab6 = (2 * maxAlpha + 5 * minAlpha) / 7 + mid;
uint8 ab7 = (1 * maxAlpha + 6 * minAlpha) / 7 + mid;
colorBlock += 3;
for(uint32 i = 0; i < 16; i++) {
uint8 a = colorBlock[i * 4];
int32 b1 = (a <= ab1);
int32 b2 = (a <= ab2);
int32 b3 = (a <= ab3);
int32 b4 = (a <= ab4);
int32 b5 = (a <= ab5);
int32 b6 = (a <= ab6);
int32 b7 = (a <= ab7);
int32 index = (b1 + b2 + b3 + b4 + b5 + b6 + b7 + 1) & 7;
indices[i] = index ^ (2 > index);
}
EmitByte(outBuf, (indices[0] >> 0) | (indices[1] << 3) | (indices[2] << 6));
EmitByte(outBuf, (indices[2] >> 2) | (indices[3] << 1) | (indices[4] << 4) | (indices[ 5] << 7));
EmitByte(outBuf, (indices[5] >> 1) | (indices[6] << 2) | (indices[7] << 5));
EmitByte(outBuf, (indices[8] >> 0) | (indices[9] << 3) | (indices[10] << 6));
EmitByte(outBuf, (indices[10] >> 2) | (indices[11] << 1) | (indices[12] << 4) | (indices[13] << 7));
EmitByte(outBuf, (indices[13] >> 1) | (indices[14] << 2) | (indices[15] << 5));
}
}