Commit 6684007a by Minmin Gong Committed by Commit Bot

Takes principal component analysis to improve the endpoint in ETC transcode.

Insteading of getting the BC1 endpoints from the max and min intensity, a PCA on pixels might give us better quality results, with limited costs. Change-Id: I7638d78c666c2580a6b0c267470bf4a0f7082e05 Reviewed-on: https://chromium-review.googlesource.com/332871 Commit-Queue: Corentin Wallez <cwallez@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Commit-Queue: Geoff Lang <geofflang@chromium.org>
parent 3d932d83
......@@ -45,14 +45,6 @@ static const int intensityModifierNonOpaque[][4] =
};
// clang-format on
// Table C.7, mapping from pixel index values to modifier value orders
// clang-format off
static const int valueMappingTable[] =
{
2, 3, 1, 0
};
// clang-format on
struct ETC2Block
{
// Decodes unsigned single or dual channel block to bytes
......@@ -738,8 +730,8 @@ struct ETC2Block
void packBC1(void *bc1,
const R8G8B8A8 *rgba,
R8G8B8A8 &minColor,
R8G8B8A8 &maxColor,
const R8G8B8A8 &minColor,
const R8G8B8A8 &maxColor,
bool opaque) const
{
uint32_t bits;
......@@ -854,7 +846,6 @@ struct ETC2Block
}
void decodeSubblock(R8G8B8A8 *rgbaBlock,
size_t pixelRange[2][2],
size_t x,
size_t y,
size_t w,
......@@ -880,19 +871,125 @@ struct ETC2Block
for (size_t i = dxBegin; i < dxEnd && (x + i) < w; i++)
{
const size_t pixelIndex = getIndex(i, j);
if (valueMappingTable[pixelIndex] < valueMappingTable[pixelRange[subblockIdx][0]])
{
pixelRange[subblockIdx][0] = pixelIndex;
}
if (valueMappingTable[pixelIndex] > valueMappingTable[pixelRange[subblockIdx][1]])
{
pixelRange[subblockIdx][1] = pixelIndex;
}
row[i] = subblockColors[subblockIdx][pixelIndex];
row[i].A = alphaValues[j][i];
}
}
}
row[i] = subblockColors[subblockIdx][pixelIndex];
row[i].A = alphaValues[j][i];
void selectEndPointPCA(const R8G8B8A8 *pixels, R8G8B8A8 *minColor, R8G8B8A8 *maxColor) const
{
static const int kNumPixels = 16;
// determine color distribution
int mu[3], min[3], max[3];
for (int ch = 0; ch < 3; ch++)
{
int muv, minv, maxv;
muv = minv = maxv = (&pixels[0].R)[ch];
for (size_t i = 1; i < kNumPixels; i++)
{
muv += (&pixels[i].R)[ch];
minv = std::min<int>(minv, (&pixels[i].R)[ch]);
maxv = std::max<int>(maxv, (&pixels[i].R)[ch]);
}
mu[ch] = (muv + kNumPixels / 2) / kNumPixels;
min[ch] = minv;
max[ch] = maxv;
}
// determine covariance matrix
int cov[6] = {0, 0, 0, 0, 0, 0};
for (size_t i = 0; i < kNumPixels; i++)
{
int r = pixels[i].R - mu[0];
int g = pixels[i].G - mu[1];
int b = pixels[i].B - mu[2];
cov[0] += r * r;
cov[1] += r * g;
cov[2] += r * b;
cov[3] += g * g;
cov[4] += g * b;
cov[5] += b * b;
}
// Power iteration algorithm to get the eigenvalues and eigenvector
// Starts with diagonal vector
float vfr = static_cast<float>(max[0] - min[0]);
float vfg = static_cast<float>(max[1] - min[1]);
float vfb = static_cast<float>(max[2] - min[2]);
float eigenvalue;
static const size_t kPowerIterations = 4;
for (size_t i = 0; i < kPowerIterations; i++)
{
float r = vfr * cov[0] + vfg * cov[1] + vfb * cov[2];
float g = vfr * cov[1] + vfg * cov[3] + vfb * cov[4];
float b = vfr * cov[2] + vfg * cov[4] + vfb * cov[5];
vfr = r;
vfg = g;
vfb = b;
eigenvalue = sqrt(r * r + g * g + b * b);
if (eigenvalue > 0)
{
float invNorm = 1.0f / eigenvalue;
vfr *= invNorm;
vfg *= invNorm;
vfb *= invNorm;
}
}
int vr, vg, vb;
static const float kDefaultLuminanceThreshold = 4.0f * 255;
static const float kQuantizeRange = 512.0f;
if (eigenvalue < kDefaultLuminanceThreshold) // too small, default to luminance
{
// Luminance weights defined by ITU-R Recommendation BT.601, scaled by 1000
vr = 299;
vg = 587;
vb = 114;
}
else
{
// From the eigenvalue and eigenvector, choose the axis to project
// colors on. When projecting colors we want to do integer computations
// for speed, so we normalize the eigenvector to the [0, 512] range.
float magn = std::max(std::max(std::abs(vfr), std::abs(vfg)), std::abs(vfb));
magn = kQuantizeRange / magn;
vr = static_cast<int>(vfr * magn);
vg = static_cast<int>(vfg * magn);
vb = static_cast<int>(vfb * magn);
}
// Pick colors at extreme points
int minD = pixels[0].R * vr + pixels[0].G * vg + pixels[0].B * vb;
int maxD = minD;
size_t minIndex = 0;
size_t maxIndex = 0;
for (size_t i = 1; i < kNumPixels; i++)
{
int dot = pixels[i].R * vr + pixels[i].G * vg + pixels[i].B * vb;
if (dot < minD)
{
minD = dot;
minIndex = i;
}
if (dot > maxD)
{
maxD = dot;
maxIndex = i;
}
}
*minColor = pixels[minIndex];
*maxColor = pixels[maxIndex];
}
void transcodeIndividualOrDifferentialBlockToBC1(uint8_t *dest,
......@@ -912,18 +1009,15 @@ struct ETC2Block
// A BC1 block has 2 endpoints, pixels is encoded as linear
// interpolations of them. A ETC1/ETC2 individual or differential block
// has 2 subblocks. Each subblock has one color and a modifier. We
// compute the max intensity and min intensity pixel values to use as
// select axis by principal component analysis (PCA) to use as
// our two BC1 endpoints and then map pixels to BC1 by projecting on the
// line between the two endpoints and choosing the right fraction.
//
// In the future, we have 2 potential improvements to this algorithm.
// In the future, we have a potential improvements to this algorithm.
// 1. We don't actually need to decode ETC blocks to RGBs. Instead,
// the subblock colors and pixel indices alreay contains enough
// information for transcode. A direct mapping would be more
// efficient here.
// 2. Currently the BC1 endpoints come from the max and min intensity
// of ETC colors. A principal component analysis (PCA) on them might
// give us better quality results, with limited costs
const auto intensityModifier =
nonOpaquePunchThroughAlpha ? intensityModifierNonOpaque : intensityModifierDefault;
......@@ -940,15 +1034,12 @@ struct ETC2Block
subblockColors[1][modifierIdx] = createRGBA(r2 + i2, g2 + i2, b2 + i2);
}
// 1 and 3 are the argmax and argmin of valueMappingTable
size_t pixelRange[2][2] = {{1, 3}, {1, 3}};
R8G8B8A8 rgbaBlock[16];
// Decode the block in rgbaBlock and store the inverse valueTableMapping
// of {min(modifier index), max(modifier index)}
// Decode the block in rgbaBlock.
for (size_t blockIdx = 0; blockIdx < 2; blockIdx++)
{
decodeSubblock(rgbaBlock, pixelRange, x, y, w, h, alphaValues, u.idht.mode.idm.flipbit,
blockIdx, subblockColors);
decodeSubblock(rgbaBlock, x, y, w, h, alphaValues, u.idht.mode.idm.flipbit, blockIdx,
subblockColors);
}
if (nonOpaquePunchThroughAlpha)
{
......@@ -956,30 +1047,8 @@ struct ETC2Block
sizeof(R8G8B8A8) * 4);
}
// Get the "min" and "max" pixel colors that have been used.
R8G8B8A8 minColor;
const R8G8B8A8 &minColor0 = subblockColors[0][pixelRange[0][0]];
const R8G8B8A8 &minColor1 = subblockColors[1][pixelRange[1][0]];
if (minColor0.R + minColor0.G + minColor0.B < minColor1.R + minColor1.G + minColor1.B)
{
minColor = minColor0;
}
else
{
minColor = minColor1;
}
R8G8B8A8 maxColor;
const R8G8B8A8 &maxColor0 = subblockColors[0][pixelRange[0][1]];
const R8G8B8A8 &maxColor1 = subblockColors[1][pixelRange[1][1]];
if (maxColor0.R + maxColor0.G + maxColor0.B < maxColor1.R + maxColor1.G + maxColor1.B)
{
maxColor = maxColor1;
}
else
{
maxColor = maxColor0;
}
R8G8B8A8 minColor, maxColor;
selectEndPointPCA(rgbaBlock, &minColor, &maxColor);
packBC1(dest, rgbaBlock, minColor, maxColor, !nonOpaquePunchThroughAlpha);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment