doom3-bfg/neo/renderer/DXT/DXTEncoder.cpp
2012-11-26 12:58:24 -06:00

4691 lines
130 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
/*
================================================================================================
Contains the DxtEncoder implementation.
================================================================================================
*/
#pragma hdrstop
#include "DXTCodec_local.h"
#include "DXTCodec.h"
#define INSET_COLOR_SHIFT 4 // inset the bounding box with ( range >> shift )
#define INSET_ALPHA_SHIFT 5 // inset alpha channel
#define C565_5_MASK 0xF8 // 0xFF minus last three bits
#define C565_6_MASK 0xFC // 0xFF minus last two bits
#define NVIDIA_7X_HARDWARE_BUG_FIX // keep the DXT5 colors sorted as: max, min
typedef uint16 word;
typedef uint32 dword;
/*
========================
idDxtEncoder::NV4XHardwareBugFix
========================
*/
void idDxtEncoder::NV4XHardwareBugFix( byte *minColor, byte *maxColor ) const {
int minq = ( ( minColor[0] << 16 ) | ( minColor[1] << 8 ) | minColor[2] ) & 0x00F8FCF8;
int maxq = ( ( maxColor[0] << 16 ) | ( maxColor[1] << 8 ) | maxColor[2] ) & 0x00F8FCF8;
int mask = -( minq > maxq ) & 0x00FFFFFF;
int min = *(int *)minColor;
int max = *(int *)maxColor;
min ^= max;
max ^= ( min & mask );
min ^= max;
*(int *)minColor = min;
*(int *)maxColor = max;
}
/*
========================
idDxtEncoder::HasConstantValuePer4x4Block
========================
*/
bool idDxtEncoder::HasConstantValuePer4x4Block( const byte *inBuf, int width, int height, int channel ) const {
if ( width < 4 || height < 4 ) {
byte value = inBuf[channel];
for ( int k = 0; k < height; k++ ) {
for ( int l = 0; l < width; l++ ) {
if ( inBuf[(k*width+l)*4+channel] != value ) {
return false;
}
}
}
return true;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
const byte *inPtr = inBuf + i * 4;
byte value = inPtr[channel];
for ( int k = 0; k < 4; k++ ) {
for ( int l = 0; l < 4; l++ ) {
if ( inPtr[(k*width+l)*4+channel] != value ) {
return false;
}
}
}
}
inBuf += srcPadding;
}
return true;
}
/*
========================
idDxtEncoder::WriteTinyColorDXT1
========================
*/
void idDxtEncoder::WriteTinyColorDXT1( const byte *inBuf, int width, int height ) {
int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 );
int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next
// example: 2x8 pixels
// numBlocks = 2
// stride = 32 bytes (8 pixels)
for ( int i = 0; i < numBlocks; i++ ) {
// FIXME: This just emits a fake block based on the color at position 0,0
EmitUShort( ColorTo565( inBuf ) );
EmitUShort( 0 ); // dummy, never used
EmitUInt( 0 ); // 4 color index bytes all use the first color
inBuf += stride;
}
}
/*
========================
idDxtEncoder::WriteTinyColorDXT5
========================
*/
void idDxtEncoder::WriteTinyColorDXT5( const byte *inBuf, int width, int height ) {
int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 );
int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next
// example: 2x8 pixels
// numBlocks = 2
// stride = 32 bytes (8 pixels)
for ( int i = 0; i < numBlocks; i++ ) {
// FIXME: This just emits a fake block based on the color at position 0,0
EmitByte( inBuf[3] );
EmitByte( 0 ); // dummy, never used
EmitByte( 0 ); // 6 alpha index bytes all use the first alpha
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitUShort( ColorTo565( inBuf ) );
EmitUShort( 0 ); // dummy, never used
EmitUInt( 0 ); // 4 color index bytes all use the first color
inBuf += stride;
}
}
/*
========================
idDxtEncoder::WriteTinyColorCTX1DXT5A
========================
*/
void idDxtEncoder::WriteTinyColorCTX1DXT5A( const byte *inBuf, int width, int height ) {
int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 );
int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next
// example: 2x8 pixels
// numBlocks = 2
// stride = 32 bytes (8 pixels)
for ( int i = 0; i < numBlocks; i++ ) {
// FIXME: This just emits a fake block based on the color at position 0,0
EmitByte( inBuf[0] );
EmitByte( inBuf[1] );
EmitByte( inBuf[0] );
EmitByte( inBuf[1] );
EmitUInt( 0 ); // 4 color index bytes all use the first color
EmitByte( inBuf[3] );
EmitByte( 0 ); // dummy, never used
EmitByte( 0 ); // 6 alpha index bytes all use the first alpha
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
inBuf += stride;
}
}
/*
========================
idDxtEncoder::WriteTinyNormalMapDXT5
========================
*/
void idDxtEncoder::WriteTinyNormalMapDXT5( const byte *inBuf, int width, int height ) {
int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 );
int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next
// example: 2x8 pixels
// numBlocks = 2
// stride = 32 bytes (8 pixels)
for ( int i = 0; i < numBlocks; i++ ) {
// FIXME: This just emits a fake block based on the normal at position 0,0
EmitByte( inBuf[3] );
EmitByte( 0 ); // dummy, never used
EmitByte( 0 ); // 6 alpha index bytes all use the first alpha
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitUShort( ColorTo565( inBuf[0], inBuf[1], inBuf[2] ) );
EmitUShort( 0 ); // dummy, never used
EmitUInt( 0 ); // 4 color index bytes all use the first color
inBuf += stride;
}
}
/*
========================
idDxtEncoder::WriteTinyNormalMapDXN
========================
*/
void idDxtEncoder::WriteTinyNormalMapDXN( const byte *inBuf, int width, int height ) {
int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 );
int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next
// example: 2x8 pixels
// numBlocks = 2
// stride = 32 bytes (8 pixels)
for ( int i = 0; i < numBlocks; i++ ) {
// FIXME: This just emits a fake block based on the normal at position 0,0
EmitByte( inBuf[0] );
EmitByte( 0 ); // dummy, never used
EmitByte( 0 ); // 6 alpha index bytes all use the first alpha
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( inBuf[1] );
EmitByte( 0 ); // dummy, never used
EmitByte( 0 ); // 6 alpha index bytes all use the first alpha
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
inBuf += stride;
}
}
/*
========================
idDxtEncoder::WriteTinyDXT5A
========================
*/
void idDxtEncoder::WriteTinyDXT5A( const byte *inBuf, int width, int height ) {
int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 );
int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next
// example: 2x8 pixels
// numBlocks = 2
// stride = 32 bytes (8 pixels)
for ( int i = 0; i < numBlocks; i++ ) {
// FIXME: This just emits a fake block based on the normal at position 0,0
EmitByte( inBuf[0] );
EmitByte( 0 ); // dummy, never used
EmitByte( 0 ); // 6 alpha index bytes all use the first alpha
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
EmitByte( 0 );
inBuf += stride;
}
}
/*
========================
idDxtEncoder::ExtractBlock
params: inPtr - input image, 4 bytes per pixel
paramO: colorBlock - 4*4 output tile, 4 bytes per pixel
========================
*/
ID_INLINE void idDxtEncoder::ExtractBlock( const byte *inPtr, int width, byte *colorBlock ) const {
for ( int j = 0; j < 4; j++ ) {
memcpy( &colorBlock[j*4*4], inPtr, 4*4 );
inPtr += width * 4;
}
}
/*
========================
SwapColors
========================
*/
void SwapColors( byte *c1, byte *c2 ) {
byte tm[3];
memcpy( tm, c1, 3 );
memcpy( c1, c2, 3 );
memcpy( c2, tm, 3 );
}
/*
========================
idDxtEncoder::GetMinMaxColorsMaxDist
Finds the two RGB colors in a 4x4 block furthest apart. Also finds the two alpha values
furthest apart.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte min color
paramO: maxColor - 4 byte max color
========================
*/
void idDxtEncoder::GetMinMaxColorsMaxDist( const byte *colorBlock, byte *minColor, byte *maxColor ) const {
int maxDistC = -1;
int maxDistA = -1;
for ( int i = 0; i < 64 - 4; i += 4 ) {
for ( int j = i + 4; j < 64; j += 4 ) {
int dc = ColorDistance( &colorBlock[i], &colorBlock[j] );
if ( dc > maxDistC ) {
maxDistC = dc;
memcpy( minColor, colorBlock+i, 3 );
memcpy( maxColor, colorBlock+j, 3 );
}
int da = AlphaDistance( colorBlock[i+3], colorBlock[j+3] );
if ( da > maxDistA ) {
maxDistA = da;
minColor[3] = colorBlock[i+3];
maxColor[3] = colorBlock[j+3];
}
}
}
if ( maxColor[0] < minColor[0] ) {
SwapColors( minColor, maxColor );
}
}
/*
========================
idDxtEncoder::GetMinMaxColorsLuminance
Finds the two RGB colors in a 4x4 block furthest apart based on luminance. Also finds the two
alpha values furthest apart.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte min color
paramO: maxColor - 4 byte max color
========================
*/
void idDxtEncoder::GetMinMaxColorsLuminance( const byte *colorBlock, byte *minColor, byte *maxColor ) const {
int maxLumC = 0, minLumC = 256 * 4;
int maxAlpha = 0, minAlpha = 256 * 4;
for ( int i = 0; i < 16; i++ ) {
int luminance = colorBlock[i*4+0] + colorBlock[i*4+1] * 2 + colorBlock[i*4+2];
if ( luminance > maxLumC ) {
maxLumC = luminance;
memcpy( maxColor, colorBlock+i*4, 3 );
}
if ( luminance < minLumC ) {
minLumC = luminance;
memcpy( minColor, colorBlock+i*4, 3 );
}
int alpha = colorBlock[i*4+3];
if ( alpha > maxAlpha ) {
maxAlpha = alpha;
maxColor[3] = (byte)alpha;
}
if ( alpha < minAlpha ) {
minAlpha = alpha;
minColor[3] = (byte)alpha;
}
}
if ( maxColor[0] < minColor[0] ) {
SwapColors( minColor, maxColor );
}
}
/*
========================
idDxtEncoder::GetSquareAlphaError
params: colorBlock - 16 pixel block for which to find color indexes
paramO: minAlpha - Min alpha found
paramO: maxAlpha - Max alpha found
return: 4 byte color index block
========================
*/
int idDxtEncoder::GetSquareAlphaError( const byte *colorBlock, const int alphaOffset, const byte minAlpha, const byte maxAlpha, int lastError ) const {
int i, j;
byte alphas[8];
alphas[0] = maxAlpha;
alphas[1] = minAlpha;
if ( maxAlpha > minAlpha ) {
alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7;
alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7;
alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7;
alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7;
alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7;
alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7;
} else {
alphas[2] = ( 4 * alphas[0] + 1 * alphas[1] ) / 5;
alphas[3] = ( 3 * alphas[0] + 2 * alphas[1] ) / 5;
alphas[4] = ( 2 * alphas[0] + 3 * alphas[1] ) / 5;
alphas[5] = ( 1 * alphas[0] + 4 * alphas[1] ) / 5;
alphas[6] = 0;
alphas[7] = 255;
}
int error = 0;
for ( i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
byte a = colorBlock[i*4+alphaOffset];
for ( j = 0; j < 8; j++ ) {
unsigned int dist = AlphaDistance( a, alphas[j] );
if ( dist < minDist ) {
minDist = dist;
}
}
error += minDist;
if ( error >= lastError ) {
return error;
}
}
return error;
}
/*
========================
idDxtEncoder::GetMinMaxAlphaHQ
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte min color found
paramO: maxColor - 4 byte max color found
========================
*/
int idDxtEncoder::GetMinMaxAlphaHQ( const byte *colorBlock, const int alphaOffset, byte *minColor, byte *maxColor ) const {
int i, j;
byte alphaMin, alphaMax;
int error, bestError = MAX_TYPE( int );
alphaMin = 255;
alphaMax = 0;
// get alpha min / max
for ( i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+alphaOffset] < alphaMin ) {
alphaMin = colorBlock[i*4+alphaOffset];
}
if ( colorBlock[i*4+alphaOffset] > alphaMax ) {
alphaMax = colorBlock[i*4+alphaOffset];
}
}
const int ALPHA_EXPAND = 32;
alphaMin = ( alphaMin <= ALPHA_EXPAND ) ? 0 : alphaMin - ALPHA_EXPAND;
alphaMax = ( alphaMax >= 255 - ALPHA_EXPAND ) ? 255 : alphaMax + ALPHA_EXPAND;
for ( i = alphaMin; i <= alphaMax; i++ ) {
for ( j = alphaMax; j >= i; j-- ) {
error = GetSquareAlphaError( colorBlock, alphaOffset, (byte)i, (byte)j, bestError );
if ( error < bestError ) {
bestError = error;
minColor[alphaOffset] = (byte)i;
maxColor[alphaOffset] = (byte)j;
}
error = GetSquareAlphaError( colorBlock, alphaOffset, (byte)j, (byte)i, bestError );
if ( error < bestError ) {
bestError = error;
minColor[alphaOffset] = (byte)i;
maxColor[alphaOffset] = (byte)j;
}
}
}
return bestError;
}
/*
========================
idDxtEncoder::GetSquareColorsError
params: colorBlock - 16 pixel block for which to find color indexes
paramO: color0 - 4 byte min color found
paramO: color1 - 4 byte max color found
return: 4 byte color index block
========================
*/
int idDxtEncoder::GetSquareColorsError( const byte *colorBlock, const unsigned short color0, const unsigned short color1, int lastError ) const {
int i, j;
byte colors[4][4];
ColorFrom565( color0, colors[0] );
ColorFrom565( color1, colors[1] );
if ( color0 > color1 ) {
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;
} else {
colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2;
colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2;
colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2;
colors[3][0] = 0;
colors[3][1] = 0;
colors[3][2] = 0;
}
int error = 0;
for ( i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
for ( j = 0; j < 4; j++ ) {
unsigned int dist = ColorDistance( &colorBlock[i*4], &colors[j][0] );
if ( dist < minDist ) {
minDist = dist;
}
}
// accumulated error
error += minDist;
if ( error > lastError ) {
return error;
}
}
return error;
}
/*
========================
idDxtEncoder::GetSquareNormalYError
params: colorBlock - 16 pixel block for which to find color indexes
paramO: color0 - 4 byte min color found
paramO: color1 - 4 byte max color found
return: 4 byte color index block
========================
*/
int idDxtEncoder::GetSquareNormalYError( const byte *colorBlock, const unsigned short color0, const unsigned short color1, int lastError, int scale ) const {
int i, j;
byte colors[4][4];
ColorFrom565( color0, colors[0] );
ColorFrom565( color1, colors[1] );
if ( color0 > color1 ) {
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;
} else {
colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2;
colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2;
colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2;
colors[3][0] = 0;
colors[3][1] = 0;
colors[3][2] = 0;
}
int error = 0;
for ( i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
for ( j = 0; j < 4; j++ ) {
float r = (float) colorBlock[i*4+1] / scale;
float s = (float) colors[j][1] / scale;
unsigned int dist = idMath::Ftoi( ( r - s ) * ( r - s ) );
if ( dist < minDist ) {
minDist = dist;
}
}
// accumulated error
error += minDist;
if ( error > lastError ) {
return error;
}
}
return error;
}
/*
========================
idDxtEncoder::GetMinMaxColorsHQ
Uses an exhaustive search to find the two RGB colors that produce the least error when used to
compress the 4x4 block. Also finds the minimum and maximum alpha values.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte min color found
paramO: maxColor - 4 byte max color found
========================
*/
int idDxtEncoder::GetMinMaxColorsHQ( const byte *colorBlock, byte *minColor, byte *maxColor, bool noBlack ) const {
int i;
int i0, i1, i2, j0, j1, j2;
unsigned short minColor565, maxColor565, bestMinColor565, bestMaxColor565;
byte bboxMin[3], bboxMax[3], minAxisDist[3];
int error, bestError = MAX_TYPE( int );
bboxMin[0] = bboxMin[1] = bboxMin[2] = 255;
bboxMax[0] = bboxMax[1] = bboxMax[2] = 0;
// get color bbox
for ( i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+0] < bboxMin[0] ) {
bboxMin[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] < bboxMin[1] ) {
bboxMin[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] < bboxMin[2] ) {
bboxMin[2] = colorBlock[i*4+2];
}
if ( colorBlock[i*4+0] > bboxMax[0] ) {
bboxMax[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] > bboxMax[1] ) {
bboxMax[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] > bboxMax[2] ) {
bboxMax[2] = colorBlock[i*4+2];
}
}
// decrease range for 565 encoding
bboxMin[0] >>= 3;
bboxMin[1] >>= 2;
bboxMin[2] >>= 3;
bboxMax[0] >>= 3;
bboxMax[1] >>= 2;
bboxMax[2] >>= 3;
// get the minimum distance the end points of the line must be apart along each axis
for ( i = 0; i < 3; i++ ) {
minAxisDist[i] = ( bboxMax[i] - bboxMin[i] );
if ( minAxisDist[i] >= 16 ) {
minAxisDist[i] = minAxisDist[i] * 3 / 4;
} else if ( minAxisDist[i] >= 8 ) {
minAxisDist[i] = minAxisDist[i] * 2 / 4;
} else if ( minAxisDist[i] >= 4 ) {
minAxisDist[i] = minAxisDist[i] * 1 / 4;
} else {
minAxisDist[i] = 0;
}
}
// expand the bounding box
const int C565_BBOX_EXPAND = 1;
bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND;
bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND;
bboxMin[2] = ( bboxMin[2] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[2] - C565_BBOX_EXPAND;
bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND;
bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND;
bboxMax[2] = ( bboxMax[2] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[2] + C565_BBOX_EXPAND;
bestMinColor565 = 0;
bestMaxColor565 = 0;
for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) {
for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) {
if ( abs( i0 - j0 ) < minAxisDist[0] ) {
continue;
}
for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) {
for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) {
if ( abs( i1 - j1 ) < minAxisDist[1] ) {
continue;
}
for ( i2 = bboxMin[2]; i2 <= bboxMax[2]; i2++ ) {
for ( j2 = bboxMax[2]; j2 >= bboxMin[2]; j2-- ) {
if ( abs( i2 - j2 ) < minAxisDist[2] ) {
continue;
}
minColor565 = (unsigned short)( ( i0 << 11 ) | ( i1 << 5 ) | ( i2 << 0 ) );
maxColor565 = (unsigned short)( ( j0 << 11 ) | ( j1 << 5 ) | ( j2 << 0 ) );
if ( !noBlack ) {
error = GetSquareColorsError( colorBlock, maxColor565, minColor565, bestError );
if ( error < bestError ) {
bestError = error;
bestMinColor565 = minColor565;
bestMaxColor565 = maxColor565;
}
} else {
if ( minColor565 <= maxColor565 ) {
SwapValues( minColor565, maxColor565 );
}
}
error = GetSquareColorsError( colorBlock, minColor565, maxColor565, bestError );
if ( error < bestError ) {
bestError = error;
bestMinColor565 = minColor565;
bestMaxColor565 = maxColor565;
}
}
}
}
}
}
}
ColorFrom565( bestMinColor565, minColor );
ColorFrom565( bestMaxColor565, maxColor );
return bestError;
}
/*
========================
idDxtEncoder::GetSquareCTX1Error
params: colorBlock - 16 pixel block for which to find color indexes
paramO: color0 - Min color found
paramO: color1 - Max color found
return: 4 byte color index block
========================
*/
int idDxtEncoder::GetSquareCTX1Error( const byte *colorBlock, const byte *color0, const byte *color1, int lastError ) const {
int i, j;
byte colors[4][4];
colors[0][0] = color0[0];
colors[0][1] = color0[1];
colors[1][0] = color1[0];
colors[1][1] = color1[1];
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[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3;
colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3;
int error = 0;
for ( i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
for ( j = 0; j < 4; j++ ) {
unsigned int dist = CTX1Distance( &colorBlock[i*4], &colors[j][0] );
if ( dist < minDist ) {
minDist = dist;
}
}
// accumulated error
error += minDist;
if ( error > lastError ) {
return error;
}
}
return error;
}
/*
========================
idDxtEncoder::GetMinMaxCTX1HQ
Uses an exhaustive search to find the two RGB colors that produce the least error when used to
compress the 4x4 block. Also finds the minimum and maximum alpha values.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte Min color found
paramO: maxColor - 4 byte Max color found
========================
*/
int idDxtEncoder::GetMinMaxCTX1HQ( const byte *colorBlock, byte *minColor, byte *maxColor ) const {
int i;
int i0, i1, j0, j1;
byte curMinColor[2], curMaxColor[2];
byte bboxMin[2], bboxMax[2], minAxisDist[2];
int error, bestError = MAX_TYPE( int );
bboxMin[0] = bboxMin[1] = 255;
bboxMax[0] = bboxMax[1] = 0;
// get color bbox
for ( i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+0] < bboxMin[0] ) {
bboxMin[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] < bboxMin[1] ) {
bboxMin[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+0] > bboxMax[0] ) {
bboxMax[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] > bboxMax[1] ) {
bboxMax[1] = colorBlock[i*4+1];
}
}
// get the minimum distance the end points of the line must be apart along each axis
for ( i = 0; i < 2; i++ ) {
minAxisDist[i] = ( bboxMax[i] - bboxMin[i] );
if ( minAxisDist[i] >= 64 ) {
minAxisDist[i] = minAxisDist[i] * 3 / 4;
} else if ( minAxisDist[i] >= 32 ) {
minAxisDist[i] = minAxisDist[i] * 2 / 4;
} else if ( minAxisDist[i] >= 16 ) {
minAxisDist[i] = minAxisDist[i] * 1 / 4;
} else {
minAxisDist[i] = 0;
}
}
// expand the bounding box
const int CXT1_BBOX_EXPAND = 6;
bboxMin[0] = ( bboxMin[0] <= CXT1_BBOX_EXPAND ) ? 0 : bboxMin[0] - CXT1_BBOX_EXPAND;
bboxMin[1] = ( bboxMin[1] <= CXT1_BBOX_EXPAND ) ? 0 : bboxMin[1] - CXT1_BBOX_EXPAND;
bboxMax[0] = ( bboxMax[0] >= 255 - CXT1_BBOX_EXPAND ) ? 255 : bboxMax[0] + CXT1_BBOX_EXPAND;
bboxMax[1] = ( bboxMax[1] >= 255 - CXT1_BBOX_EXPAND ) ? 255 : bboxMax[1] + CXT1_BBOX_EXPAND;
for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) {
for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) {
if ( abs( i0 - j0 ) < minAxisDist[0] ) {
continue;
}
for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) {
for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) {
if ( abs( i1 - j1 ) < minAxisDist[1] ) {
continue;
}
curMinColor[0] = (byte)i0;
curMinColor[1] = (byte)i1;
curMaxColor[0] = (byte)j0;
curMaxColor[1] = (byte)j1;
error = GetSquareCTX1Error( colorBlock, curMinColor, curMaxColor, bestError );
if ( error < bestError ) {
bestError = error;
memcpy( minColor, curMinColor, 2 );
memcpy( maxColor, curMaxColor, 2 );
}
}
}
}
}
return bestError;
}
/*
========================
idDxtEncoder::GetMinMaxNormalYHQ
Uses an exhaustive search to find the two RGB colors that produce the least error when used to
compress the 4x4 block. Also finds the minimum and maximum alpha values.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte Min color found
paramO: maxColor - 4 byte Max color found
========================
*/
int idDxtEncoder::GetMinMaxNormalYHQ( const byte *colorBlock, byte *minColor, byte *maxColor, bool noBlack, int scale ) const {
unsigned short bestMinColor565, bestMaxColor565;
byte bboxMin[3], bboxMax[3];
int error, bestError = MAX_TYPE( int );
bboxMin[1] = 255;
bboxMax[1] = 0;
// get color bbox
for ( int i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+1] < bboxMin[1] ) {
bboxMin[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+1] > bboxMax[1] ) {
bboxMax[1] = colorBlock[i*4+1];
}
}
// decrease range for 565 encoding
bboxMin[1] >>= 2;
bboxMax[1] >>= 2;
// expand the bounding box
const int C565_BBOX_EXPAND = 1;
bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND;
bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND;
bestMinColor565 = 0;
bestMaxColor565 = 0;
for ( int i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) {
for ( int j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) {
if ( abs( i1 - j1 ) < 0 ) {
continue;
}
unsigned short minColor565 = (unsigned short)i1 << 5;
unsigned short maxColor565 = (unsigned short)j1 << 5;
if ( !noBlack ) {
error = GetSquareNormalYError( colorBlock, maxColor565, minColor565, bestError, scale );
if ( error < bestError ) {
bestError = error;
bestMinColor565 = minColor565;
bestMaxColor565 = maxColor565;
}
} else {
if ( minColor565 <= maxColor565 ) {
SwapValues( minColor565, maxColor565 );
}
}
error = GetSquareNormalYError( colorBlock, minColor565, maxColor565, bestError, scale );
if ( error < bestError ) {
bestError = error;
bestMinColor565 = minColor565;
bestMaxColor565 = maxColor565;
}
}
}
ColorFrom565( bestMinColor565, minColor );
ColorFrom565( bestMaxColor565, maxColor );
int bias = colorBlock[0*4+0];
int size = colorBlock[0*4+2];
minColor[0] = maxColor[0] = (byte)bias;
minColor[2] = maxColor[2] = (byte)size;
return bestError;
}
ALIGN16( static float SIMD_SSE2_float_scale[4] ) = { 2.0f / 255.0f, 2.0f / 255.0f, 2.0f / 255.0f, 2.0f / 255.0f };
ALIGN16( static float SIMD_SSE2_float_descale[4] ) = { 255.0f / 2.0f, 255.0f / 2.0f, 255.0f / 2.0f, 255.0f / 2.0f };
ALIGN16( static float SIMD_SSE2_float_zero[4] ) = { 0.0f, 0.0f, 0.0f, 0.0f };
ALIGN16( static float SIMD_SSE2_float_one[4] ) = { 1.0f, 1.0f, 1.0f, 1.0f };
ALIGN16( static float SIMD_SSE2_float_half[4] ) = { 0.5f, 0.5f, 0.5f, 0.5f };
ALIGN16( static float SIMD_SSE2_float_255[4] ) = { 255.0f, 255.0f, 255.0f, 255.0f };
ALIGN16( static float SIMD_SP_rsqrt_c0[4] ) = { 3.0f, 3.0f, 3.0f, 3.0f };
ALIGN16( static float SIMD_SP_rsqrt_c1[4] ) = { -0.5f, -0.5f, -0.5f, -0.5f };
ALIGN16( static dword SIMD_SSE2_dword_maskFirstThree[4] ) = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 };
ALIGN16( static dword SIMD_SSE2_dword_maskWords[4] ) = { 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x00000000 };
#define R_SHUFFLE_PS( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 ))
/*
========================
NormalDistanceDXT1
========================
*/
int NormalDistanceDXT1( const int *vector, const int *normalized ) {
int result;
__asm {
mov esi, vector
mov edi, normalized
cvtdq2ps xmm0, [esi]
mulps xmm0, SIMD_SSE2_float_scale
subps xmm0, SIMD_SSE2_float_one
pand xmm0, SIMD_SSE2_dword_maskFirstThree
movaps xmm1, xmm0
mulps xmm1, xmm1
pshufd xmm2, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 )
addps xmm2, xmm1
pshufd xmm1, xmm2, R_SHUFFLE_PS( 1, 0, 1, 0 )
addps xmm2, xmm1
rsqrtps xmm1, xmm2
mulps xmm2, xmm1
mulps xmm2, xmm1
subps xmm2, SIMD_SP_rsqrt_c0
mulps xmm1, SIMD_SP_rsqrt_c1
mulps xmm2, xmm1
mulps xmm0, xmm2
addps xmm0, SIMD_SSE2_float_one
mulps xmm0, SIMD_SSE2_float_descale
addps xmm0, SIMD_SSE2_float_half
maxps xmm0, SIMD_SSE2_float_zero
minps xmm0, SIMD_SSE2_float_255
cvttps2dq xmm0, xmm0
psubd xmm0, [edi]
pand xmm0, SIMD_SSE2_dword_maskWords
pmullw xmm0, xmm0
pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 )
paddd xmm0, xmm1
pshufd xmm1, xmm0, R_SHUFFLE_PS( 1, 0, 1, 0 )
paddd xmm0, xmm1
movd result, xmm0
}
return result;
}
/*
========================
NormalDistanceDXT5
========================
*/
int NormalDistanceDXT5( const int *vector, const int *normalized ) {
int result;
__asm {
mov esi, vector
mov edi, normalized
#if 0 // object-space
pshufd xmm0, [esi], R_SHUFFLE_PS( 0, 1, 3, 2 )
#else
pshufd xmm0, [esi], R_SHUFFLE_PS( 1, 2, 3, 0 )
#endif
cvtdq2ps xmm0, xmm0
mulps xmm0, SIMD_SSE2_float_scale
subps xmm0, SIMD_SSE2_float_one
pand xmm0, SIMD_SSE2_dword_maskFirstThree
movaps xmm1, xmm0
mulps xmm1, xmm1
pshufd xmm2, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 )
addps xmm2, xmm1
pshufd xmm1, xmm2, R_SHUFFLE_PS( 1, 0, 1, 0 )
addps xmm2, xmm1
rsqrtps xmm1, xmm2
mulps xmm2, xmm1
mulps xmm2, xmm1
subps xmm2, SIMD_SP_rsqrt_c0
mulps xmm1, SIMD_SP_rsqrt_c1
mulps xmm2, xmm1
mulps xmm0, xmm2
addps xmm0, SIMD_SSE2_float_one
mulps xmm0, SIMD_SSE2_float_descale
addps xmm0, SIMD_SSE2_float_half
maxps xmm0, SIMD_SSE2_float_zero
minps xmm0, SIMD_SSE2_float_255
cvttps2dq xmm0, xmm0
#if 0 // object-space
pshufd xmm3, [edi], R_SHUFFLE_PS( 0, 1, 3, 2 )
#else
pshufd xmm3, [edi], R_SHUFFLE_PS( 1, 2, 3, 0 )
#endif
psubd xmm0, xmm3
pand xmm0, SIMD_SSE2_dword_maskWords
pmullw xmm0, xmm0
pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 )
paddd xmm0, xmm1
pshufd xmm1, xmm0, R_SHUFFLE_PS( 1, 0, 1, 0 )
paddd xmm0, xmm1
movd result, xmm0
}
return result;
}
/*
========================
idDxtEncoder::GetSquareNormalsDXT1Error
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: color0 - 4 byte Min color found
paramO: color1 - 4 byte Max color found
return: 4 byte color index block
========================
*/
int idDxtEncoder::GetSquareNormalsDXT1Error( const int *colorBlock, const unsigned short color0, const unsigned short color1, int lastError, unsigned int &colorIndices ) const {
byte byteColors[2][4];
ALIGN16( int colors[4][4] );
ColorFrom565( color0, byteColors[0] );
ColorFrom565( color1, byteColors[1] );
for ( int i = 0; i < 4; i++ ) {
colors[0][i] = byteColors[0][i];
colors[1][i] = byteColors[1][i];
}
if ( color0 > color1 ) {
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;
} else {
assert( color0 == color1 );
colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2;
colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2;
colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2;
colors[3][0] = 0;
colors[3][1] = 0;
colors[3][2] = 0;
}
int error = 0;
int tempColorIndices[16];
for ( int i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
for ( int j = 0; j < 4; j++ ) {
unsigned int dist = NormalDistanceDXT1( &colors[j][0], &colorBlock[i*4] );
if ( dist < minDist ) {
minDist = dist;
tempColorIndices[i] = j;
}
}
// accumulated error
error += minDist;
if ( error > lastError ) {
return error;
}
}
colorIndices = 0;
for ( int i = 0; i < 16; i++ ) {
colorIndices |= ( tempColorIndices[i] << (unsigned int)( i << 1 ) );
}
return error;
}
/*
========================
idDxtEncoder::GetMinMaxNormalsDXT1HQ
Uses an exhaustive search to find the two RGB colors that produce the least error when used to
compress the 4x4 block. Also finds the minimum and maximum alpha values.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte Min color found
paramO: maxColor - 4 byte Max color found
========================
*/
int idDxtEncoder::GetMinMaxNormalsDXT1HQ( const byte *colorBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, bool noBlack ) const {
int i;
int i0, i1, i2, j0, j1, j2;
unsigned short bestMinColor565 = 0;
unsigned short bestMaxColor565 = 0;
byte bboxMin[3], bboxMax[3], minAxisDist[3];
int error, bestError = MAX_TYPE( int );
unsigned int tempColorIndices;
ALIGN16( int intColorBlock[16*4] );
bboxMin[0] = bboxMin[1] = bboxMin[2] = 128;
bboxMax[0] = bboxMax[1] = bboxMax[2] = 128;
// get color bbox
for ( i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+0] < bboxMin[0] ) {
bboxMin[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] < bboxMin[1] ) {
bboxMin[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] < bboxMin[2] ) {
bboxMin[2] = colorBlock[i*4+2];
}
if ( colorBlock[i*4+0] > bboxMax[0] ) {
bboxMax[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] > bboxMax[1] ) {
bboxMax[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] > bboxMax[2] ) {
bboxMax[2] = colorBlock[i*4+2];
}
}
for ( int i = 0; i < 64; i++ ) {
intColorBlock[i] = colorBlock[i];
}
// decrease range for 565 encoding
bboxMin[0] >>= 3;
bboxMin[1] >>= 2;
bboxMin[2] >>= 3;
bboxMax[0] >>= 3;
bboxMax[1] >>= 2;
bboxMax[2] >>= 3;
// get the minimum distance the end points of the line must be apart along each axis
for ( i = 0; i < 3; i++ ) {
minAxisDist[i] = 0;
}
// expand the bounding box
const int C565_BBOX_EXPAND = 2;
bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND;
bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND;
bboxMin[2] = ( bboxMin[2] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[2] - C565_BBOX_EXPAND;
bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND;
bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND;
bboxMax[2] = ( bboxMax[2] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[2] + C565_BBOX_EXPAND;
for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) {
for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) {
if ( abs( i0 - j0 ) < minAxisDist[0] ) {
continue;
}
for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) {
for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) {
if ( abs( i1 - j1 ) < minAxisDist[1] ) {
continue;
}
for ( i2 = bboxMin[2]; i2 <= bboxMax[2]; i2++ ) {
for ( j2 = bboxMax[2]; j2 >= bboxMin[2]; j2-- ) {
if ( abs( i2 - j2 ) < minAxisDist[2] ) {
continue;
}
unsigned short minColor565 = (unsigned short)( ( i0 << 11 ) | ( i1 << 5 ) | ( i2 << 0 ) );
unsigned short maxColor565 = (unsigned short)( ( j0 << 11 ) | ( j1 << 5 ) | ( j2 << 0 ) );
if ( !noBlack ) {
error = GetSquareNormalsDXT1Error( intColorBlock, maxColor565, minColor565, bestError, tempColorIndices );
if ( error < bestError ) {
bestError = error;
bestMinColor565 = minColor565;
bestMaxColor565 = maxColor565;
colorIndices = tempColorIndices;
}
} else {
if ( minColor565 <= maxColor565 ) {
SwapValues( minColor565, maxColor565 );
}
}
error = GetSquareNormalsDXT1Error( intColorBlock, minColor565, maxColor565, bestError, tempColorIndices );
if ( error < bestError ) {
bestError = error;
bestMinColor565 = minColor565;
bestMaxColor565 = maxColor565;
colorIndices = tempColorIndices;
}
}
}
}
}
}
}
ColorFrom565( bestMinColor565, minColor );
ColorFrom565( bestMaxColor565, maxColor );
return bestError;
}
/*
========================
idDxtEncoder::GetSquareNormalsDXT5Error
params: normalBlock - 16 pixel block for which to find normal indexes
paramO: minNormal - Min normal found
paramO: maxNormal - Max normal found
========================
*/
int idDxtEncoder::GetSquareNormalsDXT5Error( const int *normalBlock, const byte *minNormal, const byte *maxNormal, int lastError, unsigned int &colorIndices, byte *alphaIndices ) const {
byte alphas[8];
byte colors[4][4];
unsigned short smin = ColorTo565( minNormal );
unsigned short smax = ColorTo565( maxNormal );
ColorFrom565( smax, colors[0] );
ColorFrom565( smin, colors[1] );
if ( smax > smin ) {
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;
} else {
assert( smax == smin );
colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2;
colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2;
colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2;
colors[3][0] = 0;
colors[3][1] = 0;
colors[3][2] = 0;
}
alphas[0] = maxNormal[3];
alphas[1] = minNormal[3];
if ( maxNormal[3] > minNormal[3] ) {
alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7;
alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7;
alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7;
alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7;
alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7;
alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7;
} else {
alphas[2] = ( 4 * alphas[0] + 1 * alphas[1] ) / 5;
alphas[3] = ( 3 * alphas[0] + 2 * alphas[1] ) / 5;
alphas[4] = ( 2 * alphas[0] + 3 * alphas[1] ) / 5;
alphas[5] = ( 1 * alphas[0] + 4 * alphas[1] ) / 5;
alphas[6] = 0;
alphas[7] = 255;
}
int error = 0;
int tempColorIndices[16];
int tempAlphaIndices[16];
for ( int i = 0; i < 16; i++ ) {
ALIGN16( int normal[4] );
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
for ( int j = 0; j < 4; j++ ) {
normal[0] = colors[j][0];
normal[1] = colors[j][1];
normal[2] = colors[j][2];
for ( int k = 0; k < 8; k++ ) {
normal[3] = alphas[k];
unsigned int dist = NormalDistanceDXT5( normal, &normalBlock[i*4] );
if ( dist < minDist ) {
minDist = dist;
tempColorIndices[i] = j;
tempAlphaIndices[i] = k;
}
}
}
error += minDist;
if ( error >= lastError ) {
return error;
}
}
alphaIndices[0] = byte( (tempAlphaIndices[ 0] >> 0) | (tempAlphaIndices[ 1] << 3) | (tempAlphaIndices[ 2] << 6) );
alphaIndices[1] = byte( (tempAlphaIndices[ 2] >> 2) | (tempAlphaIndices[ 3] << 1) | (tempAlphaIndices[ 4] << 4) | (tempAlphaIndices[ 5] << 7) );
alphaIndices[2] = byte( (tempAlphaIndices[ 5] >> 1) | (tempAlphaIndices[ 6] << 2) | (tempAlphaIndices[ 7] << 5) );
alphaIndices[3] = byte( (tempAlphaIndices[ 8] >> 0) | (tempAlphaIndices[ 9] << 3) | (tempAlphaIndices[10] << 6) );
alphaIndices[4] = byte( (tempAlphaIndices[10] >> 2) | (tempAlphaIndices[11] << 1) | (tempAlphaIndices[12] << 4) | (tempAlphaIndices[13] << 7) );
alphaIndices[5] = byte( (tempAlphaIndices[13] >> 1) | (tempAlphaIndices[14] << 2) | (tempAlphaIndices[15] << 5) );
colorIndices = 0;
for ( int i = 0; i < 16; i++ ) {
colorIndices |= ( tempColorIndices[i] << (unsigned int)( i << 1 ) );
}
return error;
}
/*
========================
idDxtEncoder::GetMinMaxNormalsDXT5HQ
Uses an exhaustive search to find the two RGB colors that produce the least error when used to
compress the 4x4 block. Also finds the minimum and maximum alpha values.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte Min color found
paramO: maxColor - 4 byte Max color found
========================
*/
int idDxtEncoder::GetMinMaxNormalsDXT5HQ( const byte *colorBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, byte *alphaIndices ) const {
int i;
int i0, i1, i3, j0, j1, j3;
byte bboxMin[4], bboxMax[4], minAxisDist[4];
byte tmin[4], tmax[4];
int error, bestError = MAX_TYPE( int );
unsigned int tempColorIndices;
byte tempAlphaIndices[6];
ALIGN16( int intColorBlock[16*4] );
bboxMin[0] = bboxMin[1] = bboxMin[2] = bboxMin[3] = 255;
bboxMax[0] = bboxMax[1] = bboxMax[2] = bboxMax[3] = 0;
// get color bbox
for ( i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+0] < bboxMin[0] ) {
bboxMin[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] < bboxMin[1] ) {
bboxMin[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] < bboxMin[2] ) {
bboxMin[2] = colorBlock[i*4+2];
}
if ( colorBlock[i*4+3] < bboxMin[3] ) {
bboxMin[3] = colorBlock[i*4+3];
}
if ( colorBlock[i*4+0] > bboxMax[0] ) {
bboxMax[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] > bboxMax[1] ) {
bboxMax[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] > bboxMax[2] ) {
bboxMax[2] = colorBlock[i*4+2];
}
if ( colorBlock[i*4+3] > bboxMax[3] ) {
bboxMax[3] = colorBlock[i*4+3];
}
}
for ( int i = 0; i < 64; i++ ) {
intColorBlock[i] = colorBlock[i];
}
// decrease range for 565 encoding
bboxMin[0] >>= 3;
bboxMin[1] >>= 2;
bboxMax[0] >>= 3;
bboxMax[1] >>= 2;
// get the minimum distance the end points of the line must be apart along each axis
for ( i = 0; i < 4; i++ ) {
minAxisDist[i] = 0;
}
// expand the bounding box
const int C565_BBOX_EXPAND = 2;
const int ALPHA_BBOX_EXPAND = 32;
bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND;
bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND;
bboxMin[3] = ( bboxMin[3] <= ALPHA_BBOX_EXPAND ) ? 0 : bboxMin[3] - ALPHA_BBOX_EXPAND;
bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND;
bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND;
bboxMax[3] = ( bboxMax[3] >= (255)-ALPHA_BBOX_EXPAND ) ? (255) : bboxMax[3] + ALPHA_BBOX_EXPAND;
for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) {
for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) {
if ( abs( i0 - j0 ) < minAxisDist[0] ) {
continue;
}
for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) {
for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) {
if ( abs( i1 - j1 ) < minAxisDist[1] ) {
continue;
}
tmin[0] = (byte)j0 << 3;
tmin[1] = (byte)j1 << 2;
tmin[2] = 0;
tmax[0] = (byte)i0 << 3;
tmax[1] = (byte)i1 << 2;
tmax[2] = 0;
for ( i3 = bboxMin[3]; i3 <= bboxMax[3]; i3++ ) {
for ( j3 = bboxMax[3]; j3 >= bboxMin[3]; j3-- ) {
if ( abs( i3 - j3 ) < minAxisDist[3] ) {
continue;
}
tmin[3] = (byte)j3;
tmax[3] = (byte)i3;
error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices );
if ( error < bestError ) {
bestError = error;
memcpy( minColor, tmin, 4 );
memcpy( maxColor, tmax, 4 );
colorIndices = tempColorIndices;
memcpy( alphaIndices, tempAlphaIndices, 6 );
}
tmin[3] = (byte)i3;
tmax[3] = (byte)j3;
error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices );
if ( error < bestError ) {
bestError = error;
memcpy( minColor, tmin, 4 );
memcpy( maxColor, tmax, 4 );
colorIndices = tempColorIndices;
memcpy( alphaIndices, tempAlphaIndices, 6 );
}
}
}
}
}
}
}
return bestError;
}
/*
========================
idDxtEncoder::GetMinMaxNormalsDXT5HQFast
Uses an exhaustive search to find the two RGB colors that produce the least error when used to
compress the 4x4 block. Also finds the minimum and maximum alpha values.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte Min color found
paramO: maxColor - 4 byte Max color found
========================
*/
int idDxtEncoder::GetMinMaxNormalsDXT5HQFast( const byte *colorBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, byte *alphaIndices ) const {
int i0, i1, i2, i3, j0, j1, j2, j3;
byte bboxMin[4], bboxMax[4], minAxisDist[4];
byte tmin[4], tmax[4];
int error, bestError = MAX_TYPE( int );
unsigned int tempColorIndices;
byte tempAlphaIndices[6];
ALIGN16( int intColorBlock[16*4] );
bboxMin[0] = bboxMin[1] = bboxMin[2] = bboxMin[3] = 255;
bboxMax[0] = bboxMax[1] = bboxMax[2] = bboxMax[3] = 0;
// get color bbox
for ( int i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+0] < bboxMin[0] ) {
bboxMin[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] < bboxMin[1] ) {
bboxMin[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] < bboxMin[2] ) {
bboxMin[2] = colorBlock[i*4+2];
}
if ( colorBlock[i*4+3] < bboxMin[3] ) {
bboxMin[3] = colorBlock[i*4+3];
}
if ( colorBlock[i*4+0] > bboxMax[0] ) {
bboxMax[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] > bboxMax[1] ) {
bboxMax[1] = colorBlock[i*4+1];
}
if ( colorBlock[i*4+2] > bboxMax[2] ) {
bboxMax[2] = colorBlock[i*4+2];
}
if ( colorBlock[i*4+3] > bboxMax[3] ) {
bboxMax[3] = colorBlock[i*4+3];
}
}
for ( int i = 0; i < 64; i++ ) {
intColorBlock[i] = colorBlock[i];
}
// decrease range for 565 encoding
bboxMin[0] >>= 3;
bboxMin[1] >>= 2;
bboxMin[2] >>= 3;
bboxMax[0] >>= 3;
bboxMax[1] >>= 2;
bboxMax[2] >>= 3;
bboxMin[3] = 0;
bboxMax[3] = 255;
// get the minimum distance the end points of the line must be apart along each axis
for ( int i = 0; i < 4; i++ ) {
minAxisDist[i] = 0;
}
// expand the bounding box
const int C565_BBOX_EXPAND = 1;
const int ALPHA_BBOX_EXPAND = 128;
#if 0 // object-space
bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND;
bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND;
bboxMin[2] = 0;
bboxMax[2] = 0;
#else
bboxMin[0] = 0;
bboxMax[0] = 0;
bboxMin[2] = ( bboxMin[2] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[2] - C565_BBOX_EXPAND;
bboxMax[2] = ( bboxMax[2] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[2] + C565_BBOX_EXPAND;
#endif
bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND;
bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND;
bboxMin[3] = ( bboxMin[3] <= ALPHA_BBOX_EXPAND ) ? 0 : bboxMin[3] - ALPHA_BBOX_EXPAND;
bboxMax[3] = ( bboxMax[3] >= (255)-ALPHA_BBOX_EXPAND ) ? (255) : bboxMax[3] + ALPHA_BBOX_EXPAND;
for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) {
for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) {
if ( abs( i0 - j0 ) < minAxisDist[0] ) {
continue;
}
for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) {
for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) {
if ( abs( i1 - j1 ) < minAxisDist[1] ) {
continue;
}
for ( i2 = bboxMin[2]; i2 <= bboxMax[2]; i2++ ) {
for ( j2 = bboxMax[2]; j2 >= bboxMin[2]; j2-- ) {
if ( abs( i2 - j2 ) < minAxisDist[2] ) {
continue;
}
unsigned short minColor565 = (unsigned short)( ( i0 << 11 ) | ( i1 << 5 ) | i2 );
unsigned short maxColor565 = (unsigned short)( ( j0 << 11 ) | ( j1 << 5 ) | j2 );
if ( minColor565 > maxColor565 ) {
SwapValues( minColor565, maxColor565 );
}
error = GetSquareNormalsDXT1Error( intColorBlock, maxColor565, minColor565, bestError, tempColorIndices );
if ( error < bestError ) {
bestError = error;
ColorFrom565( minColor565, minColor );
ColorFrom565( maxColor565, maxColor );
colorIndices = tempColorIndices;
}
}
}
}
}
}
}
bestError = MAX_TYPE( int );
memcpy( tmin, minColor, 4 );
memcpy( tmax, maxColor, 4 );
for ( i3 = bboxMin[3]; i3 <= bboxMax[3]; i3++ ) {
for ( j3 = bboxMax[3]; j3 >= bboxMin[3]; j3-- ) {
if ( abs( i3 - j3 ) < minAxisDist[3] ) {
continue;
}
tmin[3] = (byte)j3;
tmax[3] = (byte)i3;
error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices );
if ( error < bestError ) {
bestError = error;
memcpy( minColor, tmin, 4 );
memcpy( maxColor, tmax, 4 );
colorIndices = tempColorIndices;
memcpy( alphaIndices, tempAlphaIndices, 6 );
}
tmin[3] = (byte)i3;
tmax[3] = (byte)j3;
error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices );
if ( error < bestError ) {
bestError = error;
memcpy( minColor, tmin, 4 );
memcpy( maxColor, tmax, 4 );
colorIndices = tempColorIndices;
memcpy( alphaIndices, tempAlphaIndices, 6 );
}
}
}
return bestError;
}
/*
========================
idDxtEncoder::FindColorIndices
params: colorBlock - 16 pixel block for which find color indexes
paramO: color0 - Min color found
paramO: color1 - Max color found
return: 4 byte color index block
========================
*/
int idDxtEncoder::FindColorIndices( const byte *colorBlock, const unsigned short color0, const unsigned short color1, unsigned int &result ) const {
int i, j;
unsigned int indexes[16];
byte colors[4][4];
ColorFrom565( color0, colors[0] );
ColorFrom565( color1, colors[1] );
if ( color0 > color1 ) {
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;
} else {
colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2;
colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2;
colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2;
colors[3][0] = 0;
colors[3][1] = 0;
colors[3][2] = 0;
}
int error = 0;
for ( i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
for ( j = 0; j < 4; j++ ) {
unsigned int dist = ColorDistance( &colorBlock[i*4], &colors[j][0] );
if ( dist < minDist ) {
minDist = dist;
indexes[i] = j;
}
}
// accumulated error
error += minDist;
}
result = 0;
for ( i = 0; i < 16; i++ ) {
result |= ( indexes[i] << (unsigned int)( i << 1 ) );
}
return error;
}
/*
========================
idDxtEncoder::FindAlphaIndices
params: colorBlock - 16 pixel block for which find alpha indexes
paramO: alpha0 - Min alpha found
paramO: alpha1 - Max alpha found
params: rindexes - 6 byte alpha index block
return: error metric for this compression
========================
*/
int idDxtEncoder::FindAlphaIndices( const byte *colorBlock, const int alphaOffset, const byte alpha0, const byte alpha1, byte *rindexes ) const {
int i, j;
unsigned int indexes[16];
byte alphas[8];
alphas[0] = alpha0;
alphas[1] = alpha1;
if ( alpha0 > alpha1 ) {
alphas[2] = ( 6 * alpha0 + 1 * alpha1 ) / 7;
alphas[3] = ( 5 * alpha0 + 2 * alpha1 ) / 7;
alphas[4] = ( 4 * alpha0 + 3 * alpha1 ) / 7;
alphas[5] = ( 3 * alpha0 + 4 * alpha1 ) / 7;
alphas[6] = ( 2 * alpha0 + 5 * alpha1 ) / 7;
alphas[7] = ( 1 * alpha0 + 6 * alpha1 ) / 7;
} else {
alphas[2] = ( 4 * alpha0 + 1 * alpha1 ) / 5;
alphas[3] = ( 3 * alpha0 + 2 * alpha1 ) / 5;
alphas[4] = ( 2 * alpha0 + 3 * alpha1 ) / 5;
alphas[5] = ( 1 * alpha0 + 4 * alpha1 ) / 5;
alphas[6] = 0;
alphas[7] = 255;
}
int error = 0;
for ( i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
byte a = colorBlock[i*4+alphaOffset];
for ( j = 0; j < 8; j++ ) {
unsigned int dist = AlphaDistance( a, alphas[j] );
if ( dist < minDist ) {
minDist = dist;
indexes[i] = j;
}
}
error += minDist;
}
rindexes[0] = byte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) );
rindexes[1] = byte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) );
rindexes[2] = byte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) );
rindexes[3] = byte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) );
rindexes[4] = byte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) );
rindexes[5] = byte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) );
return error;
}
/*
========================
idDxtEncoder::FindCTX1Indices
params: colorBlock - 16 pixel block for which find color indexes
paramO: color0 - Min color found
paramO: color1 - Max color found
return: 4 byte color index block
========================
*/
int idDxtEncoder::FindCTX1Indices( const byte *colorBlock, const byte *color0, const byte *color1, unsigned int &result ) const {
int i, j;
unsigned int indexes[16];
byte colors[4][4];
colors[0][0] = color1[0];
colors[0][1] = color1[1];
colors[1][0] = color0[0];
colors[1][1] = color0[1];
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[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3;
colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3;
int error = 0;
for ( i = 0; i < 16; i++ ) {
unsigned int minDist = MAX_UNSIGNED_TYPE( int );
for ( j = 0; j < 4; j++ ) {
unsigned int dist = CTX1Distance( &colorBlock[i*4], &colors[j][0] );
if ( dist < minDist ) {
minDist = dist;
indexes[i] = j;
}
}
// accumulated error
error += minDist;
}
result = 0;
for ( i = 0; i < 16; i++ ) {
result |= ( indexes[i] << (unsigned int)( i << 1 ) );
}
return error;
}
/*
========================
idDxtEncoder::CompressImageDXT1HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressImageDXT1HQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
unsigned int colorIndices1;
unsigned int colorIndices2;
byte col1[4];
byte col2[4];
int error1;
int error2;
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT1( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxColorsHQ( block, col1, col2, false );
// Write out color data. Try and find minimum error for the two encoding methods.
unsigned short scol1 = ColorTo565( col1 );
unsigned short scol2 = ColorTo565( col2 );
error1 = FindColorIndices( block, scol1, scol2, colorIndices1 );
error2 = FindColorIndices( block, scol2, scol1, colorIndices2 );
if ( error1 < error2 ) {
EmitUShort( scol1 );
EmitUShort( scol2 );
EmitUInt( colorIndices1 );
} else {
EmitUShort( scol2 );
EmitUShort( scol1 );
EmitUInt( colorIndices2 );
}
//idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::CompressImageDXT5HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressImageDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
byte alphaIndices1[6];
byte alphaIndices2[6];
unsigned int colorIndices;
byte col1[4];
byte col2[4];
int error1;
int error2;
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT5( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxColorsHQ( block, col1, col2, true );
GetMinMaxAlphaHQ( block, 3, col1, col2 );
// Write out alpha data. Try and find minimum error for the two encoding methods.
error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 );
error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 );
if ( error1 < error2 ) {
EmitByte( col1[3] );
EmitByte( col2[3] );
EmitByte( alphaIndices1[0] );
EmitByte( alphaIndices1[1] );
EmitByte( alphaIndices1[2] );
EmitByte( alphaIndices1[3] );
EmitByte( alphaIndices1[4] );
EmitByte( alphaIndices1[5] );
} else {
EmitByte( col2[3] );
EmitByte( col1[3] );
EmitByte( alphaIndices2[0] );
EmitByte( alphaIndices2[1] );
EmitByte( alphaIndices2[2] );
EmitByte( alphaIndices2[3] );
EmitByte( alphaIndices2[4] );
EmitByte( alphaIndices2[5] );
}
#ifdef NVIDIA_7X_HARDWARE_BUG_FIX
NV4XHardwareBugFix( col2, col1 );
#endif
// Write out color data. Always take the path with 4 interpolated values.
unsigned short scol1 = ColorTo565( col1 );
unsigned short scol2 = ColorTo565( col2 );
EmitUShort( scol1 );
EmitUShort( scol2 );
FindColorIndices( block, scol1, scol2, colorIndices );
EmitUInt( colorIndices );
//idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::CompressImageCTX1HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressImageCTX1HQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
unsigned int colorIndices;
byte col1[4];
byte col2[4];
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorCTX1DXT5A( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxCTX1HQ( block, col1, col2 );
EmitByte( col2[0] );
EmitByte( col2[1] );
EmitByte( col1[0] );
EmitByte( col1[1] );
FindCTX1Indices( block, col1, col2, colorIndices );
EmitUInt( colorIndices );
//idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::ScaleYCoCg
params: colorBlock - 16 pixel block for which find color indexes
========================
*/
void idDxtEncoder::ScaleYCoCg( byte *colorBlock ) const {
ALIGN16( byte minColor[4] );
ALIGN16( byte maxColor[4] );
minColor[0] = minColor[1] = minColor[2] = minColor[3] = 255;
maxColor[0] = maxColor[1] = maxColor[2] = maxColor[3] = 0;
for ( int 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+0] > maxColor[0] ) {
maxColor[0] = colorBlock[i*4+0];
}
if ( colorBlock[i*4+1] > maxColor[1] ) {
maxColor[1] = colorBlock[i*4+1];
}
}
int m0 = abs( minColor[0] - 128 );
int m1 = abs( minColor[1] - 128 );
int m2 = abs( maxColor[0] - 128 );
int m3 = abs( maxColor[1] - 128 );
if ( m1 > m0 ) m0 = m1;
if ( m3 > m2 ) m2 = m3;
if ( m2 > m0 ) m0 = m2;
const int s0 = 128 / 2 - 1;
const int s1 = 128 / 4 - 1;
int scale = 1 + ( m0 <= s0 ) + 2 * ( m0 <= s1 );
for ( int i = 0; i < 16; i++ ) {
colorBlock[i*4+0] = byte( ( colorBlock[i*4+0] - 128 ) * scale + 128 );
colorBlock[i*4+1] = byte( ( colorBlock[i*4+1] - 128 ) * scale + 128 );
colorBlock[i*4+2] = byte( ( scale - 1 ) << 3 );
}
}
/*
========================
idDxtEncoder::CompressYCoCgDXT5HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressYCoCgDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
byte alphaIndices1[6];
byte alphaIndices2[6];
unsigned int colorIndices;
byte col1[4];
byte col2[4];
int error1;
int error2;
assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) );
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT5( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
ScaleYCoCg( block );
GetMinMaxColorsHQ( block, col1, col2, true );
GetMinMaxAlphaHQ( block, 3, col1, col2 );
// Write out alpha data. Try and find minimum error for the two encoding methods.
error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 );
error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 );
if ( error1 < error2 ) {
EmitByte( col1[3] );
EmitByte( col2[3] );
EmitByte( alphaIndices1[0] );
EmitByte( alphaIndices1[1] );
EmitByte( alphaIndices1[2] );
EmitByte( alphaIndices1[3] );
EmitByte( alphaIndices1[4] );
EmitByte( alphaIndices1[5] );
} else {
EmitByte( col2[3] );
EmitByte( col1[3] );
EmitByte( alphaIndices2[0] );
EmitByte( alphaIndices2[1] );
EmitByte( alphaIndices2[2] );
EmitByte( alphaIndices2[3] );
EmitByte( alphaIndices2[4] );
EmitByte( alphaIndices2[5] );
}
#ifdef NVIDIA_7X_HARDWARE_BUG_FIX
NV4XHardwareBugFix( col2, col1 );
#endif
// Write out color data. Always take the path with 4 interpolated values.
unsigned short scol1 = ColorTo565( col1 );
unsigned short scol2 = ColorTo565( col2 );
EmitUShort( scol1 );
EmitUShort( scol2 );
FindColorIndices( block, scol1, scol2, colorIndices );
EmitUInt( colorIndices );
//idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::CompressYCoCgCTX1DXT5AHQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressYCoCgCTX1DXT5AHQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
byte alphaIndices1[6];
byte alphaIndices2[6];
unsigned int colorIndices;
byte col1[4];
byte col2[4];
int error1;
int error2;
assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) );
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorCTX1DXT5A( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxAlphaHQ( block, 3, col1, col2 );
// Write out alpha data. Try and find minimum error for the two encoding methods.
error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 );
error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 );
if ( error1 < error2 ) {
EmitByte( col1[3] );
EmitByte( col2[3] );
EmitByte( alphaIndices1[0] );
EmitByte( alphaIndices1[1] );
EmitByte( alphaIndices1[2] );
EmitByte( alphaIndices1[3] );
EmitByte( alphaIndices1[4] );
EmitByte( alphaIndices1[5] );
} else {
EmitByte( col2[3] );
EmitByte( col1[3] );
EmitByte( alphaIndices2[0] );
EmitByte( alphaIndices2[1] );
EmitByte( alphaIndices2[2] );
EmitByte( alphaIndices2[3] );
EmitByte( alphaIndices2[4] );
EmitByte( alphaIndices2[5] );
}
GetMinMaxCTX1HQ( block, col1, col2 );
EmitByte( col2[0] );
EmitByte( col2[1] );
EmitByte( col1[0] );
EmitByte( col1[1] );
FindCTX1Indices( block, col1, col2, colorIndices );
EmitUInt( colorIndices );
//idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::RotateNormalsDXT1
========================
*/
void idDxtEncoder::RotateNormalsDXT1( byte *block ) const {
byte rotatedBlock[64];
byte col1[4];
byte col2[4];
int bestError = MAX_TYPE( int );
int bestRotation = 0;
for ( int i = 0; i < 32; i += 1 ) {
int r = ( i << 3 ) | ( i >> 2 );
float angle = ( r / 255.0f ) * idMath::PI;
float s = sin( angle );
float c = cos( angle );
for ( int j = 0; j < 16; j++ ) {
float x = block[j*4+0] / 255.0f * 2.0f - 1.0f;
float y = block[j*4+1] / 255.0f * 2.0f - 1.0f;
float rx = c * x - s * y;
float ry = s * x + c * y;
rotatedBlock[j*4+0] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f );
rotatedBlock[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f );
}
int error = GetMinMaxColorsHQ( rotatedBlock, col1, col2, true );
if ( error < bestError ) {
bestError = error;
bestRotation = r;
}
}
float angle = ( bestRotation / 255.0f ) * idMath::PI;
float s = sin( angle );
float c = cos( angle );
for ( int j = 0; j < 16; j++ ) {
float x = block[j*4+0] / 255.0f * 2.0f - 1.0f;
float y = block[j*4+1] / 255.0f * 2.0f - 1.0f;
float rx = c * x - s * y;
float ry = s * x + c * y;
block[j*4+0] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f );
block[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f );
block[j*4+2] = (byte)bestRotation;
}
}
/*
========================
idDxtEncoder::CompressNormalMapDXT1HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressNormalMapDXT1HQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
unsigned int colorIndices;
byte col1[4];
byte col2[4];
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT1( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
for ( int k = 0; k < 16; k++ ) {
block[k*4+2] = 0;
}
GetMinMaxColorsHQ( block, col1, col2, true );
// Write out color data. Always take the path with 4 interpolated values.
unsigned short scol1 = ColorTo565( col1 );
unsigned short scol2 = ColorTo565( col2 );
EmitUShort( scol1 );
EmitUShort( scol2 );
FindColorIndices( block, scol1, scol2, colorIndices );
EmitUInt( colorIndices );
//idLib::Printf( "\r%3d%%", ( j * width + i * 4 ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::CompressNormalMapDXT1RenormalizeHQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressNormalMapDXT1RenormalizeHQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
unsigned int colorIndices;
byte col1[4];
byte col2[4];
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT1( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
// clear alpha channel
for ( int k = 0; k < 16; k++ ) {
block[k*4+3] = 0;
}
GetMinMaxNormalsDXT1HQ( block, col1, col2, colorIndices, true );
// Write out color data. Always take the path with 4 interpolated values.
unsigned short scol1 = ColorTo565( col1 );
unsigned short scol2 = ColorTo565( col2 );
EmitUShort( scol1 );
EmitUShort( scol2 );
EmitUInt( colorIndices );
////idLib::Printf( "\r%3d%%", ( j * width + i * 4 ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
////idLib::Printf( "\r100%%\n" );
}
#define USE_SCALE 1
#define USE_BIAS 1
static int c_blocks;
static int c_scaled;
static int c_scaled2x;
static int c_scaled4x;
static int c_differentBias;
static int c_biasHelped;
/*
========================
idDxtEncoder::BiasScaleNormalY
* scale2x = 33%
* scale4x = 23%
* bias + scale2x = 30%
* bias + scale4x = 55%
========================
*/
void idDxtEncoder::BiasScaleNormalY( byte *colorBlock ) const {
byte minColor = 255;
byte maxColor = 0;
for ( int i = 0; i < 16; i++ ) {
if ( colorBlock[i*4+1] < minColor ) {
minColor = colorBlock[i*4+1];
}
if ( colorBlock[i*4+1] > maxColor ) {
maxColor = colorBlock[i*4+1];
}
}
int bestBias = 128;
int bestRange = Max( abs( minColor - bestBias ), abs( maxColor - bestBias ) );
#if USE_BIAS
for ( int i = 0; i < 32; i++ ) {
int bias = ( ( i << 3 ) | ( i >> 2 ) ) - 4;
int range = Max( abs( minColor - bias ), abs( maxColor - bias ) );
if ( range < bestRange ) {
bestRange = range;
bestBias = bias;
}
}
#endif
const int s0 = 128 / 2 - 1;
const int s1 = 128 / 4 - 1;
#if USE_SCALE
int scale = 1 + ( bestRange <= s0 ) + 2 * ( bestRange <= s1 );
#else
int scale = 1;
#endif
if ( scale == 1 ) {
bestBias = 128;
} else {
c_scaled++;
if ( scale == 2 ) c_scaled2x++;
if ( scale == 4 ) c_scaled4x++;
if ( bestBias != 128 ) {
c_differentBias++;
int r = Max( abs( minColor - 128 ), abs( maxColor - 128 ) );
int s = 1 + ( r <= s0 ) + 2 * ( r <= s1 );
if ( scale > s ) {
c_biasHelped++;
}
}
}
c_blocks++;
for ( int i = 0; i < 16; i++ ) {
colorBlock[i*4+0] = byte( bestBias + 4 );
colorBlock[i*4+1] = byte( ( colorBlock[i*4+1] - bestBias ) * scale + 128 );
colorBlock[i*4+2] = byte( ( scale - 1 ) << 3 );
}
}
/*
========================
idDxtEncoder::RotateNormalsDXT5
========================
*/
void idDxtEncoder::RotateNormalsDXT5( byte *block ) const {
byte rotatedBlock[64];
byte col1[4];
byte col2[4];
int bestError = MAX_TYPE( int );
int bestRotation = 0;
int bestScale = 1;
for ( int i = 0; i < 32; i += 1 ) {
int r = ( i << 3 ) | ( i >> 2 );
float angle = ( r / 255.0f ) * idMath::PI;
float s = sin( angle );
float c = cos( angle );
for ( int j = 0; j < 16; j++ ) {
float x = block[j*4+3] / 255.0f * 2.0f - 1.0f;
float y = block[j*4+1] / 255.0f * 2.0f - 1.0f;
float rx = c * x - s * y;
float ry = s * x + c * y;
rotatedBlock[j*4+3] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f );
rotatedBlock[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f );
}
#if USE_SCALE
byte minColor = 255;
byte maxColor = 0;
for ( int j = 0; j < 16; j++ ) {
if ( rotatedBlock[j*4+1] < minColor ) {
minColor = rotatedBlock[j*4+1];
}
if ( rotatedBlock[j*4+1] > maxColor ) {
maxColor = rotatedBlock[j*4+1];
}
}
const int s0 = 128 / 2 - 1;
const int s1 = 128 / 4 - 1;
int range = Max( abs( minColor - 128 ), abs( maxColor - 128 ) );
int scale = 1 + ( range <= s0 ) + 2 * ( range <= s1 );
for ( int j = 0; j < 16; j++ ) {
rotatedBlock[j*4+1] = byte( ( rotatedBlock[j*4+1] - 128 ) * scale + 128 );
}
#endif
int errorY = GetMinMaxNormalYHQ( rotatedBlock, col1, col2, true, scale );
int errorX = GetMinMaxAlphaHQ( rotatedBlock, 3, col1, col2 );
int error = errorX + errorY;
if ( error < bestError ) {
bestError = error;
bestRotation = r;
bestScale = scale;
}
}
float angle = ( bestRotation / 255.0f ) * idMath::PI;
float s = sin( angle );
float c = cos( angle );
for ( int j = 0; j < 16; j++ ) {
float x = block[j*4+3] / 255.0f * 2.0f - 1.0f;
float y = block[j*4+1] / 255.0f * 2.0f - 1.0f;
float rx = c * x - s * y;
float ry = s * x + c * y;
block[j*4+0] = (byte)bestRotation;
block[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f );
block[j*4+3] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f );
#if USE_SCALE
block[j*4+1] = byte( ( block[j*4+1] - 128 ) * bestScale + 128 );
block[j*4+2] = byte( ( bestScale - 1 ) << 3 );
#endif
}
}
/*
========================
idDxtEncoder::CompressNormalMapDXT5HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressNormalMapDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
byte alphaIndices1[6];
byte alphaIndices2[6];
unsigned int colorIndices;
byte col1[4];
byte col2[4];
int error1;
int error2;
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT5( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
// swizzle components
for ( int k = 0; k < 16; k++ ) {
block[k*4+3] = block[k*4+0];
block[k*4+0] = 0;
block[k*4+2] = 0;
}
//BiasScaleNormalY( block );
//RotateNormalsDXT5( block );
GetMinMaxNormalYHQ( block, col1, col2, true, 1 );
GetMinMaxAlphaHQ( block, 3, col1, col2 );
// Write out alpha data. Try and find minimum error for the two encoding methods.
error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 );
error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 );
if ( error1 < error2 ) {
EmitByte( col1[3] );
EmitByte( col2[3] );
EmitByte( alphaIndices1[0] );
EmitByte( alphaIndices1[1] );
EmitByte( alphaIndices1[2] );
EmitByte( alphaIndices1[3] );
EmitByte( alphaIndices1[4] );
EmitByte( alphaIndices1[5] );
} else {
EmitByte( col2[3] );
EmitByte( col1[3] );
EmitByte( alphaIndices2[0] );
EmitByte( alphaIndices2[1] );
EmitByte( alphaIndices2[2] );
EmitByte( alphaIndices2[3] );
EmitByte( alphaIndices2[4] );
EmitByte( alphaIndices2[5] );
}
#ifdef NVIDIA_7X_HARDWARE_BUG_FIX
NV4XHardwareBugFix( col2, col1 );
#endif
// Write out color data. Always take the path with 4 interpolated values.
unsigned short scol1 = ColorTo565( col1 );
unsigned short scol2 = ColorTo565( col2 );
EmitUShort( scol1 );
EmitUShort( scol2 );
FindColorIndices( block, scol1, scol2, colorIndices );
EmitUInt( colorIndices );
//idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::CompressNormalMapDXT5RenormalizeHQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressNormalMapDXT5RenormalizeHQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
unsigned int colorIndices;
byte alphaIndices[6];
byte col1[4];
byte col2[4];
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT5( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
// swizzle components
for ( int k = 0; k < 16; k++ ) {
#if 0 // object-space
block[k*4+3] = block[k*4+2];
block[k*4+2] = 0;
#else
block[k*4+3] = block[k*4+0];
block[k*4+0] = 0;
#endif
}
GetMinMaxNormalsDXT5HQFast( block, col1, col2, colorIndices, alphaIndices );
EmitByte( col2[3] );
EmitByte( col1[3] );
EmitByte( alphaIndices[0] );
EmitByte( alphaIndices[1] );
EmitByte( alphaIndices[2] );
EmitByte( alphaIndices[3] );
EmitByte( alphaIndices[4] );
EmitByte( alphaIndices[5] );
unsigned short scol1 = ColorTo565( col1 );
unsigned short scol2 = ColorTo565( col2 );
EmitUShort( scol2 );
EmitUShort( scol1 );
EmitUInt( colorIndices );
////idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
////idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::CompressNormalMapDXN2HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressNormalMapDXN2HQ( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
byte alphaIndices1[6];
byte alphaIndices2[6];
byte col1[4];
byte col2[4];
int error1;
int error2;
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
WriteTinyColorDXT5( inBuf, width, height );
return;
}
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
for ( int k = 0; k < 2; k++ ) {
GetMinMaxAlphaHQ( block, k, col1, col2 );
// Write out alpha data. Try and find minimum error for the two encoding methods.
error1 = FindAlphaIndices( block, k, col1[k], col2[k], alphaIndices1 );
error2 = FindAlphaIndices( block, k, col2[k], col1[k], alphaIndices2 );
if ( error1 < error2 ) {
EmitByte( col1[k] );
EmitByte( col2[k] );
EmitByte( alphaIndices1[0] );
EmitByte( alphaIndices1[1] );
EmitByte( alphaIndices1[2] );
EmitByte( alphaIndices1[3] );
EmitByte( alphaIndices1[4] );
EmitByte( alphaIndices1[5] );
} else {
EmitByte( col2[k] );
EmitByte( col1[k] );
EmitByte( alphaIndices2[0] );
EmitByte( alphaIndices2[1] );
EmitByte( alphaIndices2[2] );
EmitByte( alphaIndices2[3] );
EmitByte( alphaIndices2[4] );
EmitByte( alphaIndices2[5] );
}
}
//idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) );
}
outData += dstPadding;
inBuf += srcPadding;
}
//idLib::Printf( "\r100%%\n" );
}
/*
========================
idDxtEncoder::GetMinMaxBBox
Takes the extents of the bounding box of the colors in the 4x4 block in RGB space.
Also finds the minimum and maximum alpha values.
params: colorBlock - 4*4 input tile, 4 bytes per pixel
paramO: minColor - 4 byte Min color found
paramO: maxColor - 4 byte Max color found
========================
*/
ID_INLINE void idDxtEncoder::GetMinMaxBBox( const byte *colorBlock, byte *minColor, byte *maxColor ) const {
minColor[0] = minColor[1] = minColor[2] = minColor[3] = 255;
maxColor[0] = maxColor[1] = maxColor[2] = maxColor[3] = 0;
for ( int 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];
}
}
}
/*
========================
idDxtEncoder::InsetColorsBBox
========================
*/
ID_INLINE void idDxtEncoder::InsetColorsBBox( byte *minColor, byte *maxColor ) const {
byte inset[4];
inset[0] = ( maxColor[0] - minColor[0] ) >> INSET_COLOR_SHIFT;
inset[1] = ( maxColor[1] - minColor[1] ) >> INSET_COLOR_SHIFT;
inset[2] = ( maxColor[2] - minColor[2] ) >> INSET_COLOR_SHIFT;
inset[3] = ( maxColor[3] - minColor[3] ) >> INSET_ALPHA_SHIFT;
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;
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;
}
/*
========================
idDxtEncoder::SelectColorsDiagonal
========================
*/
void idDxtEncoder::SelectColorsDiagonal( const byte *colorBlock, byte *minColor, byte *maxColor ) const {
byte mid0 = byte( ( (int) minColor[0] + maxColor[0] + 1 ) >> 1 );
byte mid1 = byte( ( (int) minColor[1] + maxColor[1] + 1 ) >> 1 );
byte mid2 = byte( ( (int) minColor[2] + maxColor[2] + 1 ) >> 1 );
#if 0
// using the covariance is the best way to select the diagonal
int side0 = 0;
int side1 = 0;
for ( int i = 0; i < 16; i++ ) {
int b0 = colorBlock[i*4+0] - mid0;
int b1 = colorBlock[i*4+1] - mid1;
int b2 = colorBlock[i*4+2] - mid2;
side0 += ( b0 * b1 );
side1 += ( b1 * b2 );
}
byte mask0 = -( side0 < 0 );
byte mask1 = -( side1 < 0 );
#else
// calculating the covariance of just the sign bits is much faster and gives almost the same result
int side0 = 0;
int side1 = 0;
for ( int i = 0; i < 16; i++ ) {
byte b0 = colorBlock[i*4+0] >= mid0;
byte b1 = colorBlock[i*4+1] >= mid1;
byte b2 = colorBlock[i*4+2] >= mid2;
side0 += ( b0 ^ b1 );
side1 += ( b1 ^ b2 );
}
byte mask0 = -( side0 > 8 );
byte mask1 = -( side1 > 8 );
#endif
byte c0 = minColor[0];
byte c1 = maxColor[0];
byte c2 = minColor[2];
byte c3 = maxColor[2];
c0 ^= c1;
mask0 &= c0;
c1 ^= mask0;
c0 ^= c1;
c2 ^= c3;
mask1 &= c2;
c3 ^= mask1;
c2 ^= c3;
minColor[0] = c0;
maxColor[0] = c1;
minColor[2] = c2;
maxColor[2] = c3;
if ( ColorTo565( minColor ) > ColorTo565( maxColor ) ) {
SwapValues( minColor[0], maxColor[0] );
SwapValues( minColor[1], maxColor[1] );
SwapValues( minColor[2], maxColor[2] );
}
}
/*
========================
idDxtEncoder::EmitColorIndices
params: colorBlock - 16 pixel block for which find color indexes
paramO: minColor - Min color found
paramO: maxColor - Max color found
return: 4 byte color index block
========================
*/
void idDxtEncoder::EmitColorIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor ) {
#if 1
ALIGN16( uint16 colors[4][4] );
unsigned int 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[0][3] = 0;
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[1][3] = 0;
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[2][3] = 0;
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;
colors[3][3] = 0;
// uses sum of absolute differences instead of squared distance to find the best match
for ( int i = 15; i >= 0; i-- ) {
int c0, c1, c2, c3, m, d0, d1, d2, d3;
c0 = colorBlock[i*4+0];
c1 = colorBlock[i*4+1];
c2 = colorBlock[i*4+2];
c3 = colorBlock[i*4+3];
m = colors[0][0] - c0;
d0 = abs( m );
m = colors[1][0] - c0;
d1 = abs( m );
m = colors[2][0] - c0;
d2 = abs( m );
m = colors[3][0] - c0;
d3 = abs( m );
m = colors[0][1] - c1;
d0 += abs( m );
m = colors[1][1] - c1;
d1 += abs( m );
m = colors[2][1] - c1;
d2 += abs( m );
m = colors[3][1] - c1;
d3 += abs( m );
m = colors[0][2] - c2;
d0 += abs( m );
m = colors[1][2] - c2;
d1 += abs( m );
m = colors[2][2] - c2;
d2 += abs( m );
m = colors[3][2] - c2;
d3 += abs( m );
#if 0
int b0 = d0 > d2;
int b1 = d1 > d3;
int b2 = d0 > d3;
int b3 = d1 > d2;
int b4 = d0 > d1;
int b5 = d2 > d3;
result |= ( ( !b3 & b4 ) | ( b2 & b5 ) | ( ( ( b0 & b3 ) | ( b1 & b2 ) ) << 1 ) ) << ( i << 1 );
#else
bool b0 = d0 > d3;
bool b1 = d1 > d2;
bool b2 = d0 > d2;
bool b3 = d1 > d3;
bool b4 = d2 > d3;
int x0 = b1 & b2;
int x1 = b0 & b3;
int x2 = b0 & b4;
result |= ( x2 | ( ( x0 | x1 ) << 1 ) ) << ( i << 1 );
#endif
}
EmitUInt( result );
#elif 1
byte colors[4][4];
unsigned int indexes[16];
colors[0][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 6 );
colors[0][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 5 );
colors[0][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 6 );
colors[0][3] = 0;
colors[1][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 6 );
colors[1][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 5 );
colors[1][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 6 );
colors[1][3] = 0;
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[2][3] = 0;
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;
colors[3][3] = 0;
for ( int i = 0; i < 16; i++ ) {
int c0, c1, c2, m, d, minDist;
c0 = colorBlock[i*4+0];
c1 = colorBlock[i*4+1];
c2 = colorBlock[i*4+2];
m = colors[0][0] - c0;
d = m * m;
m = colors[0][1] - c1;
d += m * m;
m = colors[0][2] - c2;
d += m * m;
minDist = d;
indexes[i] = 0;
m = colors[1][0] - c0;
d = m * m;
m = colors[1][1] - c1;
d += m * m;
m = colors[1][2] - c2;
d += m * m;
if ( d < minDist ) {
minDist = d;
indexes[i] = 1;
}
m = colors[2][0] - c0;
d = m * m;
m = colors[2][1] - c1;
d += m * m;
m = colors[2][2] - c2;
d += m * m;
if ( d < minDist ) {
minDist = d;
indexes[i] = 2;
}
m = colors[3][0] - c0;
d = m * m;
m = colors[3][1] - c1;
d += m * m;
m = colors[3][2] - c2;
d += m * m;
if ( d < minDist ) {
minDist = d;
indexes[i] = 3;
}
}
unsigned int result = 0;
for ( int i = 0; i < 16; i++ ) {
result |= ( indexes[i] << (unsigned int)( i << 1 ) );
}
EmitUInt( result );
#else
byte colors[4][4];
unsigned int indexes[16];
colors[0][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 6 );
colors[0][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 5 );
colors[0][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 6 );
colors[0][3] = 0;
colors[1][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 6 );
colors[1][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 5 );
colors[1][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 6 );
colors[1][3] = 0;
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[2][3] = 0;
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;
colors[3][3] = 0;
for ( int i = 0; i < 16; i++ ) {
unsigned int minDist = (255*255)*4;
for ( int j = 0; j < 4; j++ ) {
unsigned int dist = ColorDistance( &colorBlock[i*4], &colors[j][0] );
if ( dist < minDist ) {
minDist = dist;
indexes[i] = j;
}
}
}
unsigned int result = 0;
for ( int i = 0; i < 16; i++ ) {
result |= ( indexes[i] << (unsigned int)( i << 1 ) );
}
EmitUInt( result );
#endif
}
/*
========================
idDxtEncoder::EmitColorAlphaIndices
params: colorBlock - 16 pixel block for which find color indexes
paramO: minColor - Min color found
paramO: maxColor - Max color found
return: 4 byte color index block
========================
*/
void idDxtEncoder::EmitColorAlphaIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor ) {
ALIGN16( uint16 colors[4][4] );
unsigned int result = 0;
colors[0][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 5 );
colors[0][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 6 );
colors[0][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 5 );
colors[0][3] = 255;
colors[1][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 5 );
colors[1][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 6 );
colors[1][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 5 );
colors[1][3] = 255;
colors[2][0] = ( colors[0][0] + colors[1][0] ) / 2;
colors[2][1] = ( colors[0][1] + colors[1][1] ) / 2;
colors[2][2] = ( colors[0][2] + colors[1][2] ) / 2;
colors[2][3] = 255;
colors[3][0] = 0;
colors[3][1] = 0;
colors[3][2] = 0;
colors[3][3] = 0;
// uses sum of absolute differences instead of squared distance to find the best match
for ( int i = 15; i >= 0; i-- ) {
int c0, c1, c2, c3, m, d0, d1, d2;
c0 = colorBlock[i*4+0];
c1 = colorBlock[i*4+1];
c2 = colorBlock[i*4+2];
c3 = colorBlock[i*4+3];
m = colors[0][0] - c0;
d0 = abs( m );
m = colors[1][0] - c0;
d1 = abs( m );
m = colors[2][0] - c0;
d2 = abs( m );
m = colors[0][1] - c1;
d0 += abs( m );
m = colors[1][1] - c1;
d1 += abs( m );
m = colors[2][1] - c1;
d2 += abs( m );
m = colors[0][2] - c2;
d0 += abs( m );
m = colors[1][2] - c2;
d1 += abs( m );
m = colors[2][2] - c2;
d2 += abs( m );
unsigned int b0 = d2 > d0;
unsigned int b1 = d2 > d1;
unsigned int b2 = d1 > d0;
unsigned int b3 = c3 < 128;
result |= ( ( ( b0 & b1 | b3 ) << 1 ) | ( b2 ^ b1 | b3 ) ) << ( i << 1 );
}
EmitUInt( result );
}
/*
========================
idDxtEncoder::EmitCTX1Indices
params: colorBlock - 16 pixel block for which find color indexes
paramO: minColor - Min color found
paramO: maxColor - Max color found
return: 4 byte color index block
========================
*/
void idDxtEncoder::EmitCTX1Indices( const byte *colorBlock, const byte *minColor, const byte *maxColor ) {
ALIGN16( uint16 colors[4][2] );
unsigned int result = 0;
colors[0][0] = maxColor[0];
colors[0][1] = maxColor[1];
colors[1][0] = minColor[0];
colors[1][1] = minColor[1];
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[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3;
colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3;
for ( int i = 15; i >= 0; i-- ) {
int c0, c1, m, d0, d1, d2, d3;
c0 = colorBlock[i*4+0];
c1 = colorBlock[i*4+1];
m = colors[0][0] - c0;
d0 = abs( m );
m = colors[1][0] - c0;
d1 = abs( m );
m = colors[2][0] - c0;
d2 = abs( m );
m = colors[3][0] - c0;
d3 = abs( m );
m = colors[0][1] - c1;
d0 += abs( m );
m = colors[1][1] - c1;
d1 += abs( m );
m = colors[2][1] - c1;
d2 += abs( m );
m = colors[3][1] - c1;
d3 += abs( m );
bool b0 = d0 > d3;
bool b1 = d1 > d2;
bool b2 = d0 > d2;
bool b3 = d1 > d3;
bool b4 = d2 > d3;
int x0 = b1 & b2;
int x1 = b0 & b3;
int x2 = b0 & b4;
result |= ( x2 | ( ( x0 | x1 ) << 1 ) ) << ( i << 1 );
}
EmitUInt( result );
}
/*
========================
idDxtEncoder::EmitAlphaIndices
params: colorBlock - 16 pixel block for which find alpha indexes
paramO: minAlpha - Min alpha found
paramO: maxAlpha - Max alpha found
========================
*/
void idDxtEncoder::EmitAlphaIndices( const byte *colorBlock, const int offset, const byte minAlpha, const byte maxAlpha ) {
assert( maxAlpha >= minAlpha );
const int ALPHA_RANGE = 7;
#if 1
byte ab1, ab2, ab3, ab4, ab5, ab6, ab7;
ALIGN16( byte indexes[16] );
ab1 = ( 13 * maxAlpha + 1 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2);
ab2 = ( 11 * maxAlpha + 3 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2);
ab3 = ( 9 * maxAlpha + 5 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2);
ab4 = ( 7 * maxAlpha + 7 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2);
ab5 = ( 5 * maxAlpha + 9 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2);
ab6 = ( 3 * maxAlpha + 11 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2);
ab7 = ( 1 * maxAlpha + 13 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2);
colorBlock += offset;
for ( int i = 0; i < 16; i++ ) {
byte a = colorBlock[i*4];
int b1 = ( a >= ab1 );
int b2 = ( a >= ab2 );
int b3 = ( a >= ab3 );
int b4 = ( a >= ab4 );
int b5 = ( a >= ab5 );
int b6 = ( a >= ab6 );
int b7 = ( a >= ab7 );
int index = ( 8 - b1 - b2 - b3 - b4 - b5 - b6 - b7 ) & 7;
indexes[i] = byte( index ^ ( 2 > index ) );
}
EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) );
EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) );
EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) );
EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) );
EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) );
EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) );
#elif 0
ALIGN16( byte indexes[16] );
byte delta = maxAlpha - minAlpha;
byte half = delta >> 1;
byte bias = delta / ( 2 * ALPHA_RANGE );
byte bottom = minAlpha + bias;
byte top = maxAlpha - bias;
colorBlock += offset;
for ( int i = 0; i < 16; i++ ) {
byte a = colorBlock[i*4];
if ( a <= bottom ) {
indexes[i] = 1;
} else if ( a >= top ) {
indexes[i] = 0;
} else {
indexes[i] = (ALPHA_RANGE+1) + ( ( minAlpha - a ) * ALPHA_RANGE - half ) / delta;
}
}
EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) );
EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) );
EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) );
EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) );
EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) );
EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) );
#elif 0
ALIGN16( byte indexes[16] );
byte delta = maxAlpha - minAlpha;
byte half = delta >> 1;
byte bias = delta / ( 2 * ALPHA_RANGE );
byte bottom = minAlpha + bias;
byte top = maxAlpha - bias;
colorBlock += offset;
for ( int i = 0; i < 16; i++ ) {
byte a = colorBlock[i*4];
int index = (ALPHA_RANGE+1) + ( ( minAlpha - a ) * ALPHA_RANGE - half ) / delta;
int c0 = a > bottom;
int c1 = a < top;
indexes[i] = ( index & -( c0 & c1 ) ) | ( c0 ^ 1 );
}
EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) );
EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) );
EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) );
EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) );
EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) );
EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) );
#else
ALIGN16( byte indexes[16] );
ALIGN16( byte alphas[8] );
alphas[0] = maxAlpha;
alphas[1] = minAlpha;
alphas[2] = ( 6 * maxAlpha + 1 * minAlpha ) / ALPHA_RANGE;
alphas[3] = ( 5 * maxAlpha + 2 * minAlpha ) / ALPHA_RANGE;
alphas[4] = ( 4 * maxAlpha + 3 * minAlpha ) / ALPHA_RANGE;
alphas[5] = ( 3 * maxAlpha + 4 * minAlpha ) / ALPHA_RANGE;
alphas[6] = ( 2 * maxAlpha + 5 * minAlpha ) / ALPHA_RANGE;
alphas[7] = ( 1 * maxAlpha + 6 * minAlpha ) / ALPHA_RANGE;
colorBlock += offset;
for ( int i = 0; i < 16; i++ ) {
int minDist = INT_MAX;
byte a = colorBlock[i*4];
for ( int j = 0; j < 8; j++ ) {
int dist = abs( a - alphas[j] );
if ( dist < minDist ) {
minDist = dist;
indexes[i] = j;
}
}
}
EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) );
EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) );
EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) );
EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) );
EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) );
EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) );
#endif
}
/*
========================
idDxtEncoder::CompressImageDXT1Fast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressImageDXT1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte minColor[4] );
ALIGN16( byte maxColor[4] );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, minColor, maxColor );
//SelectColorsDiagonal( block, minColor, maxColor );
InsetColorsBBox( minColor, maxColor );
EmitUShort( ColorTo565( maxColor ) );
EmitUShort( ColorTo565( minColor ) );
EmitColorIndices( block, minColor, maxColor );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::CompressImageDXT1AlphaFast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressImageDXT1AlphaFast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte minColor[4] );
ALIGN16( byte maxColor[4] );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, minColor, maxColor );
byte minAlpha = minColor[3];
//SelectColorsDiagonal( block, minColor, maxColor );
InsetColorsBBox( minColor, maxColor );
if ( minAlpha >= 128 ) {
EmitUShort( ColorTo565( maxColor ) );
EmitUShort( ColorTo565( minColor ) );
EmitColorIndices( block, minColor, maxColor );
} else {
EmitUShort( ColorTo565( minColor ) );
EmitUShort( ColorTo565( maxColor ) );
EmitColorAlphaIndices( block, minColor, maxColor );
}
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::CompressImageDXT5Fast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressImageDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte minColor[4] );
ALIGN16( byte maxColor[4] );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, minColor, maxColor );
//SelectColorsDiagonal( block, minColor, maxColor );
InsetColorsBBox( minColor, maxColor );
EmitByte( maxColor[3] );
EmitByte( minColor[3] );
EmitAlphaIndices( block, 3, minColor[3], maxColor[3] );
#ifdef NVIDIA_7X_HARDWARE_BUG_FIX
// the colors are already always guaranteed to be sorted properly
#endif
EmitUShort( ColorTo565( maxColor ) );
EmitUShort( ColorTo565( minColor ) );
EmitColorIndices( block, minColor, maxColor );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::ScaleYCoCg
========================
*/
void idDxtEncoder::ScaleYCoCg( byte *colorBlock, byte *minColor, byte *maxColor ) const {
int m0 = abs( minColor[0] - 128 );
int m1 = abs( minColor[1] - 128 );
int m2 = abs( maxColor[0] - 128 );
int m3 = abs( maxColor[1] - 128 );
if ( m1 > m0 ) m0 = m1;
if ( m3 > m2 ) m2 = m3;
if ( m2 > m0 ) m0 = m2;
const int s0 = 128 / 2 - 1;
const int s1 = 128 / 4 - 1;
int mask0 = -( m0 <= s0 );
int mask1 = -( m0 <= s1 );
int scale = 1 + ( 1 & mask0 ) + ( 2 & mask1 );
minColor[0] = byte( ( minColor[0] - 128 ) * scale + 128 );
minColor[1] = byte( ( minColor[1] - 128 ) * scale + 128 );
minColor[2] = byte( ( scale - 1 ) << 3 );
maxColor[0] = byte( ( maxColor[0] - 128 ) * scale + 128 );
maxColor[1] = byte( ( maxColor[1] - 128 ) * scale + 128 );
maxColor[2] = byte( ( scale - 1 ) << 3 );
for ( int i = 0; i < 16; i++ ) {
colorBlock[i*4+0] = byte( ( colorBlock[i*4+0] - 128 ) * scale + 128 );
colorBlock[i*4+1] = byte( ( colorBlock[i*4+1] - 128 ) * scale + 128 );
}
}
/*
========================
idDxtEncoder::InsetYCoCgBBox
========================
*/
ID_INLINE void idDxtEncoder::InsetYCoCgBBox( byte *minColor, byte *maxColor ) const {
#if 0
byte inset[4];
inset[0] = ( maxColor[0] - minColor[0] ) >> INSET_COLOR_SHIFT;
inset[1] = ( maxColor[1] - minColor[1] ) >> INSET_COLOR_SHIFT;
inset[3] = ( maxColor[3] - minColor[3] ) >> INSET_ALPHA_SHIFT;
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[3] = ( minColor[3] + inset[3] <= 255 ) ? minColor[3] + inset[3] : 255;
maxColor[0] = ( maxColor[0] >= inset[0] ) ? maxColor[0] - inset[0] : 0;
maxColor[1] = ( maxColor[1] >= inset[1] ) ? maxColor[1] - inset[1] : 0;
maxColor[3] = ( maxColor[3] >= inset[3] ) ? maxColor[3] - inset[3] : 0;
minColor[0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 5 );
minColor[1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 6 );
maxColor[0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 5 );
maxColor[1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 6 );
#elif 0
float inset[4];
float minf[4];
float maxf[4];
for ( int i = 0; i < 4; i++ ) {
minf[i] = minColor[i] / 255.0f;
maxf[i] = maxColor[i] / 255.0f;
}
inset[0] = ( maxf[0] - minf[0] ) / 16.0f;
inset[1] = ( maxf[1] - minf[1] ) / 16.0f;
inset[2] = ( maxf[2] - minf[2] ) / 16.0f;
inset[3] = ( maxf[3] - minf[3] ) / 32.0f;
for ( int i = 0; i < 4; i++ ) {
minf[i] = ( minf[i] + inset[i] <= 1.0f ) ? minf[i] + inset[i] : 1.0f;
maxf[i] = ( maxf[i] >= inset[i] ) ? maxf[i] - inset[i] : 0;
}
minColor[0] = ((int)floor( minf[0] * 31 )) & ( ( 1 << 5 ) - 1 );
minColor[1] = ((int)floor( minf[1] * 63 )) & ( ( 1 << 6 ) - 1 );
maxColor[0] = ((int)ceil( maxf[0] * 31 )) & ( ( 1 << 5 ) - 1 );
maxColor[1] = ((int)ceil( maxf[1] * 63 )) & ( ( 1 << 6 ) - 1 );
minColor[0] = ( minColor[0] << 3 ) | ( minColor[0] >> 2 );
minColor[1] = ( minColor[1] << 2 ) | ( minColor[1] >> 4 );
maxColor[0] = ( maxColor[0] << 3 ) | ( maxColor[0] >> 2 );
maxColor[1] = ( maxColor[1] << 2 ) | ( maxColor[1] >> 4 );
minColor[3] = (int)floor( minf[3] * 255.0f );
maxColor[3] = (int)ceil( maxf[3] * 255.0f );
#elif 0
int inset[4];
int mini[4];
int maxi[4];
inset[0] = ( maxColor[0] - minColor[0] );
inset[1] = ( maxColor[1] - minColor[1] );
inset[3] = ( maxColor[3] - minColor[3] );
mini[0] = ( minColor[0] << INSET_COLOR_SHIFT ) + inset[0];
mini[1] = ( minColor[1] << INSET_COLOR_SHIFT ) + inset[1];
mini[3] = ( minColor[3] << INSET_ALPHA_SHIFT ) + inset[3];
maxi[0] = ( maxColor[0] << INSET_COLOR_SHIFT ) - inset[0];
maxi[1] = ( maxColor[1] << INSET_COLOR_SHIFT ) - inset[1];
maxi[3] = ( maxColor[3] << INSET_ALPHA_SHIFT ) - inset[3];
mini[0] = ( mini[0] - ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+3);
mini[1] = ( mini[1] - ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+2);
mini[3] = ( mini[3] - ((1<<(2))-1) ) >> (INSET_ALPHA_SHIFT+0);
maxi[0] = ( maxi[0] + ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+3);
maxi[1] = ( maxi[1] + ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+2);
maxi[3] = ( maxi[3] + ((1<<(2))-1) ) >> (INSET_ALPHA_SHIFT+0);
if ( mini[0] < 0 ) mini[0] = 0;
if ( mini[1] < 0 ) mini[1] = 0;
if ( mini[3] < 0 ) mini[3] = 0;
if ( maxi[0] > 31 ) maxi[0] = 31;
if ( maxi[1] > 63 ) maxi[1] = 63;
if ( maxi[3] > 255 ) maxi[3] = 255;
minColor[0] = ( mini[0] << 3 ) | ( mini[0] >> 2 );
minColor[1] = ( mini[1] << 2 ) | ( mini[1] >> 4 );
minColor[3] = mini[3];
maxColor[0] = ( maxi[0] << 3 ) | ( maxi[0] >> 2 );
maxColor[1] = ( maxi[1] << 2 ) | ( maxi[1] >> 4 );
maxColor[3] = maxi[3];
#elif 1
int inset[4];
int mini[4];
int maxi[4];
inset[0] = ( maxColor[0] - minColor[0] ) - ((1<<(INSET_COLOR_SHIFT-1))-1);
inset[1] = ( maxColor[1] - minColor[1] ) - ((1<<(INSET_COLOR_SHIFT-1))-1);
inset[3] = ( maxColor[3] - minColor[3] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
mini[0] = ( ( minColor[0] << INSET_COLOR_SHIFT ) + inset[0] ) >> INSET_COLOR_SHIFT;
mini[1] = ( ( minColor[1] << INSET_COLOR_SHIFT ) + inset[1] ) >> INSET_COLOR_SHIFT;
mini[3] = ( ( minColor[3] << INSET_ALPHA_SHIFT ) + inset[3] ) >> INSET_ALPHA_SHIFT;
maxi[0] = ( ( maxColor[0] << INSET_COLOR_SHIFT ) - inset[0] ) >> INSET_COLOR_SHIFT;
maxi[1] = ( ( maxColor[1] << INSET_COLOR_SHIFT ) - inset[1] ) >> INSET_COLOR_SHIFT;
maxi[3] = ( ( maxColor[3] << INSET_ALPHA_SHIFT ) - inset[3] ) >> INSET_ALPHA_SHIFT;
mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0;
mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0;
mini[3] = ( mini[3] >= 0 ) ? mini[3] : 0;
maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255;
maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255;
maxi[3] = ( maxi[3] <= 255 ) ? maxi[3] : 255;
minColor[0] = byte( ( mini[0] & C565_5_MASK ) | ( mini[0] >> 5 ) );
minColor[1] = byte( ( mini[1] & C565_6_MASK ) | ( mini[1] >> 6 ) );
minColor[3] = byte( mini[3] );
maxColor[0] = byte( ( maxi[0] & C565_5_MASK ) | ( maxi[0] >> 5 ) );
maxColor[1] = byte( ( maxi[1] & C565_6_MASK ) | ( maxi[1] >> 6 ) );
maxColor[3] = byte( maxi[3] );
#endif
}
/*
========================
idDxtEncoder::InsetYCoCgAlpaBBox
========================
*/
ID_INLINE void idDxtEncoder::InsetYCoCgAlpaBBox( byte *minColor, byte *maxColor ) const {
int inset[4];
int mini[4];
int maxi[4];
inset[0] = ( maxColor[0] - minColor[0] ) - ((1<<(INSET_COLOR_SHIFT-1))-1);
inset[1] = ( maxColor[1] - minColor[1] ) - ((1<<(INSET_COLOR_SHIFT-1))-1);
inset[2] = ( maxColor[2] - minColor[2] ) - ((1<<(INSET_COLOR_SHIFT-1))-1);
inset[3] = ( maxColor[3] - minColor[3] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
mini[0] = ( ( minColor[0] << INSET_COLOR_SHIFT ) + inset[0] ) >> INSET_COLOR_SHIFT;
mini[1] = ( ( minColor[1] << INSET_COLOR_SHIFT ) + inset[1] ) >> INSET_COLOR_SHIFT;
mini[2] = ( ( minColor[2] << INSET_COLOR_SHIFT ) + inset[2] ) >> INSET_COLOR_SHIFT;
mini[3] = ( ( minColor[3] << INSET_ALPHA_SHIFT ) + inset[3] ) >> INSET_ALPHA_SHIFT;
maxi[0] = ( ( maxColor[0] << INSET_COLOR_SHIFT ) - inset[0] ) >> INSET_COLOR_SHIFT;
maxi[1] = ( ( maxColor[1] << INSET_COLOR_SHIFT ) - inset[1] ) >> INSET_COLOR_SHIFT;
maxi[2] = ( ( maxColor[2] << INSET_COLOR_SHIFT ) - inset[2] ) >> INSET_COLOR_SHIFT;
maxi[3] = ( ( maxColor[3] << INSET_ALPHA_SHIFT ) - inset[3] ) >> INSET_ALPHA_SHIFT;
mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0;
mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0;
mini[2] = ( mini[2] >= 0 ) ? mini[2] : 0;
mini[3] = ( mini[3] >= 0 ) ? mini[3] : 0;
maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255;
maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255;
maxi[2] = ( maxi[2] <= 255 ) ? maxi[2] : 255;
maxi[3] = ( maxi[3] <= 255 ) ? maxi[3] : 255;
minColor[0] = byte( ( mini[0] & C565_5_MASK ) | ( mini[0] >> 5 ) );
minColor[1] = byte( ( mini[1] & C565_6_MASK ) | ( mini[1] >> 6 ) );
minColor[2] = byte( ( mini[2] & C565_5_MASK ) | ( mini[2] >> 5 ) );
minColor[3] = byte( mini[3] );
maxColor[0] = byte( ( maxi[0] & C565_5_MASK ) | ( maxi[0] >> 5 ) );
maxColor[1] = byte( ( maxi[1] & C565_6_MASK ) | ( maxi[1] >> 6 ) );
maxColor[2] = byte( ( maxi[2] & C565_5_MASK ) | ( maxi[2] >> 5 ) );
maxColor[3] = byte( maxi[3] );
}
/*
========================
idDxtEncoder::SelectYCoCgDiagonal
========================
*/
void idDxtEncoder::SelectYCoCgDiagonal( const byte *colorBlock, byte *minColor, byte *maxColor ) const {
byte side = 0;
byte mid0 = byte( ( (int) minColor[0] + maxColor[0] + 1 ) >> 1 );
byte mid1 = byte( ( (int) minColor[1] + maxColor[1] + 1 ) >> 1 );
for ( int i = 0; i < 16; i++ ) {
byte b0 = colorBlock[i*4+0] >= mid0;
byte b1 = colorBlock[i*4+1] >= mid1;
side += ( b0 ^ b1 );
}
byte mask = -( side > 8 );
#if defined NVIDIA_7X_HARDWARE_BUG_FIX
mask &= -( minColor[0] != maxColor[0] );
#endif
byte c0 = minColor[1];
byte c1 = maxColor[1];
c0 ^= c1;
mask &= c0;
c1 ^= mask;
c0 ^= c1;
minColor[1] = c0;
maxColor[1] = c1;
}
/*
========================
idDxtEncoder::CompressYCoCgDXT5Fast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressYCoCgDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte minColor[4] );
ALIGN16( byte maxColor[4] );
//assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, minColor, maxColor );
ScaleYCoCg( block, minColor, maxColor );
InsetYCoCgBBox( minColor, maxColor );
SelectYCoCgDiagonal( block, minColor, maxColor );
EmitByte( maxColor[3] );
EmitByte( minColor[3] );
EmitAlphaIndices( block, 3, minColor[3], maxColor[3] );
#ifdef NVIDIA_7X_HARDWARE_BUG_FIX
// the colors are already sorted when selecting the diagonal
#endif
EmitUShort( ColorTo565( maxColor ) );
EmitUShort( ColorTo565( minColor ) );
EmitColorIndices( block, minColor, maxColor );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::CompressYCoCgAlphaDXT5Fast
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressYCoCgAlphaDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte minColor[4] );
ALIGN16( byte maxColor[4] );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
// scale down the chroma of texels that are close to gray with low luminance
for ( int k = 0; k < 16; k++ ) {
if ( abs( block[k*4+0] - 132 ) <= 8 &&
abs( block[k*4+2] - 132 ) <= 8 &&
block[k*4+3] < 96 ) {
block[k*4+0] = ( block[k*4+0] - 132 ) / 2 + 132;
block[k*4+2] = ( block[k*4+2] - 132 ) / 2 + 132;
}
}
GetMinMaxBBox( block, minColor, maxColor );
InsetYCoCgAlpaBBox( minColor, maxColor );
SelectColorsDiagonal( block, minColor, maxColor );
EmitByte( maxColor[3] );
EmitByte( minColor[3] );
EmitAlphaIndices( block, 3, minColor[3], maxColor[3] );
#ifdef NVIDIA_7X_HARDWARE_BUG_FIX
// the colors are already sorted when selecting the diagonal
#endif
EmitUShort( ColorTo565( maxColor ) );
EmitUShort( ColorTo565( minColor ) );
EmitColorIndices( block, minColor, maxColor );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::CompressYCoCgCTX1DXT5AFast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressYCoCgCTX1DXT5AFast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte minColor[4] );
ALIGN16( byte maxColor[4] );
assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, minColor, maxColor );
SelectYCoCgDiagonal( block, minColor, maxColor );
InsetColorsBBox( minColor, maxColor );
EmitByte( maxColor[3] );
EmitByte( minColor[3] );
EmitAlphaIndices( block, 3, minColor[3], maxColor[3] );
EmitByte( maxColor[0] );
EmitByte( maxColor[1] );
EmitByte( minColor[0] );
EmitByte( minColor[1] );
EmitCTX1Indices( block, minColor, maxColor );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::EmitGreenIndices
params: block - block for which to find green indices
paramO: minGreen - Min green found
paramO: maxGreen - Max green found
========================
*/
void idDxtEncoder::EmitGreenIndices( const byte *block, const int offset, const byte minGreen, const byte maxGreen ) {
assert( maxGreen >= minGreen );
const int COLOR_RANGE = 3;
#if 1
byte yb1 = ( 5 * maxGreen + 1 * minGreen + COLOR_RANGE ) / ( 2 * COLOR_RANGE );
byte yb2 = ( 3 * maxGreen + 3 * minGreen + COLOR_RANGE ) / ( 2 * COLOR_RANGE );
byte yb3 = ( 1 * maxGreen + 5 * minGreen + COLOR_RANGE ) / ( 2 * COLOR_RANGE );
unsigned int result = 0;
block += offset;
for ( int i = 15; i >= 0; i-- ) {
result <<= 2;
byte y = block[i*4];
int b1 = ( y >= yb1 );
int b2 = ( y >= yb2 );
int b3 = ( y >= yb3 );
int index = ( 4 - b1 - b2 - b3 ) & 3;
index ^= ( 2 > index );
result |= index;
}
EmitUInt( result );
#else
byte green[4];
green[0] = maxGreen;
green[1] = minGreen;
green[2] = ( 2 * green[0] + 1 * green[1] ) / 3;
green[3] = ( 1 * green[0] + 2 * green[1] ) / 3;
unsigned int result = 0;
block += offset;
for ( int i = 15; i >= 0; i-- ) {
result <<= 2;
byte y = block[i*4];
int minDist = INT_MAX;
int index;
for ( int j = 0; j < 4; j++ ) {
int dist = abs( y - green[j] );
if ( dist < minDist ) {
minDist = dist;
index = j;
}
}
result |= index;
}
EmitUInt( result );
#endif
}
/*
========================
idDxtEncoder::InsetNormalsBBoxDXT5
========================
*/
void idDxtEncoder::InsetNormalsBBoxDXT5( byte *minNormal, byte *maxNormal ) const {
int inset[4];
int mini[4];
int maxi[4];
inset[3] = ( maxNormal[3] - minNormal[3] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
inset[1] = ( maxNormal[1] - minNormal[1] ) - ((1<<(INSET_COLOR_SHIFT-1))-1);
mini[3] = ( ( minNormal[3] << INSET_ALPHA_SHIFT ) + inset[3] ) >> INSET_ALPHA_SHIFT;
mini[1] = ( ( minNormal[1] << INSET_COLOR_SHIFT ) + inset[1] ) >> INSET_COLOR_SHIFT;
maxi[3] = ( ( maxNormal[3] << INSET_ALPHA_SHIFT ) - inset[3] ) >> INSET_ALPHA_SHIFT;
maxi[1] = ( ( maxNormal[1] << INSET_COLOR_SHIFT ) - inset[1] ) >> INSET_COLOR_SHIFT;
mini[3] = ( mini[3] >= 0 ) ? mini[3] : 0;
mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0;
maxi[3] = ( maxi[3] <= 255 ) ? maxi[3] : 255;
maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255;
minNormal[3] = byte( mini[3] );
minNormal[1] = byte( ( mini[1] & C565_6_MASK ) | ( mini[1] >> 6 ) );
maxNormal[3] = byte( maxi[3] );
maxNormal[1] = byte( ( maxi[1] & C565_6_MASK ) | ( maxi[1] >> 6 ) );
}
/*
========================
idDxtEncoder::InsetNormalsBBox3Dc
========================
*/
void idDxtEncoder::InsetNormalsBBox3Dc( byte *minNormal, byte *maxNormal ) const {
int inset[4];
int mini[4];
int maxi[4];
inset[0] = ( maxNormal[0] - minNormal[0] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
inset[1] = ( maxNormal[1] - minNormal[1] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
mini[0] = ( ( minNormal[0] << INSET_ALPHA_SHIFT ) + inset[0] ) >> INSET_ALPHA_SHIFT;
mini[1] = ( ( minNormal[1] << INSET_ALPHA_SHIFT ) + inset[1] ) >> INSET_ALPHA_SHIFT;
maxi[0] = ( ( maxNormal[0] << INSET_ALPHA_SHIFT ) - inset[0] ) >> INSET_ALPHA_SHIFT;
maxi[1] = ( ( maxNormal[1] << INSET_ALPHA_SHIFT ) - inset[1] ) >> INSET_ALPHA_SHIFT;
mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0;
mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0;
maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255;
maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255;
minNormal[0] = (byte)mini[0];
minNormal[1] = (byte)mini[1];
maxNormal[0] = (byte)maxi[0];
maxNormal[1] = (byte)maxi[1];
}
/*
========================
idDxtEncoder::CompressNormalMapDXT5Fast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressNormalMapDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte normal1[4] );
ALIGN16( byte normal2[4] );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, normal1, normal2 );
InsetNormalsBBoxDXT5( normal1, normal2 );
// Write out Nx into alpha channel.
EmitByte( normal2[3] );
EmitByte( normal1[3] );
EmitAlphaIndices( block, 3, normal1[3], normal2[3] );
// Write out Ny into green channel.
EmitUShort( ColorTo565( block[0], normal2[1], block[2] ) );
EmitUShort( ColorTo565( block[0], normal1[1], block[2] ) );
EmitGreenIndices( block, 1, normal1[1], normal2[1] );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::CompressImageDXN1Fast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressImageDXN1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte min[4] );
ALIGN16( byte max[4] );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, min, max );
InsetNormalsBBox3Dc( min, max );
// Write out an alpha channel.
EmitByte( max[0] );
EmitByte( min[0] );
EmitAlphaIndices( block, 0, min[0], max[0] );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::CompressNormalMapDXN2Fast_Generic
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::CompressNormalMapDXN2Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte block[64] );
ALIGN16( byte normal1[4] );
ALIGN16( byte normal2[4] );
assert( width >= 4 && ( width & 3 ) == 0 );
assert( height >= 4 && ( height & 3 ) == 0 );
this->width = width;
this->height = height;
this->outData = outBuf;
for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
for ( int i = 0; i < width; i += 4 ) {
ExtractBlock( inBuf + i * 4, width, block );
GetMinMaxBBox( block, normal1, normal2 );
InsetNormalsBBox3Dc( normal1, normal2 );
// Write out Nx as an alpha channel.
EmitByte( normal2[0] );
EmitByte( normal1[0] );
EmitAlphaIndices( block, 0, normal1[0], normal2[0] );
// Write out Ny as an alpha channel.
EmitByte( normal2[1] );
EmitByte( normal1[1] );
EmitAlphaIndices( block, 1, normal1[1], normal2[1] );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::DecodeDXNAlphaValues
========================
*/
void idDxtEncoder::DecodeDXNAlphaValues( const byte *inBuf, byte *values ) {
int i;
unsigned int indices;
byte alphas[8];
if ( inBuf[0] <= inBuf[1] ) {
alphas[0] = inBuf[0];
alphas[1] = inBuf[1];
alphas[2] = ( 4 * alphas[0] + 1 * alphas[1] ) / 5;
alphas[3] = ( 3 * alphas[0] + 2 * alphas[1] ) / 5;
alphas[4] = ( 2 * alphas[0] + 3 * alphas[1] ) / 5;
alphas[5] = ( 1 * alphas[0] + 4 * alphas[1] ) / 5;
alphas[6] = 0;
alphas[7] = 255;
} else {
alphas[0] = inBuf[0];
alphas[1] = inBuf[1];
alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7;
alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7;
alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7;
alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7;
alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7;
alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7;
}
indices = (int)inBuf[2] | ( (int)inBuf[3] << 8 ) | ( (int)inBuf[4] << 16 );
for ( i = 0; i < 8; i++ ) {
values[i] = alphas[indices & 7];
indices >>= 3;
}
indices = (int)inBuf[5] | ( (int)inBuf[6] << 8 ) | ( (int)inBuf[7] << 16 );
for ( i = 8; i < 16; i++ ) {
values[i] = alphas[indices & 7];
indices >>= 3;
}
}
/*
========================
idDxtEncoder::EncodeNormalRGBIndices
params: values - 16 normal block for which to find normal Y indices
paramO: min - Min grayscale value
paramO: max - Max grayscale value
========================
*/
void idDxtEncoder::EncodeNormalRGBIndices( byte *outBuf, const byte min, const byte max, const byte *values ) {
const int COLOR_RANGE = 3;
byte maskedMin, maskedMax, mid, yb1, yb2, yb3;
maskedMax = max & C565_6_MASK;
maskedMin = min & C565_6_MASK;
mid = ( maskedMax - maskedMin ) / ( 2 * COLOR_RANGE );
yb1 = maskedMax - mid;
yb2 = ( 2 * maskedMax + 1 * maskedMin ) / COLOR_RANGE - mid;
yb3 = ( 1 * maskedMax + 2 * maskedMin ) / COLOR_RANGE - mid;
unsigned int result = 0;
for ( int i = 15; i >= 0; i-- ) {
result <<= 2;
byte y = values[i];
int b1 = ( y >= yb1 );
int b2 = ( y >= yb2 );
int b3 = ( y >= yb3 );
int index = ( 4 - b1 - b2 - b3 ) & 3;
index ^= ( 2 > index );
result |= index;
}
unsigned short maskedMax5 = (max & C565_5_MASK) >> 3;
unsigned short maskedMin5 = (min & C565_5_MASK) >> 3;
unsigned short smax = (maskedMax5 << 11) | (maskedMax << 3) | maskedMax5;
unsigned short smin = (maskedMin5 << 11) | (maskedMin << 3) | maskedMin5;
outBuf[0] = byte( ( smax >> 0 ) & 0xFF );
outBuf[1] = byte( ( smax >> 8 ) & 0xFF );
outBuf[2] = byte( ( smin >> 0 ) & 0xFF );
outBuf[3] = byte( ( smin >> 8 ) & 0xFF );
outBuf[4] = byte( ( result >> 0 ) & 0xFF );
outBuf[5] = byte( ( result >> 8 ) & 0xFF );
outBuf[6] = byte( ( result >> 16 ) & 0xFF );
outBuf[7] = byte( ( result >> 24 ) & 0xFF );
}
/*
========================
idDxtEncoder::ConvertNormalMapDXN2_DXT5
params: inBuf - normal map compressed in DXN2 format
paramO: outBuf - result of compression in DXT5 format
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::ConvertNormalMapDXN2_DXT5( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte values[16] );
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
assert( 0 );
return;
}
for ( int j = 0; j < height; j += 4 ) {
for ( int i = 0; i < width; i += 4, inBuf += 16, outBuf += 16 ) {
// decode normal Y stored as a DXT5 alpha channel
DecodeDXNAlphaValues( inBuf + 0, values );
// copy normal X
memcpy( outBuf + 0, inBuf + 8, 8 );
// get the min/max Y
byte minNormalY = 255;
byte maxNormalY = 0;
for ( int i = 0; i < 16; i++ ) {
if ( values[i] < minNormalY ) {
minNormalY = values[i];
}
if ( values[i] > maxNormalY ) {
maxNormalY = values[i];
}
}
// encode normal Y into DXT5 color channels
EncodeNormalRGBIndices( outBuf + 8, minNormalY, maxNormalY, values );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::DecodeNormalYValues
========================
*/
void idDxtEncoder::DecodeNormalYValues( const byte *inBuf, byte &min, byte &max, byte *values ) {
int i;
unsigned int indexes;
unsigned short normal0, normal1;
byte normalsY[4];
normal0 = inBuf[0] | (inBuf[1] << 8);
normal1 = inBuf[2] | (inBuf[3] << 8);
assert( normal0 >= normal1 );
normalsY[0] = GreenFrom565( normal0 );
normalsY[1] = GreenFrom565( normal1 );
normalsY[2] = ( 2 * normalsY[0] + 1 * normalsY[1] ) / 3;
normalsY[3] = ( 1 * normalsY[0] + 2 * normalsY[1] ) / 3;
indexes = (unsigned int)inBuf[4] | ((unsigned int)inBuf[5]<<8) | ((unsigned int)inBuf[6]<<16) | ((unsigned int)inBuf[7]<<24);
for ( i = 0; i < 16; i++ ) {
values[i] = normalsY[indexes & 3];
indexes >>= 2;
}
max = normalsY[0];
min = normalsY[1];
}
/*
========================
idDxtEncoder::EncodeDXNAlphaValues
========================
*/
void idDxtEncoder::EncodeDXNAlphaValues( byte *outBuf, const byte min, const byte max, const byte *values ) {
int i;
byte alphas[8];
int j;
unsigned int indexes[16];
alphas[0] = max;
alphas[1] = min;
alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7;
alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7;
alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7;
alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7;
alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7;
alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7;
int error = 0;
for ( i = 0; i < 16; i++ ) {
int minDist = MAX_TYPE( int );
byte a = values[i];
for ( j = 0; j < 8; j++ ) {
int dist = AlphaDistance( a, alphas[j] );
if ( dist < minDist ) {
minDist = dist;
indexes[i] = j;
}
}
error += minDist;
}
outBuf[0] = max;
outBuf[1] = min;
outBuf[2] = byte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) );
outBuf[3] = byte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) );
outBuf[4] = byte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) );
outBuf[5] = byte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) );
outBuf[6] = byte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) );
outBuf[7] = byte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) );
}
/*
========================
idDxtEncoder::ConvertNormalMapDXT5_DXN2
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::ConvertNormalMapDXT5_DXN2( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte values[16] );
byte minNormalY, maxNormalY;
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
assert( 0 );
return;
}
for ( int j = 0; j < height; j += 4 ) {
for ( int i = 0; i < width; i += 4, inBuf += 16, outBuf += 16 ) {
// decode normal Y stored as a DXT5 alpha channel
DecodeNormalYValues( inBuf + 8, minNormalY, maxNormalY, values );
memcpy( outBuf + 8, inBuf + 0, 8 );
// encode normal Y into DXT5 green channel
EncodeDXNAlphaValues( outBuf + 0, minNormalY, maxNormalY, values );
}
outData += dstPadding;
inBuf += srcPadding;
}
}
/*
========================
idDxtEncoder::ConvertImageDXN1_DXT1
params: inBuf - normal map compressed in DXN1 format
paramO: outBuf - result of compression in DXT1 format
params: width - width of image
params: height - height of image
========================
*/
void idDxtEncoder::ConvertImageDXN1_DXT1( const byte *inBuf, byte *outBuf, int width, int height ) {
ALIGN16( byte values[16] );
this->width = width;
this->height = height;
this->outData = outBuf;
if ( width > 4 && ( width & 3 ) != 0 ) {
return;
}
if ( height > 4 && ( height & 3 ) != 0 ) {
return;
}
if ( width < 4 || height < 4 ) {
assert( 0 );
return;
}
for ( int j = 0; j < height; j += 4 ) {
for ( int i = 0; i < width; i += 4, inBuf += 8, outBuf += 8 ) {
// decode single channel stored as a DXT5 alpha channel
DecodeDXNAlphaValues( inBuf + 0, values );
// get the min/max
byte min = 255;
byte max = 0;
for ( int i = 0; i < 16; i++ ) {
if ( values[i] < min ) {
min = values[i];
}
if ( values[i] > max ) {
max = values[i];
}
}
// encode single channel into DXT1
EncodeNormalRGBIndices( outBuf + 0, min, max, values );
}
outData += dstPadding;
inBuf += srcPadding;
}
}