openstreetmap-image_optim 0.21.0.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.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rubocop.yml +65 -0
  4. data/.travis.yml +42 -0
  5. data/CHANGELOG.markdown +272 -0
  6. data/CONTRIBUTING.markdown +10 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.markdown +344 -0
  10. data/Vagrantfile +33 -0
  11. data/bin/image_optim +28 -0
  12. data/image_optim.gemspec +29 -0
  13. data/lib/image_optim.rb +228 -0
  14. data/lib/image_optim/bin_resolver.rb +144 -0
  15. data/lib/image_optim/bin_resolver/bin.rb +105 -0
  16. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  17. data/lib/image_optim/bin_resolver/error.rb +6 -0
  18. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  19. data/lib/image_optim/cmd.rb +49 -0
  20. data/lib/image_optim/config.rb +205 -0
  21. data/lib/image_optim/configuration_error.rb +3 -0
  22. data/lib/image_optim/handler.rb +57 -0
  23. data/lib/image_optim/hash_helpers.rb +45 -0
  24. data/lib/image_optim/image_meta.rb +25 -0
  25. data/lib/image_optim/image_path.rb +68 -0
  26. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  27. data/lib/image_optim/option_definition.rb +32 -0
  28. data/lib/image_optim/option_helpers.rb +17 -0
  29. data/lib/image_optim/railtie.rb +38 -0
  30. data/lib/image_optim/runner.rb +139 -0
  31. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  32. data/lib/image_optim/runner/option_parser.rb +227 -0
  33. data/lib/image_optim/space.rb +29 -0
  34. data/lib/image_optim/true_false_nil.rb +16 -0
  35. data/lib/image_optim/worker.rb +159 -0
  36. data/lib/image_optim/worker/advpng.rb +35 -0
  37. data/lib/image_optim/worker/class_methods.rb +91 -0
  38. data/lib/image_optim/worker/gifsicle.rb +63 -0
  39. data/lib/image_optim/worker/jhead.rb +43 -0
  40. data/lib/image_optim/worker/jpegoptim.rb +58 -0
  41. data/lib/image_optim/worker/jpegrecompress.rb +44 -0
  42. data/lib/image_optim/worker/jpegtran.rb +46 -0
  43. data/lib/image_optim/worker/optipng.rb +45 -0
  44. data/lib/image_optim/worker/pngcrush.rb +54 -0
  45. data/lib/image_optim/worker/pngout.rb +38 -0
  46. data/lib/image_optim/worker/pngquant.rb +51 -0
  47. data/lib/image_optim/worker/svgo.rb +32 -0
  48. data/script/template/jquery-2.1.3.min.js +4 -0
  49. data/script/template/sortable-0.6.0.min.js +2 -0
  50. data/script/template/worker_analysis.erb +254 -0
  51. data/script/update_worker_options_in_readme +60 -0
  52. data/script/worker_analysis +599 -0
  53. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  54. data/spec/image_optim/bin_resolver/simple_version_spec.rb +57 -0
  55. data/spec/image_optim/bin_resolver_spec.rb +272 -0
  56. data/spec/image_optim/cmd_spec.rb +66 -0
  57. data/spec/image_optim/config_spec.rb +217 -0
  58. data/spec/image_optim/handler_spec.rb +95 -0
  59. data/spec/image_optim/hash_helpers_spec.rb +76 -0
  60. data/spec/image_optim/image_path_spec.rb +54 -0
  61. data/spec/image_optim/railtie_spec.rb +121 -0
  62. data/spec/image_optim/runner/glob_helpers_spec.rb +25 -0
  63. data/spec/image_optim/runner/option_parser_spec.rb +99 -0
  64. data/spec/image_optim/space_spec.rb +25 -0
  65. data/spec/image_optim/worker_spec.rb +192 -0
  66. data/spec/image_optim_spec.rb +242 -0
  67. data/spec/images/comparison.png +0 -0
  68. data/spec/images/decompressed.jpeg +0 -0
  69. data/spec/images/icecream.gif +0 -0
  70. data/spec/images/image.jpg +0 -0
  71. data/spec/images/invisiblepixels/generate +24 -0
  72. data/spec/images/invisiblepixels/image.png +0 -0
  73. data/spec/images/lena.jpg +0 -0
  74. data/spec/images/orient/0.jpg +0 -0
  75. data/spec/images/orient/1.jpg +0 -0
  76. data/spec/images/orient/2.jpg +0 -0
  77. data/spec/images/orient/3.jpg +0 -0
  78. data/spec/images/orient/4.jpg +0 -0
  79. data/spec/images/orient/5.jpg +0 -0
  80. data/spec/images/orient/6.jpg +0 -0
  81. data/spec/images/orient/7.jpg +0 -0
  82. data/spec/images/orient/8.jpg +0 -0
  83. data/spec/images/orient/generate +23 -0
  84. data/spec/images/orient/original.jpg +0 -0
  85. data/spec/images/quant/64.png +0 -0
  86. data/spec/images/quant/generate +25 -0
  87. data/spec/images/rails.png +0 -0
  88. data/spec/images/test.svg +3 -0
  89. data/spec/images/transparency1.png +0 -0
  90. data/spec/images/transparency2.png +0 -0
  91. data/spec/images/vergroessert.jpg +0 -0
  92. data/spec/spec_helper.rb +64 -0
  93. data/vendor/jpegrescan +143 -0
  94. metadata +308 -0
