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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mini_gmagick.rb +2 -1
  3. data/lib/mini_magick/configuration.rb +136 -0
  4. data/lib/mini_magick/image/info.rb +122 -0
  5. data/lib/mini_magick/image.rb +380 -334
  6. data/lib/mini_magick/logger.rb +40 -0
  7. data/lib/mini_magick/shell.rb +48 -0
  8. data/lib/mini_magick/tool/animate.rb +14 -0
  9. data/lib/mini_magick/tool/compare.rb +14 -0
  10. data/lib/mini_magick/tool/composite.rb +14 -0
  11. data/lib/mini_magick/tool/conjure.rb +14 -0
  12. data/lib/mini_magick/tool/convert.rb +14 -0
  13. data/lib/mini_magick/tool/display.rb +14 -0
  14. data/lib/mini_magick/tool/identify.rb +14 -0
  15. data/lib/mini_magick/tool/import.rb +14 -0
  16. data/lib/mini_magick/tool/mogrify.rb +14 -0
  17. data/lib/mini_magick/tool/montage.rb +14 -0
  18. data/lib/mini_magick/tool/stream.rb +14 -0
  19. data/lib/mini_magick/tool.rb +250 -0
  20. data/lib/mini_magick/utilities.rb +23 -50
  21. data/lib/mini_magick/version.rb +5 -5
  22. data/lib/mini_magick.rb +43 -65
  23. data/spec/fixtures/animation.gif +0 -0
  24. data/spec/fixtures/default.jpg +0 -0
  25. data/spec/fixtures/exif.jpg +0 -0
  26. data/spec/fixtures/image.psd +0 -0
  27. data/spec/fixtures/not_an_image.rb +1 -0
  28. data/spec/lib/mini_magick/configuration_spec.rb +66 -0
  29. data/spec/lib/mini_magick/image_spec.rb +367 -406
  30. data/spec/lib/mini_magick/shell_spec.rb +66 -0
  31. data/spec/lib/mini_magick/tool_spec.rb +107 -0
  32. data/spec/lib/mini_magick/utilities_spec.rb +17 -0
  33. data/spec/lib/mini_magick_spec.rb +23 -47
  34. data/spec/spec_helper.rb +17 -25
  35. data/spec/support/helpers.rb +37 -0
  36. metadata +40 -74
  37. data/lib/mini_magick/command_builder.rb +0 -94
  38. data/lib/mini_magick/errors.rb +0 -4
  39. data/spec/files/actually_a_gif.jpg +0 -0
  40. data/spec/files/animation.gif +0 -0
  41. data/spec/files/composited.jpg +0 -0
  42. data/spec/files/erroneous.jpg +0 -0
  43. data/spec/files/layers.psd +0 -0
  44. data/spec/files/leaves (spaced).tiff +0 -0
  45. data/spec/files/not_an_image.php +0 -1
  46. data/spec/files/png.png +0 -0
  47. data/spec/files/simple-minus.gif +0 -0
  48. data/spec/files/simple.gif +0 -0
  49. data/spec/files/trogdor.jpg +0 -0
  50. data/spec/files/trogdor_capitalized.JPG +0 -0
  51. data/spec/lib/mini_magick/command_builder_spec.rb +0 -153
@@ -1,499 +1,460 @@
1
- require 'spec_helper'
2
- require 'pathname'
3
- require 'tempfile'
4
-
5
- MiniMagick.processor = :mogrify
6
-
7
- describe MiniMagick::Image do
8
- context 'when ImageMagick and GraphicsMagick are both unavailable' do
9
- before do
10
- MiniMagick::Utilities.expects(:which).at_least_once.returns(nil)
11
- MiniMagick.instance_variable_set(:@processor, nil)
12
- @old_path = ENV['PATH']
13
- ENV['PATH'] = ''
14
- end
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
- after do
17
- ENV['PATH'] = @old_path
18
- end
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
- it "raises an exception with 'No such file' in the message" do
21
- begin
22
- described_class.open(SIMPLE_IMAGE_PATH)
23
- rescue => e
24
- expect(e.message).to match(/(No such file|not found)/)
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
- describe 'ported from testunit', :ported => true do
30
- it 'reads image from blob' do
31
- File.open(SIMPLE_IMAGE_PATH, 'rb') do |f|
32
- image = described_class.read(f.read)
33
- expect(image).to be_valid
34
- image.destroy!
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
- it 'reads image from tempfile', :if => !MiniMagick::Utilities.windows? do
39
- tempfile = Tempfile.new('magick')
40
+ it "can import pixels with default format" do
41
+ image = described_class.import_pixels(blob, *dimensions, depth, map)
40
42
 
