mirror of
https://github.com/yuzu-emu/FasTC.git
synced 2025-01-23 19:21:09 +00:00
fb7805d875
- 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.
338 lines
14 KiB
C++
Executable file
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));
|
|
}
|
|
}
|