首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >多幅图像及其调色板的算法

多幅图像及其调色板的算法
EN

Stack Overflow用户
提问于 2017-09-25 16:40:10
回答 1查看 1.4K关注 0票数 6

对于一个项目,我正在寻找一个算法,以将许多图像转换为调色板图像,它可以共享相同的调色板。

短篇小说

Given:

  • 一个图像列表(RGB),它已经具有应该使用的最终颜色。

结果:

  • 图像列表(已指示)
  • 调色板列表
  • 多个RGB图像通过使用不同的调色板,可以转换为one表示的图像
  • 我想使用所需的最小数量的图像和最小数量的调色板。

Limitations:

  • 有最多的n个调色板
  • 每个调色板最多有m种颜色。
  • 在结果中可以生成最大的u图像。

我的问题是什么:

  • 我不知道如何构建这个算法,这样它就可以判断以前的任何决策对于将来的问题是否是错误的。(见下文)
  • 我不知道如何解决调色板颜色和图像数据的重新排列问题,因为重新排列one图像数据可能会导致后续的重新排列问题,最终可能导致无休止的重新排列战斗:D (见下文)

以下是完整的故事

结果

这里应该是结果:在本例中,一个颜色索引表(也称为指示图像)使用两个不同的调色板生成两个不同的RGB图像。

第一步

由于输入文件都是RGB图像,我们需要首先将它们转换为调色板,并将它们与颜色索引匹配。

在下面的图像中,您可以看到算法如何开始处理前三幅图像:

  1. 转换为调色板、
  2. 检查2图像是否共享相同的颜色索引,如果是,我们可以重用颜色索引,但创建调色板(不需要!)
  3. 否则,我们可以将添加、四个新颜色、到第一个调色板、和,创建一个新的颜色索引。(未在此图像中显示)
  4. 对于第三个图像,我们找到了一个已经包含所有所需颜色的调色板,因此我们决定重用调色板,但是重新排列颜色索引以匹配现有的调色板。

让我们把事情弄复杂!

到目前一切尚好。但是,我们应该如何继续最后的形象呢?它可以共享上一个调色板的颜色索引,但它不匹配任何现有的调色板。实际上,它不匹配任何现有的调色板。

下面的图片描述了--的实际问题:如何决定什么是最适合图像的?创建一个新的调色板,创建一个新的颜色索引,如果我们过去的决定不是这样,那么一切都会好呢?我们怎么知道呢?

重排

好吧,这四张照片仍然是简单的例子。让我们想象一下算法已经处理了很多图像,并生成了一个调色板。我们输入图像列表中的下一个图像找到了一个匹配的调色板,因此它可以很容易地创建新的颜色索引,并且很好。但是:结果应该是最少的图像和调色板,所以可以有另一种方式!

通过检查所有以前的图像,我们发现,我们可以使用上一个图像的现有调色板和颜色索引,但是我们必须重新排列调色板。重新排列要求我们检查所有的以前的图像,他们是否对重新排列的调色板是否满意。

如您所见:最后一步中的调色板已被重新排列,以与下面相同的颜色相匹配。但这可能是一个复杂的过程,重新安排了许多其他图像,以及。

预先感谢您关于如何实现这样一个算法、搜索什么或者已经存在的算法产生几乎相同结果的提示。

编辑

这里是一个真实的例子图像,一些图形被盗,从一个古老的阿米加游戏:

这个项目集包含256 RGB图像,16*16像素。这些图像中的每一幅都是应该由算法处理的块。前几个图像是相等的,但是后面有几个其他的图像。可以将调色板分解为,最多有6个调色板,有16种颜色的,但限制总是第一种颜色是透明的。(实际上每个调色板有15种颜色)

在本例中,对于4种颜色的键、门和钻石,很容易重用相同的ColorIndices。

这是一个生成调色板的示例:

编辑第2号

下面是我从一个老游戏中学到的另一个例子:

调色板看起来就像这个:

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-09-28 07:35:48

看起来,我对示例输入的第一种天真的方法甚至比您的引用更好:

左边是您的输入图像,中间是使用全局group[]调色板的雪碧输出,而不是空的精灵。右边是按组排序的唯一调色板,右边的大多数列是代表该组的组调色板。

如您所见,我只有5x16色调色板,而不是6。第一个颜色索引0是为透明颜色保留的(我硬编码白色,因为我不能访问原始索引颜色)。算法如下:

  1. init精灵 每个精灵都必须使用它的调色板和全局调色板索引。
  2. 结构 为此我需要两个调色板列表。一个是同时使用的所有唯一调色板的列表(整个图像/帧),我称之为pal[],另一个称为group[],并保存要使用的最终合并调色板。
  3. pal[] 填充 所以从所有精灵身上提取所有的调色板..。测试唯一性(这只是为了提高O(n^2)搜索的性能)。为此,我对调色板进行了排序,以便在O(n)而不是O(n^2)中直接比较它们。
  4. 分组调色板 使用第一个未分组的调色板并使用它创建新的组。然后检查所有其他未分组调色板(O(n^2)),如果合并,则合并它们。我的意思是,经过处理的pal[i]至少有50%的颜色存在于group[j]中,而所有缺失的颜色仍然可以容纳到group[j]中。如果大小写将pal[i]标记为group[j]成员并将缺失的颜色添加到group[j]中。然后重复#4,直到没有剩下未分组的调色板。
  5. 现在,group[] 重新索引精灵以匹配 palettes

这里是以下简单的C++代码:

