image_optim 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /pkg/
2
+ /*.gem
3
+
4
+ /doc/
5
+ /rdoc/
6
+ /.yardoc/
7
+ /coverage/
8
+
9
+ Makefile
10
+ *.o
11
+ *.bundle
12
+ /tmp/
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Ivan Kuchin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,9 @@
1
+ # image_optim
2
+
3
+ Optimize images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout).
4
+
5
+ Based on [ImageOptim.app](http://imageoptim.pornel.net/).
6
+
7
+ ## Copyright
8
+
9
+ Copyright (c) 2012 Ivan Kuchin. See LICENSE.txt for details.
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ allow setting nice level?
2
+ timeout workers?
3
+ cyclic run until got maximum optimization?
data/bin/image_optim ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require 'image_optim'
5
+ require 'progress'
6
+ require 'optparse'
7
+
8
+ options = {}
9
+
10
+ option_parser = OptionParser.new do |op|
11
+ op.banner = <<-TEXT
12
+ #{op.program_name}, version #{ImageOptim.version}
13
+
14
+ Usege:
15
+ #{op.program_name} [options] image_path …
16
+
17
+ TEXT
18
+
19
+ op.on('--[no-]threads NUMBER', Integer, 'Number of threads or disable (defaults to number of processors)') do |threads|
20
+ options[:threads] = threads
21
+ end
22
+
23
+ ImageOptim::Worker.klasses.each do |klass|
24
+ bin = klass.underscored_name.to_sym
25
+ op.on("--[no-]#{bin} PATH", "#{bin} path or disable") do |path|
26
+ options[bin] = path
27
+ end
28
+ end
29
+
30
+ op.on_tail('-h', '--help', 'Show full help') do
31
+ puts option_parser.help
32
+ exit
33
+ end
34
+
35
+ op.on_tail('-v', '--version', 'Show version') do
36
+ puts ImageOptim.version
37
+ exit
38
+ end
39
+ end
40
+
41
+ begin
42
+ option_parser.parse!
43
+ rescue OptionParser::ParseError => e
44
+ abort "#{e.to_s}\n\n#{option_parser.help}"
45
+ end
46
+
47
+ if ARGV.empty?
48
+ abort "specify image paths to optimize\n\n#{option_parser.help}"
49
+ else
50
+ io = ImageOptim.new(options)
51
+ paths = ARGV
52
+ paths = paths.with_progress('optimizing') if paths.length > 1
53
+
54
+ lines = paths.map do |path|
55
+ before = File.size(path)
56
+ result = io.optimize_image!(path)
57
+ after = File.size(path)
58
+ "#{result ? '%5.2f%%' % (100.0 * after / before) : '--.--%'} #{path}"
59
+ end
60
+ puts lines
61
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'image_optim'
5
+ s.version = '0.1.0'
6
+ s.summary = %q{Optimize images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)}
7
+ s.homepage = "http://github.com/toy/#{s.name}"
8
+ s.authors = ['Ivan Kuchin']
9
+ s.license = 'MIT'
10
+
11
+ s.rubyforge_project = s.name
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = %w[lib]
17
+
18
+ s.add_dependency 'fspath', '~> 2.0.1'
19
+ s.add_dependency 'image_size', '~> 1.0.4'
20
+ s.add_dependency 'progress', '~> 2.4.0'
21
+ s.add_dependency 'in_threads', '~> 1.1.1'
22
+ s.add_development_dependency 'rspec'
23
+ end
@@ -0,0 +1,141 @@
1
+ require 'in_threads'
2
+
3
+ class ImageOptim
4
+ autoload :ImagePath, 'image_optim/image_path'
5
+ autoload :OptionHelpers, 'image_optim/option_helpers'
6
+ autoload :Util, 'image_optim/util'
7
+ autoload :Worker, 'image_optim/worker'
8
+
9
+ include OptionHelpers
10
+
11
+ # Hash of initialized workers by format they apply to
12
+ attr_reader :workers_by_format
13
+
14
+ # Number of threads to run with
15
+ attr_reader :threads
16
+
17
+ # Initialize workers, specify options using worker underscored name:
18
+ #
19
+ # pass false to disable worker
20
+ #
21
+ # ImageOptim.new(:pngcrush => false)
22
+ #
23
+ # string to set binary
24
+ #
25
+ # ImageOptim.new(:pngout => '/special/path/bin/pngout123')
26
+ #
27
+ # or hash with options to worker and :bin specifying binary
28
+ #
29
+ # ImageOptim.new(:advpng => {:level => 3}, :optipng => {:level => 2}, :jpegoptim => {:bin => 'jpegoptim345'})
30
+ def initialize(options = {})
31
+ @workers_by_format = {}
32
+ Worker.klasses.each do |klass|
33
+ case worker_options = options.delete(klass.underscored_name.to_sym)
34
+ when Hash
35
+ when true, nil
36
+ worker_options = {}
37
+ when false
38
+ next
39
+ when String
40
+ worker_options = {:bin => worker_options}
41
+ else
42
+ raise "Got #{worker_options.inspect} for #{klass.name} options"
43
+ end
44
+ worker = klass.new(worker_options)
45
+ klass.image_formats.each do |format|
46
+ workers_by_format[format] ||= []
47
+ workers_by_format[format] << worker
48
+ end
49
+ end
50
+ workers_by_format.each do |format, workers|
51
+ workers.replace workers.sort_by(&:run_priority)
52
+ end
53
+
54
+ threads = case options[:threads]
55
+ when true, nil
56
+ Util.processor_count
57
+ when false
58
+ 1
59
+ else
60
+ options[:threads].to_i
61
+ end
62
+ @threads = limit_with_range(threads, 1..16)
63
+
64
+ assert_options_empty!(options)
65
+ end
66
+
67
+ # Optimize one file, return new path or nil if optimization failed
68
+ def optimize_image(original)
69
+ original = ImagePath.new(original)
70
+ if workers = workers_by_format[original.format]
71
+ result = workers.inject(original) do |current, worker|
72
+ worker.optimize(current) || current
73
+ end
74
+ result == original ? nil : result
75
+ end
76
+ end
77
+
78
+ # Optimize one file in place, return optimization status
79
+ def optimize_image!(original)
80
+ original = ImagePath.new(original)
81
+ if result = optimize_image(original)
82
+ original.temp_path(original.dirname) do |temp|
83
+ original.copy(temp)
84
+ temp.write(result.read)
85
+ temp.rename(original)
86
+ end
87
+ true
88
+ end
89
+ end
90
+
91
+ # Optimize multiple images, returning list of results
92
+ # yields path and result if block given
93
+ def optimize_images(paths)
94
+ apply_threading(paths).map do |path|
95
+ result = optimize_image(path)
96
+ yield path, result if block_given?
97
+ result
98
+ end
99
+ end
100
+
101
+ # Optimize multiple images in place, returning list of results
102
+ # yields path and result if block given
103
+ def optimize_images!(paths)
104
+ apply_threading(paths).map do |path|
105
+ result = optimize_image!(path)
106
+ yield path, result if block_given?
107
+ result
108
+ end
109
+ end
110
+
111
+ # Optimization methods with default options
112
+ def self.method_missing(method, *args, &block)
113
+ if method.to_s =~ /^optimize/
114
+ new.send(method, *args, &block)
115
+ else
116
+ super
117
+ end
118
+ end
119
+
120
+ def self.version
121
+ Gem.loaded_specs['image_optim'].version.to_s rescue nil
122
+ end
123
+
124
+ private
125
+
126
+ def apply_threading(array)
127
+ if threads > 1 && array.length > 1
128
+ array.in_threads(threads)
129
+ else
130
+ array
131
+ end
132
+ end
133
+ end
134
+
135
+ %w[
136
+ pngcrush pngout optipng advpng
137
+ jpegoptim jpegtran
138
+ gifsicle
139
+ ].each do |worker|
140
+ require "image_optim/workers/#{worker}"
141
+ end
@@ -0,0 +1,22 @@
1
+ require 'fspath'
2
+ require 'image_size'
3
+
4
+ class ImageOptim
5
+ class ImagePath < FSPath
6
+ # Get temp path for this file with same extension
7
+ def temp_path(*args, &block)
8
+ ext = extname
9
+ self.class.temp_file_path([basename(ext), ext], *args, &block)
10
+ end
11
+
12
+ # Copy file to dest preserving attributes
13
+ def copy(dst)
14
+ FileUtils.copy_file(self, dst, true)
15
+ end
16
+
17
+ # Get format using ImageSize
18
+ def format
19
+ open{ |f| ImageSize.new(f) }.format
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ class ImageOptim
2
+ module OptionHelpers
3
+ # Remove option from hash and run through block or return default
4
+ def get_option!(options, name, default)
5
+ value = default
6
+ if options.has_key?(name)
7
+ value = options.delete(name)
8
+ value = yield(value) if block_given?
9
+ end
10
+ instance_variable_set("@#{name}", value)
11
+ end
12
+
13
+ # Ensure number is in range
14
+ def limit_with_range(number, range)
15
+ if range.include?(number)
16
+ number
17
+ elsif number < range.first
18
+ range.first
19
+ elsif range.exclude_end?
20
+ range.last - 1
21
+ else
22
+ range.last
23
+ end
24
+ end
25
+
26
+ # Raise unless all options are deleted
27
+ def assert_options_empty!(options)
28
+ unless options.empty?
29
+ raise "unknown options #{options.inspect} for #{self}"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ class ImageOptim
2
+ module Util
3
+ # Run command redirecting both stdout and stderr to /dev/null, raising signal if command was signaled and return successfulness
4
+ def self.run(*args)
5
+ res = system "#{args.map(&:to_s).shelljoin} &> /dev/null"
6
+ if $?.signaled?
7
+ raise SignalException.new($?.termsig)
8
+ end
9
+ res
10
+ end
11
+
12
+ # http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed
13
+ def self.processor_count
14
+ @processor_count ||= case host_os = RbConfig::CONFIG['host_os']
15
+ when /darwin9/
16
+ `hwprefs cpu_count`
17
+ when /darwin/
18
+ (`which hwprefs` != '') ? `hwprefs thread_count` : `sysctl -n hw.ncpu`
19
+ when /linux/
20
+ `grep -c processor /proc/cpuinfo`
21
+ when /freebsd/
22
+ `sysctl -n hw.ncpu`
23
+ when /mswin|mingw/
24
+ require 'win32ole'
25
+ wmi = WIN32OLE.connect('winmgmts://')
26
+ cpu = wmi.ExecQuery('select NumberOfLogicalProcessors from Win32_Processor')
27
+ cpu.to_enum.first.NumberOfLogicalProcessors
28
+ else
29
+ warn "Unknown architecture (#{host_os}) assuming one processor."
30
+ 1
31
+ end.to_i
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,67 @@
1
+ require 'shellwords'
2
+ require 'image_optim'
3
+
4
+ class ImageOptim
5
+ class Worker
6
+ class << self
7
+ # List of avaliable workers
8
+ def klasses
9
+ @klasses ||= []
10
+ end
11
+
12
+ # Remember all classes inheriting from this one
13
+ def inherited(base)
14
+ klasses << base
15
+ end
16
+
17
+ # List of formats which worker can optimize
18
+ def image_formats
19
+ format_from_name = name.downcase[/gif|jpeg|png/]
20
+ format_from_name ? [format_from_name.to_sym] : []
21
+ end
22
+
23
+ # Undercored class name
24
+ def underscored_name
25
+ @underscored_name ||= name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
26
+ end
27
+ end
28
+
29
+ include OptionHelpers
30
+
31
+ # Binary name or path
32
+ attr_reader :bin
33
+
34
+ # Configure (raises on extra options), find binary (raises if not found)
35
+ def initialize(options = {})
36
+ get_option!(options, :bin, default_bin)
37
+ parse_options(options)
38
+ raise "`#{bin}` not found" unless Util.run('which', bin)
39
+ assert_options_empty!(options)
40
+ end
41
+
42
+ # Put first in list of workers
43
+ def run_first?
44
+ end
45
+
46
+ # Optimize file, return new path or nil if optimization failed
47
+ def optimize(src)
48
+ dst = src.temp_path
49
+ if Util.run bin, *command_args(src, dst)
50
+ if dst.size? && dst.size < src.size
51
+ dst
52
+ end
53
+ end
54
+ end
55
+
56
+ # Name of binary determined from class name
57
+ def default_bin
58
+ self.class.underscored_name
59
+ end
60
+
61
+ private
62
+
63
+ def run_priority
64
+ run_first? ? 0 : 1
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,19 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ class Advpng < Worker
5
+ # Compression level: 0 - don't compress, 1 - fast, 2 - normal, 3 - extra, 4 - extreme (defaults to 4)
6
+ attr_reader :level
7
+
8
+ private
9
+
10
+ def parse_options(options)
11
+ get_option!(options, :level, 4){ |v| limit_with_range(v.to_i, 0..4) }
12
+ end
13
+
14
+ def command_args(src, dst)
15
+ src.copy(dst)
16
+ %W[-#{level} -z -q -- #{dst}]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ class Gifsicle < Worker
5
+ # Turn on interlacing (defaults to false)
6
+ attr_reader :interlace
7
+
8
+ private
9
+
10
+ def parse_options(options)
11
+ get_option!(options, :interlace, false){ |v| !!v }
12
+ end
13
+
14
+ def command_args(src, dst)
15
+ args = %W[-o #{dst} -O3 --no-comments --no-names --same-delay --same-loopcount --no-warnings -- #{src}]
16
+ args.unshift('-i') if interlace
17
+ args
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ class Jpegoptim < Worker
5
+ # Strip Comment markers from output file (defaults to true)
6
+ attr_reader :strip_comments
7
+
8
+ # Strip Exif markers from output file (defaults to true)
9
+ attr_reader :strip_exif
10
+
11
+ # Strip IPTC markers from output file (defaults to true)
12
+ attr_reader :strip_iptc
13
+
14
+ # Strip ICC profile markers from output file (defaults to true)
15
+ attr_reader :strip_icc
16
+
17
+ # Maximum image quality factor (defaults to 100)
18
+ attr_reader :max_quality
19
+
20
+ # Run first if max_quality < 100
21
+ def run_first?
22
+ max_quality < 100
23
+ end
24
+
25
+ private
26
+
27
+ def parse_options(options)
28
+ get_option!(options, :strip_comments, true){ |v| !!v }
29
+ get_option!(options, :strip_exif, true){ |v| !!v }
30
+ get_option!(options, :strip_iptc, true){ |v| !!v }
31
+ get_option!(options, :strip_icc, true){ |v| !!v }
32
+ get_option!(options, :max_quality, 100){ |v| v.to_i }
33
+ end
34
+
35
+ def command_args(src, dst)
36
+ src.copy(dst)
37
+ args = %W[-q -- #{dst}]
38
+ if strip_comments && strip_exif && strip_iptc && strip_icc
39
+ args.unshift '--strip-all'
40
+ else
41
+ args.unshift '--strip-com' if strip_comments
42
+ args.unshift '--strip-exif' if strip_exif
43
+ args.unshift '--strip-iptc' if strip_iptc
44
+ args.unshift '--strip-icc' if strip_icc
45
+ end
46
+ args.unshift "-m#{max_quality}" if max_quality < 100
47
+ args
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ class Jpegtran < Worker
5
+ # Copy all chunks or none (defaults to false)
6
+ attr_reader :copy
7
+
8
+ # Create progressive JPEG file (defaults to true)
9
+ attr_reader :progressive
10
+
11
+ private
12
+
13
+ def parse_options(options)
14
+ get_option!(options, :copy, false){ |v| !!v }
15
+ get_option!(options, :progressive, true){ |v| !!v }
16
+ end
17
+
18
+ def command_args(src, dst)
19
+ args = %W[-optimize -outfile #{dst} #{src}]
20
+ args.unshift '-copy', copy ? 'all' : 'none'
21
+ args.unshift '-progressive' if progressive
22
+ args
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ class Optipng < Worker
5
+ # Optimization level preset 0..7 (0 is least, 7 is best, defaults to 6)
6
+ attr_reader :level
7
+
8
+ # Interlace, true - interlace on, false - interlace off, nil - as is in original image (defaults to false)
9
+ attr_reader :interlace
10
+
11
+ private
12
+
13
+ def parse_options(options)
14
+ get_option!(options, :level, 6){ |v| limit_with_range(v.to_i, 0..7) }
15
+ get_option!(options, :interlace, false){ |v| v && true }
16
+ end
17
+
18
+ def command_args(src, dst)
19
+ src.copy(dst)
20
+ args = %W[-o#{level} -quiet -- #{dst}]
21
+ unless interlace.nil?
22
+ args.unshift "-i#{interlace ? 1 : 0}"
23
+ end
24
+ args
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ class Pngcrush < Worker
5
+ # List of chunks to remove or 'alla' or 'allb' (defaults to 'alla')
6
+ attr_reader :chunks
7
+
8
+ # Fix otherwise fatal conditions such as bad CRCs (defaults to false)
9
+ attr_reader :fix
10
+
11
+ # Brute force try all methods, very time-consuming and generally not worthwhile (defaults to false)
12
+ attr_reader :brute
13
+
14
+ # Always run first
15
+ def run_first?
16
+ true
17
+ end
18
+
19
+ private
20
+
21
+ def parse_options(options)
22
+ get_option!(options, :chunks, :alla){ |v| Array(v).map(&:to_s) }
23
+ get_option!(options, :fix, false){ |v| !!v }
24
+ get_option!(options, :brute, false){ |v| !!v }
25
+ end
26
+
27
+ def command_args(src, dst)
28
+ args = %W[-reduce -cc -q -- #{src} #{dst}]
29
+ Array(chunks).each do |chunk|
30
+ args.unshift '-rem', chunk
31
+ end
32
+ args.unshift '-fix' if fix
33
+ args.unshift '-brute' if brute
34
+ args
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ require 'image_optim'
2
+
3
+ class ImageOptim
4
+ class Pngout < Worker
5
+ # Keep optional chunks (defaults to false)
6
+ attr_reader :keep_chunks
7
+
8
+ # Strategy: 0 - xtreme, 1 - intense, 2 - longest Match, 3 - huffman Only, 4 - uncompressed (defaults to 0)
9
+ attr_reader :strategy
10
+
11
+ # Always run first
12
+ def run_first?
13
+ true
14
+ end
15
+
16
+ private
17
+
18
+ def parse_options(options)
19
+ get_option!(options, :keep_chunks, false){ |v| !!v }
20
+ get_option!(options, :strategy, 0){ |v| limit_with_range(v.to_i, 0..4) }
21
+ end
22
+
23
+ def command_args(src, dst)
24
+ %W[-k#{keep_chunks ? 1 : 0} -s#{strategy} -q -y #{src} #{dst}]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,74 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'rspec'
3
+ require 'image_optim'
4
+
5
+ spec_dir = ImageOptim::ImagePath.new(__FILE__).dirname.relative_path_from(Dir.pwd)
6
+ image_dir = spec_dir / 'images'
7
+
8
+ def temp_copy_path(original)
9
+ original.class.temp_dir do |dir|
10
+ temp_path = dir / original.basename
11
+ begin
12
+ original.copy(temp_path)
13
+ yield temp_path
14
+ ensure
15
+ temp_path.unlink if temp_path.exist?
16
+ end
17
+ end
18
+ end
19
+
20
+ describe ImageOptim do
21
+ image_dir.glob('*') do |original|
22
+ describe "optimizing #{original}" do
23
+ it "should optimize image" do
24
+ temp_copy_path(original) do |unoptimized|
25
+ optimized = ImageOptim.optimize_image(unoptimized)
26
+ optimized.should be_a(FSPath)
27
+ unoptimized.read.should == original.read
28
+ optimized.size.should > 0
29
+ optimized.size.should < unoptimized.size
30
+ optimized.read.should_not == unoptimized.read
31
+ end
32
+ end
33
+
34
+ it "should optimize image in place" do
35
+ temp_copy_path(original) do |path|
36
+ ImageOptim.optimize_image!(path).should be_true
37
+ path.size.should > 0
38
+ path.size.should < original.size
39
+ path.read.should_not == original.read
40
+ end
41
+ end
42
+
43
+ it "should stop optimizing" do
44
+ temp_copy_path(original) do |unoptimized|
45
+ count = (1..10).find do |i|
46
+ unoptimized = ImageOptim.optimize_image(unoptimized)
47
+ unoptimized.nil?
48
+ end
49
+ count.should >= 2
50
+ count.should < 10
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "unsupported file" do
57
+ let(:original){ ImageOptim::ImagePath.new(__FILE__) }
58
+
59
+ it "should ignore" do
60
+ temp_copy_path(original) do |unoptimized|
61
+ optimized = ImageOptim.optimize_image(unoptimized)
62
+ optimized.should be_nil
63
+ unoptimized.read.should == original.read
64
+ end
65
+ end
66
+
67
+ it "should ignore in place" do
68
+ temp_copy_path(original) do |unoptimized|
69
+ ImageOptim.optimize_image!(unoptimized).should_not be_true
70
+ unoptimized.read.should == original.read
71
+ end
72
+ end
73
+ end
74
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: image_optim
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Ivan Kuchin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-09 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: fspath
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 13
29
+ segments:
30
+ - 2
31
+ - 0
32
+ - 1
33
+ version: 2.0.1
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: image_size
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 31
45
+ segments:
46
+ - 1
47
+ - 0
48
+ - 4
49
+ version: 1.0.4
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: progress
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 31
61
+ segments:
62
+ - 2
63
+ - 4
64
+ - 0
65
+ version: 2.4.0
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: in_threads
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 17
77
+ segments:
78
+ - 1
79
+ - 1
80
+ - 1
81
+ version: 1.1.1
82
+ type: :runtime
83
+ version_requirements: *id004
84
+ - !ruby/object:Gem::Dependency
85
+ name: rspec
86
+ prerelease: false
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ type: :development
97
+ version_requirements: *id005
98
+ description:
99
+ email:
100
+ executables:
101
+ - image_optim
102
+ extensions: []
103
+
104
+ extra_rdoc_files: []
105
+
106
+ files:
107
+ - .gitignore
108
+ - LICENSE.txt
109
+ - README.markdown
110
+ - TODO
111
+ - bin/image_optim
112
+ - image_optim.gemspec
113
+ - lib/image_optim.rb
114
+ - lib/image_optim/image_path.rb
115
+ - lib/image_optim/option_helpers.rb
116
+ - lib/image_optim/util.rb
117
+ - lib/image_optim/worker.rb
118
+ - lib/image_optim/workers/advpng.rb
119
+ - lib/image_optim/workers/gifsicle.rb
120
+ - lib/image_optim/workers/jpegoptim.rb
121
+ - lib/image_optim/workers/jpegtran.rb
122
+ - lib/image_optim/workers/optipng.rb
123
+ - lib/image_optim/workers/pngcrush.rb
124
+ - lib/image_optim/workers/pngout.rb
125
+ - spec/image_optim_spec.rb
126
+ - spec/images/comparison.png
127
+ - spec/images/decompressed.jpeg
128
+ - spec/images/icecream.gif
129
+ - spec/images/image.jpg
130
+ - spec/images/lena.jpg
131
+ - spec/images/transparency1.png
132
+ - spec/images/transparency2.png
133
+ - spec/images/vergroessert.jpg
134
+ homepage: http://github.com/toy/image_optim
135
+ licenses:
136
+ - MIT
137
+ post_install_message:
138
+ rdoc_options: []
139
+
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 3
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ hash: 3
157
+ segments:
158
+ - 0
159
+ version: "0"
160
+ requirements: []
161
+
162
+ rubyforge_project: image_optim
163
+ rubygems_version: 1.8.13
164
+ signing_key:
165
+ specification_version: 3
166
+ summary: Optimize images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)
167
+ test_files:
168
+ - spec/image_optim_spec.rb
169
+ - spec/images/comparison.png
170
+ - spec/images/decompressed.jpeg
171
+ - spec/images/icecream.gif
172
+ - spec/images/image.jpg
173
+ - spec/images/lena.jpg
174
+ - spec/images/transparency1.png
175
+ - spec/images/transparency2.png
176
+ - spec/images/vergroessert.jpg
177
+ has_rdoc: