简体   繁体   中英

Reduce PNG storage size

In order to reduce file upload sizes to a webservice, we limit height/width of image files to a maximum value.
For JPG files this works fine: a (downward) image resize results in reduced file size.

Not so for PNG files, though: in most cases our code results in larger file sizes:

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 is the image loaded from disk, FEditImageRes.Value contains the largest dimension that we reduce to.

Note that we use Developer Express components, and that this code maintains alpha channel (transparency).
I'm not attached to either.

I have posted a ticket with DevExpress , but it is not an issue in their code.

I looked at what other software does:

在此处输入图片说明

In Paint.Net, if I reduce the above 890*161 screenshot to 512*93 I see mixed results depending on the algorithm used for the resize:

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

在此处输入图片说明

For this 550x386 photo reduced to 512*353, the Paint.Net results are:

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

So it looks pretty unpredictable what the results are going to be.

Question:
Is there anything I can do (change to my code) to ensure that (most) resized PNG files actually will have a reduced file size?

Some rules how to get smallest image size possible with TPngImage from vcl.imaging.PngImage

Firstly, it has CompressionLevel property, you can set integer value 0..9, 0 is no compression ar all, 9 is best compression (but slowest one). By default 7 is set. Note: PNG is always lossless, this setting affects only amount of time it takes to save image.

Second, there is Filters property, by default, its value is [pfSub] , but to achieve best compression, you should set it to [pfNone, pfSub, pfUp, pfAverage, pfPaeth] . These are prediction filters which are applied to each row of image in order to use correlation between neighbours to get better compression. When all the filters are set, each will be tried and the best one will be used.

Make sure that InterlaceMethod property is set to imNone. Maybe your original images were interlaced, in that case file size is increased 5..20% compared to non-interlaced.

There is one more possibility to get lower size of image, that's to increase MaxIDATSize property to value a bit more, than your image size. Point is, PNG pixel data is stored in one or more IDAT chunks, each one has 4 bytes of its size, then 4 bytes of its name ('IDAT'), then data and then 4 bytes of CRC. By default, size of each chunk is 65535 bytes, so in big image you'll have lots of them, and 12 bytes per chunk of waste. But increase here is very small, 0.2%, not so much.

In fact, with such settings PNGImage makes pretty small files, usually 10..20% smaller then Paint.NET, but specialized programs can compresss PNG even better.

About resizing. When trying to resize screenshots, larger file size is often the case. Original screenshot has few colors, in your case it's 431, much because of gradients. There are large areas of same color that are saved very well. After resizing, each sharp transition from one color to another is blurred, so more "mixed" colors are created, it is really harder to compress. As you see, nearest neighbor results in smallest file size exactly because it doesn't create new colors which weren't in original image already.

Your second example, 550x386 photo MUST have lower file size after resizing, and indeed, I've managed to compress resized to 512x353 photo to 303 kB. Paint.net doesn't use prediction filters at all, that's why it has awful compression of photos and other true color images.

The best approach is to use a dedicated tool to reduce the png size.

Take a look at this page for some technical details about how png size may be optimized

There are some tools which works very well, I used http://optipng.sourceforge.net

See also this comparison of png optimization tools .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM