You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Windows上快速实现无条带的四边形双线性插值?

高效无条带四边形双线性插值绘制方案(Windows 10兼容,100Hz帧率)

需求与现状

  • 核心需求:绘制矩形,每个像素由四个顶点颜色双线性插值生成,要求Windows 10兼容、帧率≥100Hz
  • 当前实现:通过GradientFill将四边形拆分为两个三角形绘制,代码如下:
GradientFill(*pDC, m_arrVert.GetData(), m_arrVert.GetSize(), 
    m_arrTri.GetData(), m_arrTri.GetSize(), GRADIENT_FILL_TRIANGLE);
  • 现存问题:三角形共享斜边处频繁出现条带伪影(artifacts)

已尝试方案及问题

  • 手动双线性插值
    • 效果:完全消除条带
    • 缺陷:SetPixelV逐像素调用系统开销极大,帧率无法达标
    • 代码:
CPaintDC dc(this); // device context for painting
CRect   rc;
GetClientRect(rc);
int w = rc.Width();
int h = rc.Height();
struct COLOR { int r; int g; int b; };
COLOR   vc[4] = {   // vertex colors
    {241, 188, 82}, // top left
    {104, 11, 211}, // top right
    {174, 51, 196}, // bottom left
    {10, 199, 247}, // bottom right
};
for (int y = 0; y < h; y++) {
    for (int x = 0; x < w; x++) {
        double  sx = double(x) / w;
        double  sy = double(y) / h;
        double  rt = vc[0].r + (vc[1].r - vc[0].r) * sx;
        double  rb = vc[2].r + (vc[3].r - vc[2].r) * sx;
        int r = Round(rt + (rb - rt) * sy);
        double  gt = vc[0].g + (vc[1].g - vc[0].g) * sx;
        double  gb = vc[2].g + (vc[3].g - vc[2].g) * sx;
        int g = Round(gt + (gb - gt) * sy);
        double  bt = vc[0].b + (vc[1].b - vc[0].b) * sx;
        double  bb = vc[2].b + (vc[3].b - vc[2].b) * sx;
        int b = Round(bt + (bb - bt) * sy);
        dc.SetPixelV(x, y, RGB(r, g, b));
    }
}
  • GDI+ PathGradientBrush
    • 缺陷:两条对角线均出现条带,且绘制速度无法满足帧率要求
    • 代码:
CPaintDC dc(this);
Graphics g(dc); 
CRect   rc;
GetClientRect(rc);
GraphicsPath    gp;
gp.AddRectangle(Rect(0, 0, rc.Width(), rc.Height()));
Color   arCol[4];
arCol[0] = Color(241, 188, 82);
arCol[1] = Color(104, 11, 211);
arCol[2] = Color(10, 199, 247);
arCol[3] = Color(174, 51, 196);
int ar = Round((arCol[0].GetR() + arCol[1].GetR() + arCol[2].GetR() + arCol[3].GetR()) / 4.0);
int ag = Round((arCol[0].GetG() + arCol[1].GetG() + arCol[2].GetG() + arCol[3].GetG()) / 4.0);
int ab = Round((arCol[0].GetB() + arCol[1].GetB() + arCol[2].GetB() + arCol[3].GetB()) / 4.0);
PathGradientBrush   br(&gp);
br.SetCenterColor(Color(ar, ag, ab));
int nCols = 4;
br.SetSurroundColors(arCol, &nCols);
g.FillRectangle(&br, 0, 0, rc.Width(), rc.Height());
  • StretchBlt缩放2×2位图
    • 缺陷:设置HALFTONE模式后仍仅显示四个大像素,无插值效果

高效无条带解决方案推荐

方案1:内存位图+预计算扫描线(优化手动插值)

放弃逐像素SetPixelV调用,改为直接操作DIB(设备无关位图)的内存缓冲区,通过预计算减少重复运算,大幅提升速度:

  1. 创建兼容内存DC与DIB位图,锁定像素缓冲区
  2. 预计算每条y扫描线的顶部/底部颜色插值参数
  3. 批量填充像素缓冲区后,一次性BitBlt到目标DC

核心实现代码:

CPaintDC dc(this);
CRect rc;
GetClientRect(rc);
int w = rc.Width(), h = rc.Height();

// 创建DIB位图(24位RGB,顶部为原点)
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = w;
bmi.bmiHeader.biHeight = -h;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;

BYTE* pBits = nullptr;
HBITMAP hBmp = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, (void**)&pBits, nullptr, 0);
HDC memDC = CreateCompatibleDC(dc);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, hBmp);

struct COLOR { int r, g, b; };
COLOR vc[4] = {
    {241, 188, 82}, {104, 11, 211},
    {174, 51, 196}, {10, 199, 247}
};

// 逐行计算并填充像素
for (int y = 0; y < h; y++) {
    double sy = (double)y / (h - 1); // 用h-1避免边缘溢出
    // 计算当前行对应的顶部、底部插值颜色
    COLOR topCol = {
        (int)Round(vc[0].r + (vc[1].r - vc[0].r) * sy),
        (int)Round(vc[0].g + (vc[1].g - vc[0].g) * sy),
        (int)Round(vc[0].b + (vc[1].b - vc[0].b) * sy)
    };
    COLOR bottomCol = {
        (int)Round(vc[2].r + (vc[3].r - vc[2].r) * sy),
        (int)Round(vc[2].g + (vc[3].g - vc[2].g) * sy),
        (int)Round(vc[2].b + (vc[3].b - vc[2].b) * sy)
    };

    // 填充当前行的每个像素(DIB为BGR顺序)
    BYTE* pRow = pBits + y * w * 3;
    for (int x = 0; x < w; x++) {
        double sx = (double)x / (w - 1);
        int r = (int)Round(topCol.r + (bottomCol.r - topCol.r) * sx);
        int g = (int)Round(topCol.g + (bottomCol.g - topCol.g) * sx);
        int b = (int)Round(topCol.b + (bottomCol.b - topCol.b) * sx);
        pRow[x*3 + 0] = (BYTE)b;
        pRow[x*3 + 1] = (BYTE)g;
        pRow[x*3 + 2] = (BYTE)r;
    }
}

// 一次性绘制到目标DC
BitBlt(dc, 0, 0, w, h, memDC, 0, 0, SRCCOPY);

// 清理资源
SelectObject(memDC, oldBmp);
DeleteObject(hBmp);
DeleteDC(memDC);
  • 优势:直接操作内存缓冲区,避免系统调用开销;预计算减少重复运算,帧率可轻松突破100Hz;完全消除条带伪影
  • 兼容性:原生支持Windows 10,无需升级工具链

方案2:GDI AlphaBlend结合渐变位图(可选)

创建两个水平渐变位图,通过垂直方向的AlphaBlend实现双线性插值,效果接近方案1,但实现复杂度略高。

关键注意事项

  • 绝对避免SetPixelV等逐像素GDI调用,这类调用存在大量系统开销,无法满足高帧率需求
  • 插值计算时使用h-1和w-1作为分母,避免边缘像素插值溢出
  • 操作DIB时注意像素顺序为BGR而非RGB

内容的提问来源于stack exchange,提问作者Chris Korda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 22:29:56