代码语言:javascript
复制
//---------------------------------------------------------------------------
const int _sprite_size=16;      // resolution
const int _palette_size=16;     // colors per palette
//---------------------------------------------------------------------------
class palette   // sprite palette
    {
public:
    int pals;                   // num of colors
    DWORD pal[_palette_size];   // palete colors
    int group;                  // group index

    // inline constructors (you can ignore this)
    palette()   {}
    palette(palette& a) { *this=a; }
    ~palette()  {}
    palette* operator = (const palette *a) { *this=*a; return this; }
    //palette* operator = (const palette &a) { ...copy... return this; }

    void draw(TCanvas *can,int x,int y,int sz,int dir)  // render palette to GDI canvas at (x,y) with square size sz and direction dir = { 0,90,180,270 } deg
        {
        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() // bubble sort desc
        {
        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)   // return true and merge if this and p are similar and mergable palettes
        {
        int equal=0,mising=0,i,j;
        DWORD m[_palette_size]; // mising palette colors
        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;   // at least half of colors must be present
        if (pals+mising>_palette_size) return 0;    // and the rest must fit in
        for (i=0;i<mising;i++) { pal[pals]=m[i]; pals++; }
        return 1;
        }
    };
//---------------------------------------------------------------------------
class sprite    // sprite
    {
public:
    int xs,ys;                              // resoltuon
    BYTE pix[_sprite_size][_sprite_size];   // pixel data (indexed colors)
    palette pal;                            // original palette
    int gpal;                               // global palette

    // inline constructors (you can ignore this)
    sprite()    {}
    sprite(sprite& a) { *this=a; }
    ~sprite()   {}
    sprite* operator = (const sprite *a) { *this=*a; return this; }
    //sprite* operator = (const sprite &a) { ...copy... return this; }
    };
//---------------------------------------------------------------------------
List<sprite> spr;   // all sprites
List<palette> pal;  // all palettes
List<palette> group;// merged palettes
picture pic0,pic1,pic2; // input, output and debug images
//---------------------------------------------------------------------------
void compute() // this is the main code you need to call/investigate
    {
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;

    int e,i,j,ix,x,y,xx,yy;
    palette p,*pp;
    DWORD c;
    // [load image and convert to indexed 16 color sprites]
    // you can ignore this part of code as you already got your sprites with palettes...
    pic0.load("SNES_images.png");
    // process whole image
    spr.num=0; sprite s,*ps;
    for (y=0;y<pic0.ys;y+=_sprite_size)
     for (x=0;x<pic0.xs;x+=_sprite_size)
        {
        // let white transparent color be always index 0
        s.pal.pals=1;
        s.pal.pal[0]=0x00F8F8F8;
        s.gpal=-1;
        e=0;
        // proces sprite image
        for (yy=0;yy<_sprite_size;yy++)
         for (xx=0;xx<_sprite_size;xx++)
            {
            // match color with palette
            c=pic0.p[y+yy][x+xx].dd&0x00F8F8F8; // 15 bit RGB 5:5:5 to 32 bit RGB
            for (ix=-1,i=0;i<s.pal.pals;i++)
             if (s.pal.pal[i]==c) { ix=i; break; }
            // add new color if no match found
            if (ix<0)
                {
                if (s.pal.pals>=_palette_size)
                    {
                    // fatal error: invalid input data
                    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);  // ignore empty sprites
        }

    // [global palette list]
    // here starts the stuff you need
    // cretae list pal[] of all unique palettes from sprites spr[]
    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;
        }

    // [palette gropus]
    // creates a list group[] with merged palette from all the pal[] in the same group
    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;
        }

    // [update sprites to match group palette]
    for (i=0,ps=spr.dat;i<spr.num;i++,ps++)
        {
        pp=&pal[ps->gpal];  // used global palette
        ps->gpal=pp->group; // update gpal in sprite to point to group palette (you can copy group palette into sprite instead)
        pp=&group[ps->gpal];// used group palette
        // compute reindex table
        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; }
        // proces sprite image
        for (yy=0;yy<_sprite_size;yy++)
         for (xx=0;xx<_sprite_size;xx++)
          if (ps->pix[yy][xx])  // ignore transparent pixels
           ps->pix[yy][xx]=idx[ps->pix[yy][xx]];
        }

    // [render groups]
    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;
            }
        }

    // [render sprites to pic1 for visual comparison using merged palettes]
    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];
        // proces sprite image
        for (yy=0;yy<_sprite_size;yy++)
         for (xx=0;xx<_sprite_size;xx++)
          if (ps->pix[yy][xx])  // ignore transparent pixels
           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; }
        }
//---------------------------------------------------------------------------

只需忽略VCLGDI呈现内容即可。

我使用我自己的图片类来处理图像,所以有些成员是:

xs,ys是以像素为单位的图像大小。

p[y][x].dd(x,y)位置的像素,为32位整数类型。

clear(color)color清除整个图像

resize(xs,ys)将图像调整为新分辨率

bmpVCL封装的带有Canvas访问的GDI位图。

pf保存图像的实际像素格式:

代码语言:javascript
复制
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和像素的编码方式如下:

代码语言:javascript
复制
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; }; /*color* operator = (const color &a) { ...copy... return this; };*/
    };

乐队包括:

代码语言:javascript
复制
enum{
    _x=0,   // dw
    _y=1,

    _b=0,   // db
    _g=1,
    _r=2,
    _a=3,

    _v=0,   // db
    _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项预先分配空间

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46410132

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档