2

对于使用硬件调色板的图形模式,可以通过在两个图像之间快速切换来实现更多颜色的错觉(我不知道任何通用名称)。

有什么算法可以从普通的全彩色图像计算出最佳(或半最佳)调色板?两个目标图像要么共享相同的调色板,要么拥有自己的调色板。两种情况的算法都很有趣(如果它们根本不同)。

一个例子:假设我有一个随机的全彩色 PNG 图像,有数千种颜色(每个通道 8 位,完全不透明),并且想要创建两个 GIF 图像(非动画),每个图像有 256 种颜色。假设我每帧(以 60Hz 的帧速率)在这些 GIF 图像之间切换,因此结果显示为两个图像之间的混合。问题是:如何计算两个 GIF 文件之间共享的最佳调色板?或者两个不同的调色板,每个文件一个?(这两种情况都很有趣)。

更新:

只是为了创建一个我想要做的例子,我通过使用随机突变进化一个合适的调色板来强制一个结果。为了便于在 2D 中可视化,我使用了去除了蓝色通道的图像。这是原来的样子。

原始图像

颜色分布如下所示。除了一些使用高达 90 次的深(几乎是黑色)灰色和一些使用约 30 次的亮黄色之外,使用的颜色之间的使用分布非常均匀。

颜色使用图

这是我想出的 16 色调色板。我不知道它离最佳调色板有多远,但它似乎很接近。

16个调色板

混合来自两个图像的颜色会产生更大的颜色集。多大取决于颜色之间的允许距离(距离越大,闪烁越明显)。最大距离为 100。

混合颜色图

这些混合颜色很好地覆盖了色彩空间,如图所示,它显示了从每种原始颜色到最近(混合)调色板颜色的距离。一些边缘颜色的距离可达 40。但几乎所有颜色的距离都不到该距离的一半。

颜色距离图

这是然后将两个图像转换为使用此调色板的结果。首先是两个 16 色图像,然后是混合结果(理想情况下看起来完全没有任何可见闪烁)。

结果

发现差异并不容易(当然取决于您拥有的显示器)。所以这里有两张只是差异的图像。左边的值与实际差异成正比,右边的值夸大了它们,以便在出现最大差异的区域变得更加直观。(显然,与在 3D 色彩空间中操作相比,会有更大的差异。)

差异

我知道(但假设的)结果也可以通过应用抖动来改善。而且我知道颜色之间距离的视觉感知在整个色彩空间中并不是恒定的。更不用说那些依赖硬件的小东西了。但首先要做的事情...

4

1 回答 1

0

嗯 2 图像听起来很合理,因为大多数复古硬件使用 50 或 60 Hz 刷新率,并且切换 2 帧将提供 25 或 30 Hz,这对于人类视觉来说仍然足够高。

交替bmp0,bmp1显示两个具有相同显示时间的图像将混合到它们的平均值:

bmp = (bmp0 + bmp1)/2

现在让我们bmp0成为一个pal0调色板截断的图像bmp

bmp0 = trunc(bmp,pal0)
bmp1 = trunc(2*bmp - bmp0,pal1)

所以这两个图像都只使用他们调色板中的颜色,但它们的平均值更接近原始图像,然后它们中的每一个......

这是使用此答案底部链接中描述的颜色量化的简单 C++/VC 示例:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#include <jpeg.hpp>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
Graphics::TBitmap *bmp0,*bmp1,*bmp,*bmpd;
//---------------------------------------------------------------------------
//--- Palette ---------------------------------------------------------------
//---------------------------------------------------------------------------
const int _pals=64+0*8192;                          // max 8K colors in palette
DWORD pal[_pals];                               // palette 0x00RRGGBB
int pals=0;                                     // colors inside palette
const int rgb_bpc=5;                            // bits per channel (after truncation)
const int rgb_sh=8-rgb_bpc;                     // bits to drop (truncation)
const int rgb_n=1<<rgb_bpc;                     // colors per channel (after truncation)
int   rgb[rgb_n][rgb_n][rgb_n];                 // recolor table
void  pal_clear();                              // clear palette to empty
void  pal_dither(int n);                        // add up to n colors for dithering to pal[pals]
void  pal_major(int n,Graphics::TBitmap *bmp);  // add up to n major colors from bmp to pal[pals]
void  pal_compute_recolor();                    // compure recolor rgb[n][n][n] array from pal[pals] where n is power of 2, and compute sh (bits to drop from 8bit channel)
void  rgb2chn(int &r,int &g,int &b,DWORD c);    // rgb color to r,g,b
DWORD chn2rgb(int r,int g,int b);               // r,g,b to rgb color
int   chn2pal(int r,int g,int b);               // r,g,b to palette index
int   rgb2pal(DWORD c);                         // rgb color to palette index
DWORD pal2rgb(int ix);                          // palette index to rgb color
void pal_render(Graphics::TBitmap *bmp,int y0); // render palette at bmp,y0
//---------------------------------------------------------------------------
void pal_clear()
    {
    pals=0;
    }
