croptoelie 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.4.1
data/croptoelie.gemspec CHANGED
@@ -1,11 +1,11 @@
1
- # Generated by jeweler
1
+ # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{croptoelie}
8
- s.version = "0.4.0"
8
+ s.version = "0.4.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Bèr Kessels"]
data/lib/croptoelie.rb CHANGED
@@ -1,33 +1,33 @@
1
1
  require 'RMagick'
2
2
  class CropToelie
3
3
  include Magick
4
-
4
+
5
5
  attr_accessor :orig
6
6
  attr_accessor :steps
7
-
8
- # Create a new CropToelie object from a ImageList single image object.
9
- # If you want to provide a file by its path use CropToelie.from_file('/path/to/image.png').
7
+
8
+ # Create a new CropToelie object from a ImageList single image object.
9
+ # If you want to provide a file by its path use CropToelie.from_file('/path/to/image.png').
10
10
  def initialize(image)
11
11
  @image = image
12
-
12
+
13
13
  # Hardcoded (but overridable) defaults.
14
14
  @steps = 10
15
15
 
16
16
  # Preprocess image.
17
17
  @image = @image.quantize
18
-
18
+
19
19
  # Prepare some often-used internal variables.
20
20
  @rows = @image.rows
21
21
  @columns = @image.columns
22
22
  end
23
-
23
+
24
24
  # Open create a croptoelie from a file on disk.
25
25
  def self.from_file(image_path)
26
26
  image = ImageList.new(image_path).last
27
27
  return CropToelie.new(image)
28
28
  end
29
29
 
30
- # Crops an image to width x height
30
+ # Crops an image to width x height
31
31
  def smart_crop(width, height)
32
32
  sq = square(width, height)
33
33
  return @image.crop!(sq[:left], sq[:top], width, height, true)
@@ -35,11 +35,11 @@ class CropToelie
35
35
 
36
36
  # Squares an image (with smart_square) and then scales that to width, heigh
37
37
  def smart_crop_and_scale(width, height)
38
- smart_square
38
+ smart_square
39
39
  return @image.scale!(width, height)
40
40
  end
41
-
42
- # Squares an image by slicing off the least interesting parts.
41
+
42
+ # Squares an image by slicing off the least interesting parts.
43
43
  # Usefull for squaring images such as thumbnails. Usefull before scaling.
44
44
  def smart_square
45
45
  if @rows != @columns #None-square images must be shaved off.
@@ -52,21 +52,21 @@ class CropToelie
52
52
  sq = square(crop_width, crop_height)
53
53
  @image.crop!(sq[:left], sq[:top], crop_width, crop_height, true)
54
54
  end
55
-
56
- @image
55
+
56
+ @image
57
57
  end
58
-
58
+
59
59
  # Finds the most interesting square with size width x height.
60
- #
60
+ #
61
61
  # Returns a hash {:left => left, :top => top, :right => right, :bottom => bottom}
62
62
  def square(width, height)
63
63
  return smart_crop_by_trim(width, height)
64
64
  end
65
-
65
+
66
66
  private
67
- # Determines if the image should be cropped.
68
- # Image should be cropped if original is larger then requested size.
69
- # In all other cases, it should not.
67
+ # Determines if the image should be cropped.
68
+ # Image should be cropped if original is larger then requested size.
69
+ # In all other cases, it should not.
70
70
  def should_crop?
71
71
  return (@columns > @width) && (@rows < @height)
72
72
  end
@@ -77,38 +77,41 @@ class CropToelie
77
77
  width, height = right, bottom
78
78
  step_size = step_size(requested_x, requested_y)
79
79
 
80
- # Slice from left and right edges until the correct width is reached.
81
- while (width > requested_x)
82
- slice_width = [(width - requested_x), step_size].min
80
+ # Avoid attempts to slice less then one pixel.
81
+ if step_size > 0
82
+ # Slice from left and right edges until the correct width is reached.
83
+ while (width > requested_x)
84
+ slice_width = [(width - requested_x), step_size].min
83
85
 
