image_squeeze 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.
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
+