image_optim 0.28.0 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/check.yml +89 -0
- data/.pre-commit-hooks.yaml +9 -0
- data/.rubocop.yml +6 -3
- data/CHANGELOG.markdown +20 -0
- data/CONTRIBUTING.markdown +1 -1
- data/Gemfile +1 -7
- data/LICENSE.txt +1 -1
- data/README.markdown +18 -9
- data/Vagrantfile +1 -1
- data/image_optim.gemspec +6 -3
- data/lib/image_optim/bin_resolver/bin.rb +9 -9
- data/lib/image_optim/cache.rb +6 -0
- data/lib/image_optim/cmd.rb +45 -6
- data/lib/image_optim/config.rb +11 -5
- data/lib/image_optim/elapsed_time.rb +26 -0
- data/lib/image_optim/errors.rb +9 -0
- data/lib/image_optim/path.rb +1 -1
- data/lib/image_optim/runner/option_parser.rb +21 -17
- data/lib/image_optim/runner.rb +1 -1
- data/lib/image_optim/timer.rb +25 -0
- data/lib/image_optim/worker/advpng.rb +7 -7
- data/lib/image_optim/worker/gifsicle.rb +11 -11
- data/lib/image_optim/worker/jhead.rb +2 -2
- data/lib/image_optim/worker/jpegoptim.rb +11 -11
- data/lib/image_optim/worker/jpegrecompress.rb +6 -6
- data/lib/image_optim/worker/jpegtran.rb +4 -4
- data/lib/image_optim/worker/optipng.rb +7 -7
- data/lib/image_optim/worker/oxipng.rb +53 -0
- data/lib/image_optim/worker/pngcrush.rb +6 -6
- data/lib/image_optim/worker/pngout.rb +7 -7
- data/lib/image_optim/worker/pngquant.rb +9 -9
- data/lib/image_optim/worker/svgo.rb +2 -2
- data/lib/image_optim/worker.rb +32 -29
- data/lib/image_optim.rb +16 -10
- data/script/update_worker_options_in_readme +1 -1
- data/script/worker_analysis +16 -18
- data/spec/image_optim/bin_resolver_spec.rb +5 -5
- data/spec/image_optim/cache_path_spec.rb +7 -10
- data/spec/image_optim/cache_spec.rb +7 -7
- data/spec/image_optim/cmd_spec.rb +64 -6
- data/spec/image_optim/config_spec.rb +36 -20
- data/spec/image_optim/elapsed_time_spec.rb +14 -0
- data/spec/image_optim/handler_spec.rb +1 -1
- data/spec/image_optim/hash_helpers_spec.rb +18 -18
- data/spec/image_optim/option_definition_spec.rb +6 -6
- data/spec/image_optim/path_spec.rb +8 -11
- data/spec/image_optim/runner/option_parser_spec.rb +4 -4
- data/spec/image_optim/timer_spec.rb +32 -0
- data/spec/image_optim/worker/jpegrecompress_spec.rb +2 -2
- data/spec/image_optim/worker/optipng_spec.rb +11 -11
- data/spec/image_optim/worker/oxipng_spec.rb +89 -0
- data/spec/image_optim/worker/pngquant_spec.rb +5 -5
- data/spec/image_optim/worker_spec.rb +17 -17
- data/spec/image_optim_spec.rb +47 -10
- data/spec/images/invisiblepixels/generate +1 -1
- data/spec/spec_helper.rb +24 -21
- metadata +35 -11
- data/.appveyor.yml +0 -53
- data/.travis.yml +0 -48
    
        data/lib/image_optim/runner.rb
    CHANGED
    
    
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'image_optim/elapsed_time'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class ImageOptim
         | 
| 6 | 
            +
              # Hold start time and timeout
         | 
| 7 | 
            +
              class Timer
         | 
| 8 | 
            +
                include ElapsedTime
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(seconds)
         | 
| 11 | 
            +
                  @start = now
         | 
| 12 | 
            +
                  @seconds = seconds
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def elapsed
         | 
| 16 | 
            +
                  now - @start
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def left
         | 
| 20 | 
            +
                  @seconds - elapsed
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                alias_method :to_f, :left
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -9,11 +9,11 @@ class ImageOptim | |
| 9 9 | 
             
                class Advpng < Worker
         | 
| 10 10 | 
             
                  LEVEL_OPTION =
         | 
| 11 11 | 
             
                  option(:level, 4, 'Compression level: '\
         | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 12 | 
            +
                                    '`0` - don\'t compress, '\
         | 
| 13 | 
            +
                                    '`1` - fast, '\
         | 
| 14 | 
            +
                                    '`2` - normal, '\
         | 
| 15 | 
            +
                                    '`3` - extra, '\
         | 
| 16 | 
            +
                                    '`4` - extreme') do |v|
         | 
| 17 17 | 
             
                    OptionHelpers.limit_with_range(v.to_i, 0..4)
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 |  | 
| @@ -21,7 +21,7 @@ class ImageOptim | |
| 21 21 | 
             
                    4
         | 
| 22 22 | 
             
                  end
         | 
