discourse_image_optim 0.24.4

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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/.appveyor.yml +46 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +110 -0
  5. data/.travis.yml +42 -0
  6. data/CHANGELOG.markdown +316 -0
  7. data/CONTRIBUTING.markdown +11 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +358 -0
  11. data/Vagrantfile +38 -0
  12. data/bin/image_optim +28 -0
  13. data/image_optim.gemspec +34 -0
  14. data/lib/image_optim.rb +267 -0
  15. data/lib/image_optim/bin_resolver.rb +142 -0
  16. data/lib/image_optim/bin_resolver/bin.rb +115 -0
  17. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  18. data/lib/image_optim/bin_resolver/error.rb +6 -0
  19. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  20. data/lib/image_optim/cache.rb +72 -0
  21. data/lib/image_optim/cache_path.rb +16 -0
  22. data/lib/image_optim/cmd.rb +122 -0
  23. data/lib/image_optim/config.rb +219 -0
  24. data/lib/image_optim/configuration_error.rb +3 -0
  25. data/lib/image_optim/handler.rb +57 -0
  26. data/lib/image_optim/hash_helpers.rb +45 -0
  27. data/lib/image_optim/image_meta.rb +20 -0
  28. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  29. data/lib/image_optim/optimized_path.rb +25 -0
  30. data/lib/image_optim/option_definition.rb +38 -0
  31. data/lib/image_optim/option_helpers.rb +17 -0
  32. data/lib/image_optim/path.rb +70 -0
  33. data/lib/image_optim/runner.rb +139 -0
  34. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  35. data/lib/image_optim/runner/option_parser.rb +246 -0
  36. data/lib/image_optim/space.rb +29 -0
  37. data/lib/image_optim/true_false_nil.rb +16 -0
  38. data/lib/image_optim/worker.rb +170 -0
  39. data/lib/image_optim/worker/advpng.rb +37 -0
  40. data/lib/image_optim/worker/class_methods.rb +107 -0
  41. data/lib/image_optim/worker/gifsicle.rb +65 -0
  42. data/lib/image_optim/worker/jhead.rb +47 -0
  43. data/lib/image_optim/worker/jpegoptim.rb +63 -0
  44. data/lib/image_optim/worker/jpegrecompress.rb +49 -0
  45. data/lib/image_optim/worker/jpegtran.rb +48 -0
  46. data/lib/image_optim/worker/optipng.rb +53 -0
  47. data/lib/image_optim/worker/pngcrush.rb +56 -0
  48. data/lib/image_optim/worker/pngout.rb +40 -0
  49. data/lib/image_optim/worker/pngquant.rb +61 -0
  50. data/lib/image_optim/worker/svgo.rb +34 -0
  51. data/script/template/jquery-2.1.3.min.js +4 -0
  52. data/script/template/sortable-0.6.0.min.js +2 -0
  53. data/script/template/worker_analysis.erb +254 -0
  54. data/script/update_worker_options_in_readme +59 -0
  55. data/script/worker_analysis +589 -0
  56. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  57. data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
  58. data/spec/image_optim/bin_resolver_spec.rb +290 -0
  59. data/spec/image_optim/cache_path_spec.rb +57 -0
  60. data/spec/image_optim/cache_spec.rb +162 -0
  61. data/spec/image_optim/cmd_spec.rb +93 -0
  62. data/spec/image_optim/config_spec.rb +254 -0
  63. data/spec/image_optim/handler_spec.rb +90 -0
  64. data/spec/image_optim/hash_helpers_spec.rb +74 -0
  65. data/spec/image_optim/image_meta_spec.rb +61 -0
  66. data/spec/image_optim/optimized_path_spec.rb +58 -0
  67. data/spec/image_optim/option_definition_spec.rb +138 -0
  68. data/spec/image_optim/option_helpers_spec.rb +25 -0
  69. data/spec/image_optim/path_spec.rb +103 -0
  70. data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
  71. data/spec/image_optim/runner/option_parser_spec.rb +105 -0
  72. data/spec/image_optim/space_spec.rb +23 -0
  73. data/spec/image_optim/worker/optipng_spec.rb +102 -0
  74. data/spec/image_optim/worker/pngquant_spec.rb +67 -0
  75. data/spec/image_optim/worker_spec.rb +303 -0
  76. data/spec/image_optim_spec.rb +259 -0
  77. data/spec/images/broken_jpeg +1 -0
  78. data/spec/images/comparison.png +0 -0
  79. data/spec/images/decompressed.jpeg +0 -0
  80. data/spec/images/icecream.gif +0 -0
  81. data/spec/images/image.jpg +0 -0
  82. data/spec/images/invisiblepixels/generate +24 -0
  83. data/spec/images/invisiblepixels/image.png +0 -0
  84. data/spec/images/lena.jpg +0 -0
  85. data/spec/images/orient/0.jpg +0 -0
  86. data/spec/images/orient/1.jpg +0 -0
  87. data/spec/images/orient/2.jpg +0 -0
  88. data/spec/images/orient/3.jpg +0 -0
  89. data/spec/images/orient/4.jpg +0 -0
  90. data/spec/images/orient/5.jpg +0 -0
  91. data/spec/images/orient/6.jpg +0 -0
  92. data/spec/images/orient/7.jpg +0 -0
  93. data/spec/images/orient/8.jpg +0 -0
  94. data/spec/images/orient/generate +23 -0
  95. data/spec/images/orient/original.jpg +0 -0
  96. data/spec/images/quant/64.png +0 -0
  97. data/spec/images/quant/generate +25 -0
  98. data/spec/images/rails.png +0 -0
  99. data/spec/images/test.svg +3 -0
  100. data/spec/images/transparency1.png +0 -0
  101. data/spec/images/transparency2.png +0 -0
  102. data/spec/images/vergroessert.jpg +0 -0
  103. data/spec/spec_helper.rb +93 -0
  104. metadata +281 -0
