mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-14 18:40:55 +00:00
Add accurate color quantization or use median cut if the sprite has more than 256 colors.
+ Implemented issue #3 - Accurate Sprite Quantize. + Remove Ben Davis code to quantize color palette (to avoid license problems).
This commit is contained in:
parent
bc753c5bc1
commit
864d13fb40
@ -175,8 +175,6 @@ data/widgets/*.xml XML files with dialogs</pre>
|
||||
<li>Jinete and Vaca are under <a href="http://www.opensource.org/licenses/bsd-license.php">BSD license</a>.</li>
|
||||
<li>Allegro is giftware license (similar to <a href="http://www.opensource.org/licenses/mit-license.html">MIT license</a>).</li>
|
||||
<li>ALFONT is under <a href="http://www.opensource.org/licenses/lgpl-2.1.php">LGPL license</a>.</li>
|
||||
<li>quantize.c is copyright by Ben Davis (you need his authorization to use his code in your own program).</li>
|
||||
<li>quantize2.c is distributed under the <a href="http://www.opensource.org/licenses/mit-license.html">MIT license</a></li>
|
||||
<li>Lua-5.0 is distributed under the <a href="http://www.opensource.org/licenses/mit-license.html">MIT license</a></li>
|
||||
<li>GIMP is distributed under <a href="http://www.opensource.org/licenses/gpl-2.0.php">GPL license</a></li>
|
||||
<li>GLib and GTK+ are distributed under <a href="http://www.opensource.org/licenses/lgpl-2.1.php">LGPL license</a>.</li>
|
||||
@ -239,8 +237,7 @@ data/widgets/*.xml XML files with dialogs</pre>
|
||||
</p>
|
||||
<div class="author">Álvaro González</div>
|
||||
<div class="details">
|
||||
For the other routine to generate
|
||||
optimized palettes (used in old versions of ASE).
|
||||
For color quantization routines (used in old versions of ASE).
|
||||
</div>
|
||||
<div class="author">Angelo Mottola</div>
|
||||
<div class="details">
|
||||
@ -249,7 +246,7 @@ data/widgets/*.xml XML files with dialogs</pre>
|
||||
</div>
|
||||
<div class="author">Ben Davis</div>
|
||||
<div class="details">
|
||||
For his optimized palette generation routine.
|
||||
For color quantization routines (used in old versions of ASE).
|
||||
</div>
|
||||
<div class="author">Billy Biggs and Lauris Kaplinski</div>
|
||||
<div class="details">
|
||||
|
2
TODO.txt
2
TODO.txt
@ -86,8 +86,6 @@ Low priority stuff
|
||||
load/save_col_file.
|
||||
- fix the fli reader (both Allegro and Gfli): when a frame hasn't
|
||||
chunks means that it's looks like the last frame;
|
||||
- talk with Ben Davis, his "quantize.c" has memory leaks (test it
|
||||
more, I don't think so);
|
||||
- talk with Elias Pschernig, his "do_ellipse_diameter" (algo_ellipse)
|
||||
has a bug with ellipses of some dimensions (I made a test, and a
|
||||
various sizes have errors).
|
||||
|
142
src/raster/color_histogram.h
Normal file
142
src/raster/color_histogram.h
Normal file
@ -0,0 +1,142 @@
|
||||
/* ASE - Allegro Sprite Editor
|
||||
* Copyright (C) 2001-2010 David Capello
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef RASTER_COLOR_HISTOGRAM_H_INCLUDED
|
||||
#define RASTER_COLOR_HISTOGRAM_H_INCLUDED
|
||||
|
||||
#include <vector>
|
||||
#include "raster/image.h"
|
||||
#include "raster/image_traits.h"
|
||||
#include "raster/median_cut.h"
|
||||
#include "raster/palette.h"
|
||||
|
||||
namespace quantization {
|
||||
|
||||
template<int RBits, int GBits, int BBits> // Number of bits for each component in the histogram
|
||||
class ColorHistogram
|
||||
{
|
||||
public:
|
||||
// Number of elements in histogram for each RGB component
|
||||
enum {
|
||||
RElements = 1 << RBits,
|
||||
GElements = 1 << GBits,
|
||||
BElements = 1 << BBits
|
||||
};
|
||||
|
||||
ColorHistogram()
|
||||
: m_histogram(RElements*GElements*BElements, 0)
|
||||
, m_useHighPrecision(true)
|
||||
{
|
||||
}
|
||||
|
||||
// Returns the number of points in the specified histogram
|
||||
// entry. Each index (i, j, k) is in the range of the
|
||||
// histogram i=[0,RElements), etc.
|
||||
size_t at(int i, int j, int k) const
|
||||
{
|
||||
return m_histogram[histogramIndex(i, j, k)];
|
||||
}
|
||||
|
||||
// Add the specified "color" in the histogram as many times as the
|
||||
// specified value in "count".
|
||||
void addSamples(ase_uint32 color, size_t count = 1)
|
||||
{
|
||||
int i = histogramIndex(color);
|
||||
|
||||
if (m_histogram[i] < std::numeric_limits<size_t>::max()-count) // Avoid overflow
|
||||
m_histogram[i] += count;
|
||||
else
|
||||
m_histogram[i] = std::numeric_limits<size_t>::max();
|
||||
|
||||
// Accurate colors are used only for less than 256 colors. If the
|
||||
// image has more than 256 colors the m_histogram is used
|
||||
// instead.
|
||||
if (m_useHighPrecision) {
|
||||
std::vector<ase_uint32>::iterator it =
|
||||
std::find(m_highPrecision.begin(), m_highPrecision.end(), color);
|
||||
|
||||
// The color is not in the high-precision table
|
||||
if (it == m_highPrecision.end()) {
|
||||
if (m_highPrecision.size() < 256) {
|
||||
m_highPrecision.push_back(color);
|
||||
}
|
||||
else {
|
||||
// In this case we reach the limit for the high-precision histogram.
|
||||
m_useHighPrecision = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a set of entries for the given palette in the given range
|
||||
// with the more important colors in the histogram. Returns the
|
||||
// number of used entries in the palette (maybe the range [from,to]
|
||||
// is more than necessary).
|
||||
int createOptimizedPalette(Palette* palette, int from, int to)
|
||||
{
|
||||
// Can we use the high-precision table?
|
||||
if (m_useHighPrecision && int(m_highPrecision.size()) <= (to-from+1)) {
|
||||
for (int i=0; i<(int)m_highPrecision.size(); ++i)
|
||||
palette->setEntry(from+i, m_highPrecision[i]);
|
||||
|
||||
return m_highPrecision.size();
|
||||
}
|
||||
// OK, we have to use the histogram and some algorithm (like
|
||||
// median-cut) to quantize "optimal" colors.
|
||||
else {
|
||||
std::vector<ase_uint32> result;
|
||||
median_cut(*this, to-from+1, result);
|
||||
|
||||
for (int i=0; i<(int)result.size(); ++i)
|
||||
palette->setEntry(from+i, result[i]);
|
||||
|
||||
return result.size();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Converts input color in a index for the histogram. It reduces
|
||||
// each 8-bit component to the resolution given in the template
|
||||
// parameters.
|
||||
size_t histogramIndex(ase_uint32 color) const
|
||||
{
|
||||
return histogramIndex((_rgba_getr(color) >> (8 - RBits)),
|
||||
(_rgba_getg(color) >> (8 - GBits)),
|
||||
(_rgba_getb(color) >> (8 - BBits)));
|
||||
}
|
||||
|
||||
size_t histogramIndex(int i, int j, int k) const
|
||||
{
|
||||
return i | (j << RBits) | (k << (RBits+GBits));
|
||||
}
|
||||
|
||||
// 3D histogram (the index in the histogram is calculated through histogramIndex() function).
|
||||
std::vector<size_t> m_histogram;
|
||||
|
||||
// High precision histogram to create an accurate palette if RGB
|
||||
// source images contains less than 256 colors.
|
||||
std::vector<ase_uint32> m_highPrecision;
|
||||
|
||||
// True if we can use m_highPrecision still (it means that the
|
||||
// number of different samples is less than 256 colors still).
|
||||
bool m_useHighPrecision;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
302
src/raster/median_cut.h
Normal file
302
src/raster/median_cut.h
Normal file
@ -0,0 +1,302 @@
|
||||
/* ASE - Allegro Sprite Editor
|
||||
* Copyright (C) 2001-2010 David Capello
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef RASTER_MEDIAN_CUT_H_INCLUDED
|
||||
#define RASTER_MEDIAN_CUT_H_INCLUDED
|
||||
|
||||
#include <list>
|
||||
#include <queue>
|
||||
|
||||
namespace quantization {
|
||||
|
||||
template<class Histogram>
|
||||
class Box {
|
||||
|
||||
// These classes are used as parameters for some Box's generic
|
||||
// member functions, so we can access to a different axis using
|
||||
// the same generic function (i=Red channel in RAxisGetter, etc.).
|
||||
struct RAxisGetter { static size_t at(const Histogram& h, int i, int j, int k) { return h.at(i, j, k); } };
|
||||
struct GAxisGetter { static size_t at(const Histogram& h, int i, int j, int k) { return h.at(j, i, k); } };
|
||||
struct BAxisGetter { static size_t at(const Histogram& h, int i, int j, int k) { return h.at(j, k, i); } };
|
||||
|
||||
// These classes are used as template parameter to split a Box
|
||||
// along an axis (see splitAlongAxis)
|
||||
struct RAxisSplitter {
|
||||
static Box box1(const Box& box, int r) { return Box(box.r1, box.g1, box.b1, r, box.g2, box.b2); }
|
||||
static Box box2(const Box& box, int r) { return Box(r, box.g1, box.b1, box.r2, box.g2, box.b2); }
|
||||
};
|
||||
struct GAxisSplitter {
|
||||
static Box box1(const Box& box, int g) { return Box(box.r1, box.g1, box.b1, box.r2, g, box.b2); }
|
||||
static Box box2(const Box& box, int g) { return Box(box.r1, g, box.b1, box.r2, box.g2, box.b2); }
|
||||
};
|
||||
struct BAxisSplitter {
|
||||
static Box box1(const Box& box, int b) { return Box(box.r1, box.g1, box.b1, box.r2, box.g2, b ); }
|
||||
static Box box2(const Box& box, int b) { return Box(box.r1, box.g1, b, box.r2, box.g2, box.b2); }
|
||||
};
|
||||
|
||||
public:
|
||||
Box(int r1, int g1, int b1,
|
||||
int r2, int g2, int b2)
|
||||
: r1(r1), g1(g1), b1(b1)
|
||||
, r2(r2), g2(g2), b2(b2)
|
||||
, volume(calculateVolume())
|
||||
, points(0) { }
|
||||
|
||||
// Shrinks each plane of the box to a position where there are
|
||||
// points in the histogram.
|
||||
void shrink(const Histogram& histogram)
|
||||
{
|
||||
axisShrink<RAxisGetter>(histogram, r1, r2, g1, g2, b1, b2);
|
||||
axisShrink<GAxisGetter>(histogram, g1, g2, r1, r2, b1, b2);
|
||||
axisShrink<BAxisGetter>(histogram, b1, b2, r1, r2, g1, g2);
|
||||
|
||||
// Calculate number of points inside the box (this is done by
|
||||
// first time here, because the Box ctor didn't calculate it).
|
||||
points = countPoints(histogram);
|
||||
|
||||
// Recalculate the volume (used in operator<).
|
||||
volume = calculateVolume();
|
||||
}
|
||||
|
||||
bool split(const Histogram& histogram, std::priority_queue<Box>& boxes) const
|
||||
{
|
||||
// Split along the largest dimension of the box.
|
||||
if ((r2-r1) >= (g2-g1) && (r2-r1) >= (b2-b1)) {
|
||||
return splitAlongAxis<RAxisGetter, RAxisSplitter>(histogram, boxes, r1, r2, g1, g2, b1, b2);
|
||||
}
|
||||
else if ((g2-g1) >= (r2-r1) && (g2-g1) >= (b2-b1)) {
|
||||
return splitAlongAxis<GAxisGetter, GAxisSplitter>(histogram, boxes, g1, g2, r1, r2, b1, b2);
|
||||
}
|
||||
else {
|
||||
return splitAlongAxis<BAxisGetter, BAxisSplitter>(histogram, boxes, b1, b2, r1, r2, g1, g2);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the color enclosed by the box calculating the mean of
|
||||
// all histogram's points inside the box.
|
||||
ase_uint32 meanColor(const Histogram& histogram) const
|
||||
{
|
||||
size_t r = 0, g = 0, b = 0;
|
||||
size_t count = 0;
|
||||
int i, j, k;
|
||||
|
||||
for (i=r1; i<=r2; ++i)
|
||||
for (j=g1; j<=g2; ++j)
|
||||
for (k=b1; k<=b2; ++k) {
|
||||
int c = histogram.at(i, j, k);
|
||||
r += c * i;
|
||||
g += c * j;
|
||||
b += c * k;
|
||||
count += c;
|
||||
}
|
||||
|
||||
// No colors in the box? This should not be possible.
|
||||
assert(count > 0 && "Box without histogram points, you must fill the histogram before using this function.");
|
||||
if (count == 0)
|
||||
return _rgba(0, 0, 0, 255);
|
||||
|
||||
// Returns the mean.
|
||||
return _rgba((255 * r / (Histogram::RElements-1)) / count,
|
||||
(255 * g / (Histogram::GElements-1)) / count,
|
||||
(255 * b / (Histogram::BElements-1)) / count, 255);
|
||||
}
|
||||
|
||||
// The boxes will be sort in the priority_queue by volume.
|
||||
bool operator<(const Box& other) const
|
||||
{
|
||||
return volume < other.volume;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Calculates the volume from the current box's dimensions. The
|
||||
// value returned by this function is cached in the "volume"
|
||||
// variable member of Box class to avoid multiplying several
|
||||
// times.
|
||||
int calculateVolume() const
|
||||
{
|
||||
return (r2-r1+1) * (g2-g1+1) * (b2-b1+1);
|
||||
}
|
||||
|
||||
// Returns the number of histogram's points inside the box bounds.
|
||||
size_t countPoints(const Histogram& histogram) const
|
||||
{
|
||||
size_t count = 0;
|
||||
int i, j, k;
|
||||
|
||||
for (i=r1; i<=r2; ++i)
|
||||
for (j=g1; j<=g2; ++j)
|
||||
for (k=b1; k<=b2; ++k)
|
||||
count += histogram.at(i, j, k);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Reduces the specified side of the box (i1/i2) along the
|
||||
// specified axis (if AxisGetter is RAxisGetter, then i1=r1,
|
||||
// i2=r2; if AxisGetter is GAxisGetter, then i1=g1, i2=g2).
|
||||
template<class AxisGetter>
|
||||
static void axisShrink(const Histogram& histogram,
|
||||
int& i1, int& i2,
|
||||
const int& j1, const int& j2,
|
||||
const int& k1, const int& k2)
|
||||
{
|
||||
int j, k;
|
||||
|
||||
// Shrink i1.
|
||||
for (; i1<i2; ++i1) {
|
||||
for (j=j1; j<=j2; ++j) {
|
||||
for (k=k1; k<=k2; ++k) {
|
||||
if (AxisGetter::at(histogram, i1, j, k) > 0)
|
||||
goto doneA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doneA:;
|
||||
|
||||
for (; i2>i1; --i2) {
|
||||
for (j=j1; j<=j2; ++j) {
|
||||
for (k=k1; k<=k2; ++k) {
|
||||
if (AxisGetter::at(histogram, i2, j, k) > 0)
|
||||
goto doneB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doneB:;
|
||||
}
|
||||
|
||||
// Splits the box in two sub-boxes (if it's possible) along the
|
||||
// specified axis by AxisGetter template parameter and "i1/i2"
|
||||
// arguments. Returns true if the split was done and the "boxes"
|
||||
// queue contains the new two sub-boxes resulting from the split
|
||||
// operation.
|
||||
template<class AxisGetter, class AxisSplitter>
|
||||
bool splitAlongAxis(const Histogram& histogram,
|
||||
std::priority_queue<Box>& boxes,
|
||||
const int& i1, const int& i2,
|
||||
const int& j1, const int& j2,
|
||||
const int& k1, const int& k2) const
|
||||
{
|
||||
// These two variables will be used to count how many points are
|
||||
// in each side of the box if we split it in "i" position.
|
||||
size_t totalPoints1 = 0;
|
||||
size_t totalPoints2 = this->points;
|
||||
int i, j, k;
|
||||
|
||||
// We will try to split the box along the "i" axis. Imagine a
|
||||
// plane which its normal vector is "i" axis, so we will try to
|
||||
// move this plane from "i1" to "i2" to find the median, where
|
||||
// the number of points in both sides of the plane are
|
||||
// approximated the same.
|
||||
for (i=i1; i<=i2; ++i) {
|
||||
size_t planePoints = 0;
|
||||
|
||||
// We count all points in "i" plane.
|
||||
for (j=j1; j<=j2; ++j)
|
||||
for (k=k1; k<=k2; ++k)
|
||||
planePoints += AxisGetter::at(histogram, i, j, k);
|
||||
|
||||
// As we move the plane to split through "i" axis One side is getting more points,
|
||||
totalPoints1 += planePoints;
|
||||
totalPoints2 -= planePoints;
|
||||
|
||||
if (totalPoints1 > totalPoints2) {
|
||||
if (totalPoints2 > 0) {
|
||||
Box box1(AxisSplitter::box1(*this, i));
|
||||
Box box2(AxisSplitter::box2(*this, i+1));
|
||||
box1.points = totalPoints1;
|
||||
box2.points = totalPoints2;
|
||||
boxes.push(box1);
|
||||
boxes.push(box2);
|
||||
return true;
|
||||
}
|
||||
else if (totalPoints1-planePoints > 0) {
|
||||
Box box1(AxisSplitter::box1(*this, i-1));
|
||||
Box box2(AxisSplitter::box2(*this, i));
|
||||
box1.points = totalPoints1-planePoints;
|
||||
box2.points = totalPoints2+planePoints;
|
||||
boxes.push(box1);
|
||||
boxes.push(box2);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int r1, g1, b1; // Min point (closest to origin)
|
||||
int r2, g2, b2; // Max point
|
||||
size_t points; // Number of points in the space which enclose this box
|
||||
int volume;
|
||||
}; // end of class Box
|
||||
|
||||
// Median Cut Alqorithm as described in P. Heckbert, “Color image
|
||||
// quantization for frame buffer display,”, Computer Graphics,
|
||||
// 16(3), pp. 297-307 (1982)
|
||||
template<class Histogram>
|
||||
void median_cut(const Histogram& histogram, size_t maxBoxes, std::vector<ase_uint32>& result)
|
||||
{
|
||||
// We need a priority queue to split bigger boxes first (see Box::operator<).
|
||||
std::priority_queue<Box<Histogram> > boxes;
|
||||
|
||||
// First we start with one big box containing all histogram's samples.
|
||||
boxes.push(Box<Histogram>(0, 0, 0,
|
||||
Histogram::RElements-1,
|
||||
Histogram::GElements-1,
|
||||
Histogram::BElements-1));
|
||||
|
||||
// Then we split each box until we reach the maximum specified by
|
||||
// the user (maxBoxes) or until there aren't more boxes to split.
|
||||
while (!boxes.empty() && boxes.size() < maxBoxes) {
|
||||
// Get and remove the first (bigger) box to process from "boxes" queue.
|
||||
Box<Histogram> box(boxes.top());
|
||||
boxes.pop();
|
||||
|
||||
// Shrink the box to the minimum, to enclose the same points in
|
||||
// the histogram.
|
||||
box.shrink(histogram);
|
||||
|
||||
// Try to split the box along the largest axis.
|
||||
if (!box.split(histogram, boxes)) {
|
||||
// If we were not able to split the box (maybe because it is
|
||||
// too small or there are not enough points to split it), then
|
||||
// we add the box's color to the "result" vector directly (the
|
||||
// box is not in the queue anymore).
|
||||
if (result.size() < maxBoxes)
|
||||
result.push_back(box.meanColor(histogram));
|
||||
else
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// When we reach the maximum number of boxes, we convert each box
|
||||
// to a color for the "result" vector.
|
||||
while (!boxes.empty() && result.size() < maxBoxes) {
|
||||
const Box<Histogram>& box(boxes.top());
|
||||
result.push_back(box.meanColor(histogram));
|
||||
boxes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -18,9 +18,14 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "gfx/hsv.h"
|
||||
#include "gfx/rgb.h"
|
||||
#include "raster/blend.h"
|
||||
#include "raster/color_histogram.h"
|
||||
#include "raster/image.h"
|
||||
#include "raster/images_collector.h"
|
||||
#include "raster/palette.h"
|
||||
@ -36,10 +41,11 @@ static Image* ordered_dithering(const Image* src_image,
|
||||
const RgbMap* rgbmap,
|
||||
const Palette* palette);
|
||||
|
||||
static int create_palette_from_bitmaps(Image** image, int nimage, RGB* pal, int* bmp_i, int fill_other);
|
||||
static void create_palette_from_bitmaps(const std::vector<Image*>& images, Palette* palette, bool has_background_layer);
|
||||
|
||||
Palette* quantization::create_palette_from_rgb(const Sprite* sprite)
|
||||
{
|
||||
bool has_background_layer = (sprite->getBackgroundLayer() != NULL);
|
||||
Palette* palette = new Palette(0, 256);
|
||||
Image* flat_image;
|
||||
|
||||
@ -63,20 +69,7 @@ Palette* quantization::create_palette_from_rgb(const Sprite* sprite)
|
||||
image_array[c++] = flat_image; // The 'flat_image'
|
||||
|
||||
// Generate an optimized palette for all images
|
||||
{
|
||||
PALETTE rgbpal;
|
||||
|
||||
for (c=0; c<256; c++)
|
||||
rgbpal[c].r = rgbpal[c].g = rgbpal[c].b = 255;
|
||||
|
||||
std::vector<int> ibmp(nimage);
|
||||
for (c=0; c<nimage; c++)
|
||||
ibmp[c] = 128;
|
||||
|
||||
create_palette_from_bitmaps(&image_array[0], nimage, rgbpal, &ibmp[0], true);
|
||||
|
||||
palette->fromAllegro(rgbpal);
|
||||
}
|
||||
create_palette_from_bitmaps(image_array, palette, has_background_layer);
|
||||
|
||||
delete flat_image;
|
||||
return palette;
|
||||
@ -328,299 +321,41 @@ static Image* ordered_dithering(const Image* src_image,
|
||||
return dst_image;
|
||||
}
|
||||
|
||||
/* quantize.c
|
||||
* Copyright (C) 2000-2002 by Ben "entheh" Davis
|
||||
*
|
||||
* Nobody can use these routines without the explicit
|
||||
* permission of Ben Davis: entheh@users.sourceforge.net
|
||||
*
|
||||
* Adapted to ASE by David A. Capello.
|
||||
*
|
||||
* See "LEGAL.txt" for details.
|
||||
/ */
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Creation of optimized palette for RGB images
|
||||
// by David Capello
|
||||
|
||||
/* HACK ALERT: We use 'filler' to represent whether a colour in the palette
|
||||
* is reserved. This is only tested when the .r element can no longer
|
||||
* indicate whether an entry is reserved, i.e. when converting bitmaps.
|
||||
* NOTE: When converting bitmaps, they will not use reserved colours.
|
||||
*/
|
||||
|
||||
#define TREE_DEPTH 2
|
||||
/* TREE_DEPTH should be a power of 2. The lower it is, the deeper the tree
|
||||
* will go. This will not usually affect the accuracy of colours generated,
|
||||
* but with images with very subtle changes of colour, the variation can be
|
||||
* lost with higher values of TREE_DEPTH.
|
||||
*
|
||||
* As these trees go deeper they use an extortionate amount of memory. If it
|
||||
* runs out, you have no choice but to increase the value of TREE_DEPTH.
|
||||
*/
|
||||
|
||||
/* create_palette_from_bitmaps:
|
||||
* generates an optimised palette for the list of
|
||||
* bitmaps specified. All bitmaps must be true-colour. The number of bitmaps
|
||||
* must be passed in n_bmp, and bmp must point to an array of pointers to
|
||||
* BITMAP structures. bmp_i should point to an array parallel to bmp,
|
||||
* containing importance values for the bitmaps. pal must point to a PALETTE
|
||||
* structure which will be filled with the optimised palette.
|
||||
*
|
||||
* pal will be scanned for predefined colours. Any entry where the .r element
|
||||
* is 255 will be considered a free entry. All others are taken as predefined
|
||||
* colours. Predefined colours will not be changed, and the rest of the
|
||||
* palette will be unaffected by them. There may be copies of these colours.
|
||||
*
|
||||
* If in the bitmaps this routine finds any occurrence of the colour to which
|
||||
* palette entry 0 is set, it will be treated as a transparency colour. It
|
||||
* will not be considered when generating the palette.
|
||||
*
|
||||
* Under some circumstances there will be palette entries left over. If
|
||||
* fill_other != 0, they will be filled with black. If fill_other == 0, they
|
||||
* will be left containing whatever values they contained before.
|
||||
*
|
||||
* This function does not convert the bitmaps to 256-colour format. The
|
||||
* conversion must be done afterwards by the main program.
|
||||
*/
|
||||
|
||||
typedef struct PALETTE_NODE
|
||||
static void create_palette_from_bitmaps(const std::vector<Image*>& images, Palette* palette, bool has_background_layer)
|
||||
{
|
||||
unsigned int rl,gl,bl,rh,gh,bh;
|
||||
unsigned int n1,n2,Sr,Sg,Sb,E;
|
||||
struct PALETTE_NODE *parent;
|
||||
struct PALETTE_NODE *subnode[2][2][2];
|
||||
} PALETTE_NODE;
|
||||
quantization::ColorHistogram<5, 6, 5> histogram;
|
||||
ase_uint32 color;
|
||||
RgbTraits::address_t address;
|
||||
|
||||
static PALETTE_NODE *rgb_node[64][64][64];
|
||||
// If the sprite has a background layer, the first entry can be
|
||||
// used, in other case the 0 indexed will be the mask color, so it
|
||||
// will not be used later in the color conversion (from RGB to
|
||||
// Indexed).
|
||||
int first_usable_entry = (has_background_layer ? 0: 1);
|
||||
|
||||
static PALETTE_NODE *create_node(unsigned int rl,
|
||||
unsigned int gl,
|
||||
unsigned int bl,
|
||||
unsigned int rh,
|
||||
unsigned int gh,
|
||||
unsigned int bh,PALETTE_NODE *parent)
|
||||
{
|
||||
PALETTE_NODE *node;
|
||||
unsigned int rm,gm,bm;
|
||||
node = new PALETTE_NODE;
|
||||
node->rl=rl;
|
||||
node->gl=gl;
|
||||
node->bl=bl;
|
||||
node->rh=rh;
|
||||
node->gh=gh;
|
||||
node->bh=bh;
|
||||
node->E=node->Sb=node->Sg=node->Sr=node->n2=node->n1=0;
|
||||
node->parent=parent;
|
||||
if (rh-rl>TREE_DEPTH) {
|
||||
rm=(rl+rh)>>1;
|
||||
gm=(gl+gh)>>1;
|
||||
bm=(bl+bh)>>1;
|
||||
node->subnode[0][0][0]=create_node(rl,gl,bl,rm,gm,bm,node);
|
||||
node->subnode[0][0][1]=create_node(rm,gl,bl,rh,gm,bm,node);
|
||||
node->subnode[0][1][0]=create_node(rl,gm,bl,rm,gh,bm,node);
|
||||
node->subnode[0][1][1]=create_node(rm,gm,bl,rh,gh,bm,node);
|
||||
node->subnode[1][0][0]=create_node(rl,gl,bm,rm,gm,bh,node);
|
||||
node->subnode[1][0][1]=create_node(rm,gl,bm,rh,gm,bh,node);
|
||||
node->subnode[1][1][0]=create_node(rl,gm,bm,rm,gh,bh,node);
|
||||
node->subnode[1][1][1]=create_node(rm,gm,bm,rh,gh,bh,node);
|
||||
} else {
|
||||
for (bm=bl;bm<bh;bm++) {
|
||||
for (gm=gl;gm<gh;gm++) {
|
||||
for (rm=rl;rm<rh;rm++) {
|
||||
rgb_node[bm][gm][rm]=node;
|
||||
for (int i=0; i<(int)images.size(); ++i) {
|
||||
const Image* image = images[i];
|
||||
|
||||
for (int y=0; y<image->h; ++y) {
|
||||
address = image_address_fast<RgbTraits>(image, 0, y);
|
||||
|
||||
for (int x=0; x<image->w; ++x) {
|
||||
color = *address;
|
||||
|
||||
if (_rgba_geta(color) > 0) {
|
||||
color |= _rgba(0, 0, 0, 255);
|
||||
histogram.addSamples(color, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
node->subnode[1][1][1]=
|
||||
node->subnode[1][1][0]=
|
||||
node->subnode[1][0][1]=
|
||||
node->subnode[1][0][0]=
|
||||
node->subnode[0][1][1]=
|
||||
node->subnode[0][1][0]=
|
||||
node->subnode[0][0][1]=
|
||||
node->subnode[0][0][0]=0;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static PALETTE_NODE *collapse_empty(PALETTE_NODE *node,unsigned int *n_colours)
|
||||
{
|
||||
unsigned int b,g,r;
|
||||
for (b=0;b<2;b++) {
|
||||
for (g=0;g<2;g++) {
|
||||
for (r=0;r<2;r++) {
|
||||
if (node->subnode[b][g][r])
|
||||
node->subnode[b][g][r]=collapse_empty(node->subnode[b][g][r],n_colours);
|
||||
++address;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node->n1==0) {
|
||||
delete node;
|
||||
node=0;
|
||||
} else if (node->n2) (*n_colours)++;
|
||||
return node;
|
||||
}
|
||||
|
||||
static PALETTE_NODE *collapse_nodes(PALETTE_NODE *node,unsigned int *n_colours,
|
||||
unsigned int n_entries,unsigned int Ep)
|
||||
{
|
||||
unsigned int b,g,r;
|
||||
if (node->E<=Ep) {
|
||||
for (b=0;b<2;b++) {
|
||||
for (g=0;g<2;g++) {
|
||||
for (r=0;r<2;r++) {
|
||||
if (node->subnode[b][g][r]) {
|
||||
node->subnode[b][g][r]=collapse_nodes(node->subnode[b][g][r],
|
||||
n_colours,n_entries,0);
|
||||
if (*n_colours<=n_entries) return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node->parent->n2) (*n_colours)--;
|
||||
node->parent->n2+=node->n2;
|
||||
node->parent->Sr+=node->Sr;
|
||||
node->parent->Sg+=node->Sg;
|
||||
node->parent->Sb+=node->Sb;
|
||||
delete node;
|
||||
node=0;
|
||||
} else {
|
||||
for (b=0;b<2;b++) {
|
||||
for (g=0;g<2;g++) {
|
||||
for (r=0;r<2;r++) {
|
||||
if (node->subnode[b][g][r]) {
|
||||
node->subnode[b][g][r]=collapse_nodes(node->subnode[b][g][r],
|
||||
n_colours,n_entries,Ep);
|
||||
if (*n_colours<=n_entries) return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static unsigned int distance_squared(int r,int g,int b)
|
||||
{
|
||||
return r*r+g*g+b*b;
|
||||
}
|
||||
|
||||
static void minimum_Ep(PALETTE_NODE *node,unsigned int *Ep)
|
||||
{
|
||||
unsigned int r,g,b;
|
||||
if (node->E<*Ep) *Ep=node->E;
|
||||
for (b=0;b<2;b++) {
|
||||
for (g=0;g<2;g++) {
|
||||
for (r=0;r<2;r++) {
|
||||
if (node->subnode[b][g][r]) minimum_Ep(node->subnode[b][g][r],Ep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_palette(PALETTE_NODE *node,unsigned int *c,RGB *pal,int depth)
|
||||
{
|
||||
unsigned int r,g,b;
|
||||
if (node->n2) {
|
||||
for (;pal[*c].r!=255;(*c)++);
|
||||
pal[*c].r=node->Sr/node->n2;
|
||||
pal[*c].g=node->Sg/node->n2;
|
||||
pal[*c].b=node->Sb/node->n2;
|
||||
(*c)++;
|
||||
}
|
||||
for (b=0;b<2;b++) {
|
||||
for (g=0;g<2;g++) {
|
||||
for (r=0;r<2;r++) {
|
||||
if (node->subnode[b][g][r])
|
||||
fill_palette(node->subnode[b][g][r],c,pal,depth+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy_tree(PALETTE_NODE *tree)
|
||||
{
|
||||
unsigned int r,g,b;
|
||||
for (b=0;b<2;b++) {
|
||||
for (g=0;g<2;g++) {
|
||||
for (r=0;r<2;r++) {
|
||||
if (tree->subnode[b][g][r]) destroy_tree(tree->subnode[b][g][r]);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete tree;
|
||||
}
|
||||
|
||||
static int create_palette_from_bitmaps(Image** image, int nimage, RGB* pal, int* bmp_i, int fill_other)
|
||||
{
|
||||
int c_bmp,x,y,r,g,b;
|
||||
unsigned int n_colours=0;
|
||||
unsigned int n_entries=0;
|
||||
unsigned int c, Ep;
|
||||
PALETTE_NODE *tree,*node;
|
||||
|
||||
/* only support RGB bitmaps */
|
||||
if ((nimage < 1) || (image[0]->imgtype != IMAGE_RGB))
|
||||
return 0;
|
||||
|
||||
/*Create the tree structure*/
|
||||
tree=create_node(0,0,0,64,64,64,0);
|
||||
/*Scan the bitmaps*/
|
||||
/* add_progress(nimage+1); */
|
||||
for (c_bmp=0;c_bmp<nimage;c_bmp++) {
|
||||
/* add_progress(image[c_bmp]->h); */
|
||||
for (y=0;y<image[c_bmp]->h;y++) {
|
||||
for (x=0;x<image[c_bmp]->w;x++) {
|
||||
c=image[c_bmp]->getpixel(x,y);
|
||||
r=_rgba_getr(c)>>2;
|
||||
g=_rgba_getg(c)>>2;
|
||||
b=_rgba_getb(c)>>2;
|
||||
node=rgb_node[b][g][r];
|
||||
node->n2+=bmp_i[c_bmp];
|
||||
node->Sr+=r*bmp_i[c_bmp];
|
||||
node->Sg+=g*bmp_i[c_bmp];
|
||||
node->Sb+=b*bmp_i[c_bmp];
|
||||
do {
|
||||
node->n1+=bmp_i[c_bmp];
|
||||
node->E+=distance_squared((r<<1)-node->rl-node->rh,
|
||||
(g<<1)-node->gl-node->gh,
|
||||
(b<<1)-node->bl-node->bh)*bmp_i[c_bmp];
|
||||
node=node->parent;
|
||||
} while (node);
|
||||
}
|
||||
/* do_progress(y); */
|
||||
}
|
||||
/* del_progress(); */
|
||||
/* do_progress(c_bmp); */
|
||||
}
|
||||
/*Collapse empty nodes in the tree, and count leaves*/
|
||||
tree=collapse_empty(tree,&n_colours);
|
||||
/*Count free palette entries*/
|
||||
for (c=0;c<256;c++) {
|
||||
if (pal[c].r==255) n_entries++;
|
||||
}
|
||||
/*Collapse nodes until there are few enough to fit in the palette*/
|
||||
if (n_colours > n_entries) {
|
||||
/* int n_colours1 = n_colours; */
|
||||
/* add_progress(n_colours1 - n_entries); */
|
||||
while (n_colours>n_entries) {
|
||||
Ep=0xFFFFFFFFul;
|
||||
minimum_Ep(tree,&Ep);
|
||||
tree=collapse_nodes(tree,&n_colours,n_entries,Ep);
|
||||
|
||||
/* if (n_colours > n_entries) */
|
||||
/* do_progress(n_colours1 - n_colours); */
|
||||
}
|
||||
/* del_progress(); */
|
||||
}
|
||||
/* del_progress(); */
|
||||
/* fill palette */
|
||||
c=0;
|
||||
fill_palette(tree,&c,pal,1);
|
||||
if (fill_other) {
|
||||
for (;c<256;c++) {
|
||||
if (pal[c].r==255)
|
||||
pal[c].b=pal[c].g=pal[c].r=0;
|
||||
}
|
||||
}
|
||||
/* free memory used by tree */
|
||||
destroy_tree(tree);
|
||||
return n_colours;
|
||||
|
||||
int used_colors = histogram.createOptimizedPalette(palette, first_usable_entry, 255);
|
||||
//palette->resize(first_usable_entry+used_colors); // TODO
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user