84
- left_entropy = entropy_slice(@image, left, 0, slice_width, bottom)
85
- right_entropy = entropy_slice(@image, (right - slice_width), 0, slice_width, bottom)
86
+ left_entropy = entropy_slice(@image, left, 0, slice_width, bottom)
87
+ right_entropy = entropy_slice(@image, (right - slice_width), 0, slice_width, bottom)
86
88
 
87
- #remove the slice with the least entropy
88
- if left_entropy < right_entropy
89
- left += slice_width
90
- else
91
- right -= slice_width
89
+ #remove the slice with the least entropy
90
+ if left_entropy < right_entropy
91
+ left += slice_width
92
+ else
93
+ right -= slice_width
94
+ end
95
+
96
+ width = (right - left)
92
97
  end
93
-
94
- width = (right - left)
95
- end
96
98
 
97
- # Slice from top and bottom edges until the correct height is reached.
98
- while (height > requested_y)
99
- slice_height = [(height - step_size), step_size].min
100
-
101
- top_entropy = entropy_slice(@image, 0, top, @columns, slice_height)
102
- bottom_entropy = entropy_slice(@image, 0, (bottom - slice_height), @columns, slice_height)
103
-
104
- #remove the slice with the least entropy
105
- if top_entropy < bottom_entropy
106
- top += slice_height
107
- else
108
- bottom -= slice_height
99
+ # Slice from top and bottom edges until the correct height is reached.
100
+ while (height > requested_y)
101
+ slice_height = [(height - step_size), step_size].min
102
+
103
+ top_entropy = entropy_slice(@image, 0, top, @columns, slice_height)
104
+ bottom_entropy = entropy_slice(@image, 0, (bottom - slice_height), @columns, slice_height)
105
+
106
+ #remove the slice with the least entropy
107
+ if top_entropy < bottom_entropy
108
+ top += slice_height
109
+ else
110
+ bottom -= slice_height
111
+ end
112
+
113
+ height = (bottom - top)
109
114
  end
110
-
111
- height = (bottom - top)
112
115
  end
113
116
 
114
117
  square = {:left => left, :top => top, :right => right, :bottom => bottom}
@@ -119,22 +122,22 @@ class CropToelie
119
122
  slice = image_data.crop(x, y, width, height)
120
123
  entropy = entropy(slice)
121
124
  end
122
-
125
+
123
126
  # Compute the entropy of an image, defined as -sum(p.*log2(p)).
124
127
  # Note: instead of log2, only available in ruby > 1.9, we use
125
128
  # log(p)/log(2). which has the same effect.
126
129
  def entropy(image_slice)
127
130
  hist = image_slice.color_histogram
128
131
  hist_size = hist.values.inject{|sum,x| sum ? sum + x : x }.to_f
129
-
132
+
130
133
  entropy = 0
131
- hist.values.each do |h|
134
+ hist.values.each do |h|
132
135
  p = h.to_f / hist_size
133
136
  entropy += (p * (Math.log(p)/Math.log(2))) if p != 0
134
137
  end
135
138
  return entropy * -1
136
139
  end
137
-
140
+
138
141
  def step_size(requested_x, requested_y)
139
142
  ((([@rows - requested_x, @columns - requested_y].max)/2)/@steps).to_i
140
143
  end
Binary file
File without changes
@@ -0,0 +1 @@
1
+ This is not an image.
Binary file
Binary file
Binary file
@@ -2,9 +2,14 @@ require 'helper'
2
2
 
3
3
  class TestCroptoelie < Test::Unit::TestCase
4
4
  def setup
5
- @filename = File.join(File.expand_path(File.dirname(__FILE__)), "entropyish.png")
5
+ @filename = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", "entropyish.png")
6
6
  @image = Magick::ImageList.new(@filename).last
7
+
8
+ @twenty_twenty = Magick::ImageList.new(
9
+ File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", "20x20.png")
10
+ ).last
7
11
  end
12
+
8
13
  should "initialize a croptoelie image from an ImageList item" do
9
14
  img = CropToelie.new(@image)
10
15
  assert_equal(img.class, CropToelie)
@@ -16,27 +21,59 @@ class TestCroptoelie < Test::Unit::TestCase
16
21
 
17
22
  should "fail on creating a croptoelie image from a textfile" do
