image_optim 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: