Dragonfly Lossless Rotate
About 60% more performance with libjpeg-turbo tools
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 use libjpeg binaries:
cjpeg
djpeg
jpegtran
pnmflip
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
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
- 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
(optimize=true)
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)
(optimize=false)
jpegtran -rotate 90 -perfect old_path > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate, 90, optimize: false).apply } }
0.250000 1.090000 8.110000 ( 8.601707)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate, 90, optimize: false).apply } }
0.360000 1.170000 21.480000 ( 22.744040)
MozJPEG
(optimize=true)
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)
(optimize=false)
mozjpeg-jpegtran -rotate 90 -perfect old_path > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate, 90, optimize: false).apply } }
0.310000 1.100000 34.960000 ( 35.947432)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate, 90, optimize: false).apply } }
0.470000 1.660000 49.050000 ( 50.823576)
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
(optimize=true)
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)
(optimize=false)
djpeg old_path | pnmflip -r270 | cjpeg > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate, 90, optimize: false).apply } }
0.330000 1.250000 15.190000 ( 11.990070)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate, 90, optimize: false).apply } }
0.330000 1.330000 29.010000 ( 26.816061)
MozJPEG
(optimize=true)
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)
(optimize=false)
mozjpeg-djpeg old_path | pnmflip -r270 | mozjpeg-cjpeg > new_path
puts Benchmark.measure { 500.times { @image.process(:lossless_rotate, 90, optimize: false).apply } }
0.240000 0.860000 40.550000 ( 38.247647)
puts Benchmark.measure { 500.times { @image.process(:safe_lossless_rotate, 90, optimize: false).apply } }
0.480000 1.330000 54.550000 ( 52.941735)