Skip to content

Commit

Permalink
Add support for Oxipng
Browse files Browse the repository at this point in the history
Based on toy#167

Oxipng is a multi-threaded rust implementation of Optipng.

https://github.com/shssoichiro/oxipng
  • Loading branch information
oblakeerickson committed Jun 7, 2021
1 parent 1ed0328 commit 50f9216
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.markdown
Expand Up @@ -2,6 +2,8 @@

## unreleased

* Add support for Oxipng [#167](https://github.com/toy/image_optim/issues/167) [@oblakeerickson](https://github.com/oblakeerickson)

## v0.30.0 (2021-05-11)

* Add `timeout` option to restrict maximum time spent on every image [#21](https://github.com/toy/image_optim/issues/21) [#148](https://github.com/toy/image_optim/pull/148) [#149](https://github.com/toy/image_optim/pull/149) [#162](https://github.com/toy/image_optim/pull/162) [#184](https://github.com/toy/image_optim/pull/184) [#189](https://github.com/toy/image_optim/pull/189) [@tgxworld](https://github.com/tgxworld) [@oblakeerickson](https://github.com/oblakeerickson) [@toy](https://github.com/toy)
Expand Down
2 changes: 1 addition & 1 deletion lib/image_optim.rb
Expand Up @@ -14,7 +14,7 @@
require 'shellwords'

%w[
pngcrush pngout advpng optipng pngquant
pngcrush pngout advpng optipng pngquant oxipng
jhead jpegoptim jpegrecompress jpegtran
gifsicle
svgo
Expand Down
2 changes: 1 addition & 1 deletion lib/image_optim/bin_resolver/bin.rb
Expand Up @@ -109,7 +109,7 @@ def version_string
case name
when :advpng
capture("#{escaped_path} --version 2> #{Path::NULL}")[/\bv(\d+(\.\d+)+|none)/, 1]
when :gifsicle, :jpegoptim, :optipng
when :gifsicle, :jpegoptim, :optipng, :oxipng
capture("#{escaped_path} --version 2> #{Path::NULL}")[/\d+(\.\d+)+/]
when :svgo, :pngquant
capture("#{escaped_path} --version 2>&1")[/\A\d+(\.\d+)+/]
Expand Down
52 changes: 52 additions & 0 deletions lib/image_optim/worker/oxipng.rb
@@ -0,0 +1,52 @@
# frozen_string_literal: true

require 'image_optim/worker'
require 'image_optim/option_helpers'
require 'image_optim/true_false_nil'

class ImageOptim
class Worker
# https://github.com/shssoichiro/oxipng
class Oxipng < Worker
LEVEL_OPTION =
option(:level, 3, 'Optimization level preset: '\
'`0` is least, '\
'`6` is best') do |v|
OptionHelpers.limit_with_range(v.to_i, 0..6)
end

INTERLACE_OPTION =
option(:interlace, false, TrueFalseNil, 'Interlace: '\
'`true` - interlace on, '\
'`false` - interlace off, '\
'`nil` - as is in original image') do |v|
TrueFalseNil.convert(v)
end

STRIP_OPTION =
option(:strip, true, 'Remove all auxiliary chunks'){ |v| !!v }

def run_order
-4
end

def optimize(src, dst)
src.copy(dst)
args = %W[
-o #{level}
--quiet
#{dst}
]
args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
if strip
args.unshift '--strip', 'all'
end
execute(:oxipng, *args) && optimized?(src, dst)
end

def optimized?(src, dst)
interlace ? dst.size? : super
end
end
end
end
89 changes: 89 additions & 0 deletions spec/image_optim/worker/oxipng_spec.rb
@@ -0,0 +1,89 @@
# frozen_string_literal: true

require 'spec_helper'
require 'image_optim/worker/oxipng'
require 'image_optim/path'

describe ImageOptim::Worker::Oxipng do
describe 'strip option' do
subject{ described_class.new(ImageOptim.new, options) }

let(:options){ {} }
let(:src){ instance_double(ImageOptim::Path, :copy => nil) }
let(:dst){ instance_double(ImageOptim::Path) }

before do
oxipng_bin = instance_double(ImageOptim::BinResolver::Bin)
allow(subject).to receive(:resolve_bin!).
with(:oxipng).and_return(oxipng_bin)

allow(subject).to receive(:optimized?)
end

context 'by default' do
it 'should add --strip all to arguments' do
expect(subject).to receive(:execute) do |_bin, *args|
expect(args.join(' ')).to match(/(^| )--strip all($| )/)
end

subject.optimize(src, dst)
end
end

context 'when strip is disabled' do
let(:options){ {:strip => false} }

it 'should not add --strip all to arguments' do
expect(subject).to receive(:execute) do |_bin, *args|
expect(args.join(' ')).not_to match(/(^| )--strip all($| )/)
end

subject.optimize(src, dst)
end
end
end

describe '#optimized?' do
let(:src){ instance_double(ImageOptim::Path, src_options) }
let(:dst){ instance_double(ImageOptim::Path, dst_options) }
let(:src_options){ {:size => 10} }
let(:dst_options){ {:size? => 9} }
let(:instance){ described_class.new(ImageOptim.new, instance_options) }
let(:instance_options){ {} }

subject{ instance.optimized?(src, dst) }

context 'when interlace option is enabled' do
let(:instance_options){ {:interlace => true} }

context 'when dst is empty' do
let(:dst_options){ {:size? => nil} }
it{ is_expected.to be_falsy }
end

context 'when dst is not empty' do
let(:dst_options){ {:size? => 20} }
it{ is_expected.to be_truthy }
end
end

context 'when interlace option is disabled' do
let(:instance_options){ {:interlace => false} }

context 'when dst is empty' do
let(:dst_options){ {:size? => nil} }
it{ is_expected.to be_falsy }
end

context 'when dst is greater than or equal to src' do
let(:dst_options){ {:size? => 10} }
it{ is_expected.to be_falsy }
end

context 'when dst is less than src' do
let(:dst_options){ {:size? => 9} }
it{ is_expected.to be_truthy }
end
end
end
end

0 comments on commit 50f9216

Please sign in to comment.