mini_magick 3.8.1 → 4.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mini_magick might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/mini_gmagick.rb +2 -1
- data/lib/mini_magick.rb +43 -65
- data/lib/mini_magick/configuration.rb +136 -0
- data/lib/mini_magick/image.rb +356 -336
- data/lib/mini_magick/image/info.rb +104 -0
- data/lib/mini_magick/logger.rb +40 -0
- data/lib/mini_magick/shell.rb +46 -0
- data/lib/mini_magick/tool.rb +233 -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/utilities.rb +23 -50
- data/lib/mini_magick/version.rb +6 -6
- 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 +318 -410
- data/spec/lib/mini_magick/shell_spec.rb +66 -0
- data/spec/lib/mini_magick/tool_spec.rb +90 -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 +42 -76
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e11b9e22bbabb55096ee32c8635e5302459b234
|
4
|
+
data.tar.gz: ed3df8df5bd3c55605760ce4d68eb070180fd890
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e97b6cce0e021ab9f21a31e74e9b8afe9aca5e95c722fcd1230d31a3ece8310bed90fe91f2dc131a783a3e9c987217cc962b6fc296926c6f73fbb8ab263ce06d
|
7
|
+
data.tar.gz: 7f489774f7ce93a2e63d5fce3d12ddea5f70aa2a98b6343ff5f49df3cf66f67d9f8fbcd135f9eb9d3214e5160af7bfcc82d425895e5491dbe5194228b66a2b50
|
data/lib/mini_gmagick.rb
CHANGED
data/lib/mini_magick.rb
CHANGED
@@ -1,75 +1,53 @@
|
|
1
|
-
require 'mini_magick/
|
2
|
-
require 'mini_magick/
|
1
|
+
require 'mini_magick/configuration'
|
2
|
+
require 'mini_magick/tool'
|
3
3
|
require 'mini_magick/image'
|
4
|
-
require 'mini_magick/utilities'
|
5
4
|
|
6
5
|
module MiniMagick
|
7
|
-
@validate_on_create = true
|
8
|
-
@validate_on_write = true
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
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
|
29
24
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
25
|
+
##
|
26
|
+
# Checks whether the CLI used is ImageMagick.
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
def self.imagemagick?
|
30
|
+
cli == :imagemagick
|
31
|
+
end
|
38
32
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
33
|
+
##
|
34
|
+
# Checks whether the CLI used is GraphicsMagick.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
def self.graphicsmagick?
|
38
|
+
cli == :graphicsmagick
|
39
|
+
end
|
47
40
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
56
49
|
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
# === Returns
|
61
|
-
# * [Boolean]
|
62
|
-
def mogrify?
|
63
|
-
processor && processor.to_sym == :mogrify
|
64
|
-
end
|
50
|
+
class Error < RuntimeError; end
|
51
|
+
class Invalid < StandardError; end
|
65
52
|
|
66
|
-
##
|
67
|
-
# Checks whether the current processor is graphicsmagick.
|
68
|
-
#
|
69
|
-
# === Returns
|
70
|
-
# * [Boolean]
|
71
|
-
def gm?
|
72
|
-
processor && processor.to_sym == :gm
|
73
|
-
end
|
74
|
-
end
|
75
53
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'mini_magick/utilities'
|
2
|
+
|
3
|
+
module MiniMagick
|
4
|
+
module Configuration
|
5
|
+
|
6
|
+
##
|
7
|
+
# Set whether you want to use [ImageMagick](http://www.imagemagick.org) or
|
8
|
+
# [GraphicsMagick](http://www.graphicsmagick.org).
|
9
|
+
#
|
10
|
+
# @return [Symbol] `:imagemagick` or `:minimagick`
|
11
|
+
#
|
12
|
+
attr_accessor :cli
|
13
|
+
# @private (for backwards compatibility)
|
14
|
+
attr_accessor :processor
|
15
|
+
|
16
|
+
##
|
17
|
+
# If you don't have the CLI tools in your PATH, you can set the path to the
|
18
|
+
# executables.
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
#
|
22
|
+
attr_accessor :cli_path
|
23
|
+
# @private (for backwards compatibility)
|
24
|
+
attr_accessor :processor_path
|
25
|
+
|
26
|
+
##
|
27
|
+
# If you don't want commands to take too long, you can set a timeout (in
|
28
|
+
# seconds).
|
29
|
+
#
|
30
|
+
# @return [Integer]
|
31
|
+
#
|
32
|
+
attr_accessor :timeout
|
33
|
+
##
|
34
|
+
# When set to `true`, it outputs each command to STDOUT in their shell
|
35
|
+
# version.
|
36
|
+
#
|
37
|
+
# @return [Boolean]
|
38
|
+
#
|
39
|
+
attr_accessor :debug
|
40
|
+
##
|
41
|
+
# Logger for {#debug}, default is `MiniMagick::Logger.new(STDOUT)`, but
|
42
|
+
# you can override it, for example if you want the logs to be written to
|
43
|
+
# a file.
|
44
|
+
#
|
45
|
+
# @return [Logger]
|
46
|
+
#
|
47
|
+
attr_accessor :logger
|
48
|
+
##
|
49
|
+
# If set to `true`, it will `identify` every newly created image, and raise
|
50
|
+
# `MiniMagick::Invalid` if the image is not valid. Useful for validating
|
51
|
+
# user input, although it adds a bit of overhead. Defaults to `true`.
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
#
|
55
|
+
attr_accessor :validate_on_create
|
56
|
+
##
|
57
|
+
# If set to `true`, it will `identify` every image that gets written (with
|
58
|
+
# {MiniMagick::Image#write}), and raise `MiniMagick::Invalid` if the image
|
59
|
+
# is not valid. Useful for validating that processing was sucessful,
|
60
|
+
# although it adds a bit of overhead. Defaults to `true`.
|
61
|
+
#
|
62
|
+
# @return [Boolean]
|
63
|
+
#
|
64
|
+
attr_accessor :validate_on_write
|
65
|
+
|
66
|
+
def self.extended(base)
|
67
|
+
base.validate_on_create = true
|
68
|
+
base.validate_on_write = true
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# @yield [self]
|
73
|
+
# @example
|
74
|
+
# MiniMagick.configure do |config|
|
75
|
+
# config.cli = :graphicsmagick
|
76
|
+
# config.timeout = 5
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
def configure
|
80
|
+
yield self
|
81
|
+
end
|
82
|
+
|
83
|
+
def processor
|
84
|
+
@processor ||= ["mogrify", "gm"].detect do |processor|
|
85
|
+
MiniMagick::Utilities.which(processor)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def processor=(processor)
|
90
|
+
@processor = processor.to_s
|
91
|
+
|
92
|
+
unless ["mogrify", "gm"].include?(@processor)
|
93
|
+
raise ArgumentError,
|
94
|
+
"processor has to be set to either \"mogrify\" or \"gm\"" \
|
95
|
+
", was set to #{@processor.inspect}"
|
96
|
+
end
|
97
|
+
|
98
|
+
reload_tools
|
99
|
+
end
|
100
|
+
|
101
|
+
def cli
|
102
|
+
@cli ||
|
103
|
+
case processor.to_s
|
104
|
+
when "mogrify" then :imagemagick
|
105
|
+
when "gm" then :graphicsmagick
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def cli=(value)
|
110
|
+
@cli = value
|
111
|
+
|
112
|
+
unless [:imagemagick, :graphicsmagick].include?(@cli)
|
113
|
+
raise ArgumentError,
|
114
|
+
"CLI has to be set to either :imagemagick or :graphicsmagick" \
|
115
|
+
", was set to #{@cli.inspect}"
|
116
|
+
end
|
117
|
+
|
118
|
+
reload_tools
|
119
|
+
end
|
120
|
+
|
121
|
+
def cli_path
|
122
|
+
@cli_path || @processor_path
|
123
|
+
end
|
124
|
+
|
125
|
+
def logger
|
126
|
+
@logger || MiniMagick::Logger.new($stdout)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def reload_tools
|
132
|
+
MiniMagick::Tool::OptionMethods.instances.each(&:reload_methods)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
data/lib/mini_magick/image.rb
CHANGED
@@ -1,188 +1,164 @@
|
|
1
1
|
require 'tempfile'
|
2
|
-
require 'subexec'
|
3
2
|
require 'stringio'
|
4
3
|
require 'pathname'
|
4
|
+
require 'uri'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
require 'mini_magick/image/info'
|
8
|
+
require 'mini_magick/utilities'
|
5
9
|
|
6
10
|
module MiniMagick
|
7
11
|
class Image
|
8
|
-
# @return [String] The location of the current working file
|
9
|
-
attr_writer :path
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
# @param stream [IOStream, String] Some kind of stream object that needs
|
32
|
-
# to be read or is a binary String blob!
|
33
|
-
# @param ext [String] A manual extension to use for reading the file. Not
|
34
|
-
# required, but if you are having issues, give this a try.
|
35
|
-
# @return [Image]
|
36
|
-
def read(stream, ext = nil)
|
37
|
-
if stream.is_a?(String)
|
38
|
-
stream = StringIO.new(stream)
|
39
|
-
elsif stream.is_a?(StringIO)
|
40
|
-
# Do nothing, we want a StringIO-object
|
41
|
-
elsif stream.respond_to? :path
|
42
|
-
if File.respond_to?(:binread)
|
43
|
-
stream = StringIO.new File.binread(stream.path.to_s)
|
44
|
-
else
|
45
|
-
stream = StringIO.new File.open(stream.path.to_s, 'rb') { |f| f.read }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
create(ext) do |f|
|
50
|
-
while chunk = stream.read(8192)
|
51
|
-
f.write(chunk)
|
52
|
-
end
|
53
|
-
end
|
13
|
+
##
|
14
|
+
# This is the primary loading method used by all of the other class
|
15
|
+
# methods.
|
16
|
+
#
|
17
|
+
# Use this to pass in a stream object. Must respond to #read(size) or be a
|
18
|
+
# binary string object (BLOBBBB)
|
19
|
+
#
|
20
|
+
# Probably easier to use the {.open} method if you want to open a file or a
|
21
|
+
# URL.
|
22
|
+
#
|
23
|
+
# @param stream [#read, String] Some kind of stream object that needs
|
24
|
+
# to be read or is a binary String blob
|
25
|
+
# @param ext [String] A manual extension to use for reading the file. Not
|
26
|
+
# required, but if you are having issues, give this a try.
|
27
|
+
# @return [MiniMagick::Image]
|
28
|
+
#
|
29
|
+
def self.read(stream, ext = nil)
|
30
|
+
if stream.is_a?(String)
|
31
|
+
stream = StringIO.new(stream)
|
54
32
|
end
|
55
33
|
|
56
|
-
|
57
|
-
|
58
|
-
warn 'Warning: MiniMagick::Image.from_blob method is deprecated. Instead, please use Image.read'
|
59
|
-
create(ext) { |f| f.write(blob) }
|
60
|
-
end
|
34
|
+
create(ext) { |file| IO.copy_stream(stream, file) }
|
35
|
+
end
|
61
36
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
37
|
+
##
|
38
|
+
# Creates an image object from a binary string blob which contains raw
|
39
|
+
# pixel data (i.e. no header data).
|
40
|
+
#
|
41
|
+
# @param blob [String] Binary string blob containing raw pixel data.
|
42
|
+
# @param columns [Integer] Number of columns.
|
43
|
+
# @param rows [Integer] Number of rows.
|
44
|
+
# @param depth [Integer] Bit depth of the encoded pixel data.
|
45
|
+
# @param map [String] A code for the mapping of the pixel data. Example:
|
46
|
+
# 'gray' or 'rgb'.
|
47
|
+
# @param format [String] The file extension of the image format to be
|
48
|
+
# used when creating the image object.
|
49
|
+
# Defaults to 'png'.
|
50
|
+
# @return [MiniMagick::Image] The loaded image.
|
51
|
+
#
|
52
|
+
def self.import_pixels(blob, columns, rows, depth, map, format = 'png')
|
53
|
+
# Create an image object with the raw pixel data string:
|
54
|
+
create(".dat", false) { |f| f.write(blob) }.tap do |image|
|
55
|
+
output_path = image.path.sub(/\.\w+$/, ".#{format}")
|
79
56
|
# Use ImageMagick to convert the raw data file to an image file of the
|
80
57
|
# desired format:
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
image.path
|
89
|
-
image
|
58
|
+
MiniMagick::Tool::Convert.new do |convert|
|
59
|
+
convert.size "#{columns}x#{rows}"
|
60
|
+
convert.depth depth
|
61
|
+
convert << "#{map}:#{image.path}"
|
62
|
+
convert << output_path
|
63
|
+
end
|
64
|
+
|
65
|
+
image.path.replace output_path
|
90
66
|
end
|
67
|
+
end
|
91
68
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
if file_or_url.include?('://')
|
109
|
-
require 'open-uri'
|
110
|
-
ext ||= File.extname(URI.parse(file_or_url).path)
|
111
|
-
Kernel.open(file_or_url) do |f|
|
112
|
-
read(f, ext)
|
113
|
-
end
|
69
|
+
##
|
70
|
+
# Opens a specific image file either on the local file system or at a URI.
|
71
|
+
# Use this if you don't want to overwrite the image file.
|
72
|
+
#
|
73
|
+
# Extension is either guessed from the path or you can specify it as a
|
74
|
+
# second parameter.
|
75
|
+
#
|
76
|
+
# @param path_or_url [String] Either a local file path or a URL that
|
77
|
+
# open-uri can read
|
78
|
+
# @param ext [String] Specify the extension you want to read it as
|
79
|
+
# @return [MiniMagick::Image] The loaded image
|
80
|
+
#
|
81
|
+
def self.open(path_or_url, ext = nil)
|
82
|
+
ext ||=
|
83
|
+
if path_or_url.to_s =~ URI.regexp
|
84
|
+
File.extname(URI(path_or_url).path)
|
114
85
|
else
|
115
|
-
|
116
|
-
File.open(file_or_url, 'rb') do |f|
|
117
|
-
read(f, ext)
|
118
|
-
end
|
86
|
+
File.extname(path_or_url)
|
119
87
|
end
|
88
|
+
|
89
|
+
Kernel.open(path_or_url, "rb") do |file|
|
90
|
+
read(file, ext)
|
120
91
|
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Used to create a new Image object data-copy. Not used to "paint" or
|
96
|
+
# that kind of thing.
|
97
|
+
#
|
98
|
+
# Takes an extension in a block and can be used to build a new Image
|
99
|
+
# object. Used by both {.open} and {.read} to create a new object. Ensures
|
100
|
+
# we have a good tempfile.
|
101
|
+
#
|
102
|
+
# @param ext [String] Specify the extension you want to read it as
|
103
|
+
# @param validate [Boolean] If false, skips validation of the created
|
104
|
+
# image. Defaults to true.
|
105
|
+
# @yield [Tempfile] You can #write bits to this object to create the new
|
106
|
+
# Image
|
107
|
+
# @return [MiniMagick::Image] The created image
|
108
|
+
#
|
109
|
+
def self.create(ext = nil, validate = MiniMagick.validate_on_create, &block)
|
110
|
+
tempfile = MiniMagick::Utilities.tempfile(ext.to_s.downcase, &block)
|
121
111
|
|
122
|
-
|
123
|
-
|
124
|
-
warn 'Warning: MiniMagick::Image.from_file is now deprecated. Please use Image.open'
|
125
|
-
open(file, ext)
|
112
|
+
new(tempfile.path, tempfile).tap do |image|
|
113
|
+
image.validate! if validate
|
126
114
|
end
|
115
|
+
end
|
127
116
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
# @param validate [Boolean] If false, skips validation of the created
|
137
|
-
# image. Defaults to true.
|
138
|
-
# @yield [IOStream] You can #write bits to this object to create the new
|
139
|
-
# Image
|
140
|
-
# @return [Image] The created image
|
141
|
-
def create(ext = nil, validate = MiniMagick.validate_on_create, &block)
|
142
|
-
tempfile = Tempfile.new(['mini_magick', ext.to_s.downcase])
|
143
|
-
tempfile.binmode
|
144
|
-
block.call(tempfile)
|
145
|
-
tempfile.close
|
146
|
-
|
147
|
-
image = new(tempfile.path, tempfile)
|
148
|
-
|
149
|
-
fail MiniMagick::Invalid if validate && !image.valid?
|
150
|
-
return image
|
151
|
-
ensure
|
152
|
-
tempfile.close if tempfile
|
117
|
+
##
|
118
|
+
# @private
|
119
|
+
# @!macro [attach] attribute
|
120
|
+
# @!attribute [r] $1
|
121
|
+
#
|
122
|
+
def self.attribute(name, key = name.to_s)
|
123
|
+
define_method(name) do |*args|
|
124
|
+
@info[key, *args]
|
153
125
|
end
|
154
126
|
end
|
155
127
|
|
156
|
-
|
128
|
+
##
|
129
|
+
# @return [String] The location of the current working file
|
130
|
+
#
|
131
|
+
attr_reader :path
|
132
|
+
|
133
|
+
##
|
134
|
+
# Create a new {MiniMagick::Image} object.
|
157
135
|
#
|
158
136
|
# _DANGER_: The file location passed in here is the *working copy*. That
|
159
|
-
# is, it gets *modified*.
|
160
|
-
#
|
161
|
-
# you and protects your original!
|
137
|
+
# is, it gets *modified*. You can either copy it yourself or use {.open}
|
138
|
+
# which creates a temporary file for you and protects your original.
|
162
139
|
#
|
163
140
|
# @param input_path [String] The location of an image file
|
164
|
-
# @
|
165
|
-
#
|
166
|
-
|
141
|
+
# @yield [MiniMagick::Tool::Mogrify] If block is given, {#combine_options}
|
142
|
+
# is called.
|
143
|
+
#
|
144
|
+
def initialize(input_path, tempfile = nil, &block)
|
167
145
|
@path = input_path
|
168
146
|
@tempfile = tempfile
|
169
|
-
@info =
|
170
|
-
reset_queue
|
171
|
-
end
|
147
|
+
@info = MiniMagick::Image::Info.new(@path)
|
172
148
|
|
173
|
-
|
174
|
-
@command_queued = false
|
175
|
-
@queue = MiniMagick::CommandBuilder.new('mogrify')
|
176
|
-
@info.clear
|
149
|
+
combine_options(&block) if block
|
177
150
|
end
|
178
151
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
152
|
+
##
|
153
|
+
# Returns raw image data.
|
154
|
+
#
|
155
|
+
# @return [String] Binary string
|
156
|
+
#
|
157
|
+
def to_blob
|
158
|
+
File.binread(path)
|
184
159
|
end
|
185
160
|
|
161
|
+
##
|
186
162
|
# Checks to make sure that MiniMagick can read the file and understand it.
|
187
163
|
#
|
188
164
|
# This uses the 'identify' command line utility to check the file. If you
|
@@ -190,89 +166,110 @@ module MiniMagick
|
|
190
166
|
# 'identify' command and see if you can figure out what the issue is.
|
191
167
|
#
|
192
168
|
# @return [Boolean]
|
169
|
+
#
|
193
170
|
def valid?
|
194
|
-
|
171
|
+
validate!
|
195
172
|
true
|
196
173
|
rescue MiniMagick::Invalid
|
197
174
|
false
|
198
175
|
end
|
199
176
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
177
|
+
##
|
178
|
+
# Runs `identify` on the current image, and raises an error if it doesn't
|
179
|
+
# pass.
|
180
|
+
#
|
181
|
+
# @raise [MiniMagick::Invalid]
|
182
|
+
#
|
183
|
+
def validate!
|
184
|
+
identify
|
185
|
+
rescue MiniMagick::Error => error
|
186
|
+
raise MiniMagick::Invalid, error.message
|
204
187
|
end
|
205
188
|
|
206
|
-
|
207
|
-
#
|
208
|
-
#
|
189
|
+
##
|
190
|
+
# Returns the image format (e.g. "JPEG", "GIF").
|
191
|
+
#
|
192
|
+
# @return [String]
|
193
|
+
#
|
194
|
+
attribute :type, "format"
|
195
|
+
##
|
196
|
+
# @return [String]
|
197
|
+
#
|
198
|
+
attribute :mime_type
|
199
|
+
##
|
200
|
+
# @return [Integer]
|
201
|
+
#
|
202
|
+
attribute :width
|
203
|
+
##
|
204
|
+
# @return [Integer]
|
205
|
+
#
|
206
|
+
attribute :height
|
207
|
+
##
|
208
|
+
# @return [Array<Integer>]
|
209
|
+
#
|
210
|
+
attribute :dimensions
|
211
|
+
##
|
212
|
+
# Returns the file size of the image.
|
213
|
+
#
|
214
|
+
# @return [Integer]
|
215
|
+
#
|
216
|
+
attribute :size
|
217
|
+
##
|
218
|
+
# @return [String]
|
219
|
+
#
|
220
|
+
attribute :colorspace
|
221
|
+
##
|
222
|
+
# @return [Hash]
|
223
|
+
#
|
224
|
+
attribute :exif
|
225
|
+
##
|
226
|
+
# Returns the resolution of the photo. You can optionally specify the
|
227
|
+
# units measurement.
|
228
|
+
#
|
229
|
+
# @example
|
230
|
+
# image.resolution("PixelsPerInch") #=> [250, 250]
|
231
|
+
# @see http://www.imagemagick.org/script/command-line-options.php#units
|
232
|
+
# @return [Array<Integer>]
|
233
|
+
#
|
234
|
+
attribute :resolution
|
235
|
+
|
236
|
+
##
|
237
|
+
# Use this method if you want to access raw Identify's format API.
|
209
238
|
#
|
210
239
|
# @example
|
211
|
-
# image["
|
212
|
-
# image["
|
213
|
-
#
|
214
|
-
#
|
215
|
-
#
|
216
|
-
#
|
217
|
-
#
|
218
|
-
# image["EXIF:ExifVersion"] #=> "0220" (Can read anything from Exif)
|
219
|
-
#
|
220
|
-
# @param format [String] A format for the "identify" command
|
221
|
-
# @see http://www.imagemagick.org/script/command-line-options.php#format
|
222
|
-
# @return [String, Numeric, Array, Time, Object] Depends on the method
|
223
|
-
# called! Defaults to String for unknown commands
|
240
|
+
# image["%w %h"] #=> "250 450"
|
241
|
+
# image["%r"] #=> "DirectClass sRGB"
|
242
|
+
#
|
243
|
+
# @param value [String]
|
244
|
+
# @see http://www.imagemagick.org/script/escape.php
|
245
|
+
# @return [String]
|
246
|
+
#
|
224
247
|
def [](value)
|
225
|
-
|
226
|
-
return retrieved unless retrieved.nil?
|
227
|
-
|
228
|
-
# Why do I go to the trouble of putting in newlines? Because otherwise
|
229
|
-
# animated gifs screw everything up
|
230
|
-
retrieved = case value.to_s
|
231
|
-
when 'colorspace'
|
232
|
-
run_command('identify', '-format', '%r\n', path).split("\n")[0].strip
|
233
|
-
when 'format'
|
234
|
-
run_command('identify', '-format', '%m\n', path).split("\n")[0]
|
235
|
-
when 'dimensions', 'width', 'height'
|
236
|
-
width_height = run_command(
|
237
|
-
'identify', '-format', MiniMagick::Utilities.windows? ? '"%w %h\n"' : '%w %h\n', path
|
238
|
-
).split("\n")[0].split.map { |v| v.to_i }
|
239
|
-
|
240
|
-
@info[:width] = width_height[0]
|
241
|
-
@info[:height] = width_height[1]
|
242
|
-
@info[:dimensions] = width_height
|
243
|
-
@info[value.to_sym]
|
244
|
-
when 'size'
|
245
|
-
File.size(path) # Do this because calling identify -format "%b" on an animated gif fails!
|
246
|
-
when 'original_at'
|
247
|
-
# Get the EXIF original capture as a Time object
|
248
|
-
Time.local(*self['EXIF:DateTimeOriginal'].split(/:|\s+/)) rescue nil
|
249
|
-
when /^EXIF\:/i
|
250
|
-
result = run_command('identify', '-format', "%[#{value}]", path).chomp
|
251
|
-
if result.include?(',')
|
252
|
-
read_character_data(result)
|
253
|
-
else
|
254
|
-
result
|
255
|
-
end
|
256
|
-
else
|
257
|
-
run_command('identify', '-format', value, path).split("\n")[0]
|
258
|
-
end
|
259
|
-
|
260
|
-
@info[value] = retrieved unless retrieved.nil?
|
261
|
-
@info[value]
|
248
|
+
@info[value.to_s]
|
262
249
|
end
|
250
|
+
alias info []
|
263
251
|
|
264
|
-
|
265
|
-
#
|
252
|
+
##
|
253
|
+
# Returns layers of the image. For example, JPEGs are 1-layered, but
|
254
|
+
# formats like PSDs, GIFs and PDFs can have multiple layers/frames/pages.
|
266
255
|
#
|
267
|
-
#
|
268
|
-
#
|
256
|
+
# @example
|
257
|
+
# image = MiniMagick::Image.new("document.pdf")
|
258
|
+
# image.pages.each_with_index do |page, idx|
|
259
|
+
# page.write("page#{idx}.pdf")
|
260
|
+
# end
|
261
|
+
# @return [Array<MiniMagick::Image>]
|
269
262
|
#
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
263
|
+
def layers
|
264
|
+
layers_count = identify.lines.count
|
265
|
+
layers_count.times.map do |idx|
|
266
|
+
MiniMagick::Image.new("#{path}[#{idx}]")
|
267
|
+
end
|
274
268
|
end
|
269
|
+
alias pages layers
|
270
|
+
alias frames layers
|
275
271
|
|
272
|
+
##
|
276
273
|
# This is used to change the format of the image. That is, from "tiff to
|
277
274
|
# jpg" or something like that. Once you run it, the instance is pointing to
|
278
275
|
# a new file with a new extension!
|
@@ -293,169 +290,192 @@ module MiniMagick
|
|
293
290
|
# @param page [Integer] If this is an animated gif, say which 'page' you
|
294
291
|
# want with an integer. Default 0 will convert only the first page; 'nil'
|
295
292
|
# will convert all pages.
|
296
|
-
# @
|
293
|
+
# @yield [MiniMagick::Tool::Convert] It optionally yields the command,
|
294
|
+
# if you want to add something.
|
295
|
+
# @return [self]
|
296
|
+
#
|
297
297
|
def format(format, page = 0)
|
298
|
-
|
298
|
+
@info.clear
|
299
|
+
|
300
|
+
if @tempfile
|
301
|
+
new_tempfile = MiniMagick::Utilities.tempfile(".#{format}")
|
302
|
+
new_path = new_tempfile.path
|
303
|
+
else
|
304
|
+
new_path = path.sub(/\.\w+$/, ".#{format}")
|
305
|
+
end
|
299
306
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
307
|
+
MiniMagick::Tool::Convert.new do |convert|
|
308
|
+
convert << (page ? "#{path}[#{page}]" : path)
|
309
|
+
yield convert if block_given?
|
310
|
+
convert << new_path
|
311
|
+
end
|
304
312
|
|
305
|
-
|
313
|
+
if @tempfile
|
314
|
+
@tempfile.unlink
|
315
|
+
@tempfile = new_tempfile
|
316
|
+
else
|
317
|
+
File.delete(path) unless path == new_path
|
318
|
+
end
|
306
319
|
|
307
|
-
|
320
|
+
path.replace new_path
|
308
321
|
|
309
|
-
|
322
|
+
self
|
323
|
+
end
|
310
324
|
|
311
|
-
|
312
|
-
|
313
|
-
|
325
|
+
##
|
326
|
+
# You can use multiple commands together using this method. Very easy to
|
327
|
+
# use!
|
328
|
+
#
|
329
|
+
# @example
|
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 "blue"
|
334
|
+
# end
|
335
|
+
#
|
336
|
+
# @yield [MiniMagick::Tool::Mogrify]
|
337
|
+
# @see http://www.imagemagick.org/script/mogrify.php
|
338
|
+
# @return [self]
|
339
|
+
#
|
340
|
+
def combine_options(&block)
|
341
|
+
mogrify(&block)
|
314
342
|
end
|
315
343
|
|
316
|
-
|
317
|
-
#
|
318
|
-
|
319
|
-
|
344
|
+
##
|
345
|
+
# If an unknown method is called then it is sent through the mogrify
|
346
|
+
# program.
|
347
|
+
#
|
348
|
+
# @see http://www.imagemagick.org/script/mogrify.php
|
349
|
+
# @return [self]
|
350
|
+
#
|
351
|
+
def method_missing(name, *args)
|
352
|
+
mogrify do |builder|
|
353
|
+
if builder.respond_to?(name)
|
354
|
+
builder.send(name, *args)
|
355
|
+
else
|
356
|
+
super
|
357
|
+
end
|
358
|
+
end
|
320
359
|
end
|
321
360
|
|
361
|
+
##
|
322
362
|
# Writes the temporary file out to either a file location (by passing in a
|
323
363
|
# String) or by passing in a Stream that you can #write(chunk) to
|
324
364
|
# repeatedly
|
325
365
|
#
|
326
|
-
# @param output_to [
|
327
|
-
# to be read or a file path as a String
|
328
|
-
#
|
329
|
-
# you get a success boolean. If its a stream, you get it back.
|
366
|
+
# @param output_to [String, Pathname, #read] Some kind of stream object
|
367
|
+
# that needs to be read or a file path as a String
|
368
|
+
#
|
330
369
|
def write(output_to)
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
'identify', MiniMagick::Utilities.path(output_to.to_s)
|
338
|
-
) # Verify that we have a good image
|
339
|
-
end
|
340
|
-
else # stream
|
341
|
-
File.open(path, 'rb') do |f|
|
342
|
-
f.binmode
|
343
|
-
while chunk = f.read(8192)
|
344
|
-
output_to.write(chunk)
|
370
|
+
case output_to
|
371
|
+
when String, Pathname
|
372
|
+
if layer?
|
373
|
+
MiniMagick::Tool::Convert.new do |builder|
|
374
|
+
builder << path
|
375
|
+
builder << output_to
|
345
376
|
end
|
377
|
+
else
|
378
|
+
FileUtils.copy_file path, output_to
|
346
379
|
end
|
347
|
-
|
380
|
+
else
|
381
|
+
IO.copy_stream File.open(path, "rb"), output_to
|
348
382
|
end
|
349
383
|
end
|
350
384
|
|
351
|
-
|
352
|
-
# @
|
353
|
-
|
354
|
-
|
385
|
+
##
|
386
|
+
# @example
|
387
|
+
# first_image = MiniMagick::Image.open "first.jpg"
|
388
|
+
# second_image = MiniMagick::Image.open "second.jpg"
|
389
|
+
# result = first_image.composite(second_image) do |c|
|
390
|
+
# c.compose "Over" # OverCompositeOp
|
391
|
+
# c.geometry "+20+20" # copy second_image onto first_image from (20, 20)
|
392
|
+
# end
|
393
|
+
# result.write "output.jpg"
|
394
|
+
#
|
395
|
+
# @see http://www.imagemagick.org/script/composite.php
|
396
|
+
#
|
397
|
+
def composite(other_image, output_extension = 'jpg', mask = nil)
|
398
|
+
output_tempfile = MiniMagick::Utilities.tempfile(".#{output_extension}")
|
399
|
+
|
400
|
+
MiniMagick::Tool::Composite.new do |composite|
|
401
|
+
yield composite if block_given?
|
402
|
+
composite << other_image.path
|
403
|
+
composite << path
|
404
|
+
composite << mask.path if mask
|
405
|
+
composite << output_tempfile.path
|
406
|
+
end
|
355
407
|
|
356
|
-
|
357
|
-
f.binmode
|
358
|
-
f.read
|
359
|
-
ensure
|
360
|
-
f.close if f
|
408
|
+
Image.new(output_tempfile.path, output_tempfile)
|
361
409
|
end
|
362
410
|
|
363
|
-
|
364
|
-
|
365
|
-
|
411
|
+
##
|
412
|
+
# Collapse images with sequences to the first frame (i.e. animated gifs) and
|
413
|
+
# preserve quality.
|
414
|
+
#
|
415
|
+
# @param frame [Integer] The frame to which to collapse to, defaults to `0`.
|
416
|
+
# @return [self]
|
417
|
+
#
|
418
|
+
def collapse!(frame = 0)
|
419
|
+
mogrify(frame) { |builder| builder.quality(100) }
|
366
420
|
end
|
367
421
|
|
368
|
-
|
369
|
-
#
|
422
|
+
##
|
423
|
+
# Destroys the tempfile (created by {.open}) if it exists.
|
370
424
|
#
|
371
|
-
|
372
|
-
|
373
|
-
@queue.send(symbol, *args)
|
374
|
-
@command_queued = true
|
425
|
+
def destroy!
|
426
|
+
@tempfile.unlink if @tempfile
|
375
427
|
end
|
376
428
|
|
377
|
-
|
378
|
-
#
|
429
|
+
##
|
430
|
+
# Runs `identify` on itself. Accepts an optional block for adding more
|
431
|
+
# options to `identify`.
|
379
432
|
#
|
380
433
|
# @example
|
381
|
-
# image
|
382
|
-
#
|
383
|
-
#
|
384
|
-
#
|
385
|
-
#
|
434
|
+
# image = MiniMagick::Image.open("image.jpg")
|
435
|
+
# image.identify do |b|
|
436
|
+
# b.verbose
|
437
|
+
# end # runs `identify -verbose image.jpg`
|
438
|
+
# @return [String] Output from `identify`
|
439
|
+
# @yield [MiniMagick::Tool::Identify]
|
386
440
|
#
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
@command_queued = true
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
def composite(other_image, output_extension = 'jpg', mask = nil, &block)
|
396
|
-
run_queue if @command_queued
|
397
|
-
begin
|
398
|
-
second_tempfile = Tempfile.new(output_extension)
|
399
|
-
second_tempfile.binmode
|
400
|
-
ensure
|
401
|
-
second_tempfile.close
|
441
|
+
def identify
|
442
|
+
MiniMagick::Tool::Identify.new do |builder|
|
443
|
+
yield builder if block_given?
|
444
|
+
builder << path
|
402
445
|
end
|
403
|
-
|
404
|
-
command = CommandBuilder.new('composite')
|
405
|
-
block.call(command) if block
|
406
|
-
command.push(other_image.path)
|
407
|
-
command.push(path)
|
408
|
-
command.push(mask.path) unless mask.nil?
|
409
|
-
command.push(second_tempfile.path)
|
410
|
-
|
411
|
-
run(command)
|
412
|
-
Image.new(second_tempfile.path, second_tempfile)
|
413
446
|
end
|
414
447
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
448
|
+
# @private
|
449
|
+
def run_command(tool_name, *args)
|
450
|
+
MiniMagick::Tool.const_get(tool_name.capitalize).new do |builder|
|
451
|
+
args.each do |arg|
|
452
|
+
builder << arg
|
453
|
+
end
|
421
454
|
end
|
422
|
-
|
423
|
-
run(CommandBuilder.new(command, *args))
|
424
455
|
end
|
425
456
|
|
426
|
-
|
427
|
-
command = command_builder.command
|
428
|
-
|
429
|
-
sub = Subexec.run(command, :timeout => MiniMagick.timeout)
|
457
|
+
private
|
430
458
|
|
431
|
-
|
432
|
-
|
433
|
-
destroy!
|
459
|
+
def mogrify(page = nil)
|
460
|
+
@info.clear
|
434
461
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
fail Error, "Command (#{command.inspect.gsub("\\", "")}) failed: #{{ :status_code => sub.exitstatus, :output => sub.output }.inspect}"
|
462
|
+
MiniMagick::Tool::Mogrify.new do |builder|
|
463
|
+
builder.instance_eval do
|
464
|
+
def format(*)
|
465
|
+
fail NoMethodError,
|
466
|
+
"you must call #format on a MiniMagick::Image directly"
|
467
|
+
end
|
442
468
|
end
|
443
|
-
|
444
|
-
|
469
|
+
yield builder if block_given?
|
470
|
+
builder << (page ? "#{path}[#{page}]" : path)
|
445
471
|
end
|
446
|
-
end
|
447
472
|
|
448
|
-
|
449
|
-
return if @tempfile.nil?
|
450
|
-
File.unlink(@path) if File.exist?(@path)
|
451
|
-
@tempfile = nil
|
473
|
+
self
|
452
474
|
end
|
453
475
|
|
454
|
-
|
455
|
-
|
456
|
-
# Sometimes we get back a list of character values
|
457
|
-
def read_character_data(string)
|
458
|
-
string.scan(/\d+/).map(&:to_i).map(&:chr).join
|
476
|
+
def layer?
|
477
|
+
path =~ /\[\d+\]$/
|
459
478
|
end
|
479
|
+
|
460
480
|
end
|
461
481
|
end
|