mini_magick 3.8.1 → 4.0.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.
- checksums.yaml +4 -4
- data/lib/mini_gmagick.rb +2 -1
- data/lib/mini_magick/configuration.rb +136 -0
- data/lib/mini_magick/image/info.rb +122 -0
- data/lib/mini_magick/image.rb +380 -334
- data/lib/mini_magick/logger.rb +40 -0
- data/lib/mini_magick/shell.rb +48 -0
- data/lib/mini_magick/tool/animate.rb +14 -0
- data/lib/mini_magick/tool/compare.rb +14 -0
- data/lib/mini_magick/tool/composite.rb +14 -0
- data/lib/mini_magick/tool/conjure.rb +14 -0
- data/lib/mini_magick/tool/convert.rb +14 -0
- data/lib/mini_magick/tool/display.rb +14 -0
- data/lib/mini_magick/tool/identify.rb +14 -0
- data/lib/mini_magick/tool/import.rb +14 -0
- data/lib/mini_magick/tool/mogrify.rb +14 -0
- data/lib/mini_magick/tool/montage.rb +14 -0
- data/lib/mini_magick/tool/stream.rb +14 -0
- data/lib/mini_magick/tool.rb +250 -0
- data/lib/mini_magick/utilities.rb +23 -50
- data/lib/mini_magick/version.rb +5 -5
- data/lib/mini_magick.rb +43 -65
- data/spec/fixtures/animation.gif +0 -0
- data/spec/fixtures/default.jpg +0 -0
- data/spec/fixtures/exif.jpg +0 -0
- data/spec/fixtures/image.psd +0 -0
- data/spec/fixtures/not_an_image.rb +1 -0
- data/spec/lib/mini_magick/configuration_spec.rb +66 -0
- data/spec/lib/mini_magick/image_spec.rb +367 -406
- data/spec/lib/mini_magick/shell_spec.rb +66 -0
- data/spec/lib/mini_magick/tool_spec.rb +107 -0
- data/spec/lib/mini_magick/utilities_spec.rb +17 -0
- data/spec/lib/mini_magick_spec.rb +23 -47
- data/spec/spec_helper.rb +17 -25
- data/spec/support/helpers.rb +37 -0
- metadata +40 -74
- data/lib/mini_magick/command_builder.rb +0 -94
- data/lib/mini_magick/errors.rb +0 -4
- data/spec/files/actually_a_gif.jpg +0 -0
- data/spec/files/animation.gif +0 -0
- data/spec/files/composited.jpg +0 -0
- data/spec/files/erroneous.jpg +0 -0
- data/spec/files/layers.psd +0 -0
- data/spec/files/leaves (spaced).tiff +0 -0
- data/spec/files/not_an_image.php +0 -1
- data/spec/files/png.png +0 -0
- data/spec/files/simple-minus.gif +0 -0
- data/spec/files/simple.gif +0 -0
- data/spec/files/trogdor.jpg +0 -0
- data/spec/files/trogdor_capitalized.JPG +0 -0
- data/spec/lib/mini_magick/command_builder_spec.rb +0 -153
|
@@ -1,499 +1,460 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
context
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "pathname"
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "stringio"
|
|
6
|
+
|
|
7
|
+
["ImageMagick", "GraphicsMagick"].each do |cli|
|
|
8
|
+
RSpec.context "With #{cli}", cli: cli.downcase.to_sym do
|
|
9
|
+
describe MiniMagick::Image do
|
|
10
|
+
subject { described_class.open(image_path) }
|
|
11
|
+
|
|
12
|
+
describe ".read" do
|
|
13
|
+
it "reads image from String" do
|
|
14
|
+
string = File.binread(image_path)
|
|
15
|
+
image = described_class.read(string)
|
|
16
|
+
expect(image).to be_valid
|
|
17
|
+
end
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
it "reads image from StringIO" do
|
|
20
|
+
stringio = StringIO.new(File.binread(image_path))
|
|
21
|
+
image = described_class.read(stringio)
|
|
22
|
+
expect(image).to be_valid
|
|
23
|
+
end
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
it "reads image from tempfile" do
|
|
26
|
+
tempfile = Tempfile.open('magick')
|
|
27
|
+
FileUtils.cp image_path, tempfile.path
|
|
28
|
+
image = described_class.read(tempfile)
|
|
29
|
+
expect(image).to be_valid
|
|
30
|
+
end
|
|
25
31
|
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
end
|
|
33
|
+
describe ".import_pixels" do
|
|
34
|
+
let(:dimensions) { [325, 200] }
|
|
35
|
+
let(:depth) { 16 } # 16 bits (2 bytes) per pixel
|
|
36
|
+
let(:map) { 'gray' }
|
|
37
|
+
let(:pixels) { Array.new(dimensions.inject(:*)) { |i| i } }
|
|
38
|
+
let(:blob) { pixels.pack('S*') } # unsigned short, native byte order
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
it "can import pixels with default format" do
|
|
41
|
+
image = described_class.import_pixels(blob, *dimensions, depth, map)
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
image = described_class.read(tempfile)
|
|
47
|
-
expect(image).to be_valid
|
|
48
|
-
image.destroy!
|
|
49
|
-
end
|
|
43
|
+
expect(image).to be_valid
|
|
44
|
+
expect(image.type).to eq 'PNG'
|
|
45
|
+
expect(image.dimensions).to eq dimensions
|
|
46
|
+
end
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
image = described_class.open(SIMPLE_IMAGE_PATH)
|
|
48
|
+
it "can import pixels with custom format" do
|
|
49
|
+
image = described_class.import_pixels(blob, *dimensions, depth, map, 'jpeg')
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
expect(image).to be_valid
|
|
52
|
+
expect(image.type).to eq 'JPEG'
|
|
53
|
+
expect(image.dimensions).to eq dimensions
|
|
54
|
+
end
|
|
55
|
+
end
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
c.pointsize '48'
|
|
64
|
-
c.interline_spacing '-9'
|
|
65
|
-
c.annotate '0', message
|
|
57
|
+
describe ".open" do
|
|
58
|
+
it "makes a copy of the image" do
|
|
59
|
+
image = described_class.open(image_path)
|
|
60
|
+
expect(image.path).not_to eq image_path
|
|
61
|
+
expect(image).to be_valid
|
|
66
62
|
end
|
|
67
|
-
}.to_not raise_error
|
|
68
|
-
end
|
|
69
63
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
end
|
|
64
|
+
it "accepts a Pathname" do
|
|
65
|
+
image = described_class.open(Pathname(image_path))
|
|
66
|
+
expect(image).to be_valid
|
|
67
|
+
end
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
69
|
+
it "loads a remote image" do
|
|
70
|
+
begin
|
|
71
|
+
image = described_class.open(image_url)
|
|
72
|
+
expect(image).to be_valid
|
|
73
|
+
rescue SocketError
|
|
74
|
+
end
|
|
75
|
+
end
|
|
82
76
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# Had to replace the old File.read with the following to work across all platforms
|
|
87
|
-
f.write(File.open(SIMPLE_IMAGE_PATH, 'rb') { |fi| fi.read })
|
|
77
|
+
it "validates the image" do
|
|
78
|
+
expect { described_class.open(image_path(:not)) }
|
|
79
|
+
.to raise_error(MiniMagick::Invalid)
|
|
88
80
|
end
|
|
89
81
|
end
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
describe ".create" do
|
|
84
|
+
def create(path = image_path)
|
|
85
|
+
described_class.create do |f|
|
|
86
|
+
f.write(File.binread(path))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
97
89
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
MiniMagick.validate_on_create = validate
|
|
90
|
+
it "creates an image" do
|
|
91
|
+
image = create
|
|
92
|
+
expect(File.exists?(image.path)).to eq true
|
|
102
93
|
end
|
|
103
94
|
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
it "validates the image if validation is set" do
|
|
96
|
+
allow(MiniMagick).to receive(:validate_on_create).and_return(true)
|
|
97
|
+
expect { create(image_path(:not)) }
|
|
98
|
+
.to raise_error(MiniMagick::Invalid)
|
|
99
|
+
end
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
it "doesn't validate image if validation is disabled" do
|
|
102
|
+
allow(MiniMagick).to receive(:validate_on_create).and_return(false)
|
|
103
|
+
expect { create(image_path(:not)) }
|
|
104
|
+
.not_to raise_error
|
|
111
105
|
end
|
|
106
|
+
end
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
describe "#initialize" do
|
|
109
|
+
it "initializes a new image" do
|
|
110
|
+
image = described_class.new(image_path)
|
|
111
|
+
expect(image).to be_valid
|
|
112
|
+
end
|
|
115
113
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
it "accepts a block which it passes on to #combine_options" do
|
|
115
|
+
image = described_class.new(subject.path) do |b|
|
|
116
|
+
b.resize "100x100!"
|
|
119
117
|
end
|
|
118
|
+
expect(image.dimensions).to eq [100, 100]
|
|
120
119
|
end
|
|
121
|
-
|
|
122
|
-
after { MiniMagick.validate_on_create = @old_validate }
|
|
123
120
|
end
|
|
124
|
-
end
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}.to_not raise_error
|
|
131
|
-
end
|
|
122
|
+
describe "equivalence" do
|
|
123
|
+
subject(:image) { described_class.new(image_path) }
|
|
124
|
+
let(:same_image) { described_class.new(image_path) }
|
|
125
|
+
let(:other_image) { described_class.new(image_path(:exif)) }
|
|
132
126
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
end
|
|
127
|
+
it "is #== and #eql? to itself" do
|
|
128
|
+
expect(image).to eq(image)
|
|
129
|
+
expect(image).to eql(image)
|
|
130
|
+
end
|
|
138
131
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
expect(image).to be_valid
|
|
144
|
-
image.destroy!
|
|
145
|
-
end
|
|
132
|
+
it "is #== and #eql? to an instance of the same image" do
|
|
133
|
+
expect(image).to eq(same_image)
|
|
134
|
+
expect(image).to eql(same_image)
|
|
135
|
+
end
|
|
146
136
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}.to_not raise_error
|
|
152
|
-
end
|
|
137
|
+
it "is not #== nor #eql? to an instance of a different image" do
|
|
138
|
+
expect(image).not_to eq(other_image)
|
|
139
|
+
expect(image).not_to eql(other_image)
|
|
140
|
+
end
|
|
153
141
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
image = described_class.open(PSD_IMAGE_PATH)
|
|
158
|
-
image.format('jpg', nil)
|
|
159
|
-
}.to_not raise_error
|
|
160
|
-
end
|
|
142
|
+
it "generates the same hash code for an instance of the same image" do
|
|
143
|
+
expect(image.hash).to eq(same_image.hash)
|
|
144
|
+
end
|
|
161
145
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
begin
|
|
165
|
-
image = described_class.new(SIMPLE_IMAGE_PATH)
|
|
166
|
-
image.write output_path
|
|
167
|
-
expect(File.exist?(output_path)).to be(true)
|
|
168
|
-
ensure
|
|
169
|
-
File.delete output_path
|
|
146
|
+
it "generates different same hash codes for a different image" do
|
|
147
|
+
expect(image.hash).not_to eq(other_image.hash)
|
|
170
148
|
end
|
|
171
|
-
image.destroy!
|
|
172
149
|
end
|
|
173
150
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
begin
|
|
177
|
-
image = described_class.new(SIMPLE_IMAGE_PATH)
|
|
178
|
-
image.write output_path
|
|
151
|
+
describe "#format" do
|
|
152
|
+
subject { described_class.open(image_path(:jpg)) }
|
|
179
153
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
154
|
+
it "changes the format of the photo" do
|
|
155
|
+
expect { subject.format("png") }
|
|
156
|
+
.to change { subject.type }
|
|
183
157
|
end
|
|
184
|
-
image.destroy!
|
|
185
|
-
end
|
|
186
158
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
image.write(stream)
|
|
192
|
-
expect(described_class.read(stream.string)).to be_valid
|
|
193
|
-
image.destroy!
|
|
194
|
-
end
|
|
159
|
+
it "reformats an image with a given extension" do
|
|
160
|
+
expect { subject.format('png') }
|
|
161
|
+
.to change { File.extname(subject.path) }.to ".png"
|
|
162
|
+
end
|
|
195
163
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
164
|
+
it "creates the file with new extension" do
|
|
165
|
+
subject.format('png')
|
|
166
|
+
expect(File.exist?(subject.path)).to eq true
|
|
167
|
+
end
|
|
199
168
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
169
|
+
it "accepts a block of additional commands" do
|
|
170
|
+
expect {
|
|
171
|
+
subject.format("png") do |b|
|
|
172
|
+
b.resize("100x100!")
|
|
173
|
+
end
|
|
174
|
+
}.to change { subject.dimensions }.to [100, 100]
|
|
203
175
|
end
|
|
204
176
|
|
|
205
|
-
|
|
177
|
+
it "works without an extension" do
|
|
178
|
+
subject = described_class.open(image_path(:without_extension))
|
|
179
|
+
expect { subject.format("png") }
|
|
180
|
+
.to change { File.extname(subject.path) }.from("").to(".png")
|
|
181
|
+
end
|
|
206
182
|
|
|
207
|
-
|
|
208
|
-
|
|
183
|
+
it "deletes the previous tempfile" do
|
|
184
|
+
old_path = subject.path.dup
|
|
185
|
+
subject.format('png')
|
|
186
|
+
expect(File.exist?(old_path)).to eq false
|
|
187
|
+
end
|
|
209
188
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
end
|
|
189
|
+
it "doesn't delete itself when formatted to the same format" do
|
|
190
|
+
subject.format(subject.type.downcase)
|
|
191
|
+
expect(File.exists?(subject.path)).to eq true
|
|
214
192
|
end
|
|
215
193
|
|
|
216
|
-
|
|
217
|
-
|
|
194
|
+
it "reformats multi-image formats to multiple images" do
|
|
195
|
+
subject = described_class.open(image_path(:animation))
|
|
196
|
+
subject.format('jpg', nil)
|
|
197
|
+
expect(Dir[subject.path.sub('.', '*.')]).not_to be_empty
|
|
198
|
+
end
|
|
218
199
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
200
|
+
it "reformats multi-image formats to a single image" do
|
|
201
|
+
subject = described_class.open(image_path(:animation))
|
|
202
|
+
subject.format('jpg')
|
|
203
|
+
expect(subject).to be_valid
|
|
223
204
|
end
|
|
224
205
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
File.delete output_path
|
|
228
|
-
MiniMagick.validate_on_write = @old_validate
|
|
206
|
+
it "returns self" do
|
|
207
|
+
expect(subject.format('png')).to eq subject
|
|
229
208
|
end
|
|
230
209
|
end
|
|
231
|
-
end
|
|
232
210
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
it "raises error when opening a file that isn't an image" do
|
|
240
|
-
expect {
|
|
241
|
-
image = described_class.open(NOT_AN_IMAGE_PATH)
|
|
242
|
-
image.destroy
|
|
243
|
-
}.to raise_error(MiniMagick::Invalid)
|
|
244
|
-
end
|
|
211
|
+
describe "#write" do
|
|
212
|
+
it "writes the image" do
|
|
213
|
+
output_path = random_path("test output")
|
|
214
|
+
subject.write(output_path)
|
|
215
|
+
expect(described_class.new(output_path)).to be_valid
|
|
216
|
+
end
|
|
245
217
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
218
|
+
it "writes an image with stream" do
|
|
219
|
+
output_stream = StringIO.new
|
|
220
|
+
subject.write(output_stream)
|
|
221
|
+
expect(described_class.read(output_stream.string)).to be_valid
|
|
222
|
+
end
|
|
251
223
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
expect(image[:format]).to match(/^gif$/i)
|
|
259
|
-
image.destroy!
|
|
260
|
-
end
|
|
224
|
+
it "writes layers" do
|
|
225
|
+
output_path = random_path(["", ".#{subject.type.downcase}"])
|
|
226
|
+
subject = described_class.new(image_path(:gif))
|
|
227
|
+
subject.frames.first.write(output_path)
|
|
228
|
+
expect(described_class.new(output_path)).to be_valid
|
|
229
|
+
end
|
|
261
230
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
end
|
|
231
|
+
it "accepts a Pathname" do
|
|
232
|
+
output_path = Pathname(random_path)
|
|
233
|
+
subject.write(output_path)
|
|
234
|
+
expect(described_class.new(output_path.to_s)).to be_valid
|
|
235
|
+
end
|
|
236
|
+
end
|
|
269
237
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
expect(image[:format]).to eq 'JPEG'
|
|
276
|
-
image.destroy!
|
|
277
|
-
end
|
|
238
|
+
describe "#valid?" do
|
|
239
|
+
it "returns true when image is valid" do
|
|
240
|
+
image = described_class.new(image_path)
|
|
241
|
+
expect(image).to be_valid
|
|
242
|
+
end
|
|
278
243
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
image.destroy!
|
|
285
|
-
end
|
|
244
|
+
it "returns false when image is not valid" do
|
|
245
|
+
image = described_class.new(image_path(:not))
|
|
246
|
+
expect(image).not_to be_valid
|
|
247
|
+
end
|
|
248
|
+
end
|
|
286
249
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
250
|
+
describe "#[]" do
|
|
251
|
+
it "inspects image meta info" do
|
|
252
|
+
expect(subject[:width]).to be_a(Fixnum)
|
|
253
|
+
expect(subject[:height]).to be_a(Fixnum)
|
|
254
|
+
expect(subject[:dimensions]).to all(be_a(Fixnum))
|
|
255
|
+
expect(subject[:colorspace]).to be_a(String)
|
|
256
|
+
expect(subject[:format]).to match(/[A-Z]/)
|
|
257
|
+
expect(subject[:signature]).to match(/[[:alnum:]]{64}/)
|
|
258
|
+
end
|
|
292
259
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
260
|
+
it "supports string keys" do
|
|
261
|
+
expect(subject["width"]).to be_a(Fixnum)
|
|
262
|
+
expect(subject["height"]).to be_a(Fixnum)
|
|
263
|
+
expect(subject["dimensions"]).to all(be_a(Fixnum))
|
|
264
|
+
expect(subject["colorspace"]).to be_a(String)
|
|
265
|
+
expect(subject["format"]).to match(/[A-Z]/)
|
|
266
|
+
expect(subject['signature']).to match(/[[:alnum:]]{64}/)
|
|
267
|
+
end
|
|
296
268
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
end
|
|
269
|
+
it "reads exif" do
|
|
270
|
+
subject = described_class.new(image_path(:exif))
|
|
271
|
+
gps_latitude = subject.exif["GPSLatitude"].split(/\s*,\s*/)
|
|
272
|
+
gps_longitude = subject.exif["GPSLongitude"].split(/\s*,\s*/)
|
|
302
273
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
274
|
+
expect(subject["EXIF:ColorSpace"]).to eq "1"
|
|
275
|
+
expect(gps_latitude.size).to eq 3
|
|
276
|
+
expect(gps_longitude.size).to eq 3
|
|
277
|
+
end
|
|
307
278
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
279
|
+
it "passes unknown values directly to -format" do
|
|
280
|
+
expect(subject["%w %h"].split.map(&:to_i)).to eq [subject[:width], subject[:height]]
|
|
281
|
+
end
|
|
282
|
+
end
|
|
312
283
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
284
|
+
it "has attributes" do
|
|
285
|
+
expect(subject.type).to match(/^[A-Z]+$/)
|
|
286
|
+
expect(subject.mime_type).to match(/^image\/[a-z]+$/)
|
|
287
|
+
expect(subject.width).to be_a(Fixnum).and be_nonzero
|
|
288
|
+
expect(subject.height).to be_a(Fixnum).and be_nonzero
|
|
289
|
+
expect(subject.dimensions).to all(be_a(Fixnum))
|
|
290
|
+
expect(subject.size).to be_a(Fixnum).and be_nonzero
|
|
291
|
+
expect(subject.colorspace).to be_a(String)
|
|
292
|
+
expect(subject.resolution).to all(be_a(Fixnum))
|
|
293
|
+
expect(subject.signature).to match(/[[:alnum:]]{64}/)
|
|
318
294
|
end
|
|
319
295
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
expect(image[:format]).to match(/\Agif\z/i)
|
|
323
|
-
image.destroy!
|
|
324
|
-
end
|
|
296
|
+
describe "#exif" do
|
|
297
|
+
subject { described_class.new(image_path(:exif)) }
|
|
325
298
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
expect {
|
|
330
|
-
image.combine_options do |c|
|
|
331
|
-
c.draw "image Over 0,0 10,10 '#{MINUS_IMAGE_PATH}'"
|
|
332
|
-
c.thumbnail '300x500>'
|
|
333
|
-
c.background background
|
|
334
|
-
end
|
|
335
|
-
}.to_not raise_error
|
|
336
|
-
image.destroy!
|
|
337
|
-
end
|
|
299
|
+
it "returns a hash of EXIF data" do
|
|
300
|
+
expect(subject.exif["DateTimeOriginal"]).to be_a(String)
|
|
301
|
+
end
|
|
338
302
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
expect(image['EXIF:ExifVersion']).to be_empty
|
|
344
|
-
image.destroy!
|
|
345
|
-
end
|
|
303
|
+
it "decodes the ExifVersion" do
|
|
304
|
+
expect(subject.exif["ExifVersion"]).to eq("0221")
|
|
305
|
+
end
|
|
306
|
+
end
|
|
346
307
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
308
|
+
describe "#resolution" do
|
|
309
|
+
it "accepts units", skip_cli: :graphicsmagick do
|
|
310
|
+
expect(subject.resolution("PixelsPerCentimeter"))
|
|
311
|
+
.not_to eq subject.resolution("PixelsPerInch")
|
|
312
|
+
end
|
|
313
|
+
end
|
|
354
314
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
315
|
+
describe "#mime_type" do
|
|
316
|
+
it "returns the correct mime type" do
|
|
317
|
+
jpg = described_class.new(image_path(:jpg))
|
|
318
|
+
expect(jpg.mime_type).to eq 'image/jpeg'
|
|
319
|
+
end
|
|
320
|
+
end
|
|
360
321
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
end
|
|
322
|
+
describe "#layers" do
|
|
323
|
+
it "returns a list of images" do
|
|
324
|
+
expect(subject.layers).to all(be_a(MiniMagick::Image))
|
|
325
|
+
expect(subject.layers.first).to be_valid
|
|
326
|
+
end
|
|
367
327
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
image.format('png')
|
|
372
|
-
expect(File.exist?(before)).to be(false)
|
|
373
|
-
image.destroy!
|
|
374
|
-
end
|
|
328
|
+
it "returns multiple images for GIFs, PDFs and PSDs" do
|
|
329
|
+
gif = described_class.new(image_path(:gif))
|
|
330
|
+
psd = described_class.new(image_path(:psd))
|
|
375
331
|
|
|
376
|
-
|
|
377
|
-
|
|
332
|
+
expect(gif.frames.count).to be > 1
|
|
333
|
+
expect(psd.layers.count).to be > 1 unless MiniMagick.graphicsmagick?
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
it "returns one image for other formats" do
|
|
337
|
+
jpg = described_class.new(image_path(:jpg))
|
|
378
338
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
tempfile.rewind
|
|
339
|
+
expect(jpg.layers.count).to eq 1
|
|
340
|
+
end
|
|
382
341
|
end
|
|
383
342
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
343
|
+
describe "missing methods" do
|
|
344
|
+
context "for a known method" do
|
|
345
|
+
it "is executed by #method_missing" do
|
|
346
|
+
expect { subject.resize '20x30!' }
|
|
347
|
+
.to change { subject.dimensions }.to [20, 30]
|
|
348
|
+
end
|
|
388
349
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
350
|
+
it "returns self" do
|
|
351
|
+
expect(subject.resize('20x30!')).to eq subject
|
|
352
|
+
end
|
|
392
353
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
354
|
+
it "can be responed to" do
|
|
355
|
+
expect(subject.respond_to?(:resize)).to eq true
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
context "for an unknown method" do
|
|
360
|
+
it "fails with a NoMethodError" do
|
|
361
|
+
expect { subject.foo }
|
|
362
|
+
.to raise_error(NoMethodError, /MiniMagick::Image/)
|
|
363
|
+
end
|
|
399
364
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
365
|
+
it "cannot be responded to" do
|
|
366
|
+
expect(subject.respond_to?(:foo)).to eq false
|
|
367
|
+
end
|
|
368
|
+
end
|
|
404
369
|
end
|
|
405
|
-
expect(File.exist?(result.path)).to be(true)
|
|
406
|
-
end
|
|
407
370
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
371
|
+
describe "#combine_options" do
|
|
372
|
+
it "chains multiple options and executes them in one command" do
|
|
373
|
+
expect {
|
|
374
|
+
subject.combine_options { |c| c.resize '20x30!' }
|
|
375
|
+
}.to change { subject.dimensions }.to [20, 30]
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
it "doesn't allow calling of #format" do
|
|
379
|
+
expect { subject.combine_options { |c| c.format("png") } }
|
|
380
|
+
.to raise_error(NoMethodError)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
it "returns self" do
|
|
384
|
+
expect(subject.combine_options {}).to eq subject
|
|
385
|
+
end
|
|
413
386
|
end
|
|
414
|
-
expect(File.exist?(result.path)).to be(true)
|
|
415
|
-
end
|
|
416
387
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
388
|
+
describe "#composite" do
|
|
389
|
+
let(:other_image) { described_class.open(image_path) }
|
|
390
|
+
let(:mask) { described_class.open(image_path) }
|
|
391
|
+
|
|
392
|
+
it "creates a composite of two images" do
|
|
393
|
+
image = subject.composite(other_image)
|
|
394
|
+
expect(image).to be_valid
|
|
424
395
|
end
|
|
425
|
-
}.to_not raise_error
|
|
426
|
-
image.destroy!
|
|
427
|
-
end
|
|
428
396
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
output = Pathname.new('test.gif')
|
|
434
|
-
image.write(output)
|
|
435
|
-
}.to_not raise_error
|
|
436
|
-
FileUtils.rm('test.gif')
|
|
437
|
-
end
|
|
397
|
+
it "creates a composite of two images with mask" do
|
|
398
|
+
image = subject.composite(other_image, 'jpg', mask)
|
|
399
|
+
expect(image).to be_valid
|
|
400
|
+
end
|
|
438
401
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
402
|
+
it "yields an optional block" do
|
|
403
|
+
expect { |b| subject.composite(other_image, &b) }
|
|
404
|
+
.to yield_with_args(an_instance_of(MiniMagick::Tool::Composite))
|
|
405
|
+
end
|
|
443
406
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}.to raise_error(MiniMagick::Invalid)
|
|
407
|
+
it "makes the composited image with the provided extension" do
|
|
408
|
+
result = subject.composite(other_image, 'png')
|
|
409
|
+
expect(result.path).to end_with ".png"
|
|
448
410
|
|
|
449
|
-
|
|
450
|
-
|
|
411
|
+
result = subject.composite(other_image)
|
|
412
|
+
expect(result.path).to end_with ".jpg"
|
|
413
|
+
end
|
|
414
|
+
end
|
|
451
415
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
rows = 200
|
|
455
|
-
depth = 16 # 16 bits (2 bytes) per pixel
|
|
456
|
-
map = 'gray'
|
|
457
|
-
pixels = Array.new(columns * rows) { |i| i }
|
|
458
|
-
blob = pixels.pack('S*') # unsigned short, native byte order
|
|
459
|
-
image = described_class.import_pixels(blob, columns, rows, depth, map)
|
|
460
|
-
|
|
461
|
-
expect(image).to be_valid
|
|
462
|
-
expect(image[:format].to_s.downcase).to eq 'png'
|
|
463
|
-
expect(image[:width]).to eq columns
|
|
464
|
-
expect(image[:height]).to eq rows
|
|
465
|
-
image.write("#{Dir.tmpdir}/imported_pixels_image.png")
|
|
466
|
-
end
|
|
416
|
+
describe "#collapse!" do
|
|
417
|
+
subject { described_class.open(image_path(:animation)) }
|
|
467
418
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
419
|
+
it "collapses the image to one frame" do
|
|
420
|
+
subject.collapse!
|
|
421
|
+
expect(subject.identify.lines.count).to eq 1
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
it "keeps the extension" do
|
|
425
|
+
expect { subject.collapse! }
|
|
426
|
+
.not_to change { subject.type }
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
it "clears the info" do
|
|
430
|
+
expect { subject.collapse! }
|
|
431
|
+
.to change { subject.size }
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
it "returns self" do
|
|
435
|
+
expect(subject.collapse!).to eq subject
|
|
436
|
+
end
|
|
437
|
+
end
|
|
484
438
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
439
|
+
describe "#identify" do
|
|
440
|
+
it "returns the output of identify" do
|
|
441
|
+
expect(subject.identify).to match(subject.type)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
it "yields an optional block" do
|
|
445
|
+
output = subject.identify do |b|
|
|
446
|
+
b.verbose
|
|
447
|
+
end
|
|
448
|
+
expect(output).to match("Format:")
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
describe "#run_command" do
|
|
453
|
+
it "runs the given command" do
|
|
454
|
+
output = subject.run_command("identify", "-format", "%w", subject.path)
|
|
455
|
+
expect(output).to eq subject.width.to_s
|
|
456
|
+
end
|
|
457
|
+
end
|
|
497
458
|
end
|
|
498
459
|
end
|
|
499
460
|
end
|