aseprite/src/util/quantize.cpp

400 lines
11 KiB
C++
Raw Normal View History

2007-11-16 18:25:45 +00:00
/* ASE - Allegro Sprite Editor
2010-02-01 21:25:40 +00:00
* Copyright (C) 2001-2010 David Capello
2007-09-18 23:57:02 +00:00
*
* 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
*/
#include "config.h"
#include <allegro/color.h>
#include "console.h"
#include "effect/effect.h"
#include "effect/images_ref.h"
2007-09-18 23:57:02 +00:00
#include "raster/image.h"
#include "raster/palette.h"
2007-09-18 23:57:02 +00:00
#include "raster/sprite.h"
#include "util/quantize.h"
static int quantize_bitmaps(Image **image, int nimage, RGB *pal, int *bmp_i, int fill_other);
void sprite_quantize(Sprite *sprite)
2007-09-18 23:57:02 +00:00
{
Palette* palette = new Palette(0, 256); // TODO sprite_quantize_ex should change the number of colors of this palette
2007-09-18 23:57:02 +00:00
sprite_quantize_ex(sprite, palette);
// Just one palette
sprite->resetPalettes();
sprite->setPalette(palette, false);
delete palette;
2007-09-18 23:57:02 +00:00
}
void sprite_quantize_ex(const Sprite *sprite, Palette *palette)
2007-09-18 23:57:02 +00:00
{
2008-09-30 21:01:54 +00:00
Image* flat_image;
Image** image_array;
ImageRef* p;
ImageRef* images;
int c, nimage;
images = images_ref_get_from_sprite(sprite, TARGET_ALL_LAYERS |
TARGET_ALL_FRAMES, false);
if (images != NULL) {
2007-09-18 23:57:02 +00:00
/* add a flat image with the current sprite's frame rendered */
flat_image = image_new(sprite->getImgType(), sprite->getWidth(), sprite->getHeight());
image_clear(flat_image, 0);
sprite->render(flat_image, 0, 0);
2007-09-18 23:57:02 +00:00
/* count images in the 'images' list */
c = 0;
for (p=images; p; p=p->next)
c++;
c++; /* the 'flat_image' */
/* create an array of images */
nimage = c;
2008-09-30 21:01:54 +00:00
image_array = (Image**)jmalloc(sizeof(Image*) * nimage);
c = 0;
for (p=images; p; p=p->next)
image_array[c++] = p->image;
image_array[c++] = flat_image; /* the 'flat_image' */
2007-09-18 23:57:02 +00:00
/* generate the optimized palette */
{
PALETTE rgbpal;
2007-09-18 23:57:02 +00:00
int *ibmp;
for (c=0; c<256; c++)
rgbpal[c].r = rgbpal[c].g = rgbpal[c].b = 255;
2007-09-18 23:57:02 +00:00
2008-09-30 21:01:54 +00:00
ibmp = (int*)jmalloc(sizeof(int) * nimage);
for (c=0; c<nimage; c++)
2007-09-18 23:57:02 +00:00
ibmp[c] = 128;
quantize_bitmaps(image_array, nimage, rgbpal, ibmp, true);
palette->fromAllegro(rgbpal);
2007-09-18 23:57:02 +00:00
jfree(ibmp);
2007-09-18 23:57:02 +00:00
}
jfree(image_array);
image_free(flat_image);
images_ref_free(images);
2007-09-18 23:57:02 +00:00
}
}
/* 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.
/ */
/* 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.
*/
/* quantize_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
{
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];
2007-09-18 23:57:02 +00:00
} PALETTE_NODE;
static PALETTE_NODE *rgb_node[64][64][64];
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;
2008-09-30 21:01:54 +00:00
node=(PALETTE_NODE*)jmalloc(sizeof(PALETTE_NODE));
if (!node)
return NULL;
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;
}
}
2007-09-18 23:57:02 +00:00
}
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;
2007-09-18 23:57:02 +00:00
}
return node;
2007-09-18 23:57:02 +00:00
}
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);
}
}
2007-09-18 23:57:02 +00:00
}
if (node->n1==0) {
jfree(node);
node=0;
} else if (node->n2) (*n_colours)++;
return node;
2007-09-18 23:57:02 +00:00
}
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;
}
}
}
2007-09-18 23:57:02 +00:00
}
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;
jfree(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;
}
}
}
2007-09-18 23:57:02 +00:00
}
}
return node;
2007-09-18 23:57:02 +00:00
}
static unsigned int distance_squared(int r,int g,int b)
{
return r*r+g*g+b*b;
2007-09-18 23:57:02 +00:00
}
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);
}
}
2007-09-18 23:57:02 +00:00
}
}
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);
}
}
2007-09-18 23:57:02 +00:00
}
}
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]);
}
}
2007-09-18 23:57:02 +00:00
}
jfree(tree);
2007-09-18 23:57:02 +00:00
}
static int quantize_bitmaps(Image **image, int nimage, RGB *pal, int *bmp_i, int fill_other)
2007-09-18 23:57:02 +00:00
{
2008-09-30 21:01:54 +00:00
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); */
2007-09-18 23:57:02 +00:00
}
/* 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++;
2007-09-18 23:57:02 +00:00
}
/*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(); */
2007-09-18 23:57:02 +00:00
}
/* 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;
}
2007-09-18 23:57:02 +00:00
}
/* free memory used by tree */
destroy_tree(tree);
return n_colours;
2007-09-18 23:57:02 +00:00
}