//---------------------------------------------------------------------------
void pal_VGA(int n)
    {
    const DWORD pal_VGA256[256]=
        {       // 0x00RRGGBB
        0x00000000,0x000000A8,0x0000A800,0x0000A8A8,0x00A80000,0x00A800A8,0x00A85400,0x00A8A8A8,
        0x00545454,0x005454FC,0x0054FC54,0x0054FCFC,0x00FC5454,0x00FC54FC,0x00FCFC54,0x00FCFCFC,
        0x00000000,0x00101010,0x00202020,0x00343434,0x00444444,0x00545454,0x00646464,0x00747474,
        0x00888888,0x00989898,0x00A8A8A8,0x00B8B8B8,0x00C8C8C8,0x00DCDCDC,0x00ECECEC,0x00FCFCFC,
        0x000000FC,0x004000FC,0x008000FC,0x00BC00FC,0x00FC00FC,0x00FC00BC,0x00FC0080,0x00FC0040,
        0x00FC0000,0x00FC4000,0x00FC8000,0x00FCBC00,0x00FCFC00,0x00BCFC00,0x0080FC00,0x0040FC00,
        0x0000FC00,0x0000FC40,0x0000FC80,0x0000FCBC,0x0000FCFC,0x0000BCFC,0x000080FC,0x000040FC,
        0x008080FC,0x009C80FC,0x00BC80FC,0x00DC80FC,0x00FC80FC,0x00FC80DC,0x00FC80BC,0x00FC809C,
        0x00FC8080,0x00FC9C80,0x00FCBC80,0x00FCDC80,0x00FCFC80,0x00DCFC80,0x00BCFC80,0x009CFC80,
        0x0080FC80,0x0080FC9C,0x0080FCBC,0x0080FCDC,0x0080FCFC,0x0080DCFC,0x0080BCFC,0x00809CFC,
        0x00B8B8FC,0x00C8B8FC,0x00DCB8FC,0x00ECB8FC,0x00FCB8FC,0x00FCB8EC,0x00FCB8DC,0x00FCB8C8,
        0x00FCB8B8,0x00FCC8B8,0x00FCDCB8,0x00FCECB8,0x00FCFCB8,0x00ECFCB8,0x00DCFCB8,0x00C8FCB8,
        0x00B8FCB8,0x00B8FCC8,0x00B8FCDC,0x00B8FCEC,0x00B8FCFC,0x00B8ECFC,0x00B8DCFC,0x00B8C8FC,
        0x00000070,0x001C0070,0x00380070,0x00540070,0x00700070,0x00700054,0x00700038,0x0070001C,
        0x00700000,0x00701C00,0x00703800,0x00705400,0x00707000,0x00547000,0x00387000,0x001C7000,
        0x00007000,0x0000701C,0x00007038,0x00007054,0x00007070,0x00005470,0x00003870,0x00001C70,
        0x00383870,0x00443870,0x00543870,0x00603870,0x00703870,0x00703860,0x00703854,0x00703844,
        0x00703838,0x00704438,0x00705438,0x00706038,0x00707038,0x00607038,0x00547038,0x00447038,
        0x00387038,0x00387044,0x00387054,0x00387060,0x00387070,0x00386070,0x00385470,0x00384470,
        0x00505070,0x00585070,0x00605070,0x00685070,0x00705070,0x00705068,0x00705060,0x00705058,
        0x00705050,0x00705850,0x00706050,0x00706850,0x00707050,0x00687050,0x00607050,0x00587050,
        0x00507050,0x00507058,0x00507060,0x00507068,0x00507070,0x00506870,0x00506070,0x00505870,
        0x00000040,0x00100040,0x00200040,0x00300040,0x00400040,0x00400030,0x00400020,0x00400010,
        0x00400000,0x00401000,0x00402000,0x00403000,0x00404000,0x00304000,0x00204000,0x00104000,
        0x00004000,0x00004010,0x00004020,0x00004030,0x00004040,0x00003040,0x00002040,0x00001040,
        0x00202040,0x00282040,0x00302040,0x00382040,0x00402040,0x00402038,0x00402030,0x00402028,
        0x00402020,0x00402820,0x00403020,0x00403820,0x00404020,0x00384020,0x00304020,0x00284020,
        0x00204020,0x00204028,0x00204030,0x00204038,0x00204040,0x00203840,0x00203040,0x00202840,
        0x002C2C40,0x00302C40,0x00342C40,0x003C2C40,0x00402C40,0x00402C3C,0x00402C34,0x00402C30,
        0x00402C2C,0x0040302C,0x0040342C,0x00403C2C,0x0040402C,0x003C402C,0x0034402C,0x0030402C,
        0x002C402C,0x002C4030,0x002C4034,0x002C403C,0x002C4040,0x002C3C40,0x002C3440,0x002C3040,
        0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
        };
    for (int i=0;(i<n)&&(i<256)&&(pals<_pals);i++,pals++) pal[pals]=pal_VGA256[i];
    }
