减少 PNG 存储大小

Reduce PNG storage size

为了减少上传到网络服务的文件大小,我们将图像文件的数量限制为 height/width 个最大值。
对于 JPG 文件,这很好用:(向下)调整图像大小会减小文件大小。

但 PNG 文件并非如此:在大多数情况下,我们的代码会生成 更大 的文件大小:

procedure TFrmImageCheckAndResize.ResizePNGImage;
var
  lSrcPNGImage, lTrgPNGImage: TdxPNGImage;
  lSrcBitmap,lDestBitMap: TcxAlphaBitmap;
  lNewWidth,lNewHeight: Integer;
  lFactor: Real;
begin
  lSrcPNGImage := TdxPNGImage.Create;
  lSrcPNGImage.LoadFromFile(FFileName);
  lSrcBitmap := TcxAlphaBitmap.CreateSize(lSrcPNGImage.Width, lSrcPNGImage.Height, True);
  lSrcBitmap.Canvas.Draw(0, 0, lSrcPNGImage);
  if lSrcPNGImage.Width > lSrcPNGImage.Height then
     if lSrcPNGImage.Width > FEditImageRes.Value then
        lFactor := lSrcPNGImage.Width
     else
        lFactor := 0
  else
     if lSrcPNGImage.Height > FEditImageRes.Value then
        lFactor := lSrcPNGImage.Height
     else
        lFactor := 0;
  if lFactor <> 0 then
  begin
     lFactor := lFactor / FEditImageRes.Value;
     lNewWidth  := Trunc(lSrcPNGImage.Width  / lFactor);
     lNewHeight := Trunc(lSrcPNGImage.Height / lFactor);
     lDestBitMap := TcxAlphaBitmap.CreateSize(lNewWidth,lNewHeight, True);
     cxSmoothResizeBitmap(lSrcBitMap, lDestBitMap, true);
     lTrgPNGImage := TdxPNGImage.CreateFromBitmap(lDestBitmap);
  end
  else
  begin
     lDestBitmap := nil; // Silence the compiler
     lTrgPNGImage := TdxPNGImage.CreateFromBitmap(lSrcBitmap);
  end;
  lTrgPNGImage.SaveToFile(StringReplace(FFileName,'.','_' + IntToStr(FEditImageRes.Value) + '.',[]));
  lSrcBitmap.Free;
  lDestBitmap.Free;
  lTrgPNGImage.Free;
  lSrcPNGImage.Free;
end;  

FFileName是从磁盘加载的图像,FEditImageRes.Value包含我们缩小到的最大尺寸。

请注意,我们使用 Developer Express 组件,并且此代码保持 alpha 通道(透明度)。
两者我都不执着。

我已经发布了一个 ticket with DevExpress,但这在他们的代码中不是问题。

我查看了其他软件的功能:

在 Paint.Net 中,如果我将上面的 890*161 屏幕截图缩小到 512*93,我会看到混合结果,具体取决于用于调整大小的算法:

15.697 Original.png
21.904 Resized_BiCubic.png
19.995 Resized_Bilineair.png
22.905 Resized_Fant.png
 6.729 Resized_NearestNeigbour.png

将这张 550x386 的照片缩小为 512*353,Paint.Net 结果为:

375.229 Photo.png
419.122 Photo_Bicubic.png
402.277 Photo_Bilineair.png
407.959 Photo_Fant.png
416.619 Photo_NearestNeighbor.png

所以结果看起来很难预测。

问题:
有什么我可以做的(更改我的代码)来确保(大多数)调整大小的 PNG 文件实际上会减小文件大小?

最好的方法是使用专用工具来减小 png 的大小。

查看 this page 了解有关如何优化 png 大小的一些技术细节

有些工具效果很好,我用过http://optipng.sourceforge.net

另见 this comparison of png optimization tools

一些规则如何使用 TPngImagevcl.imaging.PngImage

获得尽可能小的图像尺寸

首先,它有CompressionLevel 属性,你可以设置整数值0..9,0是完全不压缩,9是最好的压缩(但最慢)。默认设置为 7。注意:PNG 始终是无损的,此设置仅影响保存图像所需的时间。

其次,还有Filters 属性,默认情况下,它的值为[pfSub],但为了达到最佳压缩效果,您应该将其设置为[pfNone, pfSub, pfUp, pfAverage, pfPaeth]。这些是应用于图像每一行的预测滤波器,以便使用相邻图像之间的相关性来获得更好的压缩。设置完所有过滤器后,将尝试每一个,并使用最好的一个。

确保 InterlaceMethod 属性 设置为 imNone。也许您的原始图像是隔行扫描的,在这种情况下,与非隔行扫描相比,文件大小增加了 5..20%。

还有一种方法可以降低图像尺寸,那就是将 MaxIDATSize 属性 增加到比图像尺寸高一点的值。重点是,PNG 像素数据存储在一个或多个 IDAT 块中,每个块有 4 个字节的大小,然后是 4 个字节的名称 ('IDAT'),然后是数据,然后是 4 个字节的 CRC。默认情况下,每个块的大小是 65535 字节,所以在大图像中你会有很多,每个块浪费 12 字节。但是这里的涨幅很小,只有0.2%,没那么多。

事实上,使用这样的设置 PNGImage 可以生成非常小的文件,通常比 Paint.NET 小 10..20%,但是专门的程序可以更好地压缩 PNG。

关于调整大小。尝试调整屏幕截图大小时,通常会出现较大的文件大小。原始屏幕截图颜色很少,在您的情况下是 431,很大程度上是因为渐变。有大面积的相同颜色保存得很好。调整大小后,从一种颜色到另一种颜色的每一次急剧过渡都会变得模糊,因此会创建更多 "mixed" 颜色,这确实更难压缩。如您所见,最近邻法产生最小的文件大小正是因为它不会创建原始图像中没有的新颜色。

您的第二个示例,550x386 照片在调整大小后必须具有较小的文件大小,事实上,我已经设法将调整大小为 512x353 的照片压缩到 303 kB。 Paint.net 根本不使用预测过滤器,这就是为什么它对照片和其他真彩色图像的压缩很糟糕。