No commit activity in last 3 years
No release in over 3 years
Lossless rotating and compressing JPEG images via libjpeg tools
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

~> 1.0
 Project Readme

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)