@@ -0,0 +1,37 @@
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
+ TIMEOUT_OPTION = timeout_option
19
+
20
+ def run_order
21
+ 4
22
+ end
23
+
24
+ def optimize(src, dst)
25
+ src.copy(dst)
26
+ args = %W[
27
+ --recompress
28
+ -#{level}
29
+ --quiet
30
+ --
31
+ #{dst}
32
+ ]
33
+ execute(:advpng, *args) && optimized?(src, dst)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,107 @@
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 timeout_option
36
+ option(
37
+ :timeout,
38
+ 0,
39
+ 'Number of seconds before worker is timed out. Must be greater than' \
40
+ '0 to enable timeout.'
41
+ ){ |v| v }
42
+ end
43
+
44
+ def option(name, default, type, description = nil, &proc)
45
+ attr_reader name
46
+ OptionDefinition.new(name, default, type, description, &proc).
47
+ tap{ |option_definition| option_definitions << option_definition }
48
+ end
49
+
50
+ # Create hash with format mapped to list of workers sorted by run order
51
+ def create_all_by_format(image_optim, &options_proc)
52
+ by_format = {}
53
+ create_all(image_optim, &options_proc).each do |worker|
54
+ worker.image_formats.each do |format|
55
+ by_format[format] ||= []
56
+ by_format[format] << worker
57
+ end
58
+ end
59
+ by_format
60
+ end
61
+
62
+ # Create list of workers sorted by run order
63
+ # Workers are initialized with options provided through options_proc
64
+ # Resolve all bins of all workers, if there are errors and
65
+ # skip_missing_workers of image_optim is true - show warnings, otherwise
66
+ # fail with one joint exception
67
+ def create_all(image_optim, &options_proc)
68
+ workers = init_all(image_optim, &options_proc)
69
+
70
+ resolved = []
71
+ errors = BinResolver.collect_errors(workers) do |worker|
72
+ worker.resolve_used_bins!
73
+ resolved << worker
74
+ end
75
+
76
+ unless errors.empty?
77
+ messages = errors.map(&:to_s).uniq
78
+ if image_optim.skip_missing_workers
79
+ messages.each{ |message| warn message }
80
+ else
81
+ joint_message = ['Bin resolving errors:', *messages].join("\n")
82
+ fail BinResolver::Error, joint_message
83
+ end
84
+ end
85
+
86
+ resolved.sort_by.with_index{ |worker, i| [worker.run_order, i] }
87
+ end
88
+
89
+ private
90
+
91
+ def init_all(image_optim, &options_proc)
92
+ klasses.map do |klass|
93
+ options = options_proc[klass]
94
+ next if options[:disable]
95
+
96
+ [:allow_lossy, :timeout].each do |option_key|
97
+ if !options.key?(option_key) && klass.method_defined?(option_key)
98
+ options[option_key] = image_optim.send(option_key)
99
+ end
100
+ end
101
+
102
+ klass.init(image_optim, options)
103
+ end.compact.flatten
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,65 @@
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
+ TIMEOUT_OPTION = timeout_option
39
+
40
+ def optimize(src, dst)
41
+ args = %W[
42
+ --output=#{dst}
43
+ --no-comments
44
+ --no-names
45
+ --same-delay
46
+ --same-loopcount
47
+ --no-warnings
48
+ --
49
+ #{src}
50
+ ]
51
+
52
+ if resolve_bin!(:gifsicle).version >= '1.85'
53
+ args.unshift '--no-extensions', '--no-app-extensions'
54
+ end
55
+
56
+ unless interlace.nil?
57
+ args.unshift interlace ? '--interlace' : '--no-interlace'
58
+ end
59
+ args.unshift '--careful' if careful
60
+ args.unshift "--optimize=#{level}" if level
61
+ execute(:gifsicle, *args) && optimized?(src, dst)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,47 @@
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
+ TIMEOUT_OPTION = timeout_option
11
+
12
+ ORIENTED = 2..8 # not top-left
13
+
14
+ # Works on jpegs
15
+ def image_formats
16
+ [:jpeg]
17
+ end
18
+
19
+ # Run first, while exif is still present
20
+ def run_order
21
+ -10
22
+ end
23
+
24
+ def used_bins
25
+ [:jhead, :jpegtran]
26
+ end
27
+
28
+ def optimize(src, dst)
29
+ return false unless oriented?(src)
30
+ src.copy(dst)
31
+ args = %W[
32
+ -autorot
33
+ #{dst}
34
+ ]
35
+ resolve_bin!(:jpegtran)
36
+ execute(:jhead, *args) && dst.size?
37
+ end
38
+
39
+ private
40
+
41
+ def oriented?(image)
42
+ exif = EXIFR::JPEG.new(image.to_s)
43
+ ORIENTED.include?(exif.orientation.to_i)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,63 @@
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
+ ALLOW_LOSSY_OPTION =
9
+ option(:allow_lossy, false, 'Allow limiting maximum quality'){ |v| !!v }
10
+
11
+ STRIP_OPTION =
12
+ option(:strip, :all, Array, 'List of extra markers to strip: '\
13
+ '`:comments`, '\
14
+ '`:exif`, '\
15
+ '`:iptc`, '\
16
+ '`:icc` or '\
17
+ '`:all`') do |v|
18
+ values = Array(v).map(&:to_s)
19
+ known_values = %w[all comments exif iptc icc]
20
+ unknown_values = values - known_values
21
+ unless unknown_values.empty?
22
+ warn "Unknown markers for jpegoptim: #{unknown_values.join(', ')}"
23
+ end
24
+ values & known_values
25
+ end
26
+
27
+ MAX_QUALITY_OPTION =
28
+ option(:max_quality, 100, 'Maximum image quality factor '\
29
+ '`0`..`100`, ignored in default/lossless mode') do |v, opt_def|
30
+ if allow_lossy
31
+ OptionHelpers.limit_with_range(v.to_i, 0..100)
32
+ else
33
+ if v != opt_def.default
34
+ warn "#{self.class.bin_sym} #{opt_def.name} #{v} ignored " \
35
+ 'in lossless mode'
36
+ end
37
+ opt_def.default
38
+ end
39
+ end
40
+
41
+ TIMEOUT_OPTION = timeout_option
42
+
43
+ # Run earlier if max_quality is less than 100
44
+ def run_order
45
+ max_quality < 100 ? -1 : 0
46
+ end
47
+
48
+ def optimize(src, dst)
49
+ src.copy(dst)
50
+ args = %W[
51
+ --quiet
52
+ --
53
+ #{dst}
54
+ ]
55
+ strip.each do |strip_marker|
56
+ args.unshift "--strip-#{strip_marker}"
57
+ end
58
+ args.unshift "--max=#{max_quality}" if max_quality < 100
59
+ execute(:jpegoptim, *args) && optimized?(src, dst)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,49 @@
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
+ ALLOW_LOSSY_OPTION =
9
+ option(:allow_lossy, false, 'Allow worker, it is always lossy'){ |v| !!v }
10
+
11
+ # Initialize only if allow_lossy
12
+ def self.init(image_optim, options = {})
13
+ super if options[:allow_lossy]
14
+ end
15
+
16
+ QUALITY_NAMES = [:low, :medium, :high, :veryhigh].freeze
17
+
18
+ quality_names_desc = QUALITY_NAMES.each_with_index.map do |name, i|
19
+ "`#{i}` - #{name}"
20
+ end.join(', ')
21
+
22
+ QUALITY_OPTION =
23
+ option(:quality, 3, "JPEG quality preset: #{quality_names_desc}") do |v|
24
+ OptionHelpers.limit_with_range(v.to_i, 0...QUALITY_NAMES.length)
25
+ end
26
+
27
+ TIMEOUT_OPTION = timeout_option
28
+
29
+ def used_bins
30
+ [:'jpeg-recompress']
31
+ end
32
+
33
+ # Run early as lossy worker
34
+ def run_order
35
+ -5
36
+ end
37
+
38
+ def optimize(src, dst)
39
+ args = %W[
40
+ --quality #{QUALITY_NAMES[quality]}
41
+ --no-copy
42
+ #{src}
43
+ #{dst}
44
+ ]
45
+ execute(:'jpeg-recompress', *args) && optimized?(src, dst)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,48 @@
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
+ TIMEOUT_OPTION = timeout_option
21
+
22
+ def used_bins
23
+ jpegrescan ? [:jpegtran, :jpegrescan] : [:jpegtran]
24
+ end
25
+
26
+ def optimize(src, dst)
27
+ if jpegrescan
28
+ args = %W[
29
+ #{src}
30
+ #{dst}
31
+ ]
32
+ args.unshift '-s' unless copy_chunks
33
+ resolve_bin!(:jpegtran)
34
+ execute(:jpegrescan, *args) && optimized?(src, dst)
35
+ else
36
+ args = %W[
37
+ -optimize
38
+ -outfile #{dst}
39
+ #{src}
40
+ ]
41
+ args.unshift '-copy', (copy_chunks ? 'all' : 'none')
42
+ args.unshift '-progressive' if progressive
43
+ execute(:jpegtran, *args) && optimized?(src, dst)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,53 @@
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
+ TIMEOUT_OPTION = timeout_option
28
+
29
+ def run_order
30
+ -4
31
+ end
32
+
33
+ def optimize(src, dst)
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 resolve_bin!(:optipng).version >= '0.7'
43
+ args.unshift '-strip', 'all' if strip
44
+ end
45
+ execute(:optipng, *args) && optimized?(src, dst)
46
+ end
47
+
48
+ def optimized?(src, dst)
49
+ interlace ? dst.size? : super
50
+ end
51
+ end
52
+ end
53
+ end