41
- File.open(SIMPLE_IMAGE_PATH, 'rb') do |f|
42
- tempfile.write(f.read)
43
- tempfile.rewind
44
- end
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
- # from https://github.com/minimagick/minimagick/issues/163
52
- it 'annotates image with whitespace' do
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
- expect {
56
- message = 'a b'
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
- image.combine_options do |c|
59
- c.gravity 'SouthWest'
60
- c.fill 'white'
61
- c.stroke 'black'
62
- c.strokewidth '2'
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
- it 'opens image' do
71
- image = described_class.open(SIMPLE_IMAGE_PATH)
72
- expect(image).to be_valid
73
- image.destroy!
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
- it 'reads image from buffer' do
77
- buffer = StringIO.new File.open(SIMPLE_IMAGE_PATH, 'rb') { |f| f.read }
78
- image = described_class.read(buffer)
79
- expect(image).to be_valid
80
- image.destroy!
81
- end
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
- describe '.create' do
84
- subject(:create) do
85
- described_class.create do |f|
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
- it 'creates an image' do
92
- expect {
93
- image = create
94
- image.destroy!
95
- }.to_not raise_error
96
- end
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
- describe 'validation' do
99
- before do
100
- @old_validate = MiniMagick.validate_on_create
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
- context 'MiniMagick.validate_on_create = true' do
105
- let(:validate) { true }
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
- it 'validates image' do
108
- described_class.any_instance.expects(:valid?).returns(true)
109
- create
110
- end
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
- context 'MiniMagick.validate_on_create = false' do
114
- let(:validate) { false }
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
- it 'skips validation' do
117
- described_class.any_instance.expects(:valid?).never
118
- create
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
- it 'loads a new image' do
127
- expect {
128
- image = described_class.new(SIMPLE_IMAGE_PATH)
129
- image.destroy!
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
- it 'loads remote image' do
134
- image = described_class.open('http://upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png')
135
- expect(image).to be_valid
136
- image.destroy!
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
- it 'loads remote image with complex url' do
140
- image = described_class.open(
141
- 'http://a0.twimg.com/a/1296609216/images/fronts/logo_withbird_home.png?extra=foo&plus=bar'
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
- it 'reformats an image with a given extension' do
148
- expect {
149
- image = described_class.open(CAP_EXT_PATH)
150
- image.format 'jpg'
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
- describe '#write' do
155
- it 'reformats a PSD with a given a extension and all layers' do
156
- expect {
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
- it 'opens and writes an image' do
163
- output_path = 'output.gif'
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
- it 'opens and writes an image with space in its filename' do
175
- output_path = 'test output.gif'
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
- expect(File.exist?(output_path)).to be(true)
181
- ensure
182
- File.delete output_path
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
- it 'writes an image with stream' do
188
- stream = StringIO.new
189
- image = described_class.open(SIMPLE_IMAGE_PATH)
190
- image.write("#{Dir.tmpdir}/foo.gif")
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
- describe 'validation' do
197
- let(:image) { described_class.new(SIMPLE_IMAGE_PATH) }
198
- let(:output_path) { 'output.gif' }
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
- before do
201
- @old_validate = MiniMagick.validate_on_write
202
- MiniMagick.validate_on_write = validate
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
- subject(:write) { image.write output_path }
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
- context 'MiniMagick.validate_on_write = true' do
208
- let(:validate) { true }
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
- it 'runs post-validation' do
211
- image.expects(:run_command).with('identify', output_path)
212
- write
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
- context 'MiniMagick.validate_on_write = false' do
217
- let(:validate) { false }
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
- it 'runs post-validation' do
220
- image.expects(:run_command).never
221
- write
222
- end
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
- after do
226
- image.destroy!
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
- it 'tells when an image is invalid' do
234
- image = described_class.new(NOT_AN_IMAGE_PATH)
235
- expect(image).not_to be_valid
236
- image.destroy!
237
- end
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
- it "raises error when imagemagick raised an error during processing" do
247
- image = described_class.open(SIMPLE_IMAGE_PATH)
248
- image.rotate "invalid_value"
249
- expect { image.run_queue }.to raise_error(MiniMagick::Error)
250
- end
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
- it 'inspects image meta info' do
253
- image = described_class.new(SIMPLE_IMAGE_PATH)
254
- expect(image[:width]).to be(150)
255
- expect(image[:height]).to be(55)
256
- expect(image[:dimensions]).to match_array [150, 55]
257
- expect(image[:colorspace]).to be_an_instance_of(String)
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
- it 'supports string keys for dimension attributes' do
263
- image = described_class.new(SIMPLE_IMAGE_PATH)
264
- expect(image["width"]).to be(150)
265
- expect(image["height"]).to be(55)
266
- expect(image["dimensions"]).to match_array [150, 55]
267
- image.destroy!
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
- it 'inspects an erroneus image meta info' do
271
- image = described_class.new(ERRONEOUS_IMAGE_PATH)
272
- expect(image[:width]).to be(10)
273
- expect(image[:height]).to be(10)
274
- expect(image[:dimensions]).to match_array [10, 10]
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
- it 'inspects meta info from tiff images' do
280
- image = described_class.new(TIFF_IMAGE_PATH)
281
- expect(image[:format].to_s.downcase).to eq 'tiff'
282
- expect(image[:width]).to be(50)
283
- expect(image[:height]).to be(41)
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
- it 'inspects a gif with jpg format correctly' do
288
- image = described_class.new(GIF_WITH_JPG_EXT)
289
- expect(image[:format].to_s.downcase).to eq 'gif'
290
- image.destroy!
291
- end
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
- it 'resizes an image correctly' do
294
- image = described_class.open(SIMPLE_IMAGE_PATH)
295
- image.resize '20x30!'
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
- expect(image[:width]).to be(20)
298
- expect(image[:height]).to be(30)
299
- expect(image[:format]).to match(/^gif$/i)
300
- image.destroy!
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
- it 'resizes an image with minimum dimensions' do
304
- image = described_class.open(SIMPLE_IMAGE_PATH)
305
- original_width, original_height = image[:width], image[:height]
306
- image.resize "#{original_width + 10}x#{original_height + 10}>"
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
- expect(image[:width]).to be original_width
309
- expect(image[:height]).to be original_height
310
- image.destroy!
311
- end
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
- it 'combines options to create an image with resize and blur' do
314
- image = described_class.open(SIMPLE_IMAGE_PATH)
315
- image.combine_options do |c|
316
- c.resize '20x30!'
317
- c.blur '50'
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
- expect(image[:width]).to be(20)
321
- expect(image[:height]).to be(30)
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
- it "combines options to create an image even with minuses symbols on it's name it" do
327
- image = described_class.open(SIMPLE_IMAGE_PATH)
328
- background = '#000000'
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
- it 'inspects the EXIF of an image' do
340
- image = described_class.open(EXIF_IMAGE_PATH)
341
- expect(image['exif:ExifVersion']).to eq '0220'
342
- image = described_class.open(SIMPLE_IMAGE_PATH)
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
- it 'inspects the original at of an image' do
348
- image = described_class.open(EXIF_IMAGE_PATH)
349
- expect(image[:original_at]).to eq Time.local('2005', '2', '23', '23', '17', '24')
350
- image = described_class.open(SIMPLE_IMAGE_PATH)
351
- expect(image[:original_at]).to be_nil
352
- image.destroy!
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
- it 'has the same path for tempfile and image' do
356
- image = described_class.open(TIFF_IMAGE_PATH)
357
- expect(image.instance_eval('@tempfile.path')).to eq image.path
358
- image.destroy!
359
- end
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
- it 'has the tempfile at path after format' do
362
- image = described_class.open(TIFF_IMAGE_PATH)
363
- image.format('png')
364
- expect(File.exist?(image.path)).to be(true)
365
- image.destroy!
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
- it "hasn't previous tempfile at path after format" do
369
- image = described_class.open(TIFF_IMAGE_PATH)
370
- before = image.path.dup
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
- it 'changes the format of image with special characters', :if => !MiniMagick::Utilities.windows? do
377
- tempfile = Tempfile.new('magick with special! "chars\'')
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
- File.open(SIMPLE_IMAGE_PATH, 'rb') do |file|
380
- tempfile.write(file.read)
381
- tempfile.rewind
339
+ expect(jpg.layers.count).to eq 1
340
+ end
382
341
  end
383
342
 
384
- image = described_class.new(tempfile.path)
385
- image.format('png')
386
- expect(File.exist?(image.path)).to be(true)
387
- image.destroy!
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
- File.delete(image.path)
390
- tempfile.unlink
391
- end
350
+ it "returns self" do
351
+ expect(subject.resize('20x30!')).to eq subject
352
+ end
392
353
 
393
- it 'raises exception when calling wrong method' do
394
- image = described_class.open(TIFF_IMAGE_PATH)
395
- expect { image.to_blog }.to raise_error(NoMethodError)
396
- image.to_blob
397
- image.destroy!
398
- end
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
- it 'can create a composite of two images' do
401
- image = described_class.open(EXIF_IMAGE_PATH)
402
- result = image.composite(described_class.open(TIFF_IMAGE_PATH)) do |c|
403
- c.gravity 'center'
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
- # https://github.com/minimagick/minimagick/issues/212
409
- it 'can create a composite of two images with mask' do
410
- image = described_class.open(EXIF_IMAGE_PATH)
411
- result = image.composite(described_class.open(TIFF_IMAGE_PATH), 'jpg', described_class.open(PNG_PATH)) do |c|
412
- c.gravity 'center'
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
- # https://github.com/minimagick/minimagick/issues/8
418
- it 'has issue 8 fixed' do
419
- image = described_class.open(SIMPLE_IMAGE_PATH)
420
- expect {
421
- image.combine_options do |c|
422
- c.sample '50%'
423
- c.rotate '-90>'
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
- # https://github.com/minimagick/minimagick/issues/8
430
- it 'has issue 15 fixed' do
431
- expect {
432
- image = described_class.open(Pathname.new(SIMPLE_IMAGE_PATH))
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
- # https://github.com/minimagick/minimagick/issues/37
440
- it 'respects the language set' do
441
- original_lang = ENV['LANG']
442
- ENV['LANG'] = 'fr_FR.UTF-8'
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
- expect {
445
- image = described_class.open(NOT_AN_IMAGE_PATH)
446
- image.destroy
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
- ENV['LANG'] = original_lang
450
- end
411
+ result = subject.composite(other_image)
412
+ expect(result.path).to end_with ".jpg"
413
+ end
414
+ end
451
415
 
452
- it 'can import pixels with default format' do
453
- columns = 325
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
- it 'can import pixels with custom format' do
469
- columns = 325
470
- rows = 200
471
- depth = 16 # 16 bits (2 bytes) per pixel
472
- map = 'gray'
473
- format = 'jpeg'
474
- pixels = Array.new(columns * rows) { |i| i }
475
- blob = pixels.pack('S*') # unsigned short, native byte order
476
- image = described_class.import_pixels(blob, columns, rows, depth, map, format)
477
-
478
- expect(image).to be_valid
479
- expect(image[:format].to_s.downcase).to eq format
480
- expect(image[:width]).to eq columns
481
- expect(image[:height]).to eq rows
482
- image.write("#{Dir.tmpdir}/imported_pixels_image." + format)
483
- end
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
- it 'loads mimetype correctly' do
486
- gif = described_class.open(SIMPLE_IMAGE_PATH)
487
- jpeg = described_class.open(EXIF_IMAGE_PATH)
488
- png = described_class.open(PNG_PATH)
489
- tiff = described_class.open(TIFF_IMAGE_PATH)
490
- hidden_gif = described_class.open(GIF_WITH_JPG_EXT)
491
-
492
- expect(gif.mime_type).to eq 'image/gif'
493
- expect(jpeg.mime_type).to eq 'image/jpeg'
494
- expect(png.mime_type).to eq 'image/png'
495
- expect(tiff.mime_type).to eq 'image/tiff'
496
- expect(hidden_gif.mime_type).to eq 'image/gif'
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