tinyimg 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -2
- data/README.md +48 -23
- data/ext/tinyimg/tinyimg.c +49 -3
- data/lib/tinyimg.rb +57 -4
- data/spec/tinyimg_spec.rb +89 -4
- data/tinyimg.gemspec +3 -3
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 167b894e73f7dad09bfff8a20d4374d2dcd822f2
|
4
|
+
data.tar.gz: f92b7f089edb704b6d60cc0c3d58420b444b9d10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56d0aa8dd6ca5fab4572ee9bf04416c8d6a90b0a0c5046f23f45fd9d03008aa71acabec8f5c10a5d702e3c0190a42b758e22438189ecb934d5c8d938a1f852f1
|
7
|
+
data.tar.gz: 5533db6f10236e0eff9a4edb42758a354779ccf5339b22184a24c853a02d37f512bd0d03a4af5f0d41ffa095a0e7930c3bc1f89b2b66fa0c5e6b0f0e3804a9c8
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Tinyimg
|
2
2
|
|
3
|
-
Load a JPEG or PNG, get its dimensions, resize it, and extract it again as either a JPEG or PNG.
|
3
|
+
Load a JPEG or PNG, get its dimensions, crop it, resize it, and extract it again as either a JPEG or PNG.
|
4
4
|
|
5
5
|
This gem can work from image data stored in memory, as well as from a file or IO stream.
|
6
6
|
It's been coded to be as efficient as possible, so doesn't use temporary files.
|
@@ -28,46 +28,71 @@ Then add tinyimg to your project's Gemfile
|
|
28
28
|
|
29
29
|
Load the image with the method that fits your use case:
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
```ruby
|
32
|
+
image = Tinyimg.from_string(an_image_that_is_already_loaded)
|
33
|
+
image = Tinyimg.from_file("some_image.png")
|
34
|
+
image = Tinyimg.from_io(params[:uploaded_file])
|
35
|
+
```
|
34
36
|
|
35
|
-
|
37
|
+
Resize it by using one of these methods:
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
```ruby
|
40
|
+
image.resize_exact!(100, 100) # forces image to be exactly 100x100
|
41
|
+
image.resize!(width: 100) # width = 100 and aspect ratio maintained
|
42
|
+
image.resize!(height: 50) # height = 50 and aspect ratio maintained
|
43
|
+
image.resize_to_fit!(100, 100) # image will be 100x100 maximum
|
44
|
+
image.resize_to_fill!(100, 100) # image will be 100x100 minimum
|
45
|
+
```
|
46
|
+
|
47
|
+
Crop it using this method:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# Crops the image from (20, 20) to (70, 70), resulting in a 50x50 image.
|
51
|
+
image.crop!(x: 20, y: 20, width: 50, height: 50)
|
52
|
+
|
53
|
+
# By default, x and y arguments are zero, and width and height arguments
|
54
|
+
# are the width and height of the original image.
|
55
|
+
image.crop!(width: image.height)
|
56
|
+
```
|
40
57
|
|
41
58
|
Then get an image back:
|
42
59
|
|
43
|
-
|
44
|
-
|
45
|
-
|
60
|
+
```ruby
|
61
|
+
image.to_png # returns a string
|
62
|
+
image.to_jpeg # returns a string
|
63
|
+
image.save("some_image.jpg") # file type auto-determined by extension
|
64
|
+
```
|
46
65
|
|
47
66
|
You can ask for the image's dimensions:
|
48
67
|
|
49
|
-
|
50
|
-
|
51
|
-
|
68
|
+
```ruby
|
69
|
+
image.width # => 120
|
70
|
+
image.height # => 80
|
71
|
+
image.dimensions # => [120, 80]
|
72
|
+
```
|
52
73
|
|
53
|
-
You can also use the non-! versions of the
|
74
|
+
You can also use the non-! versions of the operation methods: `resize_exact`, `resize`, `resize_to_fit`, `resize_to_fill` and `crop`.
|
54
75
|
These create a new image in memory and return it, leaving the old image untouched. This is useful if you want
|
55
76
|
to resize an original image to multiple sizes. Using these methods will take more memory.
|
56
77
|
|
57
78
|
## Examples
|
58
79
|
|
59
|
-
Take an uploaded file, save the original as a JPEG, then resize to create a thumbnail and save that too:
|
80
|
+
Take an uploaded file that's a PNG or JPEG, save the original as a JPEG, then resize to create a thumbnail and save that too:
|
60
81
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
82
|
+
```ruby
|
83
|
+
Tinyimg
|
84
|
+
.from_io(params[:uploaded_file])
|
85
|
+
.save("#{path}/full_size.jpg")
|
86
|
+
.resize_to_fit!(100, 100)
|
87
|
+
.save("#{path}/thumbnail.jpg")
|
88
|
+
```
|
66
89
|
|
67
90
|
Load a file from disk, make a thumbnail, and return it as a JPEG so we can save it into our database:
|
68
91
|
|
69
|
-
|
70
|
-
|
92
|
+
```ruby
|
93
|
+
data = Tinyimg.from_file(image_filename).resize_to_fit!(100, 100).to_jpeg
|
94
|
+
user.update!(thumbnail_image: data)
|
95
|
+
```
|
71
96
|
|
72
97
|
## Author and licence
|
73
98
|
|
data/ext/tinyimg/tinyimg.c
CHANGED
@@ -203,7 +203,7 @@ VALUE to_png(int argc, VALUE *argv, VALUE self)
|
|
203
203
|
return output;
|
204
204
|
}
|
205
205
|
|
206
|
-
VALUE
|
206
|
+
VALUE resize_exact_bang(VALUE self, VALUE width_value, VALUE height_value)
|
207
207
|
{
|
208
208
|
gdImagePtr image_in, image_out;
|
209
209
|
int width, height;
|
@@ -214,7 +214,7 @@ VALUE resize_bang(VALUE self, VALUE width_value, VALUE height_value)
|
|
214
214
|
width = FIX2INT(width_value);
|
215
215
|
height = FIX2INT(height_value);
|
216
216
|
|
217
|
-
if (width
|
217
|
+
if (width <= 0 || height <= 0) {
|
218
218
|
rb_raise(rb_eArgError, "width and height must both be positive integers");
|
219
219
|
}
|
220
220
|
|
@@ -235,17 +235,63 @@ VALUE resize_bang(VALUE self, VALUE width_value, VALUE height_value)
|
|
235
235
|
return self;
|
236
236
|
}
|
237
237
|
|
238
|
+
VALUE internal_crop_bang(VALUE self, VALUE x_value, VALUE y_value, VALUE width_value, VALUE height_value)
|
239
|
+
{
|
240
|
+
gdImagePtr image_in, image_out;
|
241
|
+
int x, y, width, height;
|
242
|
+
|
243
|
+
Check_Type(x_value, T_FIXNUM);
|
244
|
+
Check_Type(y_value, T_FIXNUM);
|
245
|
+
Check_Type(width_value, T_FIXNUM);
|
246
|
+
Check_Type(height_value, T_FIXNUM);
|
247
|
+
|
248
|
+
x = FIX2INT(x_value);
|
249
|
+
y = FIX2INT(y_value);
|
250
|
+
width = FIX2INT(width_value);
|
251
|
+
height = FIX2INT(height_value);
|
252
|
+
|
253
|
+
if (x < 0 || y < 0) {
|
254
|
+
rb_raise(rb_eArgError, "x, y must both be zero or positive integers");
|
255
|
+
}
|
256
|
+
|
257
|
+
if (width <= 0 || height <= 0) {
|
258
|
+
rb_raise(rb_eArgError, "width and height must both be positive integers");
|
259
|
+
}
|
260
|
+
|
261
|
+
image_in = get_image_data(self);
|
262
|
+
|
263
|
+
if (x + width > gdImageSX(image_in)) {
|
264
|
+
rb_raise(rb_eArgError, "x + width is greater than the original image's width");
|
265
|
+
}
|
266
|
+
|
267
|
+
if (y + height > gdImageSY(image_in)) {
|
268
|
+
rb_raise(rb_eArgError, "y + height is greater than the original image's height");
|
269
|
+
}
|
270
|
+
|
271
|
+
image_out = gdImageCreateTrueColor(width, height);
|
272
|
+
set_alpha(image_out);
|
273
|
+
|
274
|
+
gdImageCopy(image_out, image_in, 0, 0, x, y, width, height);
|
275
|
+
|
276
|
+
set_image_data(self, image_out);
|
277
|
+
|
278
|
+
retrieve_image_dimensions(self);
|
279
|
+
|
280
|
+
return self;
|
281
|
+
}
|
282
|
+
|
238
283
|
void Init_tinyimg()
|
239
284
|
{
|
240
285
|
VALUE cTinyimg = rb_define_class("Tinyimg", rb_cObject);
|
241
286
|
rb_define_class_under(cTinyimg, "Image", rb_cObject);
|
242
287
|
|
243
|
-
rb_define_method(cTinyimg, "
|
288
|
+
rb_define_method(cTinyimg, "resize_exact!", resize_exact_bang, 2);
|
244
289
|
rb_define_method(cTinyimg, "to_jpeg", to_jpeg, -1);
|
245
290
|
rb_define_method(cTinyimg, "to_png", to_png, -1);
|
246
291
|
rb_define_private_method(cTinyimg, "initialize_copy", initialize_copy, -1);
|
247
292
|
rb_define_private_method(cTinyimg, "load_from_string", load_from_string, 2);
|
248
293
|
rb_define_private_method(cTinyimg, "retrieve_image_dimensions", retrieve_image_dimensions, 0);
|
294
|
+
rb_define_private_method(cTinyimg, "internal_crop!", internal_crop_bang, 4);
|
249
295
|
#ifdef HAVE_GDIMAGECREATEFROMFILE
|
250
296
|
rb_define_private_method(cTinyimg, "load_from_file", load_from_file, 1);
|
251
297
|
#endif
|
data/lib/tinyimg.rb
CHANGED
@@ -21,12 +21,28 @@ class Tinyimg
|
|
21
21
|
[width, height]
|
22
22
|
end
|
23
23
|
|
24
|
-
def resize(
|
25
|
-
dup.resize!(
|
24
|
+
def resize(*args)
|
25
|
+
dup.resize!(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def resize!(*args)
|
29
|
+
case args.map(&:class)
|
30
|
+
when [Fixnum, Fixnum]
|
31
|
+
resize_exact!(*args)
|
32
|
+
when [Hash]
|
33
|
+
width, height = convert_hash_to_exact_dimensions(args.first)
|
34
|
+
resize_exact!(width, height)
|
35
|
+
else
|
36
|
+
raise ArgumentError, "#resize and #resize! accept either (width, height) or a hash with :width and/or :height keys"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def resize_exact(width, height)
|
41
|
+
dup.resize_exact!(width, height)
|
26
42
|
end
|
27
43
|
|
28
44
|
# Implemented in C
|
29
|
-
# def
|
45
|
+
# def resize_exact!(width, height)
|
30
46
|
# end
|
31
47
|
|
32
48
|
def resize_to_fit(new_width, new_height)
|
@@ -49,6 +65,17 @@ class Tinyimg
|
|
49
65
|
end
|
50
66
|
end
|
51
67
|
|
68
|
+
def crop(**kwargs)
|
69
|
+
dup.crop!(**kwargs)
|
70
|
+
end
|
71
|
+
|
72
|
+
def crop!(x: 0, y: 0, width: nil, height: nil)
|
73
|
+
width ||= self.width
|
74
|
+
height ||= self.height
|
75
|
+
|
76
|
+
internal_crop!(x, y, width, height)
|
77
|
+
end
|
78
|
+
|
52
79
|
def save(filename)
|
53
80
|
if respond_to?(:save_to_file)
|
54
81
|
save_to_file(filename)
|
@@ -60,6 +87,8 @@ class Tinyimg
|
|
60
87
|
|
61
88
|
File.write(filename, data)
|
62
89
|
end
|
90
|
+
|
91
|
+
self
|
63
92
|
end
|
64
93
|
|
65
94
|
# Implemented in C
|
@@ -100,7 +129,7 @@ class Tinyimg
|
|
100
129
|
resize_height = new_height
|
101
130
|
end
|
102
131
|
|
103
|
-
|
132
|
+
resize_exact!(resize_width, resize_height)
|
104
133
|
end
|
105
134
|
|
106
135
|
def determine_type(data)
|
@@ -122,10 +151,34 @@ class Tinyimg
|
|
122
151
|
end
|
123
152
|
end
|
124
153
|
|
154
|
+
def convert_hash_to_exact_dimensions(opts)
|
155
|
+
if opts.empty? || !(opts.keys - [:width, :height]).empty?
|
156
|
+
raise ArgumentError, "expected either :width or :height or both keys"
|
157
|
+
end
|
158
|
+
|
159
|
+
if opts.values.any? { |v| !v.is_a?(Fixnum) }
|
160
|
+
raise ArgumentError, ":width and :height values must be integers"
|
161
|
+
end
|
162
|
+
|
163
|
+
new_width, new_height = opts[:width], opts[:height]
|
164
|
+
|
165
|
+
if new_height.nil?
|
166
|
+
new_height = height * new_width / width
|
167
|
+
elsif new_width.nil?
|
168
|
+
new_width = width * new_height / height
|
169
|
+
end
|
170
|
+
|
171
|
+
[new_width, new_height]
|
172
|
+
end
|
173
|
+
|
125
174
|
# Implemented in C
|
126
175
|
# def load_from_string(data, type)
|
127
176
|
# end
|
128
177
|
|
178
|
+
# Implemented in C
|
179
|
+
# def internal_crop!(x, y, width, height)
|
180
|
+
# end
|
181
|
+
|
129
182
|
# Implemented in C
|
130
183
|
# Only available with libgd 2.1.1+
|
131
184
|
# def load_from_file(filename)
|
data/spec/tinyimg_spec.rb
CHANGED
@@ -33,23 +33,83 @@ RSpec.describe Tinyimg do
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
describe "#
|
36
|
+
describe "#resize_exact" do
|
37
37
|
it "resizes the image as requested, creating a new image" do
|
38
|
-
result = sample.
|
38
|
+
result = sample.resize_exact(100, 100)
|
39
39
|
expect(result).to_not eql sample
|
40
40
|
expect(sample.dimensions).to eq [200, 153]
|
41
41
|
expect(result.dimensions).to eq [100, 100]
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
describe "#
|
45
|
+
describe "#resize_exact!" do
|
46
46
|
it "resizes the image as requested" do
|
47
|
-
result = sample.
|
47
|
+
result = sample.resize_exact!(100, 100)
|
48
48
|
expect(result).to eql sample
|
49
49
|
expect(sample.dimensions).to eq [100, 100]
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
+
describe "#resize" do
|
54
|
+
it "takes an exact width and height and resizes" do
|
55
|
+
result = sample.resize(100, 100)
|
56
|
+
expect(result.dimensions).to eq [100, 100]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "takes a width and height as a hash and resizes" do
|
60
|
+
result = sample.resize(width: 100, height: 100)
|
61
|
+
expect(result.dimensions).to eq [100, 100]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "takes just a width and resizes, calculating the height" do
|
65
|
+
result = sample.resize(width: 100)
|
66
|
+
expect(result.dimensions).to eq [100, 76]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "takes just a height and resizes, calculating the width" do
|
70
|
+
result = sample.resize(height: 100)
|
71
|
+
expect(result.dimensions).to eq [130, 100]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#resize!" do
|
76
|
+
it "takes an exact width and height and resizes" do
|
77
|
+
result = sample.resize!(100, 100)
|
78
|
+
expect(sample.dimensions).to eq [100, 100]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "takes a width and height as a hash and resizes" do
|
82
|
+
result = sample.resize!(width: 100, height: 100)
|
83
|
+
expect(sample.dimensions).to eq [100, 100]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "takes just a width and resizes, calculating the height" do
|
87
|
+
result = sample.resize!(width: 100)
|
88
|
+
expect(sample.dimensions).to eq [100, 76]
|
89
|
+
end
|
90
|
+
|
91
|
+
it "takes just a height and resizes, calculating the width" do
|
92
|
+
result = sample.resize!(height: 100)
|
93
|
+
expect(sample.dimensions).to eq [130, 100]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "raises if other keys are provided" do
|
97
|
+
expect { sample.resize!(something: 123) }.to raise_error(ArgumentError)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "raises if non-integer values are provided" do
|
101
|
+
expect { sample.resize!(width: "123") }.to raise_error(ArgumentError)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "raises if no keys are provided" do
|
105
|
+
expect { sample.resize! }.to raise_error(ArgumentError)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "raises if only one argument is provided" do
|
109
|
+
expect { sample.resize!(123) }.to raise_error(ArgumentError)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
53
113
|
describe "#resize_to_fit!" do
|
54
114
|
it "calculates the image dimensions so it fits the width and resizes" do
|
55
115
|
sample.resize_to_fit!(100, 100)
|
@@ -74,6 +134,31 @@ RSpec.describe Tinyimg do
|
|
74
134
|
end
|
75
135
|
end
|
76
136
|
|
137
|
+
describe "#crop" do
|
138
|
+
it "crops the image to the specified size and offset" do
|
139
|
+
result = sample.crop(x: 10, y: 20, width: 30, height: 40)
|
140
|
+
expect(result.dimensions).to eq [30, 40]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "#crop!" do
|
145
|
+
it "crops the image to the specified size and offset" do
|
146
|
+
sample.crop!(x: 10, y: 20, width: 30, height: 40)
|
147
|
+
expect(sample.dimensions).to eq [30, 40]
|
148
|
+
end
|
149
|
+
|
150
|
+
it "defaults x and y to zero and width and height to the original width and height" do
|
151
|
+
sample.crop!
|
152
|
+
expect(sample.dimensions).to eq [200, 153]
|
153
|
+
end
|
154
|
+
|
155
|
+
it "raises if the requested area is not inside the original image" do
|
156
|
+
expect { sample.crop!(x: 10, y: 20, width: 195, height: 40) }.to raise_error(ArgumentError)
|
157
|
+
expect { sample.crop!(x: 10, y: 20, width: 95, height: 140) }.to raise_error(ArgumentError)
|
158
|
+
expect { sample.crop!(width: 95, height: 160) }.to raise_error(ArgumentError)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
77
162
|
describe "#save" do
|
78
163
|
it "saves a JPEG" do
|
79
164
|
begin
|
data/tinyimg.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = 'tinyimg'
|
3
|
-
gem.version = '0.1.
|
4
|
-
gem.summary = "Tiny and fast JPEG/PNG
|
5
|
-
gem.description = "Convert between JPEG/PNG and resize images, either all in memory or via disk. Only
|
3
|
+
gem.version = '0.1.1'
|
4
|
+
gem.summary = "Tiny and fast JPEG/PNG cropping and resizing"
|
5
|
+
gem.description = "Convert between JPEG/PNG, crop and resize images, either all in memory or via disk. Only requires libgd to function."
|
6
6
|
gem.has_rdoc = false
|
7
7
|
gem.author = "Roger Nesbitt"
|
8
8
|
gem.email = "roger@seriousorange.com"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tinyimg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roger Nesbitt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -24,8 +24,8 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.0'
|
27
|
-
description: Convert between JPEG/PNG and resize images, either all in memory
|
28
|
-
disk. Only
|
27
|
+
description: Convert between JPEG/PNG, crop and resize images, either all in memory
|
28
|
+
or via disk. Only requires libgd to function.
|
29
29
|
email: roger@seriousorange.com
|
30
30
|
executables: []
|
31
31
|
extensions:
|
@@ -64,10 +64,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
64
|
version: '0'
|
65
65
|
requirements: []
|
66
66
|
rubyforge_project:
|
67
|
-
rubygems_version: 2.
|
67
|
+
rubygems_version: 2.4.8
|
68
68
|
signing_key:
|
69
69
|
specification_version: 4
|
70
|
-
summary: Tiny and fast JPEG/PNG
|
70
|
+
summary: Tiny and fast JPEG/PNG cropping and resizing
|
71
71
|
test_files:
|
72
72
|
- spec/samples/duck.png
|
73
73
|
- spec/tinyimg_spec.rb
|