image_optim 0.31.4 → 0.32.0
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/dependabot.yml +3 -1
- data/.github/workflows/build-test-containers.yml +60 -0
- data/.github/workflows/check.yml +12 -23
- data/.github/workflows/rubocop.yml +2 -2
- data/.rubocop.yml +2 -0
- data/CHANGELOG.markdown +9 -0
- data/Dockerfile.test +46 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +1 -1
- data/README.markdown +27 -6
- data/image_optim.gemspec +14 -4
- data/lib/image_optim/benchmark_result.rb +24 -0
- data/lib/image_optim/bin_resolver.rb +1 -1
- data/lib/image_optim/config.rb +15 -6
- data/lib/image_optim/runner/option_parser.rb +9 -0
- data/lib/image_optim/runner.rb +69 -6
- data/lib/image_optim/table.rb +64 -0
- data/lib/image_optim/worker/svgo.rb +52 -7
- data/lib/image_optim.rb +22 -0
- data/script/update-rubygems-n-bundler +50 -0
- data/spec/image_optim/bin_resolver_spec.rb +3 -3
- data/spec/image_optim/cache_spec.rb +3 -1
- data/spec/image_optim/option_definition_spec.rb +1 -3
- data/spec/image_optim/true_false_nil_spec.rb +24 -0
- data/spec/image_optim/worker/svgo_spec.rb +43 -0
- data/spec/image_optim_spec.rb +19 -4
- data/spec/images/invisiblepixels/generate +3 -1
- data/spec/images/invisiblepixels/image.png +0 -0
- data/spec/spec_helper.rb +8 -8
- metadata +11 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef1f30c74ae2a4479ec6d68f006313ae236cd92e3f8338a15619a74d5fbb2dc7
|
|
4
|
+
data.tar.gz: 3db0340fc04da059048f162c4b4e35870213f3ce40a6f8af896059ffd2703cbe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2fdbf6c9cb715760ce38200157ddaa9142e5ec83611813572b475893c24c50f18983207bf268cd9f73d98689d7fb28e3703a8ab9633d5c28bad2c9e003a9f667
|
|
7
|
+
data.tar.gz: d9826620e1554b8cac570b79995636c8abd652f66318bf8f1bf498156756250919bd48374038c0db99554c9e2ce1ba1f2af194715c73c41ff9afea97e4cc33eb
|
data/.github/dependabot.yml
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: build-test-containers
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: 11 2 13 * *
|
|
6
|
+
jobs:
|
|
7
|
+
base:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v6
|
|
11
|
+
- uses: docker/login-action@v4
|
|
12
|
+
with:
|
|
13
|
+
registry: ghcr.io
|
|
14
|
+
username: ${{ github.actor }}
|
|
15
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
16
|
+
- run:
|
|
17
|
+
docker build
|
|
18
|
+
-f Dockerfile.test
|
|
19
|
+
--target ruby-build
|
|
20
|
+
-t ghcr.io/toy/image_optim.test:ruby-build
|
|
21
|
+
--push
|
|
22
|
+
.
|
|
23
|
+
|
|
24
|
+
test-images:
|
|
25
|
+
needs: base
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
strategy:
|
|
28
|
+
matrix:
|
|
29
|
+
ruby:
|
|
30
|
+
- '1.9'
|
|
31
|
+
- '2.0'
|
|
32
|
+
- '2.1'
|
|
33
|
+
- '2.2'
|
|
34
|
+
- '2.3'
|
|
35
|
+
- '2.4'
|
|
36
|
+
- '2.5'
|
|
37
|
+
- '2.6'
|
|
38
|
+
- '2.7'
|
|
39
|
+
- '3.0'
|
|
40
|
+
- '3.1'
|
|
41
|
+
- '3.2'
|
|
42
|
+
- '3.3'
|
|
43
|
+
- '3.4'
|
|
44
|
+
- '4.0'
|
|
45
|
+
fail-fast: false
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v6
|
|
48
|
+
- uses: docker/login-action@v4
|
|
49
|
+
with:
|
|
50
|
+
registry: ghcr.io
|
|
51
|
+
username: ${{ github.actor }}
|
|
52
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
53
|
+
- run:
|
|
54
|
+
docker build
|
|
55
|
+
-f Dockerfile.test
|
|
56
|
+
--target test
|
|
57
|
+
--build-arg "RUBY_VERSION=${{ matrix.ruby }}"
|
|
58
|
+
-t "ghcr.io/toy/image_optim.test:${{ matrix.ruby }}"
|
|
59
|
+
--push
|
|
60
|
+
.
|
data/.github/workflows/check.yml
CHANGED
|
@@ -6,20 +6,22 @@ on:
|
|
|
6
6
|
- cron: 45 4 * * 2
|
|
7
7
|
jobs:
|
|
8
8
|
check:
|
|
9
|
-
runs-on: ubuntu-
|
|
9
|
+
runs-on: ubuntu-22.04
|
|
10
10
|
strategy:
|
|
11
11
|
matrix:
|
|
12
12
|
ruby:
|
|
13
|
-
- '1.9.3'
|
|
14
13
|
- '2.7'
|
|
15
14
|
- '3.0'
|
|
16
15
|
- '3.1'
|
|
17
16
|
- '3.2'
|
|
18
17
|
- '3.3'
|
|
18
|
+
- '3.4'
|
|
19
|
+
- '4.0'
|
|
19
20
|
- jruby-9.4
|
|
21
|
+
- jruby-10.1
|
|
20
22
|
fail-fast: false
|
|
21
23
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@v6
|
|
23
25
|
- uses: ruby/setup-ruby@v1
|
|
24
26
|
with:
|
|
25
27
|
ruby-version: "${{ matrix.ruby }}"
|
|
@@ -33,13 +35,12 @@ jobs:
|
|
|
33
35
|
strategy:
|
|
34
36
|
matrix:
|
|
35
37
|
container:
|
|
36
|
-
- debian:buster
|
|
37
38
|
- debian:bullseye
|
|
38
39
|
- debian:bookworm
|
|
39
40
|
# - alpine
|
|
40
41
|
fail-fast: false
|
|
41
42
|
steps:
|
|
42
|
-
- uses: actions/checkout@
|
|
43
|
+
- uses: actions/checkout@v6
|
|
43
44
|
- run: |
|
|
44
45
|
if command -v apt-get &> /dev/null; then
|
|
45
46
|
apt-get update
|
|
@@ -65,14 +66,16 @@ jobs:
|
|
|
65
66
|
- '3.1'
|
|
66
67
|
- '3.2'
|
|
67
68
|
- '3.3'
|
|
69
|
+
- '3.4'
|
|
70
|
+
- '4.0'
|
|
68
71
|
fail-fast: false
|
|
69
72
|
steps:
|
|
70
|
-
- uses: actions/checkout@
|
|
73
|
+
- uses: actions/checkout@v6
|
|
71
74
|
- uses: ruby/setup-ruby@v1
|
|
72
75
|
with:
|
|
73
76
|
ruby-version: "${{ matrix.ruby }}"
|
|
74
77
|
bundler-cache: true
|
|
75
|
-
- uses: actions/cache@
|
|
78
|
+
- uses: actions/cache@v5
|
|
76
79
|
with:
|
|
77
80
|
path: "$HOME/bin"
|
|
78
81
|
key: ${{ runner.os }}
|
|
@@ -90,23 +93,9 @@ jobs:
|
|
|
90
93
|
update_worker_options_in_readme:
|
|
91
94
|
runs-on: ubuntu-latest
|
|
92
95
|
steps:
|
|
93
|
-
- uses: actions/checkout@
|
|
96
|
+
- uses: actions/checkout@v6
|
|
94
97
|
- uses: ruby/setup-ruby@v1
|
|
95
98
|
with:
|
|
96
|
-
ruby-version: '
|
|
99
|
+
ruby-version: '4'
|
|
97
100
|
bundler-cache: true
|
|
98
101
|
- run: script/update_worker_options_in_readme -n
|
|
99
|
-
coverage:
|
|
100
|
-
runs-on: ubuntu-latest
|
|
101
|
-
env:
|
|
102
|
-
CC_TEST_REPORTER_ID: b433c6540d220a2da0663670c9b260806bafdb3a43c6f22b2e81bfb1f87b12fe
|
|
103
|
-
steps:
|
|
104
|
-
- uses: actions/checkout@v4
|
|
105
|
-
- uses: ruby/setup-ruby@v1
|
|
106
|
-
with:
|
|
107
|
-
ruby-version: '3'
|
|
108
|
-
bundler-cache: true
|
|
109
|
-
- run: npm install -g svgo
|
|
110
|
-
- uses: paambaati/codeclimate-action@v9
|
|
111
|
-
with:
|
|
112
|
-
coverageCommand: bundle exec rspec
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.markdown
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## unreleased
|
|
4
4
|
|
|
5
|
+
## v0.32.0 (2026-05-28)
|
|
6
|
+
|
|
7
|
+
* Use `Etc.nprocessors` for default number of threads with fallback to manual way [@toy](https://github.com/toy)
|
|
8
|
+
* Correct environment variable to specify `jpeg-recompress` location [@toy](https://github.com/toy)
|
|
9
|
+
* Added --benchmark, to compare performance of each tool [#217](https://github.com/toy/image_optim/issues/217) [#218](https://github.com/toy/image_optim/pull/218) [@gurgeous](https://github.com/gurgeous)
|
|
10
|
+
* Don't require presence of `git` in gemspec [@toy](https://github.com/toy)
|
|
11
|
+
* Add a basic check for names of enabled and disabled svgo plugins [@toy](https://github.com/toy)
|
|
12
|
+
* Add support for enabling/disabling plugins in svgo 2.x, 3.x, 4.x [#191](https://github.com/toy/image_optim/issues/191) [#224](https://github.com/toy/image_optim/pull/224) [@tomhughes](https://github.com/tomhughes)
|
|
13
|
+
|
|
5
14
|
## v0.31.4 (2024-11-19)
|
|
6
15
|
|
|
7
16
|
* Added `--svgo-allow-lossy` and `--svgo-precision` options to use svgo in lossy mode. This sets svgo `--precision`, which can result in substantially smaller svgs (see [#211](https://github.com/toy/image_optim/issues/211) for some experiments). Lower values are more lossy. 3 is the default, but many SVGs will work well even with 0 or 1. Like all worker specific lossy flags, this is also enabled with `--allow-lossy`. [#210](https://github.com/toy/image_optim/issues/210) [#211](https://github.com/toy/image_optim/issues/211) [@gurgeous](https://github.com/gurgeous)
|
data/Dockerfile.test
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
FROM node:slim AS ruby-build
|
|
2
|
+
|
|
3
|
+
RUN npm install -g svgo
|
|
4
|
+
|
|
5
|
+
RUN apt-get update \
|
|
6
|
+
&& apt-get install -y --no-install-recommends \
|
|
7
|
+
build-essential \
|
|
8
|
+
ca-certificates \
|
|
9
|
+
curl \
|
|
10
|
+
git \
|
|
11
|
+
imagemagick \
|
|
12
|
+
libffi-dev \
|
|
13
|
+
libgmp-dev \
|
|
14
|
+
librsvg2-bin \
|
|
15
|
+
libssl-dev \
|
|
16
|
+
libyaml-dev \
|
|
17
|
+
rustc \
|
|
18
|
+
tini \
|
|
19
|
+
zlib1g-dev \
|
|
20
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
21
|
+
|
|
22
|
+
RUN git clone https://github.com/rbenv/ruby-build.git /ruby-build \
|
|
23
|
+
&& cd /ruby-build \
|
|
24
|
+
&& git checkout $(git describe --tags "$(git rev-list --tags --max-count=1)") \
|
|
25
|
+
&& /ruby-build/install.sh \
|
|
26
|
+
&& rm -r /ruby-build
|
|
27
|
+
|
|
28
|
+
FROM ghcr.io/toy/image_optim.test:ruby-build AS test
|
|
29
|
+
|
|
30
|
+
ARG RUBY_VERSION
|
|
31
|
+
RUN FULL_VERSION=$(ruby-build --definitions | perl -e '$p=shift; while(<>){$l=$_ if index($_,$p)==0} print $l' "${RUBY_VERSION}.") \
|
|
32
|
+
&& RUBY_CONFIGURE_OPTS=--disable-install-doc ruby-build --verbose "$FULL_VERSION" /usr/local
|
|
33
|
+
|
|
34
|
+
ENV BUNDLE_SILENCE_ROOT_WARNING=1
|
|
35
|
+
COPY script/update-rubygems-n-bundler ./script/
|
|
36
|
+
RUN script/update-rubygems-n-bundler
|
|
37
|
+
|
|
38
|
+
WORKDIR /gem
|
|
39
|
+
# silence warnings about not being in git directory
|
|
40
|
+
RUN git init
|
|
41
|
+
|
|
42
|
+
COPY Gemfile *.gemspec ./
|
|
43
|
+
RUN bundle install \
|
|
44
|
+
&& rm Gemfile Gemfile.lock *.gemspec
|
|
45
|
+
|
|
46
|
+
ENTRYPOINT ["/usr/bin/tini", "--"]
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.markdown
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
[](https://rubygems.org/gems/image_optim)
|
|
2
|
-
[](https://github.com/toy/image_optim/actions/workflows/check.yml)
|
|
3
3
|
[](https://github.com/toy/image_optim/actions/workflows/rubocop.yml)
|
|
4
4
|
[](https://github.com/toy/image_optim/actions/workflows/codeql.yml)
|
|
5
|
-
[](https://codeclimate.com/github/toy/image_optim)
|
|
6
|
-
[](https://codeclimate.com/github/toy/image_optim)
|
|
7
5
|
[](https://depfu.com/github/toy/image_optim)
|
|
8
6
|
[](https://inch-ci.org/github/toy/image_optim)
|
|
9
7
|
|
|
@@ -62,7 +60,7 @@ With version:
|
|
|
62
60
|
|
|
63
61
|
<!---<update-version>-->
|
|
64
62
|
```ruby
|
|
65
|
-
gem 'image_optim', '~> 0.
|
|
63
|
+
gem 'image_optim', '~> 0.32'
|
|
66
64
|
```
|
|
67
65
|
<!---</update-version>-->
|
|
68
66
|
|
|
@@ -90,7 +88,7 @@ Simplest way for `image_optim` to locate binaries is to install them in common l
|
|
|
90
88
|
|
|
91
89
|
If you cannot install to common location, then install to custom one and add it to `PATH`.
|
|
92
90
|
|
|
93
|
-
Specify custom bin location using `XXX_BIN` environment variable (`JPEGOPTIM_BIN`, `OPTIPNG_BIN`, …).
|
|
91
|
+
Specify custom bin location using `XXX_BIN` environment variable (`JPEGOPTIM_BIN`, `OPTIPNG_BIN`, `JPEG_RECOMPRESS_BIN`, …).
|
|
94
92
|
|
|
95
93
|
Besides permanently setting environment variables in `~/.profile`, `~/.bash_profile`, `~/.bashrc`, `~/.zshrc`, … they can be set:
|
|
96
94
|
|
|
@@ -303,6 +301,29 @@ optipng:
|
|
|
303
301
|
|
|
304
302
|
`image_optim` uses standard ruby library for creating temporary files. Temporary directory can be changed using one of `TMPDIR`, `TMP` or `TEMP` environment variables.
|
|
305
303
|
|
|
304
|
+
### Benchmark
|
|
305
|
+
|
|
306
|
+
Run with `--benchmark` to compare the performance of each individual tool on your images:
|
|
307
|
+
|
|
308
|
+
```sh
|
|
309
|
+
image_optim --benchmark=isolated -r /tmp/corpus/
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
benchmarking: 100.0% (elapsed: 3.9m)
|
|
314
|
+
|
|
315
|
+
BENCHMARK RESULTS
|
|
316
|
+
|
|
317
|
+
name files elapsed kb saved kb/s
|
|
318
|
+
-------- ----- ------- -------- -------
|
|
319
|
+
oxipng 50 8.906 1867.253 209.664
|
|
320
|
+
pngquant 50 1.980 214.597 108.386
|
|
321
|
+
pngcrush 50 22.529 1753.704 77.841
|
|
322
|
+
optipng 50 142.940 1641.101 11.481
|
|
323
|
+
advpng 50 137.753 962.549 6.987
|
|
324
|
+
pngout 50 426.706 444.679 1.042
|
|
325
|
+
```
|
|
326
|
+
|
|
306
327
|
## Options
|
|
307
328
|
|
|
308
329
|
* `:nice` — Nice level, priority of all used tools with higher value meaning lower priority, in range `-20..19`, negative values can be set only if run by root user *(defaults to `10`)*
|
|
@@ -392,4 +413,4 @@ In separate file [CHANGELOG.markdown](CHANGELOG.markdown).
|
|
|
392
413
|
|
|
393
414
|
## Copyright
|
|
394
415
|
|
|
395
|
-
Copyright (c) 2012-
|
|
416
|
+
Copyright (c) 2012-2026 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
|
data/image_optim.gemspec
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = 'image_optim'
|
|
5
|
-
s.version = '0.
|
|
5
|
+
s.version = '0.32.0'
|
|
6
6
|
s.summary = %q{Command line tool and ruby interface to optimize (lossless compress, optionally lossy) jpeg, png, gif and svg images using external utilities (advpng, gifsicle, jhead, jpeg-recompress, jpegoptim, jpegrescan, jpegtran, optipng, oxipng, pngcrush, pngout, pngquant, svgo)}
|
|
7
7
|
s.homepage = "https://github.com/toy/#{s.name}"
|
|
8
8
|
s.authors = ['Ivan Kuchin']
|
|
@@ -17,9 +17,19 @@ Gem::Specification.new do |s|
|
|
|
17
17
|
'source_code_uri' => "https://github.com/toy/#{s.name}",
|
|
18
18
|
} if s.respond_to?(:metadata=)
|
|
19
19
|
|
|
20
|
-
s.files
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
s.files = Dir[*%w[
|
|
21
|
+
.gitignore
|
|
22
|
+
.pre-commit-hooks.yaml
|
|
23
|
+
.rubocop.yml
|
|
24
|
+
Dockerfile.test
|
|
25
|
+
Gemfile
|
|
26
|
+
LICENSE.txt
|
|
27
|
+
*.markdown
|
|
28
|
+
*.gemspec
|
|
29
|
+
{.github,bin,lib,script,spec,vendor}/**/*
|
|
30
|
+
]].reject(&File.method(:directory?))
|
|
31
|
+
s.test_files = Dir['spec/**/*'].reject(&File.method(:directory?))
|
|
32
|
+
s.executables = Dir['bin/*'].map(&File.method(:basename))
|
|
23
33
|
s.require_paths = %w[lib]
|
|
24
34
|
|
|
25
35
|
s.post_install_message = <<-EOF
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
# Benchmark result for one worker+src
|
|
5
|
+
class BenchmarkResult
|
|
6
|
+
attr_reader :bytes, :elapsed, :worker
|
|
7
|
+
|
|
8
|
+
def initialize(src, dst, elapsed, worker)
|
|
9
|
+
@bytes = bytes_saved(src, dst)
|
|
10
|
+
@elapsed = elapsed
|
|
11
|
+
@worker = worker.class.bin_sym.to_s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def bytes_saved(src, dst)
|
|
17
|
+
src, dst = src.size, dst.size
|
|
18
|
+
return 0 if dst == 0 # failure
|
|
19
|
+
return 0 if dst > src # the file got bigger
|
|
20
|
+
|
|
21
|
+
src - dst
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -108,7 +108,7 @@ class ImageOptim
|
|
|
108
108
|
# Check path in XXX_BIN to exist, be a file and be executable and symlink to
|
|
109
109
|
# dir as name
|
|
110
110
|
def symlink_custom_bin!(name)
|
|
111
|
-
env_name = "#{name}
|
|
111
|
+
env_name = "#{name.to_s.tr('-', '_').upcase}_BIN"
|
|
112
112
|
path = ENV.fetch(env_name, nil)
|
|
113
113
|
return unless path
|
|
114
114
|
|
data/lib/image_optim/config.rb
CHANGED
|
@@ -5,12 +5,13 @@ require 'image_optim/configuration_error'
|
|
|
5
5
|
require 'image_optim/hash_helpers'
|
|
6
6
|
require 'image_optim/worker'
|
|
7
7
|
require 'image_optim/cmd'
|
|
8
|
+
require 'etc'
|
|
8
9
|
require 'set'
|
|
9
10
|
require 'yaml'
|
|
10
11
|
|
|
11
12
|
class ImageOptim
|
|
12
13
|
# Read, merge and parse configuration
|
|
13
|
-
class Config
|
|
14
|
+
class Config # rubocop:disable Metrics/ClassLength
|
|
14
15
|
include OptionHelpers
|
|
15
16
|
|
|
16
17
|
# Global config path at `$XDG_CONFIG_HOME/image_optim.yml` (by default
|
|
@@ -134,7 +135,7 @@ class ImageOptim
|
|
|
134
135
|
end
|
|
135
136
|
|
|
136
137
|
# Verbose mode, converted to boolean
|
|
137
|
-
def verbose
|
|
138
|
+
def verbose # rubocop:disable Naming/PredicateMethod
|
|
138
139
|
!!get!(:verbose)
|
|
139
140
|
end
|
|
140
141
|
|
|
@@ -164,7 +165,7 @@ class ImageOptim
|
|
|
164
165
|
end
|
|
165
166
|
|
|
166
167
|
# Allow lossy workers and optimizations, converted to boolean
|
|
167
|
-
def allow_lossy
|
|
168
|
+
def allow_lossy # rubocop:disable Naming/PredicateMethod
|
|
168
169
|
!!get!(:allow_lossy)
|
|
169
170
|
end
|
|
170
171
|
|
|
@@ -173,7 +174,7 @@ class ImageOptim
|
|
|
173
174
|
dir unless dir.nil? || dir.empty?
|
|
174
175
|
end
|
|
175
176
|
|
|
176
|
-
def cache_worker_digests
|
|
177
|
+
def cache_worker_digests # rubocop:disable Naming/PredicateMethod
|
|
177
178
|
!!get!(:cache_worker_digests)
|
|
178
179
|
end
|
|
179
180
|
|
|
@@ -205,9 +206,17 @@ class ImageOptim
|
|
|
205
206
|
|
|
206
207
|
private
|
|
207
208
|
|
|
208
|
-
# http://stackoverflow.com/a/6420817
|
|
209
209
|
def processor_count
|
|
210
|
-
@processor_count ||=
|
|
210
|
+
@processor_count ||= if Etc.respond_to?(:nprocessors)
|
|
211
|
+
Etc.nprocessors
|
|
212
|
+
else
|
|
213
|
+
processor_count_manual
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# http://stackoverflow.com/a/6420817
|
|
218
|
+
def processor_count_manual
|
|
219
|
+
case host_os = RbConfig::CONFIG['host_os']
|
|
211
220
|
when /darwin9/
|
|
212
221
|
Cmd.capture 'hwprefs cpu_count'
|
|
213
222
|
when /darwin/
|
|
@@ -153,6 +153,15 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
|
|
|
153
153
|
options[:pack] = pack
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
op.separator nil
|
|
157
|
+
op.on(
|
|
158
|
+
'--benchmark TYPE',
|
|
159
|
+
[:isolated],
|
|
160
|
+
'Run benchmarks, to compare tools without modifying images. `isolated` is the only supported type so far.'
|
|
161
|
+
) do |benchmark|
|
|
162
|
+
options[:benchmark] = benchmark
|
|
163
|
+
end
|
|
164
|
+
|
|
156
165
|
op.separator nil
|
|
157
166
|
op.separator ' Caching:'
|
|
158
167
|
|
data/lib/image_optim/runner.rb
CHANGED
|
@@ -45,6 +45,43 @@ class ImageOptim
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# files, elapsed, kb saved, kb/s
|
|
49
|
+
class BenchmarkResults
|
|
50
|
+
def initialize
|
|
51
|
+
@all = []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add(rows)
|
|
55
|
+
@all.concat(rows)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def print
|
|
59
|
+
if @all.empty?
|
|
60
|
+
puts 'nothing to report'
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
report = @all.group_by(&:worker).map do |name, results|
|
|
65
|
+
kb = (results.sum(&:bytes) / 1024.0)
|
|
66
|
+
elapsed = results.sum(&:elapsed)
|
|
67
|
+
{
|
|
68
|
+
'name' => name,
|
|
69
|
+
'files' => results.length,
|
|
70
|
+
'elapsed' => elapsed,
|
|
71
|
+
'kb saved' => kb,
|
|
72
|
+
'kb/s' => (kb / elapsed),
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
report = report.sort_by do |row|
|
|
77
|
+
[-row['kb/s'], row['name']]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
puts "\nBENCHMARK RESULTS\n\n"
|
|
81
|
+
Table.new(report).write($stdout)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
48
85
|
def initialize(options)
|
|
49
86
|
options = HashHelpers.deep_symbolise_keys(options)
|
|
50
87
|
@recursive = options.delete(:recursive)
|
|
@@ -53,19 +90,40 @@ class ImageOptim
|
|
|
53
90
|
glob = options.delete(:"exclude_#{type}_glob") || '.*'
|
|
54
91
|
GlobHelpers.expand_braces(glob)
|
|
55
92
|
end
|
|
93
|
+
|
|
94
|
+
# --benchmark
|
|
95
|
+
@benchmark = options.delete(:benchmark)
|
|
96
|
+
if @benchmark
|
|
97
|
+
unless options[:threads].nil?
|
|
98
|
+
warning '--benchmark ignores --threads'
|
|
99
|
+
options[:threads] = 1 # for consistency
|
|
100
|
+
end
|
|
101
|
+
if options[:timeout]
|
|
102
|
+
warning '--benchmark ignores --timeout'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
56
106
|
@image_optim = ImageOptim.new(options)
|
|
57
107
|
end
|
|
58
108
|
|
|
59
|
-
def run!(args)
|
|
109
|
+
def run!(args) # rubocop:disable Naming/PredicateMethod
|
|
60
110
|
to_optimize = find_to_optimize(args)
|
|
61
111
|
unless to_optimize.empty?
|
|
62
|
-
|
|
112
|
+
if @benchmark
|
|
113
|
+
benchmark_results = BenchmarkResults.new
|
|
114
|
+
benchmark_images(to_optimize).each do |_original, rows| # rubocop:disable Style/HashEachMethods
|
|
115
|
+
benchmark_results.add(rows)
|
|
116
|
+
end
|
|
117
|
+
benchmark_results.print
|
|
118
|
+
else
|
|
119
|
+
results = Results.new
|
|
63
120
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
121
|
+
optimize_images!(to_optimize).each do |original, optimized|
|
|
122
|
+
results.add(original, optimized)
|
|
123
|
+
end
|
|
67
124
|
|
|
68
|
-
|
|
125
|
+
results.print
|
|
126
|
+
end
|
|
69
127
|
end
|
|
70
128
|
|
|
71
129
|
!@warnings
|
|
@@ -73,6 +131,11 @@ class ImageOptim
|
|
|
73
131
|
|
|
74
132
|
private
|
|
75
133
|
|
|
134
|
+
def benchmark_images(to_optimize, &block)
|
|
135
|
+
to_optimize = to_optimize.with_progress('benchmarking') if @progress
|
|
136
|
+
@image_optim.benchmark_images(to_optimize, &block)
|
|
137
|
+
end
|
|
138
|
+
|
|
76
139
|
def optimize_images!(to_optimize, &block)
|
|
77
140
|
to_optimize = to_optimize.with_progress('optimizing') if @progress
|
|
78
141
|
@image_optim.optimize_images!(to_optimize, &block)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
# Handy class for pretty printing a table in the terminal. This is very simple, switch to Terminal
|
|
5
|
+
# Table, Table Tennis or similar if we need more.
|
|
6
|
+
class Table
|
|
7
|
+
attr_reader :rows
|
|
8
|
+
|
|
9
|
+
def initialize(rows)
|
|
10
|
+
@rows = rows
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def write(io)
|
|
14
|
+
io.puts render_row(columns)
|
|
15
|
+
io.puts render_sep
|
|
16
|
+
rows.each do |row|
|
|
17
|
+
io.puts render_row(row.values)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
|
|
23
|
+
# array of column names
|
|
24
|
+
def columns
|
|
25
|
+
@columns ||= rows.first.keys
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# should columns be justified left or right?
|
|
29
|
+
def justs
|
|
30
|
+
@justs ||= columns.map do |col|
|
|
31
|
+
rows.first[col].is_a?(Numeric) ? :rjust : :ljust
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# max width of each column
|
|
36
|
+
def widths
|
|
37
|
+
@widths ||= columns.map do |col|
|
|
38
|
+
values = rows.map{ |row| fmt(row[col]) }
|
|
39
|
+
([col] + values).map(&:length).max
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# render an array of row values
|
|
44
|
+
def render_row(values)
|
|
45
|
+
values.zip(justs, widths).map do |value, just, width|
|
|
46
|
+
fmt(value).send(just, width)
|
|
47
|
+
end.join(' ')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# render a separator line
|
|
51
|
+
def render_sep
|
|
52
|
+
render_row(widths.map{ |width| '-' * width })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# format one cell value
|
|
56
|
+
def fmt(value)
|
|
57
|
+
if value.is_a?(Float)
|
|
58
|
+
format('%0.3f', value)
|
|
59
|
+
else
|
|
60
|
+
value.to_s
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
require 'image_optim/option_helpers'
|
|
4
4
|
require 'image_optim/worker'
|
|
5
|
+
require 'fspath'
|
|
5
6
|
|
|
6
7
|
class ImageOptim
|
|
7
8
|
class Worker
|
|
8
9
|
# https://github.com/svg/svgo
|
|
9
10
|
class Svgo < Worker
|
|
11
|
+
PLUGIN_NAME_R = /\A[a-zA-Z]+\z/.freeze
|
|
12
|
+
|
|
10
13
|
DISABLE_PLUGINS_OPTION =
|
|
11
14
|
option(:disable_plugins, [], 'List of plugins to disable') do |v|
|
|
12
|
-
|
|
15
|
+
parse_plugin_names(v)
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
ENABLE_PLUGINS_OPTION =
|
|
16
19
|
option(:enable_plugins, [], 'List of plugins to enable') do |v|
|
|
17
|
-
|
|
20
|
+
parse_plugin_names(v)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
ALLOW_LOSSY_OPTION =
|
|
@@ -40,15 +43,57 @@ class ImageOptim
|
|
|
40
43
|
--input #{src}
|
|
41
44
|
--output #{dst}
|
|
42
45
|
]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
if resolve_bin!(:svgo).version >= '2.0.0'
|
|
47
|
+
unless disable_plugins.empty? && enable_plugins.empty?
|
|
48
|
+
config_file = plugins_config_file
|
|
49
|
+
args.unshift "--config=#{config_file.path}"
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
disable_plugins.each do |plugin_name|
|
|
53
|
+
args.unshift "--disable=#{plugin_name}"
|
|
54
|
+
end
|
|
55
|
+
enable_plugins.each do |plugin_name|
|
|
56
|
+
args.unshift "--enable=#{plugin_name}"
|
|
57
|
+
end
|
|
48
58
|
end
|
|
49
59
|
args.unshift "--precision=#{precision}" if allow_lossy
|
|
50
60
|
execute(:svgo, args, options) && optimized?(src, dst)
|
|
51
61
|
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def parse_plugin_names(value)
|
|
66
|
+
Array(value).map(&:to_s).select do |name|
|
|
67
|
+
if name =~ PLUGIN_NAME_R
|
|
68
|
+
true
|
|
69
|
+
else
|
|
70
|
+
warn "Doesn't look like svgo plugin name: #{name}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def plugins_config_file
|
|
76
|
+
@plugins_config_file ||= FSPath.temp_file(%w[image_optim .js]).tap do |config_file|
|
|
77
|
+
config_file.puts 'export default {'
|
|
78
|
+
config_file.puts ' plugins: ['
|
|
79
|
+
config_file.puts ' {'
|
|
80
|
+
config_file.puts ' name: \'preset-default\','
|
|
81
|
+
config_file.puts ' params: {'
|
|
82
|
+
config_file.puts ' overrides: {'
|
|
83
|
+
disable_plugins.each do |plugin_name|
|
|
84
|
+
config_file.puts " #{plugin_name}: false,"
|
|
85
|
+
end
|
|
86
|
+
config_file.puts ' }'
|
|
87
|
+
config_file.puts ' }'
|
|
88
|
+
config_file.puts ' },'
|
|
89
|
+
enable_plugins.each do |plugin_name|
|
|
90
|
+
config_file.puts " '#{plugin_name}',"
|
|
91
|
+
end
|
|
92
|
+
config_file.puts ' ]'
|
|
93
|
+
config_file.puts '};'
|
|
94
|
+
config_file.close
|
|
95
|
+
end
|
|
96
|
+
end
|
|
52
97
|
end
|
|
53
98
|
end
|
|
54
99
|
end
|
data/lib/image_optim.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'image_optim/benchmark_result'
|
|
3
4
|
require 'image_optim/bin_resolver'
|
|
4
5
|
require 'image_optim/cache'
|
|
5
6
|
require 'image_optim/config'
|
|
@@ -8,6 +9,7 @@ require 'image_optim/handler'
|
|
|
8
9
|
require 'image_optim/image_meta'
|
|
9
10
|
require 'image_optim/optimized_path'
|
|
10
11
|
require 'image_optim/path'
|
|
12
|
+
require 'image_optim/table'
|
|
11
13
|
require 'image_optim/timer'
|
|
12
14
|
require 'image_optim/worker'
|
|
13
15
|
require 'in_threads'
|
|
@@ -162,6 +164,22 @@ class ImageOptim
|
|
|
162
164
|
end
|
|
163
165
|
end
|
|
164
166
|
|
|
167
|
+
def benchmark_image(original)
|
|
168
|
+
src = Path.convert(original)
|
|
169
|
+
return unless (workers = workers_for_image(src))
|
|
170
|
+
|
|
171
|
+
dst = src.temp_path
|
|
172
|
+
begin
|
|
173
|
+
workers.map do |worker|
|
|
174
|
+
start = ElapsedTime.now
|
|
175
|
+
worker.optimize(src, dst)
|
|
176
|
+
BenchmarkResult.new(src, dst, ElapsedTime.now - start, worker)
|
|
177
|
+
end
|
|
178
|
+
ensure
|
|
179
|
+
dst.unlink
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
165
183
|
# Optimize multiple images
|
|
166
184
|
# if block given yields path and result for each image and returns array of
|
|
167
185
|
# yield results
|
|
@@ -186,6 +204,10 @@ class ImageOptim
|
|
|
186
204
|
run_method_for(datas, :optimize_image_data, &block)
|
|
187
205
|
end
|
|
188
206
|
|
|
207
|
+
def benchmark_images(paths, &block)
|
|
208
|
+
run_method_for(paths, :benchmark_image, &block)
|
|
209
|
+
end
|
|
210
|
+
|
|
189
211
|
class << self
|
|
190
212
|
# Optimization methods with default options
|
|
191
213
|
def method_missing(method, *args, &block)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euxo pipefail
|
|
4
|
+
|
|
5
|
+
ruby <<'RUBY'
|
|
6
|
+
short_version = RUBY_VERSION.to_f
|
|
7
|
+
|
|
8
|
+
gemrc_path = File.expand_path('~/.gemrc')
|
|
9
|
+
unless File.exist?(gemrc_path)
|
|
10
|
+
File.open(gemrc_path, 'w') do |f|
|
|
11
|
+
if short_version < 2.0
|
|
12
|
+
f.puts 'gem: --no-ri --no-rdoc'
|
|
13
|
+
else
|
|
14
|
+
f.puts 'gem: --no-document'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sh(*args)
|
|
20
|
+
abort unless system(*args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
case
|
|
24
|
+
when short_version < 2.3
|
|
25
|
+
sh 'curl --output rubygems-update.gem https://rubygems.org/downloads/rubygems-update-2.7.11.gem'
|
|
26
|
+
sh 'gem install --local rubygems-update.gem'
|
|
27
|
+
sh 'update_rubygems'
|
|
28
|
+
File.unlink(`which bundle`.strip)
|
|
29
|
+
sh 'curl --output bundler.gem https://rubygems.org/downloads/bundler-1.17.3.gem'
|
|
30
|
+
sh 'gem install --local bundler.gem'
|
|
31
|
+
when short_version < 2.6
|
|
32
|
+
sh 'gem update --system 3.3.27'
|
|
33
|
+
sh 'gem install bundler --version 2.3.27'
|
|
34
|
+
when short_version < 3.0
|
|
35
|
+
sh 'gem update --system 3.4.22'
|
|
36
|
+
sh 'gem install bundler --version 2.4.22'
|
|
37
|
+
when short_version < 3.1
|
|
38
|
+
sh 'gem update --system 3.5.23'
|
|
39
|
+
sh 'gem install bundler --version 2.5.23'
|
|
40
|
+
when short_version < 3.2
|
|
41
|
+
sh 'gem update --system 3.6.9'
|
|
42
|
+
sh 'gem install bundler --version 2.6.9'
|
|
43
|
+
else
|
|
44
|
+
sh 'gem update --system'
|
|
45
|
+
sh 'gem install bundler'
|
|
46
|
+
end
|
|
47
|
+
RUBY
|
|
48
|
+
|
|
49
|
+
gem -v
|
|
50
|
+
bundle -v
|
|
@@ -143,14 +143,14 @@ describe ImageOptim::BinResolver do
|
|
|
143
143
|
expect(FSPath).to receive(:temp_dir).
|
|
144
144
|
once.and_return(tmpdir)
|
|
145
145
|
expect(tmpdir).to receive(:/).
|
|
146
|
-
with(:
|
|
146
|
+
with(:'the-optimizer').once.and_return(symlink)
|
|
147
147
|
expect(symlink).to receive(:make_symlink).
|
|
148
148
|
with(File.expand_path(path)).once
|
|
149
149
|
|
|
150
150
|
expect(resolver).not_to receive(:full_path)
|
|
151
151
|
bin = double
|
|
152
152
|
expect(Bin).to receive(:new).
|
|
153
|
-
with(:
|
|
153
|
+
with(:'the-optimizer', File.expand_path(path)).and_return(bin)
|
|
154
154
|
expect(bin).to receive(:check!).once
|
|
155
155
|
expect(bin).to receive(:check_fail!).exactly(5).times
|
|
156
156
|
|
|
@@ -160,7 +160,7 @@ describe ImageOptim::BinResolver do
|
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
5.times do
|
|
163
|
-
resolver.resolve!(:
|
|
163
|
+
resolver.resolve!(:'the-optimizer')
|
|
164
164
|
end
|
|
165
165
|
expect(resolver.env_path).to eq([
|
|
166
166
|
tmpdir,
|
|
@@ -15,7 +15,9 @@ describe ImageOptim::Cache do
|
|
|
15
15
|
|
|
16
16
|
let(:cache_dir) do
|
|
17
17
|
dir = '/somewhere/cache'
|
|
18
|
-
allow(
|
|
18
|
+
allow(Dir).to receive(:mkdir).with(File.dirname(dir))
|
|
19
|
+
allow(Dir).to receive(:mkdir).with(dir)
|
|
20
|
+
allow(Dir).to receive(:mkdir).with(%r{\A#{Regexp.escape(dir)}/[^/]+\z})
|
|
19
21
|
allow(FileUtils).to receive(:touch)
|
|
20
22
|
allow(FSPath).to receive(:temp_file_path) do
|
|
21
23
|
tmp_file
|
|
@@ -71,9 +71,7 @@ describe ImageOptim::OptionDefinition do
|
|
|
71
71
|
|
|
72
72
|
context 'when proc given' do
|
|
73
73
|
subject do
|
|
74
|
-
|
|
75
|
-
# to_s is just to calm rubocop
|
|
76
|
-
described_class.new('abc', :def, 'desc'){ |o| o.inspect.to_s }
|
|
74
|
+
described_class.new('abc', :def, 'desc', &:inspect)
|
|
77
75
|
end
|
|
78
76
|
|
|
79
77
|
context 'when option not provided' do
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'image_optim/true_false_nil'
|
|
5
|
+
|
|
6
|
+
describe ImageOptim::TrueFalseNil do
|
|
7
|
+
describe '.convert' do
|
|
8
|
+
it 'keeps true' do
|
|
9
|
+
expect(described_class.convert(true)).to eq(true)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'keeps false' do
|
|
13
|
+
expect(described_class.convert(false)).to eq(false)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'keeps nil' do
|
|
17
|
+
expect(described_class.convert(nil)).to eq(nil)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'converts truthy to true' do
|
|
21
|
+
expect(described_class.convert(1)).to eq(true)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -4,6 +4,49 @@ require 'spec_helper'
|
|
|
4
4
|
require 'image_optim/worker/svgo'
|
|
5
5
|
|
|
6
6
|
describe ImageOptim::Worker::Svgo do
|
|
7
|
+
%i[
|
|
8
|
+
disable_plugins
|
|
9
|
+
enable_plugins
|
|
10
|
+
].each do |option|
|
|
11
|
+
describe "#{option} option" do
|
|
12
|
+
let(:subject){ described_class.new(ImageOptim.new, value).send(option) }
|
|
13
|
+
|
|
14
|
+
context 'default' do
|
|
15
|
+
let(:value){ {} }
|
|
16
|
+
|
|
17
|
+
it{ is_expected.to eq([]) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context 'when passed single valid value' do
|
|
21
|
+
let(:value){ {option => :pluginName} }
|
|
22
|
+
|
|
23
|
+
it 'converts it to a string array' do
|
|
24
|
+
is_expected.to eq(%w[pluginName])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context 'when passed multiple valid values' do
|
|
29
|
+
let(:value){ {option => %i[pluginName anotherName]} }
|
|
30
|
+
|
|
31
|
+
it 'converts them to a string array' do
|
|
32
|
+
is_expected.to eq(%w[pluginName anotherName])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'when given invalid values' do
|
|
37
|
+
let(:value){ {option => %w[1abc pluginName alert() anotherName]} }
|
|
38
|
+
|
|
39
|
+
it 'warns and skips them' do
|
|
40
|
+
expect_any_instance_of(described_class).
|
|
41
|
+
to receive(:warn).with('Doesn\'t look like svgo plugin name: 1abc')
|
|
42
|
+
expect_any_instance_of(described_class).
|
|
43
|
+
to receive(:warn).with('Doesn\'t look like svgo plugin name: alert()')
|
|
44
|
+
is_expected.to eq(%w[pluginName anotherName])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
7
50
|
describe 'precision option' do
|
|
8
51
|
describe 'default' do
|
|
9
52
|
subject{ described_class::PRECISION_OPTION.default }
|
data/spec/image_optim_spec.rb
CHANGED
|
@@ -61,9 +61,10 @@ describe ImageOptim do
|
|
|
61
61
|
|
|
62
62
|
base_options = {skip_missing_workers: false}
|
|
63
63
|
[
|
|
64
|
-
|
|
65
|
-
['
|
|
66
|
-
|
|
64
|
+
# 120 comes from https://github.com/ImageMagick/ImageMagick/commit/8a7495a6d9
|
|
65
|
+
['lossless', base_options, 120],
|
|
66
|
+
['lossy', base_options.merge(allow_lossy: true), 30],
|
|
67
|
+
].each do |type, options, psnr_min|
|
|
67
68
|
it "does it #{type}" do
|
|
68
69
|
image_optim = ImageOptim.new(options)
|
|
69
70
|
copies = test_images.map{ |image| temp_copy(image) }
|
|
@@ -78,7 +79,7 @@ describe ImageOptim do
|
|
|
78
79
|
expect(optimized).not_to have_same_data_as(original)
|
|
79
80
|
|
|
80
81
|
compare_to = rotate_images.include?(original) ? rotated : original
|
|
81
|
-
expect(optimized).to be_similar_to(compare_to,
|
|
82
|
+
expect(optimized).to be_similar_to(compare_to, psnr_min)
|
|
82
83
|
end
|
|
83
84
|
end
|
|
84
85
|
end
|
|
@@ -268,6 +269,20 @@ describe ImageOptim do
|
|
|
268
269
|
end
|
|
269
270
|
end
|
|
270
271
|
|
|
272
|
+
describe 'benchmark_images' do
|
|
273
|
+
it 'does it' do
|
|
274
|
+
image_optim = ImageOptim.new
|
|
275
|
+
pairs = image_optim.benchmark_images(test_images)
|
|
276
|
+
test_images.zip(pairs).each do |original, (src, bm)|
|
|
277
|
+
expect(original).to equal(src)
|
|
278
|
+
expect(bm[0]).to be_a(ImageOptim::BenchmarkResult)
|
|
279
|
+
expect(bm[0].bytes).to be_a(Numeric)
|
|
280
|
+
expect(bm[0].elapsed).to be_a(Numeric)
|
|
281
|
+
expect(bm[0].worker).to be_a(String)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
271
286
|
%w[
|
|
272
287
|
optimize_image
|
|
273
288
|
optimize_image!
|
|
@@ -7,6 +7,8 @@ require 'shellwords'
|
|
|
7
7
|
|
|
8
8
|
side = 64
|
|
9
9
|
|
|
10
|
+
colors = Array.new(256){ [rand(256), rand(256), rand(256)] }
|
|
11
|
+
|
|
10
12
|
IO.popen(%W[
|
|
11
13
|
convert
|
|
12
14
|
-depth 8
|
|
@@ -18,7 +20,7 @@ IO.popen(%W[
|
|
|
18
20
|
side.times do |a|
|
|
19
21
|
side.times do |b|
|
|
20
22
|
alpha = [0, 1, 0x7f, 0xff][((a / 8) + (b / 8)) % 4]
|
|
21
|
-
f << [
|
|
23
|
+
f << [*colors.sample, alpha].pack('C*')
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|
|
Binary file
|
data/spec/spec_helper.rb
CHANGED
|
@@ -42,12 +42,12 @@ def flatten_animation(image)
|
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def psnr(image_a, image_b)
|
|
46
46
|
coalesce_a = flatten_animation(image_a)
|
|
47
47
|
coalesce_b = flatten_animation(image_b)
|
|
48
48
|
output = ImageOptim::Cmd.capture((IMAGEMAGICK_PREFIX + %W[
|
|
49
49
|
compare
|
|
50
|
-
-metric
|
|
50
|
+
-metric PSNR
|
|
51
51
|
-alpha Background
|
|
52
52
|
#{coalesce_a.to_s.shellescape}
|
|
53
53
|
#{coalesce_b.to_s.shellescape}
|
|
@@ -59,21 +59,21 @@ def mepp(image_a, image_b)
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
num_r = '\d+(?:\.\d+(?:[eE][-+]?\d+)?)?'
|
|
62
|
-
output[/\(
|
|
62
|
+
num = output[/\A(#{num_r})/, 1].to_f
|
|
63
|
+
num == 0 ? Float::INFINITY : num
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
RSpec::Matchers.define :be_smaller_than do |expected|
|
|
66
67
|
match{ |actual| actual.size < expected.size }
|
|
67
68
|
end
|
|
68
69
|
|
|
69
|
-
RSpec::Matchers.define :be_similar_to do |expected,
|
|
70
|
+
RSpec::Matchers.define :be_similar_to do |expected, psnr_min|
|
|
70
71
|
match do |actual|
|
|
71
|
-
@diff =
|
|
72
|
-
@diff
|
|
72
|
+
@diff = psnr(actual, expected)
|
|
73
|
+
@diff >= psnr_min
|
|
73
74
|
end
|
|
74
75
|
failure_message do |actual|
|
|
75
|
-
"expected #{actual}
|
|
76
|
-
"#{expected}, got mean error per pixel of #{@diff}"
|
|
76
|
+
"expected peaks signal to noise ratio between #{actual} and #{expected} to be #{psnr_min}, got #{@diff}"
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: image_optim
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.32.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ivan Kuchin
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: fspath
|
|
@@ -166,14 +165,13 @@ dependencies:
|
|
|
166
165
|
- - "~>"
|
|
167
166
|
- !ruby/object:Gem::Version
|
|
168
167
|
version: '2.0'
|
|
169
|
-
description:
|
|
170
|
-
email:
|
|
171
168
|
executables:
|
|
172
169
|
- image_optim
|
|
173
170
|
extensions: []
|
|
174
171
|
extra_rdoc_files: []
|
|
175
172
|
files:
|
|
176
173
|
- ".github/dependabot.yml"
|
|
174
|
+
- ".github/workflows/build-test-containers.yml"
|
|
177
175
|
- ".github/workflows/check.yml"
|
|
178
176
|
- ".github/workflows/rubocop.yml"
|
|
179
177
|
- ".gitignore"
|
|
@@ -181,12 +179,14 @@ files:
|
|
|
181
179
|
- ".rubocop.yml"
|
|
182
180
|
- CHANGELOG.markdown
|
|
183
181
|
- CONTRIBUTING.markdown
|
|
182
|
+
- Dockerfile.test
|
|
184
183
|
- Gemfile
|
|
185
184
|
- LICENSE.txt
|
|
186
185
|
- README.markdown
|
|
187
186
|
- bin/image_optim
|
|
188
187
|
- image_optim.gemspec
|
|
189
188
|
- lib/image_optim.rb
|
|
189
|
+
- lib/image_optim/benchmark_result.rb
|
|
190
190
|
- lib/image_optim/bin_resolver.rb
|
|
191
191
|
- lib/image_optim/bin_resolver/bin.rb
|
|
192
192
|
- lib/image_optim/bin_resolver/comparable_condition.rb
|
|
@@ -211,6 +211,7 @@ files:
|
|
|
211
211
|
- lib/image_optim/runner/glob_helpers.rb
|
|
212
212
|
- lib/image_optim/runner/option_parser.rb
|
|
213
213
|
- lib/image_optim/space.rb
|
|
214
|
+
- lib/image_optim/table.rb
|
|
214
215
|
- lib/image_optim/timer.rb
|
|
215
216
|
- lib/image_optim/true_false_nil.rb
|
|
216
217
|
- lib/image_optim/worker.rb
|
|
@@ -230,6 +231,7 @@ files:
|
|
|
230
231
|
- script/template/jquery-2.1.3.min.js
|
|
231
232
|
- script/template/sortable-0.6.0.min.js
|
|
232
233
|
- script/template/worker_analysis.erb
|
|
234
|
+
- script/update-rubygems-n-bundler
|
|
233
235
|
- script/update_worker_options_in_readme
|
|
234
236
|
- script/worker_analysis
|
|
235
237
|
- spec/files/config_with_range.yaml
|
|
@@ -252,6 +254,7 @@ files:
|
|
|
252
254
|
- spec/image_optim/runner/option_parser_spec.rb
|
|
253
255
|
- spec/image_optim/space_spec.rb
|
|
254
256
|
- spec/image_optim/timer_spec.rb
|
|
257
|
+
- spec/image_optim/true_false_nil_spec.rb
|
|
255
258
|
- spec/image_optim/worker/jpegrecompress_spec.rb
|
|
256
259
|
- spec/image_optim/worker/optipng_spec.rb
|
|
257
260
|
- spec/image_optim/worker/oxipng_spec.rb
|
|
@@ -294,7 +297,7 @@ licenses:
|
|
|
294
297
|
metadata:
|
|
295
298
|
bug_tracker_uri: https://github.com/toy/image_optim/issues
|
|
296
299
|
changelog_uri: https://github.com/toy/image_optim/blob/master/CHANGELOG.markdown
|
|
297
|
-
documentation_uri: https://www.rubydoc.info/gems/image_optim/0.
|
|
300
|
+
documentation_uri: https://www.rubydoc.info/gems/image_optim/0.32.0
|
|
298
301
|
source_code_uri: https://github.com/toy/image_optim
|
|
299
302
|
post_install_message: |
|
|
300
303
|
Rails image assets optimization is extracted into image_optim_rails gem
|
|
@@ -313,8 +316,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
313
316
|
- !ruby/object:Gem::Version
|
|
314
317
|
version: '0'
|
|
315
318
|
requirements: []
|
|
316
|
-
rubygems_version:
|
|
317
|
-
signing_key:
|
|
319
|
+
rubygems_version: 4.0.3
|
|
318
320
|
specification_version: 4
|
|
319
321
|
summary: Command line tool and ruby interface to optimize (lossless compress, optionally
|
|
320
322
|
lossy) jpeg, png, gif and svg images using external utilities (advpng, gifsicle,
|
|
@@ -341,6 +343,7 @@ test_files:
|
|
|
341
343
|
- spec/image_optim/runner/option_parser_spec.rb
|
|
342
344
|
- spec/image_optim/space_spec.rb
|
|
343
345
|
- spec/image_optim/timer_spec.rb
|
|
346
|
+
- spec/image_optim/true_false_nil_spec.rb
|
|
344
347
|
- spec/image_optim/worker/jpegrecompress_spec.rb
|
|
345
348
|
- spec/image_optim/worker/optipng_spec.rb
|
|
346
349
|
- spec/image_optim/worker/oxipng_spec.rb
|