mini_magick 3.8.0 → 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 +408 -343
- 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 +25 -26
- data/lib/mini_magick/version.rb +15 -1
- data/lib/mini_magick.rb +43 -79
- 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 +460 -0
- 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 +39 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/helpers.rb +37 -0
- metadata +50 -52
- data/lib/mini_magick/command_builder.rb +0 -110
- data/lib/mini_magick/errors.rb +0 -4
data/lib/mini_magick.rb
CHANGED
|
@@ -1,89 +1,53 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
3
|
-
require 'stringio'
|
|
4
|
-
require 'pathname'
|
|
5
|
-
require 'shellwords'
|
|
6
|
-
require 'mini_magick/command_builder'
|
|
7
|
-
require 'mini_magick/errors'
|
|
1
|
+
require 'mini_magick/configuration'
|
|
2
|
+
require 'mini_magick/tool'
|
|
8
3
|
require 'mini_magick/image'
|
|
9
|
-
require 'mini_magick/utilities'
|
|
10
4
|
|
|
11
5
|
module MiniMagick
|
|
12
|
-
@validate_on_create = true
|
|
13
|
-
@validate_on_write = true
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
:gm
|
|
33
|
-
else
|
|
34
|
-
nil
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
##
|
|
39
|
-
# Discovers the imagemagick version based on mogrify's output.
|
|
40
|
-
#
|
|
41
|
-
# === Returns
|
|
42
|
-
# * The imagemagick version
|
|
43
|
-
def image_magick_version
|
|
44
|
-
@@version ||= Gem::Version.create(`mogrify --version`.split(' ')[2].split('-').first)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
##
|
|
48
|
-
# The minimum allowed imagemagick version
|
|
49
|
-
#
|
|
50
|
-
# === Returns
|
|
51
|
-
# * The minimum imagemagick version
|
|
52
|
-
def minimum_image_magick_version
|
|
53
|
-
@@minimum_version ||= Gem::Version.create('6.6.3')
|
|
54
|
-
end
|
|
7
|
+
extend MiniMagick::Configuration
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# You might want to execute only certain blocks of processing with a
|
|
11
|
+
# different CLI, because for example that CLI does that particular thing
|
|
12
|
+
# faster. After the block CLI resets to its previous value.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# MiniMagick.with_cli :graphicsmagick do
|
|
16
|
+
# # operations that are better done with GraphicsMagick
|
|
17
|
+
# end
|
|
18
|
+
def self.with_cli(cli)
|
|
19
|
+
old_cli = self.cli
|
|
20
|
+
self.cli = cli
|
|
21
|
+
yield
|
|
22
|
+
self.cli = old_cli
|
|
23
|
+
end
|
|
55
24
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
25
|
+
##
|
|
26
|
+
# Checks whether the CLI used is ImageMagick.
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def self.imagemagick?
|
|
30
|
+
cli == :imagemagick
|
|
31
|
+
end
|
|
64
32
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
33
|
+
##
|
|
34
|
+
# Checks whether the CLI used is GraphicsMagick.
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
def self.graphicsmagick?
|
|
38
|
+
cli == :graphicsmagick
|
|
39
|
+
end
|
|
72
40
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
41
|
+
##
|
|
42
|
+
# Returns ImageMagick's/GraphicsMagick's version.
|
|
43
|
+
#
|
|
44
|
+
# @return [String]
|
|
45
|
+
def self.cli_version
|
|
46
|
+
output = MiniMagick::Tool::Identify.new(&:version)
|
|
47
|
+
output[/\d+\.\d+\.\d+(-\d+)?/]
|
|
48
|
+
end
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
# === Returns
|
|
81
|
-
# * [Boolean]
|
|
82
|
-
def gm?
|
|
83
|
-
choose_processor if processor.nil?
|
|
50
|
+
class Error < RuntimeError; end
|
|
51
|
+
class Invalid < StandardError; end
|
|
84
52
|
|
|
85
|
-
return processor.to_s.downcase.to_sym == :gm unless processor.nil?
|
|
86
|
-
false
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
53
|
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
expect(__FILE__).not_to be_an_image
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe MiniMagick::Configuration do
|
|
4
|
+
subject { Object.new.extend(MiniMagick::Configuration) }
|
|
5
|
+
|
|
6
|
+
describe "#configure" do
|
|
7
|
+
it "yields self" do
|
|
8
|
+
expect { |b| subject.configure(&b) }
|
|
9
|
+
.to yield_with_args(subject)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "#cli" do
|
|
14
|
+
it "can be assigned" do
|
|
15
|
+
subject.cli = :imagemagick
|
|
16
|
+
expect(subject.cli).to eq :imagemagick
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "returns :imagemagick if #processor is mogrify" do
|
|
20
|
+
allow(subject).to receive(:processor).and_return("mogrify")
|
|
21
|
+
expect(subject.cli).to eq :imagemagick
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "returns :graphicsmagick if #processor is gm" do
|
|
25
|
+
allow(subject).to receive(:processor).and_return("gm")
|
|
26
|
+
expect(subject.cli).to eq :graphicsmagick
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "returns nil of #processor is nil" do
|
|
30
|
+
allow(subject).to receive(:processor).and_return(nil)
|
|
31
|
+
expect(subject.cli).to eq nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "#cli=" do
|
|
36
|
+
it "raises an error when set to an invalid value" do
|
|
37
|
+
expect { subject.cli = :grapicsmagick }
|
|
38
|
+
.to raise_error(ArgumentError)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#processor" do
|
|
43
|
+
it "assigns :mogrify by default" do
|
|
44
|
+
expect(subject.processor).to eq "mogrify"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "assigns :gm if ImageMagick is not available" do
|
|
48
|
+
allow(MiniMagick::Utilities).to receive(:which).with("mogrify").and_return(nil)
|
|
49
|
+
allow(MiniMagick::Utilities).to receive(:which).with("gm").and_return(true)
|
|
50
|
+
expect(subject.processor).to eq "gm"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "returns nil if neither ImageMagick nor GraphicsMagick are available" do
|
|
54
|
+
allow(MiniMagick::Utilities).to receive(:which).with("mogrify").and_return(nil)
|
|
55
|
+
allow(MiniMagick::Utilities).to receive(:which).with("gm").and_return(nil)
|
|
56
|
+
expect(subject.processor).to eq nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "#processor=" do
|
|
61
|
+
it "raises an error when set to an invalid value" do
|
|
62
|
+
expect { subject.processor = "mogrfy" }
|
|
63
|
+
.to raise_error(ArgumentError)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,460 @@
|
|
|
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
|
|
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
|
|
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
|
|
31
|
+
end
|
|
32
|
+
|
|
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
|
|
39
|
+
|
|
40
|
+
it "can import pixels with default format" do
|
|
41
|
+
image = described_class.import_pixels(blob, *dimensions, depth, map)
|
|
42
|
+
|
|
43
|
+
expect(image).to be_valid
|
|
44
|
+
expect(image.type).to eq 'PNG'
|
|
45
|
+
expect(image.dimensions).to eq dimensions
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "can import pixels with custom format" do
|
|
49
|
+
image = described_class.import_pixels(blob, *dimensions, depth, map, 'jpeg')
|
|
50
|
+
|
|
51
|
+
expect(image).to be_valid
|
|
52
|
+
expect(image.type).to eq 'JPEG'
|
|
53
|
+
expect(image.dimensions).to eq dimensions
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
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
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "accepts a Pathname" do
|
|
65
|
+
image = described_class.open(Pathname(image_path))
|
|
66
|
+
expect(image).to be_valid
|
|
67
|
+
end
|
|
68
|
+
|
|
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
|
|
76
|
+
|
|
77
|
+
it "validates the image" do
|
|
78
|
+
expect { described_class.open(image_path(:not)) }
|
|
79
|
+
.to raise_error(MiniMagick::Invalid)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
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
|
|
89
|
+
|
|
90
|
+
it "creates an image" do
|
|
91
|
+
image = create
|
|
92
|
+
expect(File.exists?(image.path)).to eq true
|
|
93
|
+
end
|
|
94
|
+
|
|
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
|
|
100
|
+
|
|
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
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
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
|
|
113
|
+
|
|
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!"
|
|
117
|
+
end
|
|
118
|
+
expect(image.dimensions).to eq [100, 100]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
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)) }
|
|
126
|
+
|
|
127
|
+
it "is #== and #eql? to itself" do
|
|
128
|
+
expect(image).to eq(image)
|
|
129
|
+
expect(image).to eql(image)
|
|
130
|
+
end
|
|
131
|
+
|
|
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
|
|
136
|
+
|
|
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
|
|
141
|
+
|
|
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
|
|
145
|
+
|
|
146
|
+
it "generates different same hash codes for a different image" do
|
|
147
|
+
expect(image.hash).not_to eq(other_image.hash)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "#format" do
|
|
152
|
+
subject { described_class.open(image_path(:jpg)) }
|
|
153
|
+
|
|
154
|
+
it "changes the format of the photo" do
|
|
155
|
+
expect { subject.format("png") }
|
|
156
|
+
.to change { subject.type }
|
|
157
|
+
end
|
|
158
|
+
|
|
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
|
|
163
|
+
|
|
164
|
+
it "creates the file with new extension" do
|
|
165
|
+
subject.format('png')
|
|
166
|
+
expect(File.exist?(subject.path)).to eq true
|
|
167
|
+
end
|
|
168
|
+
|
|
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]
|
|
175
|
+
end
|
|
176
|
+
|
|
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
|
|
182
|
+
|
|
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
|
|
188
|
+
|
|
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
|
|
192
|
+
end
|
|
193
|
+
|
|
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
|
|
199
|
+
|
|
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
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "returns self" do
|
|
207
|
+
expect(subject.format('png')).to eq subject
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
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
|
|
217
|
+
|
|
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
|
|
223
|
+
|
|
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
|
|
230
|
+
|
|
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
|
|
237
|
+
|
|
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
|
|
243
|
+
|
|
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
|
|
249
|
+
|
|
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
|
|
259
|
+
|
|
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
|
|
268
|
+
|
|
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*/)
|
|
273
|
+
|
|
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
|
|
278
|
+
|
|
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
|
|
283
|
+
|
|
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}/)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
describe "#exif" do
|
|
297
|
+
subject { described_class.new(image_path(:exif)) }
|
|
298
|
+
|
|
299
|
+
it "returns a hash of EXIF data" do
|
|
300
|
+
expect(subject.exif["DateTimeOriginal"]).to be_a(String)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it "decodes the ExifVersion" do
|
|
304
|
+
expect(subject.exif["ExifVersion"]).to eq("0221")
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
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
|
|
314
|
+
|
|
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
|
|
321
|
+
|
|
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
|
|
327
|
+
|
|
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))
|
|
331
|
+
|
|
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))
|
|
338
|
+
|
|
339
|
+
expect(jpg.layers.count).to eq 1
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
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
|
|
349
|
+
|
|
350
|
+
it "returns self" do
|
|
351
|
+
expect(subject.resize('20x30!')).to eq subject
|
|
352
|
+
end
|
|
353
|
+
|
|
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
|
|
364
|
+
|
|
365
|
+
it "cannot be responded to" do
|
|
366
|
+
expect(subject.respond_to?(:foo)).to eq false
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
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
|
|
386
|
+
end
|
|
387
|
+
|
|
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
|
|
395
|
+
end
|
|
396
|
+
|
|
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
|
|
401
|
+
|
|
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
|
|
406
|
+
|
|
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"
|
|
410
|
+
|
|
411
|
+
result = subject.composite(other_image)
|
|
412
|
+
expect(result.path).to end_with ".jpg"
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
describe "#collapse!" do
|
|
417
|
+
subject { described_class.open(image_path(:animation)) }
|
|
418
|
+
|
|
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
|
|
438
|
+
|
|
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
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|