/* * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** \file * \ingroup imbdds */ /* * This file is based on a similar file from the NVIDIA texture tools * (http://nvidia-texture-tools.googlecode.com/) * * Original license from NVIDIA follows. */ /* This code is in the public domain - */ #include #include #include #if 0 /* Get approximate luminance. */ inline static uint colorLuminance(Color32 c) { return c.r + c.g + c.b; } /* Get the euclidean distance between the given colors. */ inline static uint colorDistance(Color32 c0, Color32 c1) { return (c0.r - c1.r) * (c0.r - c1.r) + (c0.g - c1.g) * (c0.g - c1.g) + (c0.b - c1.b) * (c0.b - c1.b); } #endif /** Default constructor. */ ColorBlock::ColorBlock() { } /** Init the color block from an array of colors. */ ColorBlock::ColorBlock(const uint *linearImage) { for (uint i = 0; i < 16; i++) { color(i) = Color32(linearImage[i]); } } /** Init the color block with the contents of the given block. */ ColorBlock::ColorBlock(const ColorBlock &block) { for (uint i = 0; i < 16; i++) { color(i) = block.color(i); } } /** Initialize this color block. */ ColorBlock::ColorBlock(const Image *img, uint x, uint y) { init(img, x, y); } void ColorBlock::init(const Image *img, uint x, uint y) { init(img->width(), img->height(), (const uint *)img->pixels(), x, y); } void ColorBlock::init(uint w, uint h, const uint *data, uint x, uint y) { const uint bw = MIN(w - x, 4U); const uint bh = MIN(h - y, 4U); /* Blocks that are smaller than 4x4 are handled by repeating the pixels. * @@ That's only correct when block size is 1, 2 or 4, but not with 3. :( * @@ Ideally we should zero the weights of the pixels out of range. */ for (uint i = 0; i < 4; i++) { const int by = i % bh; for (uint e = 0; e < 4; e++) { const int bx = e % bw; const uint idx = (y + by) * w + x + bx; color(e, i).u = data[idx]; } } } void ColorBlock::init(uint w, uint h, const float *data, uint x, uint y) { const uint bw = MIN(w - x, 4U); const uint bh = MIN(h - y, 4U); /* Blocks that are smaller than 4x4 are handled by repeating the pixels. * @@ That's only correct when block size is 1, 2 or 4, but not with 3. :( * @@ Ideally we should zero the weights of the pixels out of range. */ uint srcPlane = w * h; for (uint i = 0; i < 4; i++) { const uint by = i % bh; for (uint e = 0; e < 4; e++) { const uint bx = e % bw; const uint idx = ((y + by) * w + x + bx); Color32 &c = color(e, i); c.r = uint8(255 * CLAMP(data[idx + 0 * srcPlane], 0.0f, 1.0f)); /* @@ Is this the right way to quantize floats to bytes? */ c.g = uint8(255 * CLAMP(data[idx + 1 * srcPlane], 0.0f, 1.0f)); c.b = uint8(255 * CLAMP(data[idx + 2 * srcPlane], 0.0f, 1.0f)); c.a = uint8(255 * CLAMP(data[idx + 3 * srcPlane], 0.0f, 1.0f)); } } } static inline uint8 component(Color32 c, uint i) { if (i == 0) { return c.r; } if (i == 1) { return c.g; } if (i == 2) { return c.b; } if (i == 3) { return c.a; } if (i == 4) { return 0xFF; } return 0; } void ColorBlock::swizzle(uint x, uint y, uint z, uint w) { for (int i = 0; i < 16; i++) { Color32 c = m_color[i]; m_color[i].r = component(c, x); m_color[i].g = component(c, y); m_color[i].b = component(c, z); m_color[i].a = component(c, w); } } /** Returns true if the block has a single color. */ bool ColorBlock::isSingleColor(Color32 mask /*= Color32(0xFF, 0xFF, 0xFF, 0x00)*/) const { uint u = m_color[0].u & mask.u; for (int i = 1; i < 16; i++) { if (u != (m_color[i].u & mask.u)) { return false; } } return true; } #if 0 /** Returns true if the block has a single color, ignoring transparent pixels. */ bool ColorBlock::isSingleColorNoAlpha() const { Color32 c; int i; for (i = 0; i < 16; i++) { if (m_color[i].a != 0) { c = m_color[i]; } } Color32 mask(0xFF, 0xFF, 0xFF, 0x00); uint u = c.u & mask.u; for (; i < 16; i++) { if (u != (m_color[i].u & mask.u)) { return false; } } return true; } #endif #if 0 /** Count number of unique colors in this color block. */ uint ColorBlock::countUniqueColors() const { uint count = 0; /* @@ This does not have to be o(n^2) */ for (int i = 0; i < 16; i++) { bool unique = true; for (int j = 0; j < i; j++) { if (m_color[i] != m_color[j]) { unique = false; } } if (unique) { count++; } } return count; } #endif #if 0 /** Get average color of the block. */ Color32 ColorBlock::averageColor() const { uint r, g, b, a; r = g = b = a = 0; for (uint i = 0; i < 16; i++) { r += m_color[i].r; g += m_color[i].g; b += m_color[i].b; a += m_color[i].a; } return Color32(uint8(r / 16), uint8(g / 16), uint8(b / 16), uint8(a / 16)); } #endif /** Return true if the block is not fully opaque. */ bool ColorBlock::hasAlpha() const { for (uint i = 0; i < 16; i++) { if (m_color[i].a != 255) { return true; } } return false; } #if 0 /** Get diameter color range. */ void ColorBlock::diameterRange(Color32 *start, Color32 *end) const { Color32 c0, c1; uint best_dist = 0; for (int i = 0; i < 16; i++) { for (int j = i + 1; j < 16; j++) { uint dist = colorDistance(m_color[i], m_color[j]); if (dist > best_dist) { best_dist = dist; c0 = m_color[i]; c1 = m_color[j]; } } } *start = c0; *end = c1; } /** Get luminance color range. */ void ColorBlock::luminanceRange(Color32 *start, Color32 *end) const { Color32 minColor, maxColor; uint minLuminance, maxLuminance; maxLuminance = minLuminance = colorLuminance(m_color[0]); for (uint i = 1; i < 16; i++) { uint luminance = colorLuminance(m_color[i]); if (luminance > maxLuminance) { maxLuminance = luminance; maxColor = m_color[i]; } else if (luminance < minLuminance) { minLuminance = luminance; minColor = m_color[i]; } } *start = minColor; *end = maxColor; } /** Get color range based on the bounding box. */ void ColorBlock::boundsRange(Color32 *start, Color32 *end) const { Color32 minColor(255, 255, 255); Color32 maxColor(0, 0, 0); for (uint i = 0; i < 16; i++) { if (m_color[i].r < minColor.r) { minColor.r = m_color[i].r; } if (m_color[i].g < minColor.g) { minColor.g = m_color[i].g; } if (m_color[i].b < minColor.b) { minColor.b = m_color[i].b; } if (m_color[i].r > maxColor.r) { maxColor.r = m_color[i].r; } if (m_color[i].g > maxColor.g) { maxColor.g = m_color[i].g; } if (m_color[i].b > maxColor.b) { maxColor.b = m_color[i].b; } } /* Offset range by 1/16 of the extents */ Color32 inset; inset.r = (maxColor.r - minColor.r) >> 4; inset.g = (maxColor.g - minColor.g) >> 4; inset.b = (maxColor.b - minColor.b) >> 4; minColor.r = (minColor.r + inset.r <= 255) ? minColor.r + inset.r : 255; minColor.g = (minColor.g + inset.g <= 255) ? minColor.g + inset.g : 255; minColor.b = (minColor.b + inset.b <= 255) ? minColor.b + inset.b : 255; maxColor.r = (maxColor.r >= inset.r) ? maxColor.r - inset.r : 0; maxColor.g = (maxColor.g >= inset.g) ? maxColor.g - inset.g : 0; maxColor.b = (maxColor.b >= inset.b) ? maxColor.b - inset.b : 0; *start = minColor; *end = maxColor; } /** Get color range based on the bounding box. */ void ColorBlock::boundsRangeAlpha(Color32 *start, Color32 *end) const { Color32 minColor(255, 255, 255, 255); Color32 maxColor(0, 0, 0, 0); for (uint i = 0; i < 16; i++) { if (m_color[i].r < minColor.r) { minColor.r = m_color[i].r; } if (m_color[i].g < minColor.g) { minColor.g = m_color[i].g; } if (m_color[i].b < minColor.b) { minColor.b = m_color[i].b; } if (m_color[i].a < minColor.a) { minColor.a = m_color[i].a; } if (m_color[i].r > maxColor.r) { maxColor.r = m_color[i].r; } if (m_color[i].g > maxColor.g) { maxColor.g = m_color[i].g; } if (m_color[i].b > maxColor.b) { maxColor.b = m_color[i].b; } if (m_color[i].a > maxColor.a) { maxColor.a = m_color[i].a; } } /* Offset range by 1/16 of the extents */ Color32 inset; inset.r = (maxColor.r - minColor.r) >> 4; inset.g = (maxColor.g - minColor.g) >> 4; inset.b = (maxColor.b - minColor.b) >> 4; inset.a = (maxColor.a - minColor.a) >> 4; minColor.r = (minColor.r + inset.r <= 255) ? minColor.r + inset.r : 255; minColor.g = (minColor.g + inset.g <= 255) ? minColor.g + inset.g : 255; minColor.b = (minColor.b + inset.b <= 255) ? minColor.b + inset.b : 255; minColor.a = (minColor.a + inset.a <= 255) ? minColor.a + inset.a : 255; maxColor.r = (maxColor.r >= inset.r) ? maxColor.r - inset.r : 0; maxColor.g = (maxColor.g >= inset.g) ? maxColor.g - inset.g : 0; maxColor.b = (maxColor.b >= inset.b) ? maxColor.b - inset.b : 0; maxColor.a = (maxColor.a >= inset.a) ? maxColor.a - inset.a : 0; *start = minColor; *end = maxColor; } #endif #if 0 /** Sort colors by abosolute value in their 16 bit representation. */ void ColorBlock::sortColorsByAbsoluteValue() { /* Dummy selection sort. */ for (uint a = 0; a < 16; a++) { uint max = a; Color16 cmax(m_color[a]); for (uint b = a + 1; b < 16; b++) { Color16 cb(m_color[b]); if (cb.u > cmax.u) { max = b; cmax = cb; } } swap(m_color[a], m_color[max]); } } #endif #if 0 /** Find extreme colors in the given axis. */ void ColorBlock::computeRange(Vector3::Arg axis, Color32 *start, Color32 *end) const { int mini, maxi; mini = maxi = 0; float min, max; min = max = dot(Vector3(m_color[0].r, m_color[0].g, m_color[0].b), axis); for (uint i = 1; i < 16; i++) { const Vector3 vec(m_color[i].r, m_color[i].g, m_color[i].b); float val = dot(vec, axis); if (val < min) { mini = i; min = val; } else if (val > max) { maxi = i; max = val; } } *start = m_color[mini]; *end = m_color[maxi]; } #endif #if 0 /** Sort colors in the given axis. */ void ColorBlock::sortColors(const Vector3 &axis) { float luma_array[16]; for (uint i = 0; i < 16; i++) { const Vector3 vec(m_color[i].r, m_color[i].g, m_color[i].b); luma_array[i] = dot(vec, axis); } /* Dummy selection sort. */ for (uint a = 0; a < 16; a++) { uint min = a; for (uint b = a + 1; b < 16; b++) { if (luma_array[b] < luma_array[min]) { min = b; } } swap(luma_array[a], luma_array[min]); swap(m_color[a], m_color[min]); } } #endif #if 0 /** Get the volume of the color block. */ float ColorBlock::volume() const { Box bounds; bounds.clearBounds(); for (int i = 0; i < 16; i++) { const Vector3 point(m_color[i].r, m_color[i].g, m_color[i].b); bounds.addPointToBounds(point); } return bounds.volume(); } #endif