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

短篇小说

给出:

  • 图像(RGB)的列表,这些列表已经具有应使用的最终颜色。

  • 结果:
  • 图片列表(指示)
  • 调色板列表
  • 通过使用不同的调色板,可以将多个 RGB图像转换为一个指示图像
  • 我想使用最少数量的图像和最少数量的调色板。

  • 局限性:
  • 最多有n个调色板
  • 每个调色板最多有m种颜色
  • 结果
  • 中最多可以生成u张图像

    我的问题是:
  • 我不知道如何构建算法,因此它可以决定是否对先前的问题做出任何先前的决定。 (请参见下文)
  • 我不知道如何解决调色板颜色和图像数据的重新排列,因为重新排列一个图像数据可能会导致跟进重新排列的问题,这可能会导致无休止的重新排列战斗:D(请参阅下文)


  • 这是全文

    结果

    因此,这应该是结果:在这种情况下,一个颜色索引表(又名指示图像)使用两个不同的调色板生成两个不同的RGB图像。

    javascript - 适用于许多图像及其调色板的算法-LMLPHP

    第一步

    由于输入文件都是RGB图像,因此我们需要先将它们转换为具有匹配颜色索引的调色板。

    在下图中,您可以看到该算法如何开始处理前三个图像:
  • 转换为调色板颜色索引
  • 检查是否两个图像共享相同的颜色索引,如果是这样,我们可以重用颜色索引但创建调色板(不是必需的!)
  • 否则,我们可以四种新颜色添加到第一个调色板中,以创建新的颜色索引。 (此图中未显示)
  • 对于第三张图像,我们找到了一个已经包含所有必需颜色的调色板,因此我们决定重用该调色板,但要重新排列颜色索引以匹配现有调色板。

  • javascript - 适用于许多图像及其调色板的算法-LMLPHP

    让我们变得复杂!

    到现在为止还挺好。但是,我们应该如何继续处理最后一个图像呢?它可能共享前一个的颜色索引,但是那时不匹配任何现有的调色板。实际上,它与任何现有调色板都不匹配。

    因此,下图描述了实际问题:如何确定最适合图像的图像?创建一个新的调色板,创建一个新的颜色索引,如果我们过去做出其他决定,如果一切都会好起来怎么办?我们如何找到答案?

    javascript - 适用于许多图像及其调色板的算法-LMLPHP

    重新排列

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

    通过检查所有先前的图像,我们发现可以使用先前图像的现有调色板颜色索引,但是我们必须重新排列调色板。重新排列后,我们需要检查所有以前的图像是否适合重新排列的调色板。

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

    javascript - 适用于许多图像及其调色板的算法-LMLPHP

    在此先感谢您提供有关如何实现这种算法,要搜索的内容或已有的算法产生几乎相同结果的任何技巧。

    编辑

    这是一个真实的示例图像,其中的一些图形是从旧的amiga游戏中盗取的:

    javascript - 适用于许多图像及其调色板的算法-LMLPHP

    此图块包含256张16 * 16像素的RGB图像。每个图像都是应由算法处理的图块。前几张图片彼此相等,但随后又出现了其他几张图片。可以将调色板分解为,最多可将6种调色板分为16种颜色,但要始终将第一种颜色设为透明色是有局限性的。 (实际上每个调色板有15种颜色)

    在此示例中,可以轻松地将相同的ColorIndices重复用于4种彩色键,门和直径。

    这是一个示例生成的调色板:
    javascript - 适用于许多图像及其调色板的算法-LMLPHP

    编辑2号

    这是我从旧游戏中摘下来的另一个示例:

    javascript - 适用于许多图像及其调色板的算法-LMLPHP

    调色板看起来可能像这样:

    javascript - 适用于许多图像及其调色板的算法-LMLPHP

    最佳答案

    看来,我对样本输入的第一种天真的方法甚至比引用文献还好:
    javascript - 适用于许多图像及其调色板的算法-LMLPHP
    左边是您的输入图像,中间是仅使用空group[]调色板的 Sprite 输出,没有空的 Sprite 。右侧是按组排序的唯一调色板,最右边的列是代表该组的组调色板。
    如您所见,我只有5个16个调色板而不是6个调色板。第一个颜色索引0保留用于透明颜色(我将白色硬编码,因为我无法访问原始的索引颜色)。该算法是这样的:

  • 初始化 Sprite
    每个 Sprite 必须具有其调色板和使用的全局调色板索引。
  • 结构
    我需要2个调色板列表。一个是一次使用的所有唯一调色板(整个图像/帧)的列表,我将其称为pal[],另一个称为group[],其中包含要使用的最终合并调色板。
  • 填充pal[]
    因此只需从所有 Sprite 中提取所有调色板...测试唯一性(这只是为了提高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直到没有剩余未分组的调色板。
  • 现在重新排列 Sprite 的索引以匹配group[]调色板

  • 这里是简单的C++代码:
    //---------------------------------------------------------------------------
    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; }
            }
    //---------------------------------------------------------------------------
    
    只需忽略 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和像素的编码如下:
    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; };*/
        };
    
    乐队是:
    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=0xxx.allocate(100)100项目预分配空间

    关于javascript - 适用于许多图像及其调色板的算法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/46410132/

    10-13 05:50