18
23
  assert_raise Magick::ImageMagickError, NoMethodError do
19
- CropToelie.new(File.join(File.expand_path(File.dirname(__FILE__)), "entropyish.txt"))
24
+ CropToelie.new(File.join(File.expand_path(File.dirname(__FILE__)), "fixtures","entropyish.txt"))
20
25
  end
21
26
  end
22
-
27
+
23
28
  should "crop to 100x100 without scaling with smart_crop" do
24
29
  img = CropToelie.new(@image)
25
30
  img = img.smart_crop(100, 100)
26
31
  size = [img.rows, img.columns]
27
32
  assert_equal(size, [100, 100])
28
33
  end
29
-
34
+
30
35
  should "crop to 100x100 with scaling with smart_crop_and_scale" do
31
36
  img = CropToelie.new(@image)
32
37
  img = img.smart_crop_and_scale(100, 100)
33
38
  size = [img.rows, img.columns]
34
39
  assert_equal(size, [100, 100])
35
40
  end
36
-
41
+
37
42
  should "square image without scaling" do
38
43
  img = CropToelie.new(@image)
39
44
  img = img.smart_square
40
45
  assert_equal(img.rows, img.columns)
41
46
  end
47
+
48
+ should "not crop small images" do
49
+ img = CropToelie.new(@twenty_twenty)
50
+ img = img.smart_crop(100, 100)
51
+ size = [img.rows, img.columns]
52
+ assert_equal([20, 20], size)
53
+ end
54
+
55
+ should "still crop a slice of one pixel" do
56
+ img = CropToelie.new(@twenty_twenty)
57
+ img = img.smart_crop(19, 19)
58
+ size = [img.rows, img.columns]
59
+ assert_equal([19, 19], size)
60
+ end
61
+
62
+ ###########################################################################
63
+ # Images reported to fail by issue #5 #
64
+ ###########################################################################
65
+ [:smart_crop, :smart_crop_and_scale, :smart_square].each do |method|
66
+ full_path = File.join File.dirname(__FILE__), "fixtures", "errors"
67
+ Dir.open(full_path).select{|f| !File.directory?(f)}.each do |file|
68
+
69
+ should "'not fail on reported-as-broken image '#{file}' with '#{method}'" do
70
+ realpath = File.realpath(File.join full_path, file)
71
+
72
+ img = CropToelie.new(Magick::ImageList.new(realpath).last)
73
+ img = img.smart_crop(200, 200)
74
+ size = [img.rows, img.columns]
75
+ assert_equal(size, [200, 200])
76
+ end
77
+ end
78
+ end
42
79
  end
metadata CHANGED
@@ -1,96 +1,126 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: croptoelie
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
4
5
  prerelease:
5
- version: 0.4.0
6
6
  platform: ruby
7
- authors:
8
- - "B\xC3\xA8r Kessels"
7
+ authors:
8
+ - Bèr Kessels
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-04-19 00:00:00 +02:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2012-11-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: rmagick
18
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
19
17
  none: false
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
24
22
  type: :runtime
25
23
  prerelease: false
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
28
31
  name: shoulda
29
- requirement: &id002 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
30
33
  none: false
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: "0"
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
35
38
  type: :development
36
39
  prerelease: false
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
39
47
  name: bundler
40
- requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
41
49
  none: false
42
- requirements:
50
+ requirements:
43
51
  - - ~>
44
- - !ruby/object:Gem::Version
52
+ - !ruby/object:Gem::Version
45
53
  version: 1.0.0
46
54
  type: :development
47
55
  prerelease: false
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ - !ruby/object:Gem::Dependency
50
63
  name: jeweler
51
- requirement: &id004 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
52
65
  none: false
53
- requirements:
66
+ requirements:
54
67
  - - ~>
55
- - !ruby/object:Gem::Version
68
+ - !ruby/object:Gem::Version
56
69
  version: 1.5.2
57
70
  type: :development
58
71
  prerelease: false
59
- version_requirements: *id004
60
- - !ruby/object:Gem::Dependency
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.5.2
78
+ - !ruby/object:Gem::Dependency
61
79
  name: rcov
