mini_magick 3.7.0 → 4.11.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 +5 -5
- data/Rakefile +3 -3
- data/lib/mini_gmagick.rb +1 -0
- data/lib/mini_magick/configuration.rb +190 -0
- data/lib/mini_magick/image/info.rb +202 -0
- data/lib/mini_magick/image.rb +540 -311
- data/lib/mini_magick/shell.rb +81 -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/magick.rb +14 -0
- data/lib/mini_magick/tool/mogrify.rb +14 -0
- data/lib/mini_magick/tool/mogrify_restricted.rb +15 -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 +307 -0
- data/lib/mini_magick/utilities.rb +25 -21
- data/lib/mini_magick/version.rb +15 -1
- data/lib/mini_magick.rb +54 -70
- metadata +65 -30
- data/lib/mini_magick/command_builder.rb +0 -104
- data/lib/mini_magick/errors.rb +0 -4
data/lib/mini_magick/image.rb
CHANGED
@@ -1,405 +1,634 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'stringio'
|
3
|
+
require 'pathname'
|
4
|
+
require 'uri'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
require 'mini_magick/image/info'
|
8
|
+
require 'mini_magick/utilities'
|
9
|
+
|
1
10
|
module MiniMagick
|
2
11
|
class Image
|
3
|
-
# @return [String] The location of the current working file
|
4
|
-
attr_accessor :path
|
5
12
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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 (BLOB)
|
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)
|
32
|
+
end
|
12
33
|
|
13
|
-
|
14
|
-
MiniMagick::Utilities.windows? ? path_for_windows_quote_space(@path) : @path
|
34
|
+
create(ext) { |file| IO.copy_stream(stream, file) }
|
15
35
|
end
|
16
36
|
|
17
|
-
|
18
|
-
|
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}")
|
56
|
+
# Use ImageMagick to convert the raw data file to an image file of the
|
57
|
+
# desired format:
|
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
|
66
|
+
end
|
19
67
|
end
|
20
68
|
|
21
|
-
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
+
# @param options [Hash] Specify options for the open method
|
80
|
+
# @return [MiniMagick::Image] The loaded image
|
81
|
+
#
|
82
|
+
def self.open(path_or_url, ext = nil, options = {})
|
83
|
+
options, ext = ext, nil if ext.is_a?(Hash)
|
84
|
+
|
85
|
+
# Don't use Kernel#open, but reuse its logic
|
86
|
+
openable =
|
87
|
+
if path_or_url.respond_to?(:open)
|
88
|
+
path_or_url
|
89
|
+
elsif path_or_url.respond_to?(:to_str) &&
|
90
|
+
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ path_or_url &&
|
91
|
+
(uri = URI.parse(path_or_url)).respond_to?(:open)
|
92
|
+
uri
|
93
|
+
else
|
94
|
+
options = { binmode: true }.merge(options)
|
95
|
+
Pathname(path_or_url)
|
46
96
|
end
|
47
97
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
98
|
+
if openable.is_a?(URI::Generic)
|
99
|
+
ext ||= File.extname(openable.path)
|
100
|
+
else
|
101
|
+
ext ||= File.extname(openable.to_s)
|
53
102
|
end
|
103
|
+
ext.sub!(/:.*/, '') # hack for filenames or URLs that include a colon
|
54
104
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
105
|
+
if openable.is_a?(URI::Generic)
|
106
|
+
openable.open(options) { |file| read(file, ext) }
|
107
|
+
else
|
108
|
+
openable.open(**options) { |file| read(file, ext) }
|
59
109
|
end
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Used to create a new Image object data-copy. Not used to "paint" or
|
114
|
+
# that kind of thing.
|
115
|
+
#
|
116
|
+
# Takes an extension in a block and can be used to build a new Image
|
117
|
+
# object. Used by both {.open} and {.read} to create a new object. Ensures
|
118
|
+
# we have a good tempfile.
|
119
|
+
#
|
120
|
+
# @param ext [String] Specify the extension you want to read it as
|
121
|
+
# @param validate [Boolean] If false, skips validation of the created
|
122
|
+
# image. Defaults to true.
|
123
|
+
# @yield [Tempfile] You can #write bits to this object to create the new
|
124
|
+
# Image
|
125
|
+
# @return [MiniMagick::Image] The created image
|
126
|
+
#
|
127
|
+
def self.create(ext = nil, validate = MiniMagick.validate_on_create, &block)
|
128
|
+
tempfile = MiniMagick::Utilities.tempfile(ext.to_s.downcase, &block)
|
60
129
|
|
61
|
-
|
62
|
-
|
63
|
-
# === Returns
|
64
|
-
#
|
65
|
-
# * [Image] The loaded image.
|
66
|
-
#
|
67
|
-
# === Parameters
|
68
|
-
#
|
69
|
-
# * [blob] <tt>String</tt> -- Binary string blob containing raw pixel data.
|
70
|
-
# * [columns] <tt>Integer</tt> -- Number of columns.
|
71
|
-
# * [rows] <tt>Integer</tt> -- Number of rows.
|
72
|
-
# * [depth] <tt>Integer</tt> -- Bit depth of the encoded pixel data.
|
73
|
-
# * [map] <tt>String</tt> -- A code for the mapping of the pixel data. Example: 'gray' or 'rgb'.
|
74
|
-
# * [format] <tt>String</tt> -- The file extension of the image format to be used when creating the image object. Defaults to 'png'.
|
75
|
-
#
|
76
|
-
def import_pixels(blob, columns, rows, depth, map, format="png")
|
77
|
-
# Create an image object with the raw pixel data string:
|
78
|
-
image = create(".dat", validate = false) { |f| f.write(blob) }
|
79
|
-
# Use ImageMagick to convert the raw data file to an image file of the desired format:
|
80
|
-
converted_image_path = image.path[0..-4] + format
|
81
|
-
arguments = ["-size", "#{columns}x#{rows}", "-depth", "#{depth}", "#{map}:#{image.path}", "#{converted_image_path}"]
|
82
|
-
cmd = CommandBuilder.new("convert", *arguments) #Example: convert -size 256x256 -depth 16 gray:blob.dat blob.png
|
83
|
-
image.run(cmd)
|
84
|
-
# Update the image instance with the path of the properly formatted image, and return:
|
85
|
-
image.path = converted_image_path
|
86
|
-
image
|
130
|
+
new(tempfile.path, tempfile).tap do |image|
|
131
|
+
image.validate! if validate
|
87
132
|
end
|
133
|
+
end
|
88
134
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# @param ext [String] Specify the extension you want to read it as
|
99
|
-
# @return [Image] The loaded image
|
100
|
-
def open(file_or_url, ext = nil)
|
101
|
-
file_or_url = file_or_url.to_s # Force it to be a String... hell or highwater
|
102
|
-
if file_or_url.include?("://")
|
103
|
-
require 'open-uri'
|
104
|
-
ext ||= File.extname(URI.parse(file_or_url).path)
|
105
|
-
Kernel::open(file_or_url) do |f|
|
106
|
-
self.read(f, ext)
|
107
|
-
end
|
135
|
+
##
|
136
|
+
# @private
|
137
|
+
# @!macro [attach] attribute
|
138
|
+
# @!attribute [r] $1
|
139
|
+
#
|
140
|
+
def self.attribute(name, key = name.to_s)
|
141
|
+
define_method(name) do |*args|
|
142
|
+
if args.any? && name != :resolution
|
143
|
+
mogrify { |b| b.send(name, *args) }
|
108
144
|
else
|
109
|
-
|
110
|
-
File.open(file_or_url, "rb") do |f|
|
111
|
-
self.read(f, ext)
|
112
|
-
end
|
145
|
+
@info[key, *args]
|
113
146
|
end
|
114
147
|
end
|
148
|
+
end
|
115
149
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
150
|
+
##
|
151
|
+
# @return [String] The location of the current working file
|
152
|
+
#
|
153
|
+
attr_reader :path
|
154
|
+
##
|
155
|
+
# @return [Tempfile] The underlying temporary file
|
156
|
+
#
|
157
|
+
attr_reader :tempfile
|
121
158
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
159
|
+
##
|
160
|
+
# Create a new {MiniMagick::Image} object.
|
161
|
+
#
|
162
|
+
# _DANGER_: The file location passed in here is the *working copy*. That
|
163
|
+
# is, it gets *modified*. You can either copy it yourself or use {.open}
|
164
|
+
# which creates a temporary file for you and protects your original.
|
165
|
+
#
|
166
|
+
# @param input_path [String, Pathname] The location of an image file
|
167
|
+
# @yield [MiniMagick::Tool::Mogrify] If block is given, {#combine_options}
|
168
|
+
# is called.
|
169
|
+
#
|
170
|
+
def initialize(input_path, tempfile = nil, &block)
|
171
|
+
@path = input_path.to_s
|
172
|
+
@tempfile = tempfile
|
173
|
+
@info = MiniMagick::Image::Info.new(@path)
|
174
|
+
|
175
|
+
combine_options(&block) if block
|
176
|
+
end
|
177
|
+
|
178
|
+
def ==(other)
|
179
|
+
self.class == other.class && signature == other.signature
|
180
|
+
end
|
181
|
+
alias eql? ==
|
182
|
+
|
183
|
+
def hash
|
184
|
+
signature.hash
|
148
185
|
end
|
149
186
|
|
150
|
-
|
187
|
+
##
|
188
|
+
# Returns raw image data.
|
151
189
|
#
|
152
|
-
#
|
153
|
-
# you can either copy it yourself or use the MiniMagick::Image.open(path) method which creates a
|
154
|
-
# temporary file for you and protects your original!
|
190
|
+
# @return [String] Binary string
|
155
191
|
#
|
156
|
-
|
157
|
-
|
158
|
-
def initialize(input_path, tempfile = nil)
|
159
|
-
@path = input_path
|
160
|
-
@tempfile = tempfile # ensures that the tempfile will stick around until this image is garbage collected.
|
192
|
+
def to_blob
|
193
|
+
File.binread(path)
|
161
194
|
end
|
162
195
|
|
196
|
+
##
|
163
197
|
# Checks to make sure that MiniMagick can read the file and understand it.
|
164
198
|
#
|
165
|
-
# This uses the 'identify' command line utility to check the file. If you
|
166
|
-
# issues with this, then please work directly with the
|
167
|
-
# can figure out what the issue is.
|
199
|
+
# This uses the 'identify' command line utility to check the file. If you
|
200
|
+
# are having issues with this, then please work directly with the
|
201
|
+
# 'identify' command and see if you can figure out what the issue is.
|
168
202
|
#
|
169
203
|
# @return [Boolean]
|
204
|
+
#
|
170
205
|
def valid?
|
171
|
-
|
206
|
+
validate!
|
172
207
|
true
|
173
208
|
rescue MiniMagick::Invalid
|
174
209
|
false
|
175
210
|
end
|
176
211
|
|
177
|
-
|
178
|
-
# the
|
212
|
+
##
|
213
|
+
# Runs `identify` on the current image, and raises an error if it doesn't
|
214
|
+
# pass.
|
215
|
+
#
|
216
|
+
# @raise [MiniMagick::Invalid]
|
217
|
+
#
|
218
|
+
def validate!
|
219
|
+
identify
|
220
|
+
rescue MiniMagick::Error => error
|
221
|
+
raise MiniMagick::Invalid, error.message
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Returns the image format (e.g. "JPEG", "GIF").
|
226
|
+
#
|
227
|
+
# @return [String]
|
228
|
+
#
|
229
|
+
attribute :type, "format"
|
230
|
+
##
|
231
|
+
# @return [String]
|
232
|
+
#
|
233
|
+
attribute :mime_type
|
234
|
+
##
|
235
|
+
# @return [Integer]
|
236
|
+
#
|
237
|
+
attribute :width
|
238
|
+
##
|
239
|
+
# @return [Integer]
|
240
|
+
#
|
241
|
+
attribute :height
|
242
|
+
##
|
243
|
+
# @return [Array<Integer>]
|
244
|
+
#
|
245
|
+
attribute :dimensions
|
246
|
+
##
|
247
|
+
# Returns the file size of the image (in bytes).
|
248
|
+
#
|
249
|
+
# @return [Integer]
|
250
|
+
#
|
251
|
+
attribute :size
|
252
|
+
##
|
253
|
+
# Returns the file size in a human readable format.
|
254
|
+
#
|
255
|
+
# @return [String]
|
256
|
+
#
|
257
|
+
attribute :human_size
|
258
|
+
##
|
259
|
+
# @return [String]
|
260
|
+
#
|
261
|
+
attribute :colorspace
|
262
|
+
##
|
263
|
+
# @return [Hash]
|
264
|
+
#
|
265
|
+
attribute :exif
|
266
|
+
##
|
267
|
+
# Returns the resolution of the photo. You can optionally specify the
|
268
|
+
# units measurement.
|
269
|
+
#
|
270
|
+
# @example
|
271
|
+
# image.resolution("PixelsPerInch") #=> [250, 250]
|
272
|
+
# @see http://www.imagemagick.org/script/command-line-options.php#units
|
273
|
+
# @return [Array<Integer>]
|
274
|
+
#
|
275
|
+
attribute :resolution
|
276
|
+
##
|
277
|
+
# Returns the message digest of this image as a SHA-256, hexidecimal
|
278
|
+
# encoded string. This signature uniquely identifies the image and is
|
279
|
+
# convenient for determining if an image has been modified or whether two
|
280
|
+
# images are identical.
|
281
|
+
#
|
282
|
+
# @example
|
283
|
+
# image.signature #=> "60a7848c4ca6e36b8e2c5dea632ecdc29e9637791d2c59ebf7a54c0c6a74ef7e"
|
284
|
+
# @see http://www.imagemagick.org/api/signature.php
|
285
|
+
# @return [String]
|
286
|
+
#
|
287
|
+
attribute :signature
|
288
|
+
##
|
289
|
+
# Returns the information from `identify -verbose` in a Hash format, for
|
290
|
+
# ImageMagick.
|
291
|
+
#
|
292
|
+
# @return [Hash]
|
293
|
+
attribute :data
|
294
|
+
##
|
295
|
+
# Returns the information from `identify -verbose` in a Hash format, for
|
296
|
+
# GraphicsMagick.
|
297
|
+
#
|
298
|
+
# @return [Hash]
|
299
|
+
attribute :details
|
300
|
+
|
301
|
+
##
|
302
|
+
# Use this method if you want to access raw Identify's format API.
|
179
303
|
#
|
180
304
|
# @example
|
181
|
-
# image["
|
182
|
-
# image["
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
# image["EXIF:ExifVersion"] #=> "0220" (Can read anything from Exif)
|
189
|
-
#
|
190
|
-
# @param format [String] A format for the "identify" command
|
191
|
-
# @see For reference see http://www.imagemagick.org/script/command-line-options.php#format
|
192
|
-
# @return [String, Numeric, Array, Time, Object] Depends on the method called! Defaults to String for unknown commands
|
305
|
+
# image["%w %h"] #=> "250 450"
|
306
|
+
# image["%r"] #=> "DirectClass sRGB"
|
307
|
+
#
|
308
|
+
# @param value [String]
|
309
|
+
# @see http://www.imagemagick.org/script/escape.php
|
310
|
+
# @return [String]
|
311
|
+
#
|
193
312
|
def [](value)
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
if result.include?(",")
|
214
|
-
read_character_data(result)
|
215
|
-
else
|
216
|
-
result
|
217
|
-
end
|
218
|
-
else
|
219
|
-
run_command('identify', '-format', value, path).split("\n")[0]
|
313
|
+
@info[value.to_s]
|
314
|
+
end
|
315
|
+
alias info []
|
316
|
+
|
317
|
+
##
|
318
|
+
# Returns layers of the image. For example, JPEGs are 1-layered, but
|
319
|
+
# formats like PSDs, GIFs and PDFs can have multiple layers/frames/pages.
|
320
|
+
#
|
321
|
+
# @example
|
322
|
+
# image = MiniMagick::Image.new("document.pdf")
|
323
|
+
# image.pages.each_with_index do |page, idx|
|
324
|
+
# page.write("page#{idx}.pdf")
|
325
|
+
# end
|
326
|
+
# @return [Array<MiniMagick::Image>]
|
327
|
+
#
|
328
|
+
def layers
|
329
|
+
layers_count = identify.lines.count
|
330
|
+
layers_count.times.map do |idx|
|
331
|
+
MiniMagick::Image.new("#{path}[#{idx}]")
|
220
332
|
end
|
221
333
|
end
|
334
|
+
alias pages layers
|
335
|
+
alias frames layers
|
222
336
|
|
223
|
-
|
337
|
+
##
|
338
|
+
# Returns a matrix of pixels from the image. The matrix is constructed as
|
339
|
+
# an array (1) of arrays (2) of arrays (3) of unsigned integers:
|
340
|
+
#
|
341
|
+
# 1) one for each row of pixels
|
342
|
+
# 2) one for each column of pixels
|
343
|
+
# 3) three elements in the range 0-255, one for each of the RGB color channels
|
344
|
+
#
|
345
|
+
# @example
|
346
|
+
# img = MiniMagick::Image.open 'image.jpg'
|
347
|
+
# pixels = img.get_pixels
|
348
|
+
# pixels[3][2][1] # the green channel value from the 4th-row, 3rd-column pixel
|
349
|
+
#
|
350
|
+
# It can also be called after applying transformations:
|
224
351
|
#
|
225
|
-
#
|
352
|
+
# @example
|
353
|
+
# img = MiniMagick::Image.open 'image.jpg'
|
354
|
+
# img.crop '20x30+10+5'
|
355
|
+
# img.colorspace 'Gray'
|
356
|
+
# pixels = img.get_pixels
|
357
|
+
#
|
358
|
+
# In this example, all pixels in pix should now have equal R, G, and B values.
|
359
|
+
#
|
360
|
+
# @return [Array] Matrix of each color of each pixel
|
361
|
+
def get_pixels
|
362
|
+
convert = MiniMagick::Tool::Convert.new
|
363
|
+
convert << path
|
364
|
+
convert.depth(8)
|
365
|
+
convert << "RGB:-"
|
366
|
+
|
367
|
+
# Do not use `convert.call` here. We need the whole binary (unstripped) output here.
|
368
|
+
shell = MiniMagick::Shell.new
|
369
|
+
output, * = shell.run(convert.command)
|
370
|
+
|
371
|
+
pixels_array = output.unpack("C*")
|
372
|
+
pixels = pixels_array.each_slice(3).each_slice(width).to_a
|
373
|
+
|
374
|
+
# deallocate large intermediary objects
|
375
|
+
output.clear
|
376
|
+
pixels_array.clear
|
377
|
+
|
378
|
+
pixels
|
379
|
+
end
|
380
|
+
|
381
|
+
##
|
382
|
+
# This is used to create image from pixels. This might be required if you
|
383
|
+
# create pixels for some image processing reasons and you want to form
|
384
|
+
# image from those pixels.
|
226
385
|
#
|
227
|
-
#
|
228
|
-
|
229
|
-
|
386
|
+
# *DANGER*: This operation can be very expensive. Please try to use with
|
387
|
+
# caution.
|
388
|
+
#
|
389
|
+
# @example
|
390
|
+
# # It is given in readme.md file
|
391
|
+
##
|
392
|
+
def self.get_image_from_pixels(pixels, dimension, map, depth, mime_type)
|
393
|
+
pixels = pixels.flatten
|
394
|
+
blob = pixels.pack('C*')
|
395
|
+
import_pixels(blob, *dimension, depth, map, mime_type)
|
230
396
|
end
|
231
397
|
|
232
|
-
|
233
|
-
#
|
398
|
+
##
|
399
|
+
# This is used to change the format of the image. That is, from "tiff to
|
400
|
+
# jpg" or something like that. Once you run it, the instance is pointing to
|
401
|
+
# a new file with a new extension!
|
234
402
|
#
|
235
|
-
# *DANGER*: This renames the file that the instance is pointing to. So, if
|
236
|
-
# file with Image.new(file_path)...
|
237
|
-
#
|
403
|
+
# *DANGER*: This renames the file that the instance is pointing to. So, if
|
404
|
+
# you manually opened the file with Image.new(file_path)... Then that file
|
405
|
+
# is DELETED! If you used Image.open(file) then you are OK. The original
|
406
|
+
# file will still be there. But, any changes to it might not be...
|
238
407
|
#
|
239
|
-
# Formatting an animation into a non-animated type will result in
|
240
|
-
# pages (starting with 0). You can choose
|
241
|
-
# first page.
|
408
|
+
# Formatting an animation into a non-animated type will result in
|
409
|
+
# ImageMagick creating multiple pages (starting with 0). You can choose
|
410
|
+
# which page you want to manipulate. We default to the first page.
|
242
411
|
#
|
243
412
|
# If you would like to convert between animated formats, pass nil as your
|
244
413
|
# page and ImageMagick will copy all of the pages.
|
245
414
|
#
|
246
|
-
# @param format [String] The target format...
|
247
|
-
# @param page [Integer] If this is an animated gif, say which 'page' you
|
248
|
-
# with an integer. Default 0 will convert only the first page; 'nil'
|
249
|
-
# convert all pages.
|
250
|
-
# @
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
415
|
+
# @param format [String] The target format... Like 'jpg', 'gif', 'tiff' etc.
|
416
|
+
# @param page [Integer] If this is an animated gif, say which 'page' you
|
417
|
+
# want with an integer. Default 0 will convert only the first page; 'nil'
|
418
|
+
# will convert all pages.
|
419
|
+
# @param read_opts [Hash] Any read options to be passed to ImageMagick
|
420
|
+
# for example: image.format('jpg', page, {density: '300'})
|
421
|
+
# @yield [MiniMagick::Tool::Convert] It optionally yields the command,
|
422
|
+
# if you want to add something.
|
423
|
+
# @return [self]
|
424
|
+
#
|
425
|
+
def format(format, page = 0, read_opts={})
|
426
|
+
if @tempfile
|
427
|
+
new_tempfile = MiniMagick::Utilities.tempfile(".#{format}")
|
428
|
+
new_path = new_tempfile.path
|
429
|
+
else
|
430
|
+
new_path = Pathname(path).sub_ext(".#{format}").to_s
|
431
|
+
end
|
432
|
+
|
433
|
+
input_path = path.dup
|
434
|
+
input_path << "[#{page}]" if page && !layer?
|
435
|
+
|
436
|
+
MiniMagick::Tool::Convert.new do |convert|
|
437
|
+
read_opts.each do |opt, val|
|
438
|
+
convert.send(opt.to_s, val)
|
439
|
+
end
|
440
|
+
convert << input_path
|
441
|
+
yield convert if block_given?
|
442
|
+
convert << new_path
|
443
|
+
end
|
444
|
+
|
445
|
+
if @tempfile
|
446
|
+
destroy!
|
447
|
+
@tempfile = new_tempfile
|
256
448
|
else
|
257
|
-
|
449
|
+
File.delete(path) unless path == new_path || layer?
|
258
450
|
end
|
259
|
-
run(c)
|
260
451
|
|
261
|
-
|
262
|
-
|
263
|
-
|
452
|
+
path.replace new_path
|
453
|
+
@info.clear
|
454
|
+
|
455
|
+
self
|
456
|
+
end
|
457
|
+
|
458
|
+
##
|
459
|
+
# You can use multiple commands together using this method. Very easy to
|
460
|
+
# use!
|
461
|
+
#
|
462
|
+
# @example
|
463
|
+
# image.combine_options do |c|
|
464
|
+
# c.draw "image Over 0,0 10,10 '#{MINUS_IMAGE_PATH}'"
|
465
|
+
# c.thumbnail "300x500>"
|
466
|
+
# c.background "blue"
|
467
|
+
# end
|
468
|
+
#
|
469
|
+
# @yield [MiniMagick::Tool::Mogrify]
|
470
|
+
# @see http://www.imagemagick.org/script/mogrify.php
|
471
|
+
# @return [self]
|
472
|
+
#
|
473
|
+
def combine_options(&block)
|
474
|
+
mogrify(&block)
|
475
|
+
end
|
264
476
|
|
265
|
-
|
266
|
-
|
477
|
+
##
|
478
|
+
# If an unknown method is called then it is sent through the mogrify
|
479
|
+
# program.
|
480
|
+
#
|
481
|
+
# @see http://www.imagemagick.org/script/mogrify.php
|
482
|
+
# @return [self]
|
483
|
+
#
|
484
|
+
def method_missing(name, *args)
|
485
|
+
mogrify do |builder|
|
486
|
+
builder.send(name, *args)
|
267
487
|
end
|
268
488
|
end
|
269
489
|
|
270
|
-
|
271
|
-
|
272
|
-
def collapse!
|
273
|
-
run_command("mogrify", "-quality", "100", "#{path}[0]")
|
490
|
+
def respond_to_missing?(method_name, include_private = false)
|
491
|
+
MiniMagick::Tool::Mogrify.option_methods.include?(method_name.to_s)
|
274
492
|
end
|
275
493
|
|
276
|
-
|
277
|
-
#
|
494
|
+
##
|
495
|
+
# Writes the temporary file out to either a file location (by passing in a
|
496
|
+
# String) or by passing in a Stream that you can #write(chunk) to
|
497
|
+
# repeatedly
|
498
|
+
#
|
499
|
+
# @param output_to [String, Pathname, #read] Some kind of stream object
|
500
|
+
# that needs to be read or a file path as a String
|
278
501
|
#
|
279
|
-
# @param output_to [IOStream, String] Some kind of stream object that needs to be read or a file path as a String
|
280
|
-
# @return [IOStream, Boolean] If you pass in a file location [String] then you get a success boolean. If its a stream, you get it back.
|
281
|
-
# Writes the temporary image that we are using for processing to the output path
|
282
502
|
def write(output_to)
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
while chunk = f.read(8192)
|
290
|
-
output_to.write(chunk)
|
503
|
+
case output_to
|
504
|
+
when String, Pathname
|
505
|
+
if layer?
|
506
|
+
MiniMagick::Tool::Convert.new do |builder|
|
507
|
+
builder << path
|
508
|
+
builder << output_to
|
291
509
|
end
|
510
|
+
else
|
511
|
+
FileUtils.copy_file path, output_to unless path == output_to.to_s
|
292
512
|
end
|
293
|
-
|
513
|
+
else
|
514
|
+
IO.copy_stream File.open(path, "rb"), output_to
|
294
515
|
end
|
295
516
|
end
|
296
517
|
|
297
|
-
|
298
|
-
# @
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
518
|
+
##
|
519
|
+
# @example
|
520
|
+
# first_image = MiniMagick::Image.open "first.jpg"
|
521
|
+
# second_image = MiniMagick::Image.open "second.jpg"
|
522
|
+
# result = first_image.composite(second_image) do |c|
|
523
|
+
# c.compose "Over" # OverCompositeOp
|
524
|
+
# c.geometry "+20+20" # copy second_image onto first_image from (20, 20)
|
525
|
+
# end
|
526
|
+
# result.write "output.jpg"
|
527
|
+
#
|
528
|
+
# @see http://www.imagemagick.org/script/composite.php
|
529
|
+
#
|
530
|
+
def composite(other_image, output_extension = type.downcase, mask = nil)
|
531
|
+
output_tempfile = MiniMagick::Utilities.tempfile(".#{output_extension}")
|
532
|
+
|
533
|
+
MiniMagick::Tool::Composite.new do |composite|
|
534
|
+
yield composite if block_given?
|
535
|
+
composite << other_image.path
|
536
|
+
composite << path
|
537
|
+
composite << mask.path if mask
|
538
|
+
composite << output_tempfile.path
|
539
|
+
end
|
540
|
+
|
541
|
+
Image.new(output_tempfile.path, output_tempfile)
|
305
542
|
end
|
306
543
|
|
307
|
-
|
308
|
-
|
309
|
-
|
544
|
+
##
|
545
|
+
# Collapse images with sequences to the first frame (i.e. animated gifs) and
|
546
|
+
# preserve quality.
|
547
|
+
#
|
548
|
+
# @param frame [Integer] The frame to which to collapse to, defaults to `0`.
|
549
|
+
# @return [self]
|
550
|
+
#
|
551
|
+
def collapse!(frame = 0)
|
552
|
+
mogrify(frame) { |builder| builder.quality(100) }
|
310
553
|
end
|
311
554
|
|
312
|
-
|
313
|
-
#
|
314
|
-
|
315
|
-
|
316
|
-
|
555
|
+
##
|
556
|
+
# Destroys the tempfile (created by {.open}) if it exists.
|
557
|
+
#
|
558
|
+
def destroy!
|
559
|
+
if @tempfile
|
560
|
+
FileUtils.rm_f @tempfile.path.sub(/mpc$/, "cache") if @tempfile.path.end_with?(".mpc")
|
561
|
+
@tempfile.unlink
|
317
562
|
end
|
318
563
|
end
|
319
564
|
|
320
|
-
|
565
|
+
##
|
566
|
+
# Runs `identify` on itself. Accepts an optional block for adding more
|
567
|
+
# options to `identify`.
|
321
568
|
#
|
322
569
|
# @example
|
323
|
-
# image
|
324
|
-
#
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
570
|
+
# image = MiniMagick::Image.open("image.jpg")
|
571
|
+
# image.identify do |b|
|
572
|
+
# b.verbose
|
573
|
+
# end # runs `identify -verbose image.jpg`
|
574
|
+
# @return [String] Output from `identify`
|
575
|
+
# @yield [MiniMagick::Tool::Identify]
|
328
576
|
#
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
c << path if tool.to_s == "convert"
|
334
|
-
block.call(c)
|
335
|
-
c << path
|
336
|
-
run(c)
|
337
|
-
end
|
338
|
-
|
339
|
-
def composite(other_image, output_extension = 'jpg', &block)
|
340
|
-
begin
|
341
|
-
second_tempfile = Tempfile.new(output_extension)
|
342
|
-
second_tempfile.binmode
|
343
|
-
ensure
|
344
|
-
second_tempfile.close
|
577
|
+
def identify
|
578
|
+
MiniMagick::Tool::Identify.new do |builder|
|
579
|
+
yield builder if block_given?
|
580
|
+
builder << path
|
345
581
|
end
|
346
|
-
|
347
|
-
command = CommandBuilder.new("composite")
|
348
|
-
block.call(command) if block
|
349
|
-
command.push(other_image.path)
|
350
|
-
command.push(self.path)
|
351
|
-
command.push(second_tempfile.path)
|
352
|
-
|
353
|
-
run(command)
|
354
|
-
return Image.new(second_tempfile.path, second_tempfile)
|
355
582
|
end
|
356
583
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
args.
|
584
|
+
# @private
|
585
|
+
def run_command(tool_name, *args)
|
586
|
+
MiniMagick::Tool.const_get(tool_name.capitalize).new do |builder|
|
587
|
+
args.each do |arg|
|
588
|
+
builder << arg
|
589
|
+
end
|
361
590
|
end
|
362
|
-
|
363
|
-
run(CommandBuilder.new(command, *args))
|
364
591
|
end
|
365
592
|
|
366
|
-
def
|
367
|
-
|
593
|
+
def mogrify(page = nil)
|
594
|
+
MiniMagick::Tool::MogrifyRestricted.new do |builder|
|
595
|
+
yield builder if block_given?
|
596
|
+
builder << (page ? "#{path}[#{page}]" : path)
|
597
|
+
end
|
368
598
|
|
369
|
-
|
599
|
+
@info.clear
|
370
600
|
|
371
|
-
|
372
|
-
|
373
|
-
destroy!
|
601
|
+
self
|
602
|
+
end
|
374
603
|
|
375
|
-
|
376
|
-
|
377
|
-
raise Invalid, sub.output
|
378
|
-
else
|
379
|
-
# TODO: should we do something different if the command times out ...?
|
380
|
-
# its definitely better for logging.. otherwise we dont really know
|
381
|
-
raise Error, "Command (#{command.inspect.gsub("\\", "")}) failed: #{{:status_code => sub.exitstatus, :output => sub.output}.inspect}"
|
382
|
-
end
|
383
|
-
else
|
384
|
-
sub.output
|
385
|
-
end
|
604
|
+
def layer?
|
605
|
+
path =~ /\[\d+\]$/
|
386
606
|
end
|
387
607
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
608
|
+
##
|
609
|
+
# Compares if image width
|
610
|
+
# is greater than height
|
611
|
+
# ============
|
612
|
+
# | |
|
613
|
+
# | |
|
614
|
+
# ============
|
615
|
+
# @return [Boolean]
|
616
|
+
def landscape?
|
617
|
+
width > height
|
392
618
|
end
|
393
619
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
620
|
+
##
|
621
|
+
# Compares if image height
|
622
|
+
# is greater than width
|
623
|
+
# ======
|
624
|
+
# | |
|
625
|
+
# | |
|
626
|
+
# | |
|
627
|
+
# | |
|
628
|
+
# ======
|
629
|
+
# @return [Boolean]
|
630
|
+
def portrait?
|
631
|
+
height > width
|
632
|
+
end
|
404
633
|
end
|
405
634
|
end
|