因工作需要使用软件方法缩放PNG图片,询问chatgpt拿到了c++双线性插值算法,开始很顺利,整理一下代码,封装一下接口,就可以使用了,效果还不错,马上编译发给测试组测试,测试发现有一些图片缩放后出现黑边,还有一些图片缩放时程序崩溃了。有问题的代码如下。
BOOL hbmp_stretch_Bilinear(HBITMAP hbmp_dst, HBITMAP hbmp_src)
{BITMAP info_src, info_dst;GetObject(hbmp_src, sizeof(BITMAP), &info_src);GetObject(hbmp_dst, sizeof(BITMAP), &info_dst);int srcWidth = info_src.bmWidth;int srcHeight = info_src.bmHeight;int dstWidth = info_dst.bmWidth; // 新的图像宽度int dstHeight = info_dst.bmHeight; // 新的图像高度float rateX = (float)(srcWidth - 1) / (float)(dstWidth - 1);float rateY = (float)(srcHeight - 1) / (float)(dstHeight - 1);BYTE* bits = (BYTE*)info_src.bmBits;float srcX, srcY;int x0, x1, y0, y1;int x, y;float fL, fR, fT, fB, fTL, fTR, fBL, fBR;for (y = 0; y < dstHeight; y++) {for (x = 0; x < dstWidth; x++) {srcX = (float)x * rateX;srcY = (float)y * rateY;x0 = (int)srcX;y0 = (int)srcY;x1 = x0 + 1;y1 = y0 + 1;BYTE* pTL = &bits[info_src.bmWidthBytes * y0 + (x0 << 2)];BYTE* pTR = &bits[info_src.bmWidthBytes * y0 + (x1 << 2)];BYTE* pBL = &bits[info_src.bmWidthBytes * y1 + (x0 << 2)];BYTE* pBR = &bits[info_src.bmWidthBytes * y1 + (x1 << 2)];BYTE* pTarget = &((BYTE*)info_dst.bmBits)[info_dst.bmWidthBytes * y + (x << 2)];fR = srcX - x0;fB = srcY - y0;fL = (1.0f - fR);fT = (1.0f - fB);fTL = fL * fT;fTR = fR * fT;fBL = fL * fB;fBR = fR * fB;pTarget[0] = (BYTE)(fTL * (float)pTL[0] + fTR * (float)pTR[0] + fBL * (float)pBL[0] + fBR * (float)pBR[0]);pTarget[1] = (BYTE)(fTL * (float)pTL[1] + fTR * (float)pTR[1] + fBL * (float)pBL[1] + fBR * (float)pBR[1]);pTarget[2] = (BYTE)(fTL * (float)pTL[2] + fTR * (float)pTR[2] + fBL * (float)pBL[2] + fBR * (float)pBR[2]);pTarget[3] = (BYTE)(fTL * (float)pTL[3] + fTR * (float)pTR[3] + fBL * (float)pBL[3] + fBR * (float)pBR[3]);}}return true;
}
用有问题的图片单步跟踪执行,很快锁定了有问题的代码行:
float rateX = (float)(srcWidth - 1) / (float)(dstWidth - 1);float rateY = (float)(srcHeight - 1) / (float)(dstHeight - 1);。。。for (y = 0; y < dstHeight; y++) {for (x = 0; x < dstWidth; x++) {srcX = (float)x * rateX;srcY = (float)y * rateY;x0 = (int)srcX;y0 = (int)srcY;
x0是源图像素点列坐标,代入转换:
x0=(int)srcX=x*rateX=x*(srcWidth - 1)/(dstWidth - 1)
srcWidth:源图片的宽度 dstWidth:目标图片的宽度
当到达像素行尾时,x=dstWidth-1,根据上面的公式,x0的理论值是srcWidth - 1,正好覆盖源图片的一行。但是rateX转换浮点数时有可能会发生截断误差,2个整数相除,其结果有可能是有限不循环小数,也有可能是无限循环小数,第一种结果小数位数超过7位与第二种结果都会产生截断,当发生向下截断时,x=dstWidth-1,x0的值是srcWidth - 2,x1=x0+1的值不会越界,其它情况x0的值是srcWidth - 1,x1=x0+1的值越界,所以有些图片正常,有些图片应用程序因为内存越界而崩溃。同理,计算y0也存在同样的问题。找到了问题原因,可以在计算rateX、rateY时额外加上或者减去一个微小值,让rateX、rateY的截断发生在同一方向(向上截断或者向下截断),相应的得到两种解决方案。
解决方案一
在计算rateX、rateY时额外加上0.0001,让rateX、rateY的截断全部是向上截断,没有向下截断。
float rateX = (float)(srcWidth -1 + 0.0001) / (float)(dstWidth - 1);
float rateY = (float)(srcHeight -1 + 0.0001) / (float)(dstHeight - 1);
此时遍历到行尾时x1会产生越界,遍历到最后一行时y1会产生越界,因此行尾与最后一行移出循环体,做特别的处理。代码如下:
BOOL hbmp_stretch_Bilinear_border(HBITMAP hbmp_dst, HBITMAP hbmp_src)
{BITMAP info_src, info_dst;GetObject(hbmp_src, sizeof(BITMAP), &info_src);GetObject(hbmp_dst, sizeof(BITMAP), &info_dst);int srcWidth = info_src.bmWidth;int srcHeight = info_src.bmHeight;int dstWidth = info_dst.bmWidth; // 新的图像宽度int dstHeight = info_dst.bmHeight; // 新的图像高度float rateX = (float)(srcWidth -1 + 0.0001) / (float)(dstWidth - 1);float rateY = (float)(srcHeight -1 + 0.0001) / (float)(dstHeight - 1);BYTE* bits = (BYTE*)info_src.bmBits;float srcX, srcY;int x0, x1, y0, y1;int x, y;float fL, fR, fT, fB, fTL, fTR, fBL, fBR;for (y = 0; y < dstHeight-1; y++) {for (x = 0; x < dstWidth-1; x++) {srcX = (float)x * rateX;srcY = (float)y * rateY;x0 = (int)srcX;y0 = (int)srcY;x1 = x0 + 1;y1 = y0 + 1;BYTE* pTL = &bits[info_src.bmWidthBytes * y0 + (x0 << 2)];BYTE* pTR = &bits[info_src.bmWidthBytes * y0 + (x1 << 2)];BYTE* pBL = &bits[info_src.bmWidthBytes * y1 + (x0 << 2)];BYTE* pBR = &bits[info_src.bmWidthBytes * y1 + (x1 << 2)];BYTE* pTarget = &((BYTE*)info_dst.bmBits)[info_dst.bmWidthBytes * y + (x << 2)];fR = srcX - x0;fB = srcY - y0;fL = (1.0f - fR);fT = (1.0f - fB);fTL = fL * fT;fTR = fR * fT;fBL = fL * fB;fBR = fR * fB;pTarget[0] = (BYTE)(fTL * (float)pTL[0] + fTR * (float)pTR[0] + fBL * (float)pBL[0] + fBR * (float)pBR[0]);pTarget[1] = (BYTE)(fTL * (float)pTL[1] + fTR * (float)pTR[1] + fBL * (float)pBL[1] + fBR * (float)pBR[1]);pTarget[2] = (BYTE)(fTL * (float)pTL[2] + fTR * (float)pTR[2] + fBL * (float)pBL[2] + fBR * (float)pBR[2]);pTarget[3] = (BYTE)(fTL * (float)pTL[3] + fTR * (float)pTR[3] + fBL * (float)pBL[3] + fBR * (float)pBR[3]);}int x_src = srcWidth - 1;int y_src = (int)y * rateY;BYTE* src = &bits[info_src.bmWidthBytes * y_src + (x_src << 2)];BYTE* target = &((BYTE*)info_dst.bmBits)[info_dst.bmWidthBytes * y + ((dstWidth - 1) << 2)];target[0] = src[0];target[1] = src[1];target[2] = src[2];target[3] = src[3];}for (int x_dst = 0; x_dst < dstWidth; x_dst++) {int x_src = (int)x_dst * rateX;BYTE* src = &bits[info_src.bmWidthBytes * (srcHeight-1) + (x_src << 2)];BYTE* target = &((BYTE*)info_dst.bmBits)[info_dst.bmWidthBytes * (dstHeight-1)+(x_dst << 2)];target[0] = src[0];target[1] = src[1];target[2] = src[2];target[3] = src[3];}return true;
}
解决方案二
在计算rateX、rateY时额外减去0.0001,让rateX、rateY的截断全部是向下截断,没有向上截断。
float rateX = (float)(srcWidth -1 - 0.0001) / (float)(dstWidth - 1);
float rateY = (float)(srcHeight -1 - 0.0001) / (float)(dstHeight - 1);
这种方法巧妙利用在行尾与最后一行权重fBL、fBR约等于1.0,从而避免特别处理行尾与最后一行。
BOOL hbmp_stretch_Bilinear(HBITMAP hbmp_dst, HBITMAP hbmp_src)
{BITMAP info_src, info_dst;GetObject(hbmp_src, sizeof(BITMAP), &info_src);GetObject(hbmp_dst, sizeof(BITMAP), &info_dst);int srcWidth = info_src.bmWidth;int srcHeight = info_src.bmHeight;int dstWidth = info_dst.bmWidth; // 新的图像宽度int dstHeight = info_dst.bmHeight; // 新的图像高度float rateX = (float)(srcWidth - 1 - 0.0001) / (float)(dstWidth - 1);float rateY = (float)(srcHeight - 1 - 0.0001) / (float)(dstHeight - 1);BYTE* bits = (BYTE*)info_src.bmBits;float srcX, srcY;int x0, x1, y0, y1;int x, y;float fL, fR, fT, fB, fTL, fTR, fBL, fBR;for (y = 0; y < dstHeight; y++) {for (x = 0; x < dstWidth; x++) {srcX = (float)x * rateX;srcY = (float)y * rateY;x0 = (int)srcX;y0 = (int)srcY;x1 = x0 + 1;y1 = y0 + 1;BYTE* pTL = &bits[info_src.bmWidthBytes * y0 + (x0 << 2)];BYTE* pTR = &bits[info_src.bmWidthBytes * y0 + (x1 << 2)];BYTE* pBL = &bits[info_src.bmWidthBytes * y1 + (x0 << 2)];BYTE* pBR = &bits[info_src.bmWidthBytes * y1 + (x1 << 2)];BYTE* pTarget = &((BYTE*)info_dst.bmBits)[info_dst.bmWidthBytes * y + (x << 2)];fR = srcX - x0;fB = srcY - y0;fL = (1.0f - fR);fT = (1.0f - fB);fTL = fL * fT;fTR = fR * fT;fBL = fL * fB;fBR = fR * fB;pTarget[0] = (BYTE)(fTL * (float)pTL[0] + fTR * (float)pTR[0] + fBL * (float)pBL[0] + fBR * (float)pBR[0]);pTarget[1] = (BYTE)(fTL * (float)pTL[1] + fTR * (float)pTR[1] + fBL * (float)pBL[1] + fBR * (float)pBR[1]);pTarget[2] = (BYTE)(fTL * (float)pTL[2] + fTR * (float)pTR[2] + fBL * (float)pBL[2] + fBR * (float)pBR[2]);pTarget[3] = (BYTE)(fTL * (float)pTL[3] + fTR * (float)pTR[3] + fBL * (float)pBL[3] + fBR * (float)pBR[3]);}}return true;
}