mini_magick 3.7.0 → 4.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|