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,3 @@
1
+ class ImageOptim
2
+ class ConfigurationError < StandardError; end
3
+ end
@@ -0,0 +1,57 @@
1
+ require 'image_optim/image_path'
2
+
3
+ class ImageOptim
4
+ # Handles processing of original to result using upto two temp files
5
+ class Handler
6
+ # Holds latest successful result
7
+ attr_reader :result
8
+
9
+ # original must respond to temp_path
10
+ def initialize(original)
11
+ unless original.respond_to?(:temp_path)
12
+ fail ArgumentError, 'original should respond to temp_path'
13
+ end
14
+
15
+ @original = original
16
+ @result = nil
17
+ end
18
+
19
+ # with no associated block, works as new. Otherwise creates instance and
20
+ # passes it to block, runs cleanup and returns result of handler
21
+ def self.for(original)
22
+ handler = new(original)
23
+ if block_given?
24
+ begin
25
+ yield handler
26
+ handler.result
27
+ ensure
28
+ handler.cleanup
29
+ end
30
+ else
31
+ handler
32
+ end
33
+ end
34
+
35
+ # Yields two paths, one to latest successful result or original, second to
36
+ # temp path
37
+ def process
38
+ @src ||= @original
39
+ @dst ||= @original.temp_path
40
+
41
+ return unless yield @src, @dst
42
+ @result = @dst
43
+ if @src == @original
44
+ @src, @dst = @dst, nil
45
+ else
46
+ @src, @dst = @dst, @src
47
+ end
48
+ end
49
+
50
+ # Remove extra temp files
51
+ def cleanup
52
+ return unless @dst
53
+ @dst.unlink
54
+ @dst = nil
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,45 @@
1
+ class ImageOptim
2
+ # Helper methods to manipulate Hash, mainly used in config
3
+ module HashHelpers
4
+ class << self
5
+ # Returns a new hash with all keys of root and nested hashes converted to
6
+ # strings
7
+ def deep_stringify_keys(hash)
8
+ deep_transform_keys(hash, &:to_s)
9
+ end
10
+
11
+ # Returns a new hash with all keys of root and nested hashes converted to
12
+ # symbols
13
+ def deep_symbolise_keys(hash)
14
+ deep_transform_keys(hash, &:to_sym)
15
+ end
16
+
17
+ # Returns a new hash with recursive merge of all keys
18
+ def deep_merge(a, b)
19
+ a.merge(b) do |_k, v_a, v_b|
20
+ if v_a.is_a?(Hash) && v_b.is_a?(Hash)
21
+ deep_merge(v_a, v_b)
22
+ else
23
+ v_b
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # Returns a new hash with all keys of root and nested hashes converted by
31
+ # provided block
32
+ def deep_transform_keys(hash, &block)
33
+ new_hash = {}
34
+ hash.each do |k, v|
35
+ new_hash[block.call(k)] = if v.is_a?(Hash)
36
+ deep_transform_keys(v, &block)
37
+ else
38
+ v
39
+ end
40
+ end
41
+ new_hash
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ require 'image_size'
2
+
3
+ class ImageOptim
4
+ # Getting format of image at path or as data
5
+ class ImageMeta
6
+ def self.for_path(path)
7
+ is = ImageSize.path(path)
8
+ new(is.format)
9
+ rescue ImageSize::FormatError => e
10
+ warn "#{e} (detecting format of image at #{path})"
11
+ end
12
+
13
+ def self.for_data(data)
14
+ is = ImageSize.new(data)
15
+ new(is.format)
16
+ rescue ImageSize::FormatError => e
17
+ warn "#{e} (detecting format of image data)"
18
+ end
19
+
20
+ attr_reader :format
21
+ def initialize(format)
22
+ @format = format
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,68 @@
1
+ require 'fspath'
2
+ require 'image_optim/image_meta'
3
+
4
+ class ImageOptim
5
+ # FSPath with additional helpful methods
6
+ class ImagePath < FSPath
7
+ # Holds optimized image with reference to original and its size
8
+ class Optimized < DelegateClass(self)
9
+ def initialize(path, original_or_size = nil)
10
+ path = ImagePath.convert(path)
11
+ __setobj__(path)
12
+ if original_or_size.is_a?(Integer)
13
+ @original = path
14
+ @original_size = original_or_size
15
+ elsif original_or_size
16
+ @original = ImagePath.convert(original_or_size)
17
+ @original_size = @original.size
18
+ end
19
+ end
20
+
21
+ # Original path, use original_size to get its size as original can be
22
+ # overwritten
23
+ attr_reader :original
24
+
25
+ # Stored size of original
26
+ attr_reader :original_size
27
+ end
28
+
29
+ # Get temp path for this file with same extension
30
+ def temp_path(*args, &block)
31
+ ext = extname
32
+ self.class.temp_file_path([basename(ext).to_s, ext], *args, &block)
33
+ end
34
+
35
+ # Copy file to dest preserving attributes
36
+ def copy(dst)
37
+ FileUtils.copy_file(self, dst, true)
38
+ end
39
+
40
+ # Atomic replace src with self
41
+ def replace(src)
42
+ src = self.class.new(src)
43
+ src.temp_path(src.dirname) do |temp|
44
+ src.copy(temp)
45
+ temp.write(read)
46
+ temp.rename(src.to_s)
47
+ unlink
48
+ end
49
+ end
50
+
51
+ # Get format using ImageSize
52
+ def format
53
+ image_meta = ImageMeta.for_path(self)
54
+ image_meta && image_meta.format
55
+ end
56
+
57
+ # Read binary data
58
+ def binread
59
+ open('rb', &:read)
60
+ end
61
+
62
+ # Returns path if it is already an instance of this class otherwise new
63
+ # instance
64
+ def self.convert(path)
65
+ path.is_a?(self) ? path : new(path)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,11 @@
1
+ class ImageOptim
2
+ # Denote range of non negative integers for worker option
3
+ class NonNegativeIntegerRange
4
+ # Add handling of range of non negative integers in OptionParser instance
5
+ def self.add_to_option_parser(option_parser)
6
+ option_parser.accept(self, /(\d+)(?:-|\.\.)(\d+)/) do |_, m, n|
7
+ m.to_i..n.to_i
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ class ImageOptim
2
+ # Hold information about an option
3
+ class OptionDefinition
4
+ attr_reader :name, :default, :type, :description, :proc
5
+
6
+ def initialize(name, default, type_or_description, description = nil, &proc)
7
+ if type_or_description.is_a?(Class)
8
+ type = type_or_description
9
+ else
10
+ type, description = default.class, type_or_description
11
+ end
12
+
13
+ @name = name.to_sym
14
+ @description = description.to_s
15
+ @default, @type, @proc = default, type, proc
16
+ end
17
+
18
+ # Get value for worker from options
19
+ def value(worker, options)
20
+ value = options.key?(name) ? options[name] : default
21
+ if proc
22
+ if proc.arity == 2
23
+ worker.instance_exec(value, self, &proc)
24
+ else
25
+ worker.instance_exec(value, &proc)
26
+ end
27
+ else
28
+ value
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ class ImageOptim
2
+ # Helper methods for options
3
+ module OptionHelpers
4
+ # Ensure number is in range
5
+ def self.limit_with_range(number, range)
6
+ if range.include?(number)
7
+ number
8
+ elsif number < range.first
9
+ range.first
10
+ elsif range.exclude_end?
11
+ range.last - 1
12
+ else
13
+ range.last
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ # Adds image_optim as preprocessor for gif, jpeg, png and svg images
5
+ class Railtie < Rails::Railtie
6
+ initializer 'image_optim.initializer' do |app|
7
+ register_preprocessor(app) if register_preprocessor?(app)
8
+ end
9
+
10
+ def register_preprocessor?(app)
11
+ return if app.config.assets.compress == false
12
+ return if app.config.assets.image_optim == false
13
+
14
+ app.assets
15
+ end
16
+
17
+ def options(app)
18
+ if app.config.assets.image_optim == true
19
+ {}
20
+ else
21
+ app.config.assets.image_optim || {}
22
+ end
23
+ end
24
+
25
+ def register_preprocessor(app)
26
+ image_optim = ImageOptim.new(options(app))
27
+
28
+ processor = proc do |_context, data|
29
+ image_optim.optimize_image_data(data) || data
30
+ end
31
+
32
+ app.assets.register_preprocessor 'image/gif', :image_optim, &processor
33
+ app.assets.register_preprocessor 'image/jpeg', :image_optim, &processor
34
+ app.assets.register_preprocessor 'image/png', :image_optim, &processor
35
+ app.assets.register_preprocessor 'image/svg+xml', :image_optim, &processor
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,139 @@
1
+ require 'image_optim'
2
+ require 'image_optim/hash_helpers'
3
+ require 'image_optim/runner/glob_helpers'
4
+ require 'image_optim/space'
5
+ require 'progress'
6
+ require 'find'
7
+ require 'yaml'
8
+
9
+ class ImageOptim
10
+ # Handling optimization using image_optim binary
11
+ class Runner
12
+ # Collect and output results of optimization
13
+ class Results
14
+ def initialize
15
+ @lines = []
16
+ @original_size_sum = 0
17
+ @optimized_size_sum = 0
18
+ end
19
+
20
+ def add(original, optimized)
21
+ original_size = optimized ? optimized.original_size : original.size
22
+ optimized_size = optimized ? optimized.size : original.size
23
+ @lines << "#{size_percent(original_size, optimized_size)} #{original}"
24
+ @original_size_sum += original_size
25
+ @optimized_size_sum += optimized_size
26
+ end
27
+
28
+ def print
29
+ puts @lines
30
+ puts "Total: #{size_percent(@original_size_sum, @optimized_size_sum)}"
31
+ end
32
+
33
+ private
34
+
35
+ def size_percent(size_a, size_b)
36
+ if size_a == size_b
37
+ "------ #{Space::EMPTY_SPACE}"
38
+ else
39
+ percent = 100 - 100.0 * size_b / size_a
40
+ space = Space.space(size_a - size_b)
41
+ format('%5.2f%% %s', percent, space)
42
+ end
43
+ end
44
+ end
45
+
46
+ def initialize(options)
47
+ options = HashHelpers.deep_symbolise_keys(options)
48
+ @recursive = options.delete(:recursive)
49
+ @progress = options.delete(:show_progress) != false
50
+ @exclude_dir_globs, @exclude_file_globs = %w[dir file].map do |type|
51
+ glob = options.delete(:"exclude_#{type}_glob") || '.*'
52
+ GlobHelpers.expand_braces(glob)
53
+ end
54
+ @image_optim = ImageOptim.new(options)
55
+ end
56
+
57
+ def run!(args)
58
+ to_optimize = find_to_optimize(args)
59
+ unless to_optimize.empty?
60
+ results = Results.new
61
+
62
+ optimize_images!(to_optimize).each do |original, optimized|
63
+ results.add(original, optimized)
64
+ end
65
+
66
+ results.print
67
+ end
68
+
69
+ !@warnings
70
+ end
71
+
72
+ private
73
+
74
+ def optimize_images!(to_optimize, &block)
75
+ to_optimize = to_optimize.with_progress('optimizing') if @progress
76
+ @image_optim.optimize_images!(to_optimize, &block)
77
+ end
78
+
79
+ def find_to_optimize(paths)
80
+ to_optimize = []
81
+ paths.each do |path|
82
+ if File.file?(path)
83
+ if @image_optim.optimizable?(path)
84
+ to_optimize << path
85
+ else
86
+ warning "#{path} is not an image or there is no optimizer for it"
87
+ end
88
+ elsif File.directory?(path)
89
+ if @recursive
90
+ to_optimize += find_to_optimize_recursive(path)
91
+ else
92
+ warning "#{path} is a directory, use --recursive option"
93
+ end
94
+ else
95
+ warning "#{path} is not a file or a directory or does not exist"
96
+ end
97
+ end
98
+ to_optimize
99
+ end
100
+
101
+ def find_to_optimize_recursive(dir)
102
+ to_optimize = []
103
+ Find.find(dir) do |path|
104
+ if File.file?(path)
105
+ next if exclude_file?(dir, path)
106
+ next unless @image_optim.optimizable?(path)
107
+ to_optimize << path
108
+ elsif File.directory?(path)
109
+ Find.prune if dir != path && exclude_dir?(dir, path)
110
+ end
111
+ end
112
+ to_optimize
113
+ end
114
+
115
+ def exclude_dir?(dir, path)
116
+ exclude?(dir, path, @exclude_dir_globs)
117
+ end
118
+
119
+ def exclude_file?(dir, path)
120
+ exclude?(dir, path, @exclude_file_globs)
121
+ end
122
+
123
+ # Check if any of globs matches either part of path relative from dir or
124
+ # just basename
125
+ def exclude?(dir, path, globs)
126
+ relative_path = Pathname(path).relative_path_from(Pathname(dir)).to_s
127
+ basename = File.basename(path)
128
+ globs.any? do |glob|
129
+ File.fnmatch(glob, relative_path, File::FNM_PATHNAME) ||
130
+ File.fnmatch(glob, basename, File::FNM_PATHNAME)
131
+ end
132
+ end
133
+
134
+ def warning(message)
135
+ @warnings = true
136
+ warn message
137
+ end
138
+ end
139
+ end