//---------------------------------------------------------------------------
void pal_dither(int n)
    {
    }
//---------------------------------------------------------------------------
void pal_major(int n,Graphics::TBitmap *bmp)    // only for rgb_bits=5 !!!
    {
    union { DWORD dd; BYTE db[4]; } c0,c1;
    int i,x,y,xs,ys,a,aa,hists;
    DWORD *p,cc,r,g,b;
    DWORD his[32768];
    DWORD idx[32768];
    // init
    xs=bmp->Width;
    ys=bmp->Height;
    n+=pals;
    // 15bit histogram
    for (x=0;x<32768;x++) { his[x]=0; idx[x]=x; }
    for (                           y=0;y<ys;y++)
     for (p=(DWORD*)bmp->ScanLine[y],x=0;x<xs;x++)
        {
        cc=p[x];
        cc=((cc>>3)&0x1F)|((cc>>6)&0x3E0)|((cc>>9)&0x7C00);
        if (his[cc]<0xFFFFFFFF) his[cc]++;
        }
    // remove zeroes
     for (x=0,y=0;y<32768;y++)
        {
        his[x]=his[y];
        idx[x]=idx[y];
        if (his[x]) x++;
        } hists=x;
    // sort by hist
    for (i=1;i;)
     for (i=0,x=0,y=1;y<hists;x++,y++)
      if (his[x]<his[y])
        {
        i=his[x]; his[x]=his[y]; his[y]=i;
        i=idx[x]; idx[x]=idx[y]; idx[y]=i; i=1;
        }
    // set pal color palete
    for (x=0;x<hists;x++) // main colors
        {
        cc=idx[x];
        b= cc     &31;
        g=(cc>> 5)&31;
        r=(cc>>10)&31;
        c0.db[0]=b;
        c0.db[1]=g;
        c0.db[2]=r;
        c0.dd=(c0.dd<<3)&0x00F8F8F8;
        // skip if similar color already in pal[]
        for (a=0,i=0;i<pals;i++)
            {
            c1.dd=pal[i];
            aa=int(BYTE(c1.db[0]))-int(BYTE(c0.db[0])); if (aa<=0) aa=-aa; a =aa;
            aa=int(BYTE(c1.db[1]))-int(BYTE(c0.db[1])); if (aa<=0) aa=-aa; a+=aa;
            aa=int(BYTE(c1.db[2]))-int(BYTE(c0.db[2])); if (aa<=0) aa=-aa; a+=aa;
            if (a<=16) { a=1; break; } a=0; // *** treshold ***
            }
        if (!a)
            {
            pal[pals]=c0.dd; pals++;
            if (pals>=n) { x++; break; }
            }
        }
    }
