Reputation: 9096
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?
Upvotes: 1
Views: 1819
Reputation: 1440
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.
Upvotes: 4
Reputation: 43033
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.
Upvotes: 1