嗯 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
尽可能小。