//---------------------------------------------------------------------------
void  pal_compute_recolor()
    {
    int i,j,x,y,c,r,g,b,rr,gg,bb;
    // test all truncated rgb colors
    for (r=0;r<rgb_n;r++)
     for (g=0;g<rgb_n;g++)
      for (b=0;b<rgb_n;b++)
        {
        // find closest match in pal[m]
        for (j=-1,x=1000000,i=0;i<pals;i++)
            {
            c=pal[i];
            bb= c     &255; bb-=b<<rgb_sh; bb*=bb;
            gg=(c>> 8)&255; gg-=g<<rgb_sh; gg*=gg;
            rr=(c>>16)&255; rr-=r<<rgb_sh; rr*=rr;
            y=rr+gg+bb;
            if (x>y){ x=y; j=i; }
            }
        // store it as recolor value
        rgb[r][g][b]=j;
        }
    }
//---------------------------------------------------------------------------
void rgb2chn(int &r,int &g,int &b,DWORD c)
    {
    b= c     &255;
    g=(c>> 8)&255;
    r=(c>>16)&255;
    }
//---------------------------------------------------------------------------
DWORD chn2rgb(int r,int g,int b)
    {
    return b+(g<<8)+(r<<16);
    }
//---------------------------------------------------------------------------
int chn2pal(int r,int g,int b)
    {
    return rgb[r>>rgb_sh][g>>rgb_sh][b>>rgb_sh];
    }
//---------------------------------------------------------------------------
int rgb2pal(DWORD c)
    {
    int r,g,b;
    b= c     &255;
    g=(c>> 8)&255;
    r=(c>>16)&255;
    return rgb[r>>rgb_sh][g>>rgb_sh][b>>rgb_sh];
    }
//---------------------------------------------------------------------------
DWORD pal2rgb(int ix)
    {
    return pal[ix];
    }
