Dragonfly Lossless Rotate
About 60% more performance with libjpeg-turbo tools
ImageMagick convert command which used in Dragonfly for rotation is lossy for JPEG images. This gem uses libjpeg for lossless rotation.
NOTE
Tool jpegtran from MozJPEG may work incorrectly and not rotate same image many times. You should test it before run in production.
Setup
gem "dragonfly-lossless_rotate"
Dragonfly.app.configure
require "dragonfly/lossless_rotate"
plugin :lossless_rotate
end
Requirements
By default gem uses libjpeg binaries and pnmflip from netpbm:
cjpeg
djpeg
jpegtran
pnmflip
Ubuntu
sudo apt-get install libjpeg-turbo-progs netpbm
macOS
brew install libjpeg netpbm
FreeBSD
MozJPEG binaries
But you can set MozJPEG binaries in ENV CJPEG_BIN=mozjpeg-cjpeg
or in config:
Dragonfly.app.configure
require "dragonfly/lossless_rotate"
plugin :lossless_rotate, cjpeg_bin: "mozjpeg-cjpeg",
djpeg_bin: "mozjpeg-djpeg",
jpegtran_bin: "mozjpeg-jpegtran"
end
Use pamflip as pnmflip compatible binary
plugin :lossless_rotate, pnmflip_bin: "pamflip"
Usage
JPEG only:
@image.process(:lossless_rotate) # default 90
@image.process(:lossless_rotate, 180)
@image.process(:lossless_rotate, 270)
@image.process(:lossless_rotate, -90)
With fallback for other formats (rotate via ImageMagick):
@image.process(:safe_lossless_rotate)
Other options:
# Without JPEG optimization (default: true)
@image.process(:lossless_rotate, 90, optimize: false)
# Set default value
plugin :lossless_rotate, libjpeg_optimize: false
# Create progressive JPEG file (default: false)
@image.process(:lossless_rotate, 90, progressive: true)
# Set default value
plugin :lossless_rotate, libjpeg_progressive: true
Benchmark
- ImageMagick 6.8.9-9 Q16 x86_64 2017-07-31
- libjpeg-turbo version 1.4.2 (build 20160222)
- MozJPEG version 3.3.2 (build 20180713)
JPEG 85KB 552x416px
ImageMagick rotate
convert old_path -rotate 90 new_path
puts Benchmark.measure { 500.times { @image.rotate(90).apply } }
0.360000 1.570000 25.270000 ( 25.168681)
Lossless rotate
libjpeg-turbo
jpegtran -rotate 90 -perfect -optimize old_path > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate).apply } }
0.280000 1.160000 9.170000 ( 9.876645)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate).apply } }
0.560000 1.780000 22.710000 ( 23.879913)
MozJPEG
mozjpeg-jpegtran -rotate 90 -perfect -optimize old_path > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate).apply } }
0.270000 1.110000 35.230000 ( 36.693039)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate).apply } }
0.550000 1.540000 48.880000 ( 50.171667)
Fallback when jpegtran transformation is not perfect
if the image dimensions are not a multiple of the iMCU size (usually 8 or 16 pixels)
Same image but resized to 556x417px
libjpeg-turbo
djpeg old_path | pnmflip -r270 | cjpeg -optimize > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate).apply } }
0.410000 1.280000 16.310000 ( 13.220535)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate).apply } }
0.310000 1.330000 30.300000 ( 28.332533)
MozJPEG
mozjpeg-djpeg old_path | pnmflip -r270 | mozjpeg-cjpeg -optimize > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate).apply } }
0.400000 1.150000 41.190000 ( 37.970843)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate).apply } }
0.420000 1.670000 55.700000 ( 52.835614)