image_squeeze 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/CONTRIBUTORS +16 -0
  2. data/LICENSE +19 -0
  3. data/README.md +30 -0
  4. data/Rakefile +40 -0
  5. data/lib/image_squeeze/image_identifier.rb +24 -0
  6. data/lib/image_squeeze/log_factory.rb +14 -0
  7. data/lib/image_squeeze/processors/gif_to_png_processor.rb +19 -0
  8. data/lib/image_squeeze/processors/gifsicle_processor.rb +11 -0
  9. data/lib/image_squeeze/processors/jpeg_tran_non_progressive_processor.rb +11 -0
  10. data/lib/image_squeeze/processors/jpeg_tran_progressive_processor.rb +11 -0
  11. data/lib/image_squeeze/processors/png_crush_processor.rb +11 -0
  12. data/lib/image_squeeze/processors/processor.rb +37 -0
  13. data/lib/image_squeeze/result.rb +20 -0
  14. data/lib/image_squeeze/utils.rb +13 -0
  15. data/lib/image_squeeze.rb +108 -0
  16. data/test/fixtures/already_optimized_animated_gif.gif +0 -0
  17. data/test/fixtures/already_optimized_gif.gif +0 -0
  18. data/test/fixtures/already_optimized_jpg.jpg +0 -0
  19. data/test/fixtures/already_optimized_png.png +0 -0
  20. data/test/fixtures/better_as_progressive.jpg +0 -0
  21. data/test/fixtures/better_without_progressive.jpg +0 -0
  22. data/test/fixtures/gif.gif.gif.gif +0 -0
  23. data/test/fixtures/mislabeled_gif.png +0 -0
  24. data/test/fixtures/textfile.txt +0 -0
  25. data/test/fixtures/unoptimized_animated_gif.gif +0 -0
  26. data/test/fixtures/unoptimized_gif.gif +0 -0
  27. data/test/fixtures/unoptimized_png.png +0 -0
  28. data/test/functional/default_processors_test.rb +60 -0
  29. data/test/functional/image_identifier_test.rb +39 -0
  30. data/test/functional/result_test.rb +11 -0
  31. data/test/functional/squeeze_test.rb +116 -0
  32. data/test/test_helper.rb +12 -0
  33. metadata +93 -0