62
- requirement: &id005 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
63
81
  none: false
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: "0"
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
68
86
  type: :development
69
87
  prerelease: false
70
- version_requirements: *id005
71
- - !ruby/object:Gem::Dependency
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
72
95
  name: rmagick
73
- requirement: &id006 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
74
97
  none: false
75
- requirements:
76
- - - ">"
77
- - !ruby/object:Gem::Version
98
+ requirements:
99
+ - - ! '>'
100
+ - !ruby/object:Gem::Version
78
101
  version: 2.11.0
79
102
  type: :runtime
80
103
  prerelease: false
81
- version_requirements: *id006
82
- description: "Crops images based on entropy: leaving the most interesting part intact. Don't expect this to be a replacement for human cropping, it is an algorythm and not an extremely smart one at that :). Best results achieved in combination with scaling: the cropping is then only used to square the image, cutting off the least interesting part. The trimming simply chops off te edge that is least interesting, and continues doing so, untill it reached the requested size."
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>'
108
+ - !ruby/object:Gem::Version
109
+ version: 2.11.0
110
+ description: ! 'Crops images based on entropy: leaving the most interesting part intact.
111
+ Don''t expect this to be a replacement for human cropping, it is an algorythm and
112
+ not an extremely smart one at that :). Best results achieved in combination with
113
+ scaling: the cropping is then only used to square the image, cutting off the least
114
+ interesting part. The trimming simply chops off te edge that is least interesting,
115
+ and continues doing so, untill it reached the requested size.'
83
116
  email: ber@webschuur.com
84
117
  executables: []
85
-
86
118
  extensions: []
87
-
88
- extra_rdoc_files:
119
+ extra_rdoc_files:
89
120
  - LICENSE.txt
90
121
  - README.md
91
- files:
122
+ files:
92
123
  - .document
93
- - .rvmrc
94
124
  - Gemfile
95
125
  - Gemfile.lock
96
126
  - LICENSE.txt
@@ -102,43 +132,45 @@ files:
102
132
  - doc/croptoelie_test.rb
103
133
  - doc/histogram.rb
104
134
  - lib/croptoelie.rb
105
- - test/entropyish.png
106
- - test/entropyish.txt
135
+ - test/fixtures/20x20.png
136
+ - test/fixtures/entropyish.png
137
+ - test/fixtures/entropyish.txt
138
+ - test/fixtures/errors/flo-rida.png
139
+ - test/fixtures/errors/hollywood-undead.jpg
140
+ - test/fixtures/errors/maroon-5.jpg
141
+ - test/fixtures/errors/yo-gotti.jpg
107
142
  - test/helper.rb
108
143
  - test/profiler.rb
109
144
  - test/test_croptoelie.rb
110
- has_rdoc: true
111
145
  homepage: http://github.com/berkes/croptoelie
112
- licenses:
146
+ licenses:
113
147
  - MIT
114
148
  post_install_message:
115
149
  rdoc_options: []
116
-
117
- require_paths:
150
+ require_paths:
118
151
  - lib
119
- required_ruby_version: !ruby/object:Gem::Requirement
152
+ required_ruby_version: !ruby/object:Gem::Requirement
120
153
  none: false
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- hash: -831961247
125
- segments:
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ segments:
126
159
  - 0
127
- version: "0"
128
- required_rubygems_version: !ruby/object:Gem::Requirement
160
+ hash: 259147617
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
162
  none: false
130
- requirements:
131
- - - ">="
132
- - !ruby/object:Gem::Version
133
- version: "0"
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
134
167
  requirements: []
135
-
136
168
  rubyforge_project:
137
- rubygems_version: 1.5.2
169
+ rubygems_version: 1.8.24
138
170
  signing_key:
139
171
  specification_version: 3
140
172
  summary: Content aware cropper.
141
- test_files:
173
+ test_files:
142
174
  - test/helper.rb
143
175
  - test/profiler.rb
144
176
  - test/test_croptoelie.rb
data/.rvmrc DELETED
@@ -1 +0,0 @@
1
- rvm ruby-1.9.2-p0@croptoelie
data/test/entropyish.txt DELETED
File without changes