看起来我的第一个天真的方法对于你的示例输入甚至比你的参考更好:
![result](https://istack.dev59.com/xA6lq.webp)
在左边是您的输入图像,在中间是使用全局
group[]
调色板输出的精灵,而没有空精灵。右边是按组排序的唯一调色板,最右边的列是代表该组的组调色板。
正如您所看到的,我只有5个16色调色板,而不是6个。第一个颜色索引0保留为透明颜色(我硬编码白色,因为我无法访问原始索引颜色)。算法如下:
初始化精灵
每个精灵都必须有其使用的全局调色板的调色板和索引。
结构
我需要两个调色板列表。一个是所有同时使用的唯一调色板列表(整个图像/帧),我称之为pal[]
,另一个称为group[]
,保存最终合并的调色板以供使用。
填充pal[]
只需从所有精灵中提取所有调色板...测试其唯一性(这只是为了提高O(n^2)
搜索的性能)。为此,我对调色板进行了排序,以便可以直接在O(n)
而不是O(n^2)
中进行比较。
分组调色板
取第一个未分组的调色板并创建新组。然后检查所有其他未分组的调色板(O(n^2)
),如果可合并,则合并它们。可合并指处理过的pal[i]
在group[j]
中至少有50%的颜色,并且所有缺失的颜色仍然可以适合group[j]
。如果是这种情况,则将pal[i]
标记为group[j]
成员,并将缺失的颜色添加到group[j]
中。然后重复#4,直到没有未分组的调色板为止。
现在重新索引精灵以匹配group[]
调色板
这是有关编程的简单C++代码:
Here simple C++ code for this:
const int _sprite_size=16;
const int _palette_size=16;
class palette
{
public:
int pals;
DWORD pal[_palette_size];
int group;
palette() {}
palette(palette& a) { *this=a; }
~palette() {}
palette* operator = (const palette *a) { *this=*a; return this; }
void draw(TCanvas *can,int x,int y,int sz,int dir)
{
int i;
color c;
for (i=0;i<pals;i++)
{
c.dd=pal[i]; rgb2bgr(c);
can->Pen->Color=TColor(0x00202020);
can->Brush->Color=TColor(c.dd);
can->Rectangle(x,y,x+sz,y+sz);
if (dir== 0) x+=sz;
if (dir== 90) y-=sz;
if (dir==180) x-=sz;
if (dir==270) y+=sz;
}
}
void sort()
{
int i,e,n=pals; DWORD q;
for (e=1;e;n--)
for (e=0,i=1;i<n;i++)
if (pal[i-1]<pal[i])
{ q=pal[i-1]; pal[i-1]=pal[i]; pal[i]=q; e=1; }
}
int operator == (palette &a) { if (pals!=a.pals) return 0; for (int i=0;i<pals;i++) if (pal[i]!=a.pal[i]) return 0; return 1; }
int merge(palette &p)
{
int equal=0,mising=0,i,j;
DWORD m[_palette_size];
for (i=0;i<p.pals;i++)
{
m[mising]=p.pal[i];
mising++;
for (j=0;j<pals;j++)
if (p.pal[i]==pal[j])
{
mising--;
equal++;
}
}
if (equal+equal<p.pals) return 0;
if (pals+mising>_palette_size) return 0;
for (i=0;i<mising;i++) { pal[pals]=m[i]; pals++; }
return 1;
}
};
class sprite
{
public:
int xs,ys;
BYTE pix[_sprite_size][_sprite_size];
palette pal;
int gpal;
sprite() {}
sprite(sprite& a) { *this=a; }
~sprite() {}
sprite* operator = (const sprite *a) { *this=*a; return this; }
};
List<sprite> spr;
List<palette> pal;
List<palette> group;
picture pic0,pic1,pic2;
void compute()
{
bmp=new Graphics::TBitmap;
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
int e,i,j,ix,x,y,xx,yy;
palette p,*pp;
DWORD c;
pic0.load("SNES_images.png");
spr.num=0; sprite s,*ps;
for (y=0;y<pic0.ys;y+=_sprite_size)
for (x=0;x<pic0.xs;x+=_sprite_size)
{
s.pal.pals=1;
s.pal.pal[0]=0x00F8F8F8;
s.gpal=-1;
e=0;
for (yy=0;yy<_sprite_size;yy++)
for (xx=0;xx<_sprite_size;xx++)
{
c=pic0.p[y+yy][x+xx].dd&0x00F8F8F8;
for (ix=-1,i=0;i<s.pal.pals;i++)
if (s.pal.pal[i]==c) { ix=i; break; }
if (ix<0)
{
if (s.pal.pals>=_palette_size)
{
ix=-1;
break;
}
ix=s.pal.pals;
s.pal.pal[s.pal.pals]=c;
s.pal.pals++;
}
s.pix[yy][xx]=ix; e|=ix;
}
if (e) spr.add(s);
}
pal.num=0;
for (i=0,ps=spr.dat;i<spr.num;i++,ps++)
{
p=ps->pal; p.sort(); ix=-1;
for (x=0;x<pal.num;x++) if (pal[x]==p) { ix=x; break; }
if (ix<0) { ix=pal.num; pal.add(p); }
ps->gpal=ix;
}
group.num=0;
for (i=0;i<pal.num;i++) pal[i].group=-1;
for (i=0;i<pal.num;i++)
{
if (pal[i].group<0)
{
pal[i].group=group.num; group.add(pal[i]);
pp=&group[group.num-1];
}
for (j=i+1;j<pal.num;j++)
if (pal[j].group<0)
if (pp->merge(pal[j]))
pal[j].group=pp->group;
}
for (i=0,ps=spr.dat;i<spr.num;i++,ps++)
{
pp=&pal[ps->gpal];
ps->gpal=pp->group;
pp=&group[ps->gpal];
int idx[_palette_size];
for (x=0;x<ps->pal.pals;x++)
for (idx[x]=0,y=0;y<pp->pals;y++)
if (ps->pal.pal[x]==pp->pal[y])
{idx[x]=y; break; }
for (yy=0;yy<_sprite_size;yy++)
for (xx=0;xx<_sprite_size;xx++)
if (ps->pix[yy][xx])
ps->pix[yy][xx]=idx[ps->pix[yy][xx]];
}
e=6;
xx=(e*_palette_size);
yy=(e*pal.num);
pic2.resize(xx+e+xx,yy);
pic2.clear(0);
for (x=0,y=0,ix=0;ix<group.num;ix++,y+=e)
{
group[ix].draw(pic2.bmp->Canvas,x+xx,y,e,0);
for (i=0;i<pal.num;i++)
if (pal[i].group==ix)
{
pal[i].draw(pic2.bmp->Canvas,x,y,e,0);
y+=e;
}
}
pic1.resize(pic0.xs,pic0.ys);
pic1.clear(0);
for (x=0,y=0,i=0,ps=spr.dat;i<spr.num;i++,ps++)
{
pp=&group[ps->gpal];
for (yy=0;yy<_sprite_size;yy++)
for (xx=0;xx<_sprite_size;xx++)
if (ps->pix[yy][xx])
pic1.p[y+yy][x+xx].dd=pp->pal[ps->pix[yy][xx]];
x+=_sprite_size; if (x+_sprite_size>pic1.xs) { x=0;
y+=_sprite_size; if (y+_sprite_size>pic1.ys) break; }
}
请忽略VCL和GDI渲染内容。
我使用自己的图片类来处理图像,其中一些成员包括:
xs,ys
是图像的像素大小
p[y][x].dd
是位于(x,y)
位置的像素,以32位整数类型表示
clear(color)
用color
清除整个图像
resize(xs,ys)
将图像调整为新分辨率
bmp
是封装了VCL的GDI位图,可访问Canvas
pf
保存图像的实际像素格式:
enum _pixel_format_enum
{
_pf_none=0, // undefined
_pf_rgba, // 32 bit RGBA
_pf_s, // 32 bit signed int
_pf_u, // 32 bit unsigned int
_pf_ss, // 2x16 bit signed int
_pf_uu, // 2x16 bit unsigned int
_pixel_format_enum_end
}
“color”和“pixels”的编码方式如下:
color
和像素的编码方式如下:
union color
{
DWORD dd; WORD dw[2]; byte db[4];
int i; short int ii[2];
color(){}; color(color& a){ *this=a; }; ~color(){}; color* operator = (const color *a) { dd=a->dd; return this; };
};
这段文本的英译为:
这些乐队是:
enum{
_x=0,
_y=1,
_b=0,
_g=1,
_r=2,
_a=3,
_v=0,
_s=1,
_h=2,
};
我也使用我的动态列表模板,如下:
List<double> xxx;
相当于
double xxx[];
xxx.add(5);
将
5
添加到列表的末尾
xxx[7]
访问数组元素(安全)
xxx.dat[7]
访问数组元素(不安全但直接访问速度快)
xxx.num
是数组的实际使用大小
xxx.reset()
清空数组并设置
xxx.num=0
xxx.allocate(100)
预分配空间以容纳
100
个项目