//---------------------------------------------------------------------------
void pal_render(Graphics::TBitmap *bmp,int y0)
    {
    int xs,ys,x,y,i,j,c,*p;
    xs=bmp->Width;
    ys=bmp->Height;
    for (c=y0,i=0;(i<pals)&&(c+8<ys);c+=10,i=j)
     for (y=c;y<c+8;y++)
      for (p=(int*)bmpd->ScanLine[y],j=i,x=0;(x<xs)&&(j<pals);x++)
       { p[x]=pal[j]; if ((x&7)==7){ x++; j++; if (x+8>xs) break; }}
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void compute() // bmp -> bmp0+bmp1 using pal[]
    {
    const int colors=64;
    int i,j,r,g,b,rr,gg,bb,x,y,xs,ys,c;
    int *p,*p0,*p1,*pd;
    int pal0[colors],pal1[colors];
    // allow direct pixel access and resize to coomon size
    xs=bmp->Width;
    ys=bmp->Height;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    bmp0->HandleType=bmDIB;
    bmp0->PixelFormat=pf32bit;
    bmp0->SetSize(xs,ys);
    bmp1->HandleType=bmDIB;
    bmp1->PixelFormat=pf32bit;
    bmp1->SetSize(xs,ys);
    bmpd->PixelFormat=pf32bit;
    bmpd->SetSize(xs,ys);
    // compute palette for bmp0
    pal_clear();
    pal_major(colors,bmp);
    pal_compute_recolor();
    for (i=0;i<colors;i++) pal0[i]=pal[i];  // store palette for later
    // recolor bmp0,bmp1
    for (y=0;y<ys;y++)
        {
        p =(int*)bmp ->ScanLine[y];
        p0=(int*)bmp0->ScanLine[y];
        p1=(int*)bmp1->ScanLine[y];
        for (x=0;x<xs;x++)
            {
            // i = recolor(p)   // bmp0
            rgb2chn(r,g,b,p[x]); i=chn2pal(r,g,b);
            // p1 = (2*p-p0)    // bmp1
            rgb2chn(rr,gg,bb,pal[i]);
            bb=b+b-bb; if (bb>255) bb=255; if (bb<0) bb=0;
            gg=g+g-gg; if (gg>255) gg=255; if (gg<0) gg=0;
            rr=r+r-rr; if (rr>255) rr=255; if (rr<0) rr=0;
            // copy pixels to bmps
            p0[x]=pal[i];                   // quantized
            p1[x]=chn2rgb(rr,gg,bb);        // true color for now
            }
        }

    // compute palette for bmp1
    pal_clear();
    pal_major(colors,bmp1);
    pal_compute_recolor();
    for (i=0;i<colors;i++) pal1[i]=pal[i];  // store palette for later
    // recolor bmp1
    for (y=0;y<ys;y++)
        {
        p1=(int*)bmp1->ScanLine[y];
        for (x=0;x<xs;x++) p1[x]=pal[rgb2pal(p1[x])]; // quantized
        }

    // Blend and difference for debug
    for (y=0;y<ys;y++)
        {
        p =(int*)bmp ->ScanLine[y];
        p0=(int*)bmp0->ScanLine[y];
        p1=(int*)bmp1->ScanLine[y];
        pd=(int*)bmpd->ScanLine[y];
        for (x=0;x<xs;x++)
            {
            // get r,g,b
            rgb2chn(r ,g ,b ,p0[x]);
            rgb2chn(rr,gg,bb,p1[x]);
            // blend
            r=(r+rr)>>1;
            g=(g+gg)>>1;
            b=(b+bb)>>1;
            // diff
            rgb2chn(rr,gg,bb,p[x]);
            i=2;    // scale
            rr=abs(r-rr)<<i; if (rr>255) rr=255;
            gg=abs(g-gg)<<i; if (gg>255) gg=255;
            bb=abs(b-bb)<<i; if (bb>255) bb=255;
            // copy pixels
            p[x]=chn2rgb(r,g,b);
            pd[x]=chn2rgb(rr,gg,bb);
            }
        }
    // render palettes
    for (i=0;i<colors;i++) pal[i]=pal0[i]; pal_render(bmpd,0);
    for (i=0;i<colors;i++) pal[i]=pal1[i]; pal_render(bmpd,100*colors/xs+10);


    bmp ->SaveToFile("out_blend.bmp");
    bmp0->SaveToFile("out_bmp0.bmp");
    bmp1->SaveToFile("out_bmp1.bmp");
    bmpd->SaveToFile("out_diff.bmp");
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    bmp=new Graphics::TBitmap;
    bmp0=new Graphics::TBitmap;
    bmp1=new Graphics::TBitmap;
    bmpd=new Graphics::TBitmap;
    // load jpg into bmp
    TJPEGImage *jpg = new TJPEGImage();
    jpg->LoadFromFile("in.jpg");
    bmp->Assign(jpg);
    delete jpg;
    // resize window
    ClientWidth=bmp->Width<<2;
    ClientHeight=bmp->Height;
    // compute
    compute();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    delete bmp;
    delete bmp0;
    delete bmp1;
    delete bmpd;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
/*
    // alternatin images
    static int cnt=0;
    cnt=(cnt+1)&1;
    if (cnt==0) Canvas->Draw(0,0,bmp0);
    if (cnt==1) Canvas->Draw(0,0,bmp1);
    Canvas->Draw(bmp->Width,0,bmpd);
*/
    // debug view of all images
    int x=0;
    Canvas->Draw(x,0,bmp ); x+=bmp ->Width;
    Canvas->Draw(x,0,bmp0); x+=bmp0->Width;
    Canvas->Draw(x,0,bmp1); x+=bmp1->Width;
    Canvas->Draw(x,0,bmpd); x+=bmpd->Width;

    }
//---------------------------------------------------------------------------

只需忽略 VCL 的东西。函数compute将计算bmp0,bmp1及其对应的调色板pal0,pal1。颜色的数量可以通过 constant 进行配置colors

这里输出 64 色调色板:

预习

原图:

输入

差异被夸大了4倍。2 个使用过的调色板也被渲染在那里,顶部一个用于bmp0,另一个用于bmp1.

bmp0此外,为了避免闪烁,您应该保持和之间的差异bmp1尽可能小。

于 2020-11-16T12:29:22.620 回答