data/CONTRIBUTORS ADDED
@@ -0,0 +1,16 @@
1
+ Images files from the artwork at:
2
+ -------------------------------------------------------
3
+
4
+ - http://www.kongregate.com/collabs/art/spartan619/chillin-patato
5
+ - http://www.kongregate.com/collabs/art/ethaN12349/face
6
+ - http://www.kongregate.com/collabs/art/ETHANR26/super-lightningrod-guitar-gif
7
+ - http://www.kongregate.com/collabs/art/DeathQueen/dq
8
+ - http://www.kongregate.com/collabs/art/nygiantsroxursox/the-beach
9
+
10
+ Additional thanks to:
11
+ -------------------------------------------------------
12
+
13
+ - Nicole Sullivan and Stoyan Stefanov for the idea (http://www.smushit.com/ysmush.it/)
14
+ - Steve Souders and all the authors of Even Faster Web Sites
15
+ - Duncan Beevers
16
+ - All the cool guys at Kongregate
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Andrew Grim, Kongregate
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # image_squeeze
2
+
3
+ A library for automated lossless image optimization
4
+
5
+ ## Installation
6
+
7
+ The default processors depend on ImageMagick, pngcrush, gifsicle, and jpegtran. ImageMagick is required for all processors.
8
+
9
+ ## Usage
10
+
11
+ # set up an ImageSqueeze with the default processors
12
+ squeezer = ImageSqueeze.new(:default_processors => true)
13
+
14
+ # in-place squeeze of our png
15
+ squeezer.squeeze!('my_logo.png')
16
+
17
+ # non-destructive squeeze
18
+ result = squeezer.squeeze('your_logo.png')
19
+ puts "result saved #{result.bytes_saved} bytes, new image located at #{result.output_filename}"
20
+
21
+ # move tmp file to final location
22
+ squeezer.finalize_results(result)
23
+
24
+ ## TODO
25
+
26
+ * Command line runner
27
+
28
+ ## Copyright
29
+
30
+ Copyright (c) 2010 Andrew Grim. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+ require 'lib/image_squeeze'
5
+
6
+ desc "Run all tests"
7
+ task 'default' => ['test:functionals']
8
+
9
+ namespace 'test' do
10
+ functional_tests = FileList['test/functional/**/*_test.rb']
11
+
12
+ desc "Run functional tests"
13
+ Rake::TestTask.new('functionals') do |t|
14
+ t.libs << 'test'
15
+ t.test_files = functional_tests
16
+ t.verbose = true
17
+ t.warning = false
18
+ end
19
+ end
20
+
21
+ spec = Gem::Specification.new do |s|
22
+ s.platform = Gem::Platform::RUBY
23
+ s.summary = "a library for automated lossless image optimization"
24
+ s.name = 'image_squeeze'
25
+ s.version = ImageSqueeze::VERSION
26
+ s.requirements << 'none'
27
+ s.require_path = 'lib'
28
+ s.files = FileList[
29
+ "lib/**/*", "test/**/*", "[a-zA-Z]*"
30
+ ].exclude(/\.gitignore/)
31
+ s.description = "a library for automated lossless image optimization"
32
+ s.homepage = "http://github.com/stopdropandrew/image_squeeze"
33
+ s.author = "Andrew Grim"
34
+ s.email = "stopdropandrew@gmail.com"
35
+ end
36
+
37
+ Rake::GemPackageTask.new(spec) do |pkg|
38
+ pkg.need_zip = true
39
+ pkg.need_tar = true
40
+ end
@@ -0,0 +1,24 @@
1
+ class ImageSqueeze
2
+ def self.image_type(filename)
3
+ return ImageSqueeze::NOT_FOUND unless File.exist?(filename)
4
+
5
+ case identified_format(filename)
6
+ when /^GIFGIF/
7
+ ImageSqueeze::ANIMATED_GIF
8
+ when /GIF/
9
+ ImageSqueeze::GIF
10
+ when /JPEG/
11
+ ImageSqueeze::JPEG
12
+ when /PNG/
13
+ ImageSqueeze::PNG
14
+ else
15
+ ImageSqueeze::UNKNOWN
16
+ end
17
+
18
+ end
19
+
20
+ private
21
+ def self.identified_format(filename)
22
+ `identify -format %m #{filename} 2> /dev/null`
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ class ImageSqueeze
2
+ class LogFactory
3
+ def self.logger
4
+ @logger ||= create_logger
5
+ end
6
+
7
+ private
8
+ def self.create_logger
9
+ return Rails.logger if defined?(Rails) && defined?(Rails.logger)
10
+ return RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)
11
+ return Logger.new(STDERR)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ class ImageSqueeze
2
+ class GIFToPNGProcessor < Processor
3
+ def self.input_type
4
+ GIF
5
+ end
6
+
7
+ def self.output_extension
8
+ '.png'
9
+ end
10
+
11
+ def self.squeeze(filename, output_filename)
12
+ intermediate_tmp_filename = tmp_filename(filename)
13
+
14
+ system("convert #{filename} PNG:#{intermediate_tmp_filename} 2> /dev/null")
15
+
16
+ PNGCrushProcessor.squeeze(intermediate_tmp_filename, output_filename) # run it through PNGCrush afterwards
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ class ImageSqueeze
2
+ class GifsicleProcessor < Processor
3
+ def self.input_type
4
+ ANIMATED_GIF
5
+ end
6
+
7
+ def self.squeeze(filename, output_filename)
8
+ system("gifsicle -O2 #{filename} > #{output_filename} 2> /dev/null")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class ImageSqueeze
2
+ class JPEGTranNonProgressiveProcessor < Processor
3
+ def self.input_type
4
+ JPEG
5
+ end
6
+
7
+ def self.squeeze(filename, output_filename)
8
+ system("jpegtran -copy none -optimize #{filename} > #{output_filename}")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class ImageSqueeze
2
+ class JPEGTranProgressiveProcessor < Processor
3
+ def self.input_type
4
+ JPEG
5
+ end
6
+
7
+ def self.squeeze(filename, output_filename)
8
+ system("jpegtran -copy none -optimize -progressive #{filename} > #{output_filename}")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class ImageSqueeze
2
+ class PNGCrushProcessor < Processor
3
+ def self.input_type
4
+ PNG
5
+ end
6
+
7
+ def self.squeeze(filename, output_filename)
8
+ system("pngcrush -rem alla -brute -reduce #{filename} #{output_filename} > /dev/null")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require 'tempfile'
2
+
3
+ class ImageSqueeze
4
+ class Processor
5
+ attr_reader :filename
6
+
7
+ def self.tmp_filename(filename)
8
+ t = Time.now.strftime("%Y%m%d")
9
+ path = "#{File.basename(filename)}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
10
+
11
+ File.join(Dir::tmpdir, path)
12
+ end
13
+
14
+ def self.squeeze_to_tmp(filename)
15
+ tmp = tmp_filename(filename)
16
+ squeeze(filename, tmp)
17
+ tmp
18
+ end
19
+
20
+ def self.squeeze(filename, output_filename)
21
+ raise "#{to_s}#squeeze should be defined in subclass and should convert filename to something at output_filename"
22
+ end
23
+
24
+ def self.handles?(image_type)
25
+ image_type == input_type
26
+ end
27
+
28
+ def self.input_type
29
+ raise "#{to_s}#input_type should be defined in subclass"
30
+ end
31
+
32
+ # override if you want a different output extensions
33
+ def self.output_extension
34
+ ImageSqueeze::IMAGE_TYPE_TO_EXTENSION[input_type]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ class ImageSqueeze
2
+ class Result
3
+ attr_reader :filename, :output_filename, :bytes_saved, :output_extension
4
+
5
+ def initialize(options = {})
6
+ @filename = options[:filename]
7
+ @output_filename = options[:output_filename]
8
+ @bytes_saved = options[:bytes_saved]
9
+ @output_extension = options[:output_extension]
10
+ end
11
+
12
+ def optimized?
13
+ bytes_saved.to_i > 0
14
+ end
15
+
16
+ def <=>(other)
17
+ self.bytes_saved <=> other.bytes_saved
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ class ImageSqueeze
2
+ module Utils
3
+ def self.image_utility_available?(bin, extension, log_level = Logger::WARN)
4
+ return true if system("which #{bin} > /dev/null")
5
+ if log_level >= Logger::ERROR
6
+ ImageSqueeze.logger.error("#{bin} utility is required for running ImageSqueeze, get it installed already")
7
+ raise StandardError, "#{bin} utility is required for running ImageSqueeze, get it installed already"
8
+ else
9
+ ImageSqueeze.logger.log(log_level, "#{bin} utility could not be found, your #{extension} files won't be squeezed")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,108 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'logger'
4
+ require 'image_squeeze/log_factory'
5
+ require 'image_squeeze/utils'
6
+ require 'image_squeeze/image_identifier'
7
+ require 'image_squeeze/result'
8
+
9
+ # processors
10
+ require 'image_squeeze/processors/processor'
11
+ require 'image_squeeze/processors/png_crush_processor'
12
+ require 'image_squeeze/processors/jpeg_tran_progressive_processor'
13
+ require 'image_squeeze/processors/jpeg_tran_non_progressive_processor'
14
+ require 'image_squeeze/processors/gifsicle_processor'
15
+ require 'image_squeeze/processors/gif_to_png_processor'
16
+
17
+ class ImageSqueeze
18
+ attr_reader :processors
19
+
20
+ VERSION = '0.1.0'
21
+
22
+ # Image Types
23
+ GIF = 'gif'
24
+ ANIMATED_GIF = 'animated gif'
25
+ JPEG = 'jpeg'
26
+ PNG = 'png'
27
+ UNKNOWN = 'unknown'
28
+ NOT_FOUND = 'not_found'
29
+
30
+ IMAGE_TYPE_TO_EXTENSION = {
31
+ GIF => '.gif',
32
+ ANIMATED_GIF => '.gif',
33
+ JPEG => '.jpg',
34
+ PNG => '.png'
35
+ }
36
+
37
+ def initialize(options = {})
38
+ # identify is required
39
+ ImageSqueeze::Utils.image_utility_available?('identify', 'all image', Logger::ERROR)
40
+
41
+ @processors = options[:processors] || []
42
+ @processors += self.class.default_processors if options[:default_processors]
43
+ end
44
+
45
+ def self.default
46
+ @processors = self.class.default_processors
47
+ end
48
+
49
+ def squeeze(filename)
50
+ image_type = self.class.image_type(filename)
51
+ return if [UNKNOWN, NOT_FOUND].include?(image_type)
52
+
53
+ processors = @processors.select{ |processor| processor.handles?(image_type) }
54
+ return if processors.empty?
55
+
56
+ original_file_size = File.size(filename)
57
+ sorted_results = processors.map do |processor_class|
58
+ output_filename = processor_class.squeeze_to_tmp(filename)
59
+ output_file_size = File.size(output_filename)
60
+ result_options = { :filename => filename, :output_filename => output_filename, :bytes_saved => original_file_size - output_file_size, :output_extension => processor_class.output_extension }
61
+ Result.new(result_options)
62
+ end.sort
63
+
64
+ most_optimized = sorted_results.pop if sorted_results[-1].optimized?
65
+ sorted_results.each do |result|
66
+ FileUtils.rm(result.output_filename)
67
+ end
68
+ most_optimized
69
+ end
70
+
71
+ def squeeze!(filename)
72
+ result = squeeze(filename)
73
+
74
+ output_filename = filename
75
+ output_filename = finalize_result(result) if result
76
+ end
77
+
78
+ def finalize_result(result)
79
+ filename = output_filename = result.filename
80
+ if File.extname(result.filename) != result.output_extension
81
+ output_filename = filename.sub(Regexp.new(Regexp.escape(File.extname(result.filename)) + '$'), result.output_extension)
82
+ FileUtils.cp(result.output_filename, output_filename)
83
+ FileUtils.rm(result.filename)
84
+ else
85
+ FileUtils.cp(result.output_filename, result.filename)
86
+ end
87
+ output_filename
88
+ end
89
+
90
+ def logger
91
+ LogFactory.logger
92
+ end
93
+
94
+ def self.default_processors
95
+ processors = []
96
+ ImageSqueeze::Utils.image_utility_available?('identify', 'all image', Logger::ERROR)
97
+ if ImageSqueeze::Utils.image_utility_available?('pngcrush', 'pngs and gif', Logger::WARN)
98
+ processors << PNGCrushProcessor
99
+ processors << GIFToPNGProcessor if ImageSqueeze::Utils.image_utility_available?('convert', 'gif', Logger::WARN)
100
+ end
101
+ processors << GifsicleProcessor if ImageSqueeze::Utils.image_utility_available?('gifsicle', 'animated gif', Logger::WARN)
102
+ if ImageSqueeze::Utils.image_utility_available?('jpegtran', 'jpeg', Logger::WARN)
103
+ processors << JPEGTranProgressiveProcessor
104
+ processors << JPEGTranNonProgressiveProcessor
105
+ end
106
+ processors
107
+ end
108
+ end
Binary file
Binary file
File without changes
Binary file
Binary file
@@ -0,0 +1,60 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class DefaultProcessorsTest < Test::Unit::TestCase
4
+ def test_png_crush_processor_with_unoptimize_png
5
+ assert_processor_optimizes_file(ImageSqueeze::PNGCrushProcessor, 'unoptimized_png.png')
6
+ end
7
+
8
+ def test_png_crush_processor_with_optimized_png
9
+ assert_processor_doesnt_optimize_file(ImageSqueeze::PNGCrushProcessor, 'already_optimized_png.png')
10
+ end
11
+
12
+ def test_jpegtran_progressive_processor_with_already_optimized
13
+ assert_processor_doesnt_optimize_file(ImageSqueeze::JPEGTranProgressiveProcessor, 'already_optimized_jpg.jpg')
14
+ end
15
+
16
+ def test_jpegtran_progressive_processor_with_progressive_optimized
17
+ assert_processor_optimizes_file(ImageSqueeze::JPEGTranProgressiveProcessor, 'better_as_progressive.jpg')
18
+ end
19
+
20
+ def test_jpegtran_non_progressive_processor_with_already_optimized
21
+ assert_processor_doesnt_optimize_file(ImageSqueeze::JPEGTranNonProgressiveProcessor, 'already_optimized_jpg.jpg')
22
+ end
23
+
24
+ def test_jpegtran_non_progressive_processor_with_progressive_optimized
25
+ assert_processor_optimizes_file(ImageSqueeze::JPEGTranNonProgressiveProcessor, 'better_without_progressive.jpg')
26
+ end
27
+
28
+ def test_gifsicle_processor_with_unoptimized_animated_gif
29
+ assert_processor_optimizes_file(ImageSqueeze::GifsicleProcessor, 'unoptimized_animated_gif.gif')
30
+ end
31
+
32
+ def test_gifsicle_processor_with_already_optimized_animated_gif
33
+ assert_processor_doesnt_optimize_file(ImageSqueeze::GifsicleProcessor, 'already_optimized_animated_gif.gif')
34
+ end
35
+
36
+ def test_gif_to_png_processor_with_unoptimized_gif
37
+ assert_processor_optimizes_file(ImageSqueeze::GIFToPNGProcessor, 'unoptimized_gif.gif')
38
+ end
39
+
40
+ def test_gif_to_png_processor_with_already_optimized_animated_gif
41
+ assert_processor_doesnt_optimize_file(ImageSqueeze::GIFToPNGProcessor, 'already_optimized_gif.gif')
42
+ end
43
+
44
+ private
45
+ def assert_processor_optimizes_file(processor, file)
46
+ squeezer = ImageSqueeze.new(:processors => [processor])
47
+ filename = fixtures(file)
48
+ old_size = File.size(filename)
49
+ new_filename = squeezer.squeeze!(filename)
50
+ assert File.size(new_filename) < old_size, "New file size of #{File.size(new_filename)} should be smaller than original of #{old_size}"
51
+ end
52
+
53
+ def assert_processor_doesnt_optimize_file(processor, file)
54
+ squeezer = ImageSqueeze.new(:processors => [processor])
55
+ filename = fixtures(file)
56
+ old_size = File.size(filename)
57
+ new_filename = squeezer.squeeze!(filename)
58
+ assert File.size(new_filename) >= old_size, "New file size of #{File.size(new_filename)} should be at least as big as original of #{old_size}"
59
+ end
60
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class ImageTypeTest < Test::Unit::TestCase
4
+ def test_image_type_not_found_when_no_file
5
+ image_type = ImageSqueeze.image_type('blah.jpg')
6
+ assert_equal ImageSqueeze::NOT_FOUND, image_type
7
+ end
8
+
9
+ def test_image_type_correctly_identifies_gif
10
+ image_type = ImageSqueeze.image_type(fixtures('already_optimized_gif.gif'))
11
+ assert_equal ImageSqueeze::GIF, image_type
12
+ end
13
+
14
+ def test_image_type_correctly_identifies_animated_gif
15
+ image_type = ImageSqueeze.image_type(fixtures('unoptimized_animated_gif.gif'))
16
+ assert_equal ImageSqueeze::ANIMATED_GIF, image_type
17
+ end
18
+
19
+ def test_image_type_correctly_identifies_png
20
+ image_type = ImageSqueeze.image_type(fixtures('already_optimized_png.png'))
21
+ assert_equal ImageSqueeze::PNG, image_type
22
+ end
23
+
24
+ def test_image_type_correctly_identifies_jpeg
25
+ image_type = ImageSqueeze.image_type(fixtures('already_optimized_jpg.jpg'))
26
+ assert_equal ImageSqueeze::JPEG, image_type
27
+ end
28
+
29
+ def test_image_type_correctly_identifies_non_images
30
+ image_type = ImageSqueeze.image_type(fixtures('textfile.txt'))
31
+ assert_equal ImageSqueeze::UNKNOWN, image_type
32
+ end
33
+
34
+ def test_image_type_correctly_identifies_image_with_wrong_extension
35
+ image_type = ImageSqueeze.image_type(fixtures('mislabeled_gif.png'))
36
+ assert_equal ImageSqueeze::GIF, image_type
37
+ end
38
+
39
+ end
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class ResultTest < Test::Unit::TestCase
4
+ def test_results_sort_by_bytes_saved
5
+ small = ImageSqueeze::Result.new(:bytes_saved => -10)
6
+ medium = ImageSqueeze::Result.new(:bytes_saved => 20)
7
+ big = ImageSqueeze::Result.new(:bytes_saved => 100)
8
+
9
+ assert_equal [small, medium, big], [medium, big, small].sort
10
+ end
11
+ end
@@ -0,0 +1,116 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+ require 'mocha'
3
+
4
+ class SqueezeTest < Test::Unit::TestCase
5
+ def setup
6
+ @image_squeezer = ImageSqueeze.new
7
+ end
8
+
9
+ def test_squeeze_results_in_unoptimized_for_unknown_file_type
10
+ assert !@image_squeezer.squeeze(__FILE__)
11
+ end
12
+
13
+ def test_squeeze_result_is_unoptimized_for_file_not_found
14
+ assert !@image_squeezer.squeeze('thisisnotarealfile.jjj')
15
+ end
16
+
17
+ def test_squeeze_a_image_without_a_processor
18
+ assert !@image_squeezer.squeeze(fixtures('already_optimized_gif.gif'))
19
+ end
20
+
21
+ def test_squeeze_result_is_unoptimized_when_no_optimization_is_made
22
+ image_squeezer = custom_image_squeezer(NeverOptimize)
23
+ assert !image_squeezer.squeeze(fixtures('already_optimized_gif.gif'))
24
+ end
25
+
26
+ def test_squeeze_result_is_deleted_when_no_optimization_is_made
27
+ image_squeezer = custom_image_squeezer(NeverOptimize)
28
+ NeverOptimize.stubs(:tmp_filename).returns(output_filename = File.join(File.dirname(__FILE__), '..', 'tmp', 'tmpfile'))
29
+ assert !image_squeezer.squeeze(fixtures('already_optimized_gif.gif'))
30
+ assert !File.exists?(output_filename)
31
+ end
32
+
33
+ def test_squeeze_result_is_optimized_when_an_optimization_is_made
34
+ image_squeezer = custom_image_squeezer(AlwaysOptimize)
35
+ result = image_squeezer.squeeze(fixtures('already_optimized_gif.gif'))
36
+ assert result.optimized?
37
+ end
38
+
39
+ def test_squeeze_picks_the_best_result_when_processors_return_different_sizes
40
+ image_squeezer = custom_image_squeezer(AlwaysOptimize, NeverOptimize)
41
+ result = image_squeezer.squeeze(fixtures('already_optimized_gif.gif'))
42
+ assert result.optimized?
43
+ end
44
+
45
+ def test_squeezebang_overwrites_original_file
46
+ image_squeezer = custom_image_squeezer(AlwaysOptimize)
47
+ filename = fixtures('already_optimized_gif.gif')
48
+ old_size = File.size(filename)
49
+ result = image_squeezer.squeeze!(filename)
50
+
51
+ assert File.size(filename) < old_size
52
+ end
53
+
54
+ def test_squeezebang_removes_old_file_when_extension_is_changed
55
+ image_squeezer = custom_image_squeezer(PNGOutput)
56
+ filename = fixtures('already_optimized_gif.gif')
57
+ result = image_squeezer.squeeze!(filename)
58
+
59
+ assert !File.exists?(filename), "Old file should be gone"
60
+ assert File.exists?(filename.gsub(/\.gif/, '.png')), "New file should exist"
61
+ end
62
+
63
+ def test_squeezebang_handles_changing_extension_when_dot_extension_is_repeated
64
+ image_squeezer = custom_image_squeezer(PNGOutput)
65
+ filename = fixtures('gif.gif.gif.gif')
66
+ result = image_squeezer.squeeze!(filename)
67
+
68
+ assert !File.exists?(filename), "Old file should be gone"
69
+ assert File.exists?(filename.gsub(/\.gif$/, '.png')), "New file should exist"
70
+ end
71
+
72
+ def test_squeezebang_doesnt_overwrite_when_file_not_optimized
73
+ image_squeezer = custom_image_squeezer(AlwaysBigger)
74
+ filename = fixtures('already_optimized_gif.gif')
75
+ old_size = File.size(filename)
76
+ result = image_squeezer.squeeze!(filename)
77
+ assert_equal old_size, File.size(filename)
78
+ end
79
+
80
+ private
81
+ class AlwaysOptimize < ImageSqueeze::Processor
82
+ def self.squeeze(filename, output_filename)
83
+ `echo 'real small' > #{output_filename}` # this will make the new file really small
84
+ end
85
+
86
+ def self.input_type; ImageSqueeze::GIF; end
87
+ end
88
+
89
+ class NeverOptimize < ImageSqueeze::Processor
90
+ def self.squeeze(filename, output_filename)
91
+ `cp #{filename} #{output_filename}`
92
+ end
93
+
94
+ def self.input_type; ImageSqueeze::GIF; end
95
+ end
96
+
97
+ class AlwaysBigger < ImageSqueeze::Processor
98
+ def self.squeeze(filename, output_filename)
99
+ `cp #{filename} #{output_filename}; echo "hi" >> #{output_filename}`
100
+ end
101
+ def self.input_type; ImageSqueeze::GIF; end
102
+ end
103
+
104
+ class PNGOutput < ImageSqueeze::Processor
105
+ def self.squeeze(filename, output_filename)
106
+ `echo 'real small' > #{output_filename}` # this will make the new file really small
107
+ end
108
+
109
+ def self.input_type; ImageSqueeze::GIF; end
110
+ def self.output_extension; '.png'; end
111
+ end
112
+
113
+ def custom_image_squeezer(*processors)
114
+ ImageSqueeze.new(:processors => processors )
115
+ end
116
+ end
@@ -0,0 +1,12 @@
1
+ require 'test/unit'
2
+ require 'ruby-debug'
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'image_squeeze')
4
+
5
+ class Test::Unit::TestCase
6
+ def fixtures(filename)
7
+ original_file = File.join(File.dirname(__FILE__), 'fixtures', filename)
8
+ new_file = File.join(Dir::tmpdir, filename)
9
+ FileUtils.cp(original_file, new_file)
10
+ new_file
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: image_squeeze
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Andrew Grim
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-17 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: a library for automated lossless image optimization
22
+ email: stopdropandrew@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/image_squeeze/image_identifier.rb
31
+ - lib/image_squeeze/log_factory.rb
32
+ - lib/image_squeeze/processors/gif_to_png_processor.rb
33
+ - lib/image_squeeze/processors/gifsicle_processor.rb
34
+ - lib/image_squeeze/processors/jpeg_tran_non_progressive_processor.rb
35
+ - lib/image_squeeze/processors/jpeg_tran_progressive_processor.rb
36
+ - lib/image_squeeze/processors/png_crush_processor.rb
37
+ - lib/image_squeeze/processors/processor.rb
38
+ - lib/image_squeeze/result.rb
39
+ - lib/image_squeeze/utils.rb
40
+ - lib/image_squeeze.rb
41
+ - test/fixtures/already_optimized_animated_gif.gif
42
+ - test/fixtures/already_optimized_gif.gif
43
+ - test/fixtures/already_optimized_jpg.jpg
44
+ - test/fixtures/already_optimized_png.png
45
+ - test/fixtures/better_as_progressive.jpg
46
+ - test/fixtures/better_without_progressive.jpg
47
+ - test/fixtures/gif.gif.gif.gif
48
+ - test/fixtures/mislabeled_gif.png
49
+ - test/fixtures/textfile.txt
50
+ - test/fixtures/unoptimized_animated_gif.gif
51
+ - test/fixtures/unoptimized_gif.gif
52
+ - test/fixtures/unoptimized_png.png
53
+ - test/functional/default_processors_test.rb
54
+ - test/functional/image_identifier_test.rb
55
+ - test/functional/result_test.rb
56
+ - test/functional/squeeze_test.rb
57
+ - test/test_helper.rb
58
+ - CONTRIBUTORS
59
+ - LICENSE
60
+ - Rakefile
61
+ - README.md
62
+ has_rdoc: true
63
+ homepage: http://github.com/stopdropandrew/image_squeeze
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements:
86
+ - none
87
+ rubyforge_project:
88
+ rubygems_version: 1.3.6
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: a library for automated lossless image optimization
92
+ test_files: []
93
+