| 23 23 |  | 
| 24 | 
            -
                  def optimize(src, dst)
         | 
| 24 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 25 25 | 
             
                    src.copy(dst)
         | 
| 26 26 | 
             
                    args = %W[
         | 
| 27 27 | 
             
                      --recompress
         | 
| @@ -30,7 +30,7 @@ class ImageOptim | |
| 30 30 | 
             
                      --
         | 
| 31 31 | 
             
                      #{dst}
         | 
| 32 32 | 
             
                    ]
         | 
| 33 | 
            -
                    execute(:advpng,  | 
| 33 | 
            +
                    execute(:advpng, args, options) && optimized?(src, dst)
         | 
| 34 34 | 
             
                  end
         | 
| 35 35 | 
             
                end
         | 
| 36 36 | 
             
              end
         | 
| @@ -12,32 +12,32 @@ class ImageOptim | |
| 12 12 | 
             
                    return super if options.key?(:interlace)
         | 
| 13 13 |  | 
| 14 14 | 
             
                    [false, true].map do |interlace|
         | 
| 15 | 
            -
                      new(image_optim, options.merge(: | 
| 15 | 
            +
                      new(image_optim, options.merge(interlace: interlace))
         | 
| 16 16 | 
             
                    end
         | 
| 17 17 | 
             
                  end
         | 
| 18 18 |  | 
| 19 19 | 
             
                  INTERLACE_OPTION =
         | 
| 20 20 | 
             
                  option(:interlace, false, TrueFalseNil, 'Interlace: '\
         | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 21 | 
            +
                                                          '`true` - interlace on, '\
         | 
| 22 | 
            +
                                                          '`false` - interlace off, '\
         | 
| 23 | 
            +
                                                          '`nil` - as is in original image '\
         | 
| 24 | 
            +
                                                          '(defaults to running two instances, one with interlace off and '\
         | 
| 25 | 
            +
                                                          'one with on)') do |v|
         | 
| 26 26 | 
             
                    TrueFalseNil.convert(v)
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| 29 29 | 
             
                  LEVEL_OPTION =
         | 
| 30 30 | 
             
                  option(:level, 3, 'Compression level: '\
         | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 31 | 
            +
                                    '`1` - light and fast, '\
         | 
| 32 | 
            +
                                    '`2` - normal, '\
         | 
| 33 | 
            +
                                    '`3` - heavy (slower)') do |v|
         | 
| 34 34 | 
             
                    OptionHelpers.limit_with_range(v.to_i, 1..3)
         | 
| 35 35 | 
             
                  end
         | 
| 36 36 |  | 
| 37 37 | 
             
                  CAREFUL_OPTION =
         | 
| 38 38 | 
             
                  option(:careful, false, 'Avoid bugs with some software'){ |v| !!v }
         | 
| 39 39 |  | 
| 40 | 
            -
                  def optimize(src, dst)
         | 
| 40 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 41 41 | 
             
                    args = %W[
         | 
| 42 42 | 
             
                      --output=#{dst}
         | 
| 43 43 | 
             
                      --no-comments
         | 
| @@ -58,7 +58,7 @@ class ImageOptim | |
| 58 58 | 
             
                    end
         | 
| 59 59 | 
             
                    args.unshift '--careful' if careful
         | 
| 60 60 | 
             
                    args.unshift "--optimize=#{level}" if level
         | 
| 61 | 
            -
                    execute(:gifsicle,  | 
| 61 | 
            +
                    execute(:gifsicle, args, options) && optimized?(src, dst)
         | 
| 62 62 | 
             
                  end
         | 
| 63 63 | 
             
                end
         | 
| 64 64 | 
             
              end
         | 
| @@ -25,7 +25,7 @@ class ImageOptim | |
| 25 25 | 
             
                    [:jhead, :jpegtran]
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 | 
            -
                  def optimize(src, dst)
         | 
| 28 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 29 29 | 
             
                    return false unless oriented?(src)
         | 
| 30 30 |  | 
| 31 31 | 
             
                    src.copy(dst)
         | 
| @@ -34,7 +34,7 @@ class ImageOptim | |
| 34 34 | 
             
                      #{dst}
         | 
| 35 35 | 
             
                    ]
         | 
| 36 36 | 
             
                    resolve_bin!(:jpegtran)
         | 
| 37 | 
            -
                    execute(:jhead,  | 
| 37 | 
            +
                    execute(:jhead, args, options) && dst.size?
         | 
| 38 38 | 
             
                  end
         | 
| 39 39 |  | 
| 40 40 | 
             
                private
         | 
| @@ -12,13 +12,13 @@ class ImageOptim | |
| 12 12 |  | 
| 13 13 | 
             
                  STRIP_OPTION =
         | 
| 14 14 | 
             
                  option(:strip, :all, Array, 'List of markers to strip: '\
         | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 15 | 
            +
                                              '`:com`, '\
         | 
| 16 | 
            +
                                              '`:exif`, '\
         | 
| 17 | 
            +
                                              '`:iptc`, '\
         | 
| 18 | 
            +
                                              '`:icc`, '\
         | 
| 19 | 
            +
                                              '`:xmp`, '\
         | 
| 20 | 
            +
                                              '`:none` or '\
         | 
| 21 | 
            +
                                              '`:all`') do |v|
         | 
| 22 22 | 
             
                    values = Array(v).map(&:to_s)
         | 
| 23 23 | 
             
                    known_values = %w[com exif iptc icc xmp none all]
         | 
| 24 24 | 
             
                    unknown_values = values - known_values
         | 
| @@ -30,13 +30,13 @@ class ImageOptim | |
| 30 30 |  | 
| 31 31 | 
             
                  MAX_QUALITY_OPTION =
         | 
| 32 32 | 
             
                  option(:max_quality, 100, 'Maximum image quality factor '\
         | 
| 33 | 
            -
             | 
| 33 | 
            +
                                            '`0`..`100`, ignored in default/lossless mode') do |v, opt_def|
         | 
| 34 34 | 
             
                    if allow_lossy
         | 
| 35 35 | 
             
                      OptionHelpers.limit_with_range(v.to_i, 0..100)
         | 
| 36 36 | 
             
                    else
         | 
| 37 37 | 
             
                      if v != opt_def.default
         | 
| 38 38 | 
             
                        warn "#{self.class.bin_sym} #{opt_def.name} #{v} ignored " \
         | 
| 39 | 
            -
             | 
| 39 | 
            +
                             'in lossless mode'
         | 
| 40 40 | 
             
                      end
         | 
| 41 41 | 
             
                      opt_def.default
         | 
| 42 42 | 
             
                    end
         | 
| @@ -47,7 +47,7 @@ class ImageOptim | |
| 47 47 | 
             
                    max_quality < 100 ? -1 : 0
         | 
| 48 48 | 
             
                  end
         | 
| 49 49 |  | 
| 50 | 
            -
                  def optimize(src, dst)
         | 
| 50 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 51 51 | 
             
                    src.copy(dst)
         | 
| 52 52 | 
             
                    args = %W[
         | 
| 53 53 | 
             
                      --quiet
         | 
| @@ -58,7 +58,7 @@ class ImageOptim | |
| 58 58 | 
             
                      args.unshift "--strip-#{strip_marker}"
         | 
| 59 59 | 
             
                    end
         | 
| 60 60 | 
             
                    args.unshift "--max=#{max_quality}" if max_quality < 100
         | 
| 61 | 
            -
                    execute(:jpegoptim,  | 
| 61 | 
            +
                    execute(:jpegoptim, args, options) && optimized?(src, dst)
         | 
| 62 62 | 
             
                  end
         | 
| 63 63 | 
             
                end
         | 
| 64 64 | 
             
              end
         | 
| @@ -28,10 +28,10 @@ class ImageOptim | |
| 28 28 |  | 
| 29 29 | 
             
                  METHOD_OPTION =
         | 
| 30 30 | 
             
                  option(:method, 'ssim', 'Comparison Metric: '\
         | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 31 | 
            +
                                          '`mpe` - Mean pixel error, '\
         | 
| 32 | 
            +
                                          '`ssim` - Structural similarity, '\
         | 
| 33 | 
            +
                                          '`ms-ssim` - Multi-scale structural similarity (slow!), '\
         | 
| 34 | 
            +
                                          '`smallfry` - Linear-weighted BBCQ-like (may be patented)') do |v, opt_def|
         | 
| 35 35 | 
             
                    if %w[mpe ssim ms-ssim smallfry].include? v
         | 
| 36 36 | 
             
                      v
         | 
| 37 37 | 
             
                    else
         | 
| @@ -49,7 +49,7 @@ class ImageOptim | |
| 49 49 | 
             
                    -5
         | 
| 50 50 | 
             
                  end
         | 
| 51 51 |  | 
| 52 | 
            -
                  def optimize(src, dst)
         | 
| 52 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 53 53 | 
             
                    args = %W[
         | 
| 54 54 | 
             
                      --quality #{QUALITY_NAMES[quality]}
         | 
| 55 55 | 
             
                      --method #{method}
         | 
| @@ -57,7 +57,7 @@ class ImageOptim | |
| 57 57 | 
             
                      #{src}
         | 
| 58 58 | 
             
                      #{dst}
         | 
| 59 59 | 
             
                    ]
         | 
| 60 | 
            -
                    execute(:'jpeg-recompress',  | 
| 60 | 
            +
                    execute(:'jpeg-recompress', args, options) && optimized?(src, dst)
         | 
| 61 61 | 
             
                  end
         | 
| 62 62 | 
             
                end
         | 
| 63 63 | 
             
              end
         | 
| @@ -17,13 +17,13 @@ class ImageOptim | |
| 17 17 |  | 
| 18 18 | 
             
                  JPEGRESCAN_OPTION =
         | 
| 19 19 | 
             
                  option(:jpegrescan, true, 'Use jpegtran through jpegrescan, '\
         | 
| 20 | 
            -
             | 
| 20 | 
            +
                                            'ignore progressive option'){ |v| !!v }
         | 
| 21 21 |  | 
| 22 22 | 
             
                  def used_bins
         | 
| 23 23 | 
             
                    jpegrescan ? [:jpegtran, :jpegrescan] : [:jpegtran]
         | 
| 24 24 | 
             
                  end
         | 
| 25 25 |  | 
| 26 | 
            -
                  def optimize(src, dst)
         | 
| 26 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 27 27 | 
             
                    if jpegrescan
         | 
| 28 28 | 
             
                      args = %W[
         | 
| 29 29 | 
             
                        #{src}
         | 
| @@ -31,7 +31,7 @@ class ImageOptim | |
| 31 31 | 
             
                      ]
         | 
| 32 32 | 
             
                      args.unshift '-s' unless copy_chunks
         | 
| 33 33 | 
             
                      resolve_bin!(:jpegtran)
         | 
| 34 | 
            -
                      execute(:jpegrescan,  | 
| 34 | 
            +
                      execute(:jpegrescan, args, options) && optimized?(src, dst)
         | 
| 35 35 | 
             
                    else
         | 
| 36 36 | 
             
                      args = %W[
         | 
| 37 37 | 
             
                        -optimize
         | 
| @@ -40,7 +40,7 @@ class ImageOptim | |
| 40 40 | 
             
                      ]
         | 
| 41 41 | 
             
                      args.unshift '-copy', (copy_chunks ? 'all' : 'none')
         | 
| 42 42 | 
             
                      args.unshift '-progressive' if progressive
         | 
| 43 | 
            -
                      execute(:jpegtran,  | 
| 43 | 
            +
                      execute(:jpegtran, args, options) && optimized?(src, dst)
         | 
| 44 44 | 
             
                    end
         | 
| 45 45 | 
             
                  end
         | 
| 46 46 | 
             
                end
         | 
| @@ -10,16 +10,16 @@ class ImageOptim | |
| 10 10 | 
             
                class Optipng < Worker
         | 
| 11 11 | 
             
                  LEVEL_OPTION =
         | 
| 12 12 | 
             
                  option(:level, 6, 'Optimization level preset: '\
         | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 13 | 
            +
                                    '`0` is least, '\
         | 
| 14 | 
            +
                                    '`7` is best') do |v|
         | 
| 15 15 | 
             
                    OptionHelpers.limit_with_range(v.to_i, 0..7)
         | 
| 16 16 | 
             
                  end
         | 
| 17 17 |  | 
| 18 18 | 
             
                  INTERLACE_OPTION =
         | 
| 19 19 | 
             
                  option(:interlace, false, TrueFalseNil, 'Interlace: '\
         | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 20 | 
            +
                                                          '`true` - interlace on, '\
         | 
| 21 | 
            +
                                                          '`false` - interlace off, '\
         | 
| 22 | 
            +
                                                          '`nil` - as is in original image') do |v|
         | 
| 23 23 | 
             
                    TrueFalseNil.convert(v)
         | 
| 24 24 | 
             
                  end
         | 
| 25 25 |  | 
| @@ -30,7 +30,7 @@ class ImageOptim | |
| 30 30 | 
             
                    -4
         | 
| 31 31 | 
             
                  end
         | 
| 32 32 |  | 
| 33 | 
            -
                  def optimize(src, dst)
         | 
| 33 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 34 34 | 
             
                    src.copy(dst)
         | 
| 35 35 | 
             
                    args = %W[
         | 
| 36 36 | 
             
                      -o #{level}
         | 
| @@ -42,7 +42,7 @@ class ImageOptim | |
| 42 42 | 
             
                    if strip && resolve_bin!(:optipng).version >= '0.7'
         | 
| 43 43 | 
             
                      args.unshift '-strip', 'all'
         | 
| 44 44 | 
             
                    end
         | 
| 45 | 
            -
                    execute(:optipng,  | 
| 45 | 
            +
                    execute(:optipng, args, options) && optimized?(src, dst)
         | 
| 46 46 | 
             
                  end
         | 
| 47 47 |  | 
| 48 48 | 
             
                  def optimized?(src, dst)
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'image_optim/worker'
         | 
| 4 | 
            +
            require 'image_optim/option_helpers'
         | 
| 5 | 
            +
            require 'image_optim/true_false_nil'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class ImageOptim
         | 
| 8 | 
            +
              class Worker
         | 
| 9 | 
            +
                # https://github.com/shssoichiro/oxipng
         | 
| 10 | 
            +
                class Oxipng < Worker
         | 
| 11 | 
            +
                  LEVEL_OPTION =
         | 
| 12 | 
            +
                  option(:level, 3, 'Optimization level preset: '\
         | 
| 13 | 
            +
                                    '`0` is least, '\
         | 
| 14 | 
            +
                                    '`6` is best') do |v|
         | 
| 15 | 
            +
                    OptionHelpers.limit_with_range(v.to_i, 0..6)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  INTERLACE_OPTION =
         | 
| 19 | 
            +
                  option(:interlace, false, TrueFalseNil, 'Interlace: '\
         | 
| 20 | 
            +
                                                          '`true` - interlace on, '\
         | 
| 21 | 
            +
                                                          '`false` - interlace off, '\
         | 
| 22 | 
            +
                                                          '`nil` - as is in original image') do |v|
         | 
| 23 | 
            +
                    TrueFalseNil.convert(v)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  STRIP_OPTION =
         | 
| 27 | 
            +
                  option(:strip, true, 'Remove all auxiliary chunks'){ |v| !!v }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def run_order
         | 
| 30 | 
            +
                    -4
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 34 | 
            +
                    src.copy(dst)
         | 
| 35 | 
            +
                    args = %W[
         | 
| 36 | 
            +
                      -o #{level}
         | 
| 37 | 
            +
                      --quiet
         | 
| 38 | 
            +
                      --
         | 
| 39 | 
            +
                      #{dst}
         | 
| 40 | 
            +
                    ]
         | 
| 41 | 
            +
                    args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
         | 
| 42 | 
            +
                    if strip
         | 
| 43 | 
            +
                      args.unshift '--strip', 'all'
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                    execute(:oxipng, args, options) && optimized?(src, dst)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def optimized?(src, dst)
         | 
| 49 | 
            +
                    interlace ? dst.size? : super
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -8,18 +8,18 @@ class ImageOptim | |
| 8 8 | 
             
                class Pngcrush < Worker
         | 
| 9 9 | 
             
                  CHUNKS_OPTION =
         | 
| 10 10 | 
             
                  option(:chunks, :alla, Array, 'List of chunks to remove or '\
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 11 | 
            +
                                                '`:alla` - all except tRNS/transparency or '\
         | 
| 12 | 
            +
                                                '`:allb` - all except tRNS and gAMA/gamma') do |v|
         | 
| 13 13 | 
             
                    Array(v).map(&:to_s)
         | 
| 14 14 | 
             
                  end
         | 
| 15 15 |  | 
| 16 16 | 
             
                  FIX_OPTION =
         | 
| 17 17 | 
             
                  option(:fix, false, 'Fix otherwise fatal conditions '\
         | 
| 18 | 
            -
             | 
| 18 | 
            +
                                      'such as bad CRCs'){ |v| !!v }
         | 
| 19 19 |  | 
| 20 20 | 
             
                  BRUTE_OPTION =
         | 
| 21 21 | 
             
                  option(:brute, false, 'Brute force try all methods, '\
         | 
| 22 | 
            -
             | 
| 22 | 
            +
                                        'very time-consuming and generally not worthwhile'){ |v| !!v }
         | 
| 23 23 |  | 
| 24 24 | 
             
                  BLACKEN_OPTION =
         | 
| 25 25 | 
             
                  option(:blacken, true, 'Blacken fully transparent pixels'){ |v| !!v }
         | 
| @@ -28,7 +28,7 @@ class ImageOptim | |
| 28 28 | 
             
                    -6
         | 
| 29 29 | 
             
                  end
         | 
| 30 30 |  | 
| 31 | 
            -
                  def optimize(src, dst)
         | 
| 31 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 32 32 | 
             
                    flags = %w[
         | 
| 33 33 | 
             
                      -reduce
         | 
| 34 34 | 
             
                      -cc
         | 
| @@ -49,7 +49,7 @@ class ImageOptim | |
| 49 49 | 
             
                      #{dst}
         | 
| 50 50 | 
             
                    ]
         | 
| 51 51 |  | 
| 52 | 
            -
                    execute(:pngcrush,  | 
| 52 | 
            +
                    execute(:pngcrush, args, options) && optimized?(src, dst)
         | 
| 53 53 | 
             
                  end
         | 
| 54 54 | 
             
                end
         | 
| 55 55 | 
             
              end
         | 
| @@ -12,11 +12,11 @@ class ImageOptim | |
| 12 12 |  | 
| 13 13 | 
             
                  STRATEGY_OPTION =
         | 
| 14 14 | 
             
                  option(:strategy, 0, 'Strategy: '\
         | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 15 | 
            +
                                       '`0` - xtreme, '\
         | 
| 16 | 
            +
                                       '`1` - intense, '\
         | 
| 17 | 
            +
                                       '`2` - longest Match, '\
         | 
| 18 | 
            +
                                       '`3` - huffman Only, '\
         | 
| 19 | 
            +
                                       '`4` - uncompressed') do |v|
         | 
| 20 20 | 
             
                    OptionHelpers.limit_with_range(v.to_i, 0..4)
         | 
| 21 21 | 
             
                  end
         | 
| 22 22 |  | 
| @@ -24,7 +24,7 @@ class ImageOptim | |
| 24 24 | 
             
                    2
         | 
| 25 25 | 
             
                  end
         | 
| 26 26 |  | 
| 27 | 
            -
                  def optimize(src, dst)
         | 
| 27 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 28 28 | 
             
                    args = %W[
         | 
| 29 29 | 
             
                      -k#{copy_chunks ? 1 : 0}
         | 
| 30 30 | 
             
                      -s#{strategy}
         | 
| @@ -33,7 +33,7 @@ class ImageOptim | |
| 33 33 | 
             
                      #{src}
         | 
| 34 34 | 
             
                      #{dst}
         | 
| 35 35 | 
             
                    ]
         | 
| 36 | 
            -
                    execute(:pngout,  | 
| 36 | 
            +
                    execute(:pngout, args, options) && optimized?(src, dst)
         | 
| 37 37 | 
             
                  rescue SignalException => e
         | 
| 38 38 | 
             
                    raise unless Signal.list.key(e.signo) == 'SEGV'
         | 
| 39 39 | 
             
                    raise unless resolve_bin!(:pngout).version <= '20150920'
         | 
| @@ -19,9 +19,9 @@ class ImageOptim | |
| 19 19 | 
             
                  QUALITY_OPTION =
         | 
| 20 20 | 
             
                  option(:quality, '`100..100`, `0..100` in lossy mode',
         | 
| 21 21 | 
             
                         NonNegativeIntegerRange, 'min..max - don\'t '\
         | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 22 | 
            +
                                                  'save below min, use less colors below max (both in range `0..100`; '\
         | 
| 23 | 
            +
                                                  'in yaml - `!ruby/range 0..100`), ignored in default/lossless '\
         | 
| 24 | 
            +
                                                  'mode') do |v, opt_def|
         | 
| 25 25 | 
             
                    if allow_lossy
         | 
| 26 26 | 
             
                      if v == opt_def.default
         | 
| 27 27 | 
             
                        0..100
         | 
| @@ -32,7 +32,7 @@ class ImageOptim | |
| 32 32 | 
             
                    else
         | 
| 33 33 | 
             
                      if v != opt_def.default
         | 
| 34 34 | 
             
                        warn "#{self.class.bin_sym} #{opt_def.name} #{v} ignored " \
         | 
| 35 | 
            -
             | 
| 35 | 
            +
                             'in lossless mode'
         | 
| 36 36 | 
             
                      end
         | 
| 37 37 | 
             
                      100..100
         | 
| 38 38 | 
             
                    end
         | 
| @@ -40,9 +40,9 @@ class ImageOptim | |
| 40 40 |  | 
| 41 41 | 
             
                  SPEED_OPTION =
         | 
| 42 42 | 
             
                  option(:speed, 3, 'speed/quality trade-off: '\
         | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 43 | 
            +
                                    '`1` - slow, '\
         | 
| 44 | 
            +
                                    '`3` - default, '\
         | 
| 45 | 
            +
                                    '`11` - fast & rough') do |v|
         | 
| 46 46 | 
             
                    OptionHelpers.limit_with_range(v.to_i, 1..11)
         | 
| 47 47 | 
             
                  end
         | 
| 48 48 |  | 
| @@ -50,7 +50,7 @@ class ImageOptim | |
| 50 50 | 
             
                    -2
         | 
| 51 51 | 
             
                  end
         | 
| 52 52 |  | 
| 53 | 
            -
                  def optimize(src, dst)
         | 
| 53 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 54 54 | 
             
                    args = %W[
         | 
| 55 55 | 
             
                      --quality=#{quality.begin}-#{quality.end}
         | 
| 56 56 | 
             
                      --speed=#{speed}
         | 
| @@ -61,7 +61,7 @@ class ImageOptim | |
| 61 61 | 
             
                      --
         | 
| 62 62 | 
             
                      #{src}
         | 
| 63 63 | 
             
                    ]
         | 
| 64 | 
            -
                    execute(:pngquant,  | 
| 64 | 
            +
                    execute(:pngquant, args, options) && optimized?(src, dst)
         | 
| 65 65 | 
             
                  end
         | 
| 66 66 | 
             
                end
         | 
| 67 67 | 
             
              end
         | 
| @@ -16,7 +16,7 @@ class ImageOptim | |
| 16 16 | 
             
                    Array(v).map(&:to_s)
         | 
| 17 17 | 
             
                  end
         | 
| 18 18 |  | 
| 19 | 
            -
                  def optimize(src, dst)
         | 
| 19 | 
            +
                  def optimize(src, dst, options = {})
         | 
| 20 20 | 
             
                    args = %W[
         | 
| 21 21 | 
             
                      --input #{src}
         | 
| 22 22 | 
             
                      --output #{dst}
         | 
| @@ -27,7 +27,7 @@ class ImageOptim | |
| 27 27 | 
             
                    enable_plugins.each do |plugin_name|
         | 
| 28 28 | 
             
                      args.unshift "--enable=#{plugin_name}"
         | 
| 29 29 | 
             
                    end
         | 
| 30 | 
            -
                    execute(:svgo,  | 
| 30 | 
            +
                    execute(:svgo, args, options) && optimized?(src, dst)
         | 
| 31 31 | 
             
                  end
         | 
| 32 32 | 
             
                end
         | 
| 33 33 | 
             
              end
         | 
    
        data/lib/image_optim/worker.rb
    CHANGED
    
    | @@ -3,6 +3,7 @@ | |
| 3 3 |  | 
| 4 4 | 
             
            require 'image_optim/cmd'
         | 
| 5 5 | 
             
            require 'image_optim/configuration_error'
         | 
| 6 | 
            +
            require 'image_optim/elapsed_time'
         | 
| 6 7 | 
             
            require 'image_optim/path'
         | 
| 7 8 | 
             
            require 'image_optim/worker/class_methods'
         | 
| 8 9 | 
             
            require 'shellwords'
         | 
| @@ -41,7 +42,7 @@ class ImageOptim | |
| 41 42 |  | 
| 42 43 | 
             
                # Optimize image at src, output at dst, must be overriden in subclass
         | 
| 43 44 | 
             
                # return true on success
         | 
| 44 | 
            -
                def optimize(_src, _dst)
         | 
| 45 | 
            +
                def optimize(_src, _dst, options = {})
         | 
| 45 46 | 
             
                  fail NotImplementedError, "implement method optimize in #{self.class}"
         | 
| 46 47 | 
             
                end
         | 
| 47 48 |  | 
| @@ -104,7 +105,7 @@ class ImageOptim | |
| 104 105 | 
             
                  return if unknown_options.empty?
         | 
| 105 106 |  | 
| 106 107 | 
             
                  fail ConfigurationError, "unknown options #{unknown_options.inspect} "\
         | 
| 107 | 
            -
             | 
| 108 | 
            +
                                           "for #{self}"
         | 
| 108 109 | 
             
                end
         | 
| 109 110 |  | 
| 110 111 | 
             
                # Forward bin resolving to image_optim
         | 
| @@ -117,47 +118,49 @@ class ImageOptim | |
| 117 118 | 
             
                def wrap_resolver_error_message(message)
         | 
| 118 119 | 
             
                  name = self.class.bin_sym
         | 
| 119 120 | 
             
                  "#{name} worker: #{message}; please provide proper binary or "\
         | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 121 | 
            +
                    "disable this worker (--no-#{name} argument or "\
         | 
| 122 | 
            +
                    "`:#{name} => false` through options)"
         | 
| 122 123 | 
             
                end
         | 
| 123 124 |  | 
| 124 125 | 
             
                # Run command setting priority and hiding output
         | 
| 125 | 
            -
                def execute(bin,  | 
| 126 | 
            +
                def execute(bin, arguments, options)
         | 
| 126 127 | 
             
                  resolve_bin!(bin)
         | 
| 127 128 |  | 
| 128 129 | 
             
                  cmd_args = [bin, *arguments].map(&:to_s)
         | 
| 129 130 |  | 
| 130 | 
            -
                  start = Time.now
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                  success = run_command(cmd_args)
         | 
| 133 | 
            -
             | 
| 134 131 | 
             
                  if @image_optim.verbose
         | 
| 135 | 
            -
                     | 
| 136 | 
            -
             | 
| 132 | 
            +
                    run_command_verbose(cmd_args, options)
         | 
| 133 | 
            +
                  else
         | 
| 134 | 
            +
                    run_command(cmd_args, options)
         | 
| 137 135 | 
             
                  end
         | 
| 138 | 
            -
             | 
| 139 | 
            -
                  success
         | 
| 140 136 | 
             
                end
         | 
| 141 137 |  | 
| 142 138 | 
             
                # Run command defining environment, setting nice level, removing output and
         | 
| 143 139 | 
             
                # reraising signal exception
         | 
| 144 | 
            -
                def run_command(cmd_args)
         | 
| 145 | 
            -
                  args =  | 
| 146 | 
            -
                     | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 151 | 
            -
                    ].join(' ')
         | 
| 152 | 
            -
                  else
         | 
| 153 | 
            -
                    [
         | 
| 154 | 
            -
                      {'PATH' => @image_optim.env_path},
         | 
| 155 | 
            -
                      %W[nice -n #{@image_optim.nice}],
         | 
| 156 | 
            -
                      cmd_args,
         | 
| 157 | 
            -
                      {:out => Path::NULL, :err => Path::NULL},
         | 
| 158 | 
            -
                    ].flatten
         | 
| 159 | 
            -
                  end
         | 
| 140 | 
            +
                def run_command(cmd_args, options)
         | 
| 141 | 
            +
                  args = [
         | 
| 142 | 
            +
                    {'PATH' => @image_optim.env_path},
         | 
| 143 | 
            +
                    *%W[nice -n #{@image_optim.nice}],
         | 
| 144 | 
            +
                    *cmd_args,
         | 
| 145 | 
            +
                    options.merge(out: Path::NULL, err: Path::NULL),
         | 
| 146 | 
            +
                  ]
         | 
| 160 147 | 
             
                  Cmd.run(*args)
         | 
| 161 148 | 
             
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                # Wrap run_command and output status, elapsed time and command
         | 
| 151 | 
            +
                def run_command_verbose(cmd_args, options)
         | 
| 152 | 
            +
                  start = ElapsedTime.now
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  begin
         | 
| 155 | 
            +
                    success = run_command(cmd_args, options)
         | 
| 156 | 
            +
                    status = success ? '✓' : '✗'
         | 
| 157 | 
            +
                    success
         | 
| 158 | 
            +
                  rescue Errors::TimeoutExceeded
         | 
| 159 | 
            +
                    status = 'timeout'
         | 
| 160 | 
            +
                    raise
         | 
| 161 | 
            +
                  ensure
         | 
| 162 | 
            +
                    $stderr << format("%s %.1fs %s\n", status, ElapsedTime.now - start, cmd_args.shelljoin)
         | 
| 163 | 
            +
                  end
         | 
| 164 | 
            +
                end
         | 
| 162 165 | 
             
              end
         | 
| 163 166 | 
             
            end
         | 
    
        data/lib/image_optim.rb
    CHANGED
    
    | @@ -3,16 +3,18 @@ | |
| 3 3 | 
             
            require 'image_optim/bin_resolver'
         | 
| 4 4 | 
             
            require 'image_optim/cache'
         | 
| 5 5 | 
             
            require 'image_optim/config'
         | 
| 6 | 
            +
            require 'image_optim/errors'
         | 
| 6 7 | 
             
            require 'image_optim/handler'
         | 
| 7 8 | 
             
            require 'image_optim/image_meta'
         | 
| 8 9 | 
             
            require 'image_optim/optimized_path'
         | 
| 9 10 | 
             
            require 'image_optim/path'
         | 
| 11 | 
            +
            require 'image_optim/timer'
         | 
| 10 12 | 
             
            require 'image_optim/worker'
         | 
| 11 13 | 
             
            require 'in_threads'
         | 
| 12 14 | 
             
            require 'shellwords'
         | 
| 13 15 |  | 
| 14 16 | 
             
            %w[
         | 
| 15 | 
            -
              pngcrush pngout advpng optipng pngquant
         | 
| 17 | 
            +
              pngcrush pngout advpng optipng pngquant oxipng
         | 
| 16 18 | 
             
              jhead jpegoptim jpegrecompress jpegtran
         | 
| 17 19 | 
             
              gifsicle
         | 
| 18 20 | 
             
              svgo
         | 
| @@ -46,6 +48,9 @@ class ImageOptim | |
| 46 48 | 
             
              # Cache worker digests
         | 
| 47 49 | 
             
              attr_reader :cache_worker_digests
         | 
| 48 50 |  | 
| 51 | 
            +
              # Timeout in seconds for each image
         | 
| 52 | 
            +
              attr_reader :timeout
         | 
| 53 | 
            +
             | 
| 49 54 | 
             
              # Initialize workers, specify options using worker underscored name:
         | 
| 50 55 | 
             
              #
         | 
| 51 56 | 
             
              # pass false to disable worker
         | 
| @@ -78,6 +83,7 @@ class ImageOptim | |
| 78 83 | 
             
                  allow_lossy
         | 
| 79 84 | 
             
                  cache_dir
         | 
| 80 85 | 
             
                  cache_worker_digests
         | 
| 86 | 
            +
                  timeout
         | 
| 81 87 | 
             
                ].each do |name|
         | 
| 82 88 | 
             
                  instance_variable_set(:"@#{name}", config.send(name))
         | 
| 83 89 | 
             
                  $stderr << "#{name}: #{send(name)}\n" if verbose
         | 
| @@ -110,11 +116,17 @@ class ImageOptim | |
| 110 116 | 
             
                return unless (workers = workers_for_image(original))
         | 
| 111 117 |  | 
| 112 118 | 
             
                optimized = @cache.fetch(original) do
         | 
| 119 | 
            +
                  timer = timeout && Timer.new(timeout)
         | 
| 120 | 
            +
             | 
| 113 121 | 
             
                  Handler.for(original) do |handler|
         | 
| 114 | 
            -
                     | 
| 115 | 
            -
                       | 
| 116 | 
            -
                         | 
| 122 | 
            +
                    begin
         | 
| 123 | 
            +
                      workers.each do |worker|
         | 
| 124 | 
            +
                        handler.process do |src, dst|
         | 
| 125 | 
            +
                          worker.optimize(src, dst, timeout: timer)
         | 
| 126 | 
            +
                        end
         | 
| 117 127 | 
             
                      end
         | 
| 128 | 
            +
                    rescue Errors::TimeoutExceeded
         | 
| 129 | 
            +
                      handler.result
         | 
| 118 130 | 
             
                    end
         | 
| 119 131 | 
             
                  end
         | 
| 120 132 | 
             
                end
         | 
| @@ -188,12 +200,6 @@ class ImageOptim | |
| 188 200 | 
             
                  optimize_image_method?(method) || super
         | 
| 189 201 | 
             
                end
         | 
| 190 202 |  | 
| 191 | 
            -
                if RUBY_VERSION < '1.9'
         | 
| 192 | 
            -
                  def respond_to?(method, include_private = false)
         | 
| 193 | 
            -
                    optimize_image_method?(method) || super
         | 
| 194 | 
            -
                  end
         | 
| 195 | 
            -
                end
         | 
| 196 | 
            -
             | 
| 197 203 | 
             
                # Version of image_optim gem spec loaded
         | 
| 198 204 | 
             
                def version
         | 
| 199 205 | 
             
                  Gem.loaded_specs['image_optim'].version.to_s
         |