@@ -0,0 +1,35 @@
1
+ require 'image_optim/worker'
2
+ require 'image_optim/option_helpers'
3
+
4
+ class ImageOptim
5
+ class Worker
6
+ # http://advancemame.sourceforge.net/doc-advpng.html
7
+ class Advpng < Worker
8
+ LEVEL_OPTION =
9
+ option(:level, 4, 'Compression level: '\
10
+ '`0` - don\'t compress, '\
11
+ '`1` - fast, '\
12
+ '`2` - normal, '\
13
+ '`3` - extra, '\
14
+ '`4` - extreme') do |v|
15
+ OptionHelpers.limit_with_range(v.to_i, 0..4)
16
+ end
17
+
18
+ def run_order
19
+ 4
20
+ end
21
+
22
+ def optimize(src, dst)
23
+ src.copy(dst)
24
+ args = %W[
25
+ --recompress
26
+ -#{level}
27
+ --quiet
28
+ --
29
+ #{dst}
30
+ ]
31
+ execute(:advpng, *args) && optimized?(src, dst)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,91 @@
1
+ require 'image_optim/bin_resolver'
2
+ require 'image_optim/option_definition'
3
+
4
+ class ImageOptim
5
+ class Worker
6
+ # Class methods of ImageOptim::Worker
7
+ module ClassMethods
8
+ def self.extended(klass)
9
+ klass.instance_variable_set(:@klasses, [])
10
+ end
11
+
12
+ # List of available workers
13
+ def klasses
14
+ @klasses.to_enum
15
+ end
16
+
17
+ # Remember all classes inheriting from this one
18
+ def inherited(base)
19
+ @klasses << base
20
+ end
21
+
22
+ # Underscored class name symbol
23
+ def bin_sym
24
+ @underscored_name ||=
25
+ name.
26
+ split('::').last. # get last part
27
+ gsub(/([a-z])([A-Z])/, '\1_\2').downcase. # convert AbcDef to abc_def
28
+ to_sym
29
+ end
30
+
31
+ def option_definitions
32
+ @option_definitions ||= []
33
+ end
34
+
35
+ def option(name, default, type, description = nil, &proc)
36
+ attr_reader name
37
+ OptionDefinition.new(name, default, type, description, &proc).
38
+ tap{ |option_definition| option_definitions << option_definition }
39
+ end
40
+
41
+ # Create hash with format mapped to list of workers sorted by run order
42
+ def create_all_by_format(image_optim, &options_proc)
43
+ by_format = {}
44
+ create_all(image_optim, &options_proc).each do |worker|
45
+ worker.image_formats.each do |format|
46
+ by_format[format] ||= []
47
+ by_format[format] << worker
48
+ end
49
+ end
50
+ by_format
51
+ end
52
+
53
+ # Create list of workers sorted by run order
54
+ # Workers are initialized with options provided through options_proc
55
+ # Resolve all bins of all workers, if there are errors and
56
+ # skip_missing_workers of image_optim is true - show warnings, otherwise
57
+ # fail with one joint exception
58
+ def create_all(image_optim, &options_proc)
59
+ workers = init_all(image_optim, &options_proc)
60
+
61
+ resolved = []
62
+ errors = BinResolver.collect_errors(workers) do |worker|
63
+ worker.resolve_used_bins!
64
+ resolved << worker
65
+ end
66
+
67
+ unless errors.empty?
68
+ if image_optim.skip_missing_workers
69
+ errors.each{ |error| warn error }
70
+ else
71
+ message = ['Bin resolving errors:', *errors].join("\n")
72
+ fail BinResolver::Error, message
73
+ end
74
+ end
75
+
76
+ resolved.sort_by.with_index{ |worker, i| [worker.run_order, i] }
77
+ end
78
+
79
+ private
80
+
81
+ def init_all(image_optim, &options_proc)
82
+ klasses.map do |klass|
83
+ options = options_proc[klass]
84
+ next if options[:disable]
85
+ options = options.merge(:allow_lossy => image_optim.allow_lossy)
86
+ klass.init(image_optim, options)
87
+ end.compact.flatten
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,63 @@
1
+ require 'image_optim/worker'
2
+
3
+ class ImageOptim
4
+ class Worker
5
+ # http://www.lcdf.org/gifsicle/
6
+ class Gifsicle < Worker
7
+ # If interlace specified initialize one instance
8
+ # Otherwise initialize two, one with interlace off and one with on
9
+ def self.init(image_optim, options = {})
10
+ return super if options.key?(:interlace)
11
+
12
+ [false, true].map do |interlace|
13
+ new(image_optim, options.merge(:interlace => interlace))
14
+ end
15
+ end
16
+
17
+ INTERLACE_OPTION =
18
+ option(:interlace, false, TrueFalseNil, 'Interlace: '\
19
+ '`true` - interlace on, '\
20
+ '`false` - interlace off, '\
21
+ '`nil` - as is in original image '\
22
+ '(defaults to running two instances, one with interlace off and '\
23
+ 'one with on)') do |v|
24
+ TrueFalseNil.convert(v)
25
+ end
26
+
27
+ LEVEL_OPTION =
28
+ option(:level, 3, 'Compression level: '\
29
+ '`1` - light and fast, '\
30
+ '`2` - normal, '\
31
+ '`3` - heavy (slower)') do |v|
32
+ OptionHelpers.limit_with_range(v.to_i, 1..3)
33
+ end
34
+
35
+ CAREFUL_OPTION =
36
+ option(:careful, false, 'Avoid bugs with some software'){ |v| !!v }
37
+
38
+ def optimize(src, dst)
39
+ args = %W[
40
+ --output=#{dst}
41
+ --no-comments
42
+ --no-names
43
+ --same-delay
44
+ --same-loopcount
45
+ --no-warnings
46
+ --
47
+ #{src}
48
+ ]
49
+
50
+ if resolve_bin!(:gifsicle).version >= '1.85'
51
+ args.unshift '--no-extensions', '--no-app-extensions'
52
+ end
53
+
54
+ unless interlace.nil?
55
+ args.unshift interlace ? '--interlace' : '--no-interlace'
56
+ end
57
+ args.unshift '--careful' if careful
58
+ args.unshift "--optimize=#{level}" if level
59
+ execute(:gifsicle, *args) && optimized?(src, dst)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,43 @@
1
+ require 'image_optim/worker'
2
+ require 'exifr'
3
+
4
+ class ImageOptim
5
+ class Worker
6
+ # http://www.sentex.net/~mwandel/jhead/
7
+ #
8
+ # Jhead internally uses jpegtran which should be on path
9
+ class Jhead < Worker
10
+ # Works on jpegs
11
+ def image_formats
12
+ [:jpeg]
13
+ end
14
+
15
+ # Run first, while exif is still present
16
+ def run_order
17
+ -10
18
+ end
19
+
20
+ def used_bins
21
+ [:jhead, :jpegtran]
22
+ end
23
+
24
+ def optimize(src, dst)
25
+ return false unless oriented?(src)
26
+ src.copy(dst)
27
+ args = %W[
28
+ -autorot
29
+ #{dst}
30
+ ]
31
+ resolve_bin!(:jpegtran)
32
+ execute(:jhead, *args) && dst.size?
33
+ end
34
+
35
+ private
36
+
37
+ def oriented?(image)
38
+ exif = EXIFR::JPEG.new(image.to_s)
39
+ (2..8).include?(exif.orientation.to_i)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,58 @@
1
+ require 'image_optim/worker'
2
+ require 'image_optim/option_helpers'
3
+
4
+ class ImageOptim
5
+ class Worker
6
+ # http://www.kokkonen.net/tjko/projects.html
7
+ class Jpegoptim < Worker
8
+ STRIP_OPTION =
9
+ option(:strip, :all, Array, 'List of extra markers to strip: '\
10
+ '`:comments`, '\
11
+ '`:exif`, '\
12
+ '`:iptc`, '\
13
+ '`:icc` or '\
14
+ '`:all`') do |v|
15
+ values = Array(v).map(&:to_s)
16
+ known_values = %w[all comments exif iptc icc]
17
+ unknown_values = values - known_values
18
+ unless unknown_values.empty?
19
+ warn "Unknown markers for jpegoptim: #{unknown_values.join(', ')}"
20
+ end
21
+ values & known_values
22
+ end
23
+
24
+ MAX_QUALITY_OPTION =
25
+ option(:max_quality, 100, 'Maximum image quality factor '\
26
+ '`0`..`100`, ignored in default/lossless mode') do |v, opt_def|
27
+ if allow_lossy
28
+ OptionHelpers.limit_with_range(v.to_i, 0..100)
29
+ else
30
+ if v != opt_def.default
31
+ warn "#{self.class.bin_sym} #{opt_def.name} #{v} ignored " \
32
+ 'in lossless mode'
33
+ end
34
+ opt_def.default
35
+ end
36
+ end
37
+
38
+ # Run earlier if max_quality is less than 100
39
+ def run_order
40
+ max_quality < 100 ? -1 : 0
41
+ end
42
+
43
+ def optimize(src, dst)
44
+ src.copy(dst)
45
+ args = %W[
46
+ --quiet
47
+ --
48
+ #{dst}
49
+ ]
50
+ strip.each do |strip_marker|
51
+ args.unshift "--strip-#{strip_marker}"
52
+ end
53
+ args.unshift "--max=#{max_quality}" if max_quality < 100
54
+ execute(:jpegoptim, *args) && optimized?(src, dst)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ require 'image_optim/worker'
2
+ require 'image_optim/option_helpers'
3
+
4
+ class ImageOptim
5
+ class Worker
6
+ # https://github.com/danielgtaylor/jpeg-archive#jpeg-recompress
7
+ class Jpegrecompress < Worker
8
+ # Initialize only if allow_lossy
9
+ def self.init(image_optim, options = {})
10
+ super if options[:allow_lossy]
11
+ end
12
+
13
+ QUALITY_NAMES = [:low, :medium, :high, :veryhigh]
14
+
15
+ quality_names_desc = QUALITY_NAMES.each_with_index.map do |name, i|
16
+ "`#{i}` - #{name}"
17
+ end.join(', ')
18
+
19
+ QUALITY_OPTION =
20
+ option(:quality, 3, "JPEG quality preset: #{quality_names_desc}") do |v|
21
+ OptionHelpers.limit_with_range(v.to_i, 0...QUALITY_NAMES.length)
22
+ end
23
+
24
+ def used_bins
25
+ [:'jpeg-recompress']
26
+ end
27
+
28
+ # Run early as lossy worker
29
+ def run_order
30
+ -5
31
+ end
32
+
33
+ def optimize(src, dst)
34
+ args = %W[
35
+ --quality #{QUALITY_NAMES[quality]}
36
+ --no-copy
37
+ #{src}
38
+ #{dst}
39
+ ]
40
+ execute(:'jpeg-recompress', *args) && optimized?(src, dst)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,46 @@
1
+ require 'image_optim/worker'
2
+
3
+ class ImageOptim
4
+ class Worker
5
+ # http://www.ijg.org/
6
+ #
7
+ # Uses jpegtran through jpegrescan if enabled, jpegrescan is vendored with
8
+ # this gem
9
+ class Jpegtran < Worker
10
+ COPY_CHUNKS_OPTION =
11
+ option(:copy_chunks, false, 'Copy all chunks'){ |v| !!v }
12
+
13
+ PROGRESSIVE_OPTION =
14
+ option(:progressive, true, 'Create progressive JPEG file'){ |v| !!v }
15
+
16
+ JPEGRESCAN_OPTION =
17
+ option(:jpegrescan, false, 'Use jpegtran through jpegrescan, '\
18
+ 'ignore progressive option'){ |v| !!v }
19
+
20
+ def used_bins
21
+ jpegrescan ? [:jpegtran, :jpegrescan] : [:jpegtran]
22
+ end
23
+
24
+ def optimize(src, dst)
25
+ if jpegrescan
26
+ args = %W[
27
+ #{src}
28
+ #{dst}
29
+ ]
30
+ args.unshift '-s' unless copy_chunks
31
+ resolve_bin!(:jpegtran)
32
+ execute(:jpegrescan, *args) && optimized?(src, dst)
33
+ else
34
+ args = %W[
35
+ -optimize
36
+ -outfile #{dst}
37
+ #{src}
38
+ ]
39
+ args.unshift '-copy', (copy_chunks ? 'all' : 'none')
40
+ args.unshift '-progressive' if progressive
41
+ execute(:jpegtran, *args) && optimized?(src, dst)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,45 @@
1
+ require 'image_optim/worker'
2
+ require 'image_optim/option_helpers'
3
+ require 'image_optim/true_false_nil'
4
+
5
+ class ImageOptim
6
+ class Worker
7
+ # http://optipng.sourceforge.net/
8
+ class Optipng < Worker
9
+ LEVEL_OPTION =
10
+ option(:level, 6, 'Optimization level preset: '\
11
+ '`0` is least, '\
12
+ '`7` is best') do |v|
13
+ OptionHelpers.limit_with_range(v.to_i, 0..7)
14
+ end
15
+
16
+ INTERLACE_OPTION =
17
+ option(:interlace, false, TrueFalseNil, 'Interlace: '\
18
+ '`true` - interlace on, '\
19
+ '`false` - interlace off, '\
20
+ '`nil` - as is in original image') do |v|
21
+ TrueFalseNil.convert(v)
22
+ end
23
+
24
+ STRIP_OPTION =
25
+ option(:strip, true, 'Remove all auxiliary chunks'){ |v| !!v }
26
+
27
+ def run_order
28
+ -4
29
+ end
30
+
31
+ def optimize(src, dst)
32
+ src.copy(dst)
33
+ args = %W[
34
+ -o #{level}
35
+ -quiet
36
+ --
37
+ #{dst}
38
+ ]
39
+ args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
40
+ args.unshift '-strip', 'all' if strip
41
+ execute(:optipng, *args) && optimized?(src, dst)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ require 'image_optim/worker'
2
+
3
+ class ImageOptim
4
+ class Worker
5
+ # http://pmt.sourceforge.net/pngcrush/
6
+ class Pngcrush < Worker
7
+ CHUNKS_OPTION =
8
+ option(:chunks, :alla, Array, 'List of chunks to remove or '\
9
+ '`:alla` - all except tRNS/transparency or '\
10
+ '`:allb` - all except tRNS and gAMA/gamma') do |v|
11
+ Array(v).map(&:to_s)
12
+ end
13
+
14
+ FIX_OPTION =
15
+ option(:fix, false, 'Fix otherwise fatal conditions '\
16
+ 'such as bad CRCs'){ |v| !!v }
17
+
18
+ BRUTE_OPTION =
19
+ option(:brute, false, 'Brute force try all methods, '\
20
+ 'very time-consuming and generally not worthwhile'){ |v| !!v }
21
+
22
+ BLACKEN_OPTION =
23
+ option(:blacken, true, 'Blacken fully transparent pixels'){ |v| !!v }
24
+
25
+ def run_order
26
+ -6
27
+ end
28
+
29
+ def optimize(src, dst)
30
+ flags = %w[
31
+ -reduce
32
+ -cc
33
+ -q
34
+ ]
35
+ chunks.each do |chunk|
36
+ flags.push '-rem', chunk
37
+ end
38
+ flags.push '-fix' if fix
39
+ flags.push '-brute' if brute
40
+ if resolve_bin!(:pngcrush).version >= '1.7.38'
41
+ flags.push '-blacken' if blacken
42
+ end
43
+
44
+ args = flags + %W[
45
+ --
46
+ #{src}
47
+ #{dst}
48
+ ]
49
+
50
+ execute(:pngcrush, *args) && optimized?(src, dst)
51
+ end
52
+ end
53
+ end
54
+ end