I need to resize huge (up to 30000x30000) JPEG files using no RAM, speed doesn't matter, is there any way to do so? I tried different libraries (nativejpg and others) but they use all free RAM and and crash with errors like "Out of memory" or "Not enough storage is available to process this command". I even tried command line utility imagemagick, but it also uses gigabytes of memory.
3 Answers
I would suggest you have a look at vips
. It is documented here.
I can create a 10000x10000 image of noise like this with ImageMagick
convert -size 10000x10000! xc:gray50 +noise poisson image.jpg
and check it is the correct size like this:
identify image.jpg
image.jpg JPEG 10000x10000 10000x10000+0+0 8-bit sRGB 154.9MB 0.000u
I can now use vips
to resize the 10000x10000 image down to 2500x2500 like this
time vipsthumbnail image.jpg -s 2500 -o small.jpg --vips-leak
memory: high-water mark 20.48 MB
real 0m1.974s
user 0m2.158s
sys 0m0.096s
Note the memory usage peaked at just 20MB
Check the result like this with ImageMagick
identify result.jpg
result.jpg JPEG 2500x2500 2500x2500+0+0 8-bit sRGB 1.33MB 0.000u 0:00.000
Have a look at the Technical Note too, regarding performance and memory usage - here.
You can also call it from C
as well as the command line.
You can do this with imagemagick if you turn on libjpeg shrink-on-load. Try:
$ identify big.jpg
big.jpg JPEG 30000x30000 30000x30000+0+0 8-bit sRGB 128MB 0.000u 0:00.000
$ time convert -define jpeg:size=2500x2500 big.jpg -resize 2500x2500 small.jpg
real 0m3.169s
user 0m2.999s
sys 0m0.159s
peak mem: 170MB
How this works: libjpeg has a great shrink-on-load feature. When you open an image, you can ask the library to downsample by x2, x4 or x8 during the loading process -- the library then just decodes part of each DCT block.
However, this feature must be enabled when the image is opened, you can't set it later. So convert
needs a hint that when it opens big.jpg
, it only needs to get an image of at least size 2500x2500 (your target size). Now all -resize
is doing is shrinking a 3800x3800 pixel image down to 2500x2500, a pretty easy operation. You'll only need 1/64th of the CPU and memory.
As @mark-setchell said above, vipsthumbnail
is even faster:
$ time vipsthumbnail big.jpg -s 2500 -o small.jpg --vips-leak
memory: high-water mark 29.93 MB
real 0m2.362s
user 0m2.873s
sys 0m0.082s
Though the speedup is not very dramatic, since both systems are really just resizing 3800 -> 2500.
If you try tif instead, you do see a large difference, since there's no shrink-on-load trick you can use:
$ identify 360mp.tif
360mp.tif TIFF 18000x18000 18000x18000+0+0 8-bit sRGB 972MB 0.000u 0:00.000
$ time convert 360mp.tif -resize 2500 x.tif
peak mem: 2.8 GB
real 0m8.397s
user 0m25.508s
sys 0m1.648s
$ time vipsthumbnail 360mp.tif -o x.tif -s 2500 --vips-leak
memory: high-water mark 122.08 MB
real 0m2.583s
user 0m9.012s
sys 0m0.308s
Now vipsthumbnail
is about 4x faster and needs only 1/20th of the memory.
With built in Delphi Jpeg support you can load large jpeg image resampled to smaller size while loading without excessive usage of RAM.
Jpeg image Scale property can have following values jsFullSize
, jsHalf
, jsQuarter
, jsEighth
procedure ScaleJpg(const Source, Dest: string);
var
SourceImg, DestImg: TJPEGImage;
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
SourceImg := TJPEGImage.Create;
try
SourceImg.Scale := jsEighth;
SourceImg.LoadFromFile(Source);
Bmp.Width := SourceImg.Width;
Bmp.Height := SourceImg.Height;
Bmp.Canvas.Draw(0, 0, SourceImg);
finally
SourceImg.Free;
end;
DestImg := TJPEGImage.Create;
try
DestImg := TJPEGImage.Create;
DestImg.Assign(Bmp);
DestImg.SaveToFile(Dest);
finally
DestImg.Free;
end;
finally
Bmp.Free;
end;
end;
Once you have roughly rescaled image to size that can be comfortably processed in memory you can apply ordinary scaling algorithms to get the actual image size you want.