micro_magick 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +9 -9
- data/lib/micro_magick.rb +44 -31
- data/lib/micro_magick/identify_parser.rb +54 -0
- data/lib/micro_magick/image.rb +116 -0
- data/lib/micro_magick/version.rb +1 -1
- data/test/corrupt.jpg +0 -0
- data/test/identify_parser_test.rb +185 -0
- data/test/image_tests.rb +82 -0
- data/test/micro_gmagick_test.rb +5 -8
- data/test/micro_imagick_test.rb +5 -8
- data/test/micro_magick_test.rb +12 -25
- data/test/test_helper.rb +3 -0
- metadata +71 -10
- data/lib/micro_magick/convert.rb +0 -84
- data/lib/micro_magick/geometry.rb +0 -22
- data/test/geometry_test.rb +0 -10
- data/test/micro_magick_test_base.rb +0 -66
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YmJkMmI2NzI4MTk4ZDBiZTZjNmFkZDc4ZTljNWZjYzkwZTMyYzdmOQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
7
|
-
|
6
|
+
Yzk2YzEyNTk2OTEzYzkyOWI1MGE4NTc3MGNiNmY0ZWI3OThiZDI0NA==
|
7
|
+
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OTc4OGExNDUyOGRmMWFlZjIxODk3MGQ4Njg0ODRjMGUyODBlYzNjODQ5MjIy
|
10
|
+
YmJmZGI0ZTY1NTI3Y2U4NDdkN2M1YmY3YzdmMjUyNDgzN2YwYjcxMTE0OThj
|
11
|
+
MWJlNjFlZmUxYTIxMTgyZDQ4ZWVmOWQ3MGQ4YzI5YTg0ZWFkMmY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NmI1ZmE4OTBlOTBlMGU4MjAxYzFmYjUzNTE2NGQ1OWE1NjEwMTRhYzAyNzUx
|
14
|
+
NmI3M2EwZDJmMzZmMjM1ZGMyODFkNjhlM2I0OTg1YjZmZjBmNTgxZTdjZDlm
|
15
|
+
NTI4ZWQzYWVhMGUwMjY0MzcyMTg2ZWJiOWViOTM0MjZmMTM2ZWI=
|
data/lib/micro_magick.rb
CHANGED
@@ -1,49 +1,62 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require 'micro_magick/version'
|
2
|
+
require 'micro_magick/identify_parser'
|
3
|
+
require 'micro_magick/image'
|
4
|
+
require 'open3'
|
5
5
|
|
6
6
|
module MicroMagick
|
7
7
|
|
8
|
-
|
8
|
+
Error = Class.new(StandardError)
|
9
|
+
NoSuchFile = Class.new(Error)
|
10
|
+
MissingBinaries = Class.new(Error)
|
11
|
+
ArgumentError = Class.new(Error)
|
12
|
+
CorruptImageError = Class.new(Error)
|
9
13
|
|
10
|
-
|
14
|
+
def self.use_imagemagick
|
15
|
+
@cmd_prefix = ''
|
16
|
+
end
|
11
17
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@engine = engine
|
18
|
+
def self.use_graphicsmagick
|
19
|
+
@cmd_prefix = 'gm '
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.reset!
|
23
|
+
@cmd_prefix = nil
|
19
24
|
end
|
20
25
|
|
21
26
|
def self.cmd_prefix
|
22
|
-
@
|
23
|
-
if system(
|
24
|
-
|
25
|
-
elsif system(
|
26
|
-
|
27
|
+
@cmd_prefix ||= begin
|
28
|
+
if system('hash gm 2>&-')
|
29
|
+
'gm '
|
30
|
+
elsif system('hash convert 2>&-')
|
31
|
+
''
|
27
32
|
else
|
28
|
-
raise
|
33
|
+
raise MissingBinaries, 'Please install either GraphicsMagick or ImageMagick'
|
29
34
|
end
|
30
35
|
end
|
31
|
-
ENGINE2PREFIX[@engine]
|
32
36
|
end
|
33
37
|
|
34
|
-
def self.exec(
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
def self.exec(cmds, return_stdout = false)
|
39
|
+
final_cmd = cmd_prefix + cmds.join(' ')
|
40
|
+
stdout = Open3.popen3(final_cmd) do |stdin, stdout, stderr|
|
41
|
+
err = stderr.read.strip
|
42
|
+
if err.size > 0
|
43
|
+
error = if err =~ /unrecognized option/i
|
44
|
+
ArgumentError
|
45
|
+
elsif err =~ /corrupt/i
|
46
|
+
CorruptImageError
|
47
|
+
elsif err =~ /no such file or directory|unable to open/i
|
48
|
+
NoSuchFile
|
49
|
+
else
|
50
|
+
Error
|
51
|
+
end
|
52
|
+
raise error, "#{final_cmd} failed: #{err}"
|
53
|
+
end
|
54
|
+
stdout.read.strip
|
43
55
|
end
|
44
|
-
|
56
|
+
return_stdout ? stdout : final_cmd
|
57
|
+
rescue Errno::ENOENT => e
|
58
|
+
raise NoSuchFile, e.message
|
45
59
|
end
|
46
|
-
|
47
60
|
end
|
48
61
|
|
49
62
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module MicroMagick
|
2
|
+
class IdentifyParser
|
3
|
+
|
4
|
+
attr_reader :results
|
5
|
+
|
6
|
+
def initialize(stdout)
|
7
|
+
cleaned = stdout.encode('UTF-8', 'binary', :invalid => :replace, :undef => :replace, :replace => '')
|
8
|
+
@lines = cleaned.split("\n").select { |ea| ea =~ /\S+:/ }
|
9
|
+
if @lines.empty?
|
10
|
+
@results = {}
|
11
|
+
else
|
12
|
+
@results = parse[:image] # < remove the "image" prefix.
|
13
|
+
if (m = /(\d+)x(\d+)/.match(@results[:geometry]))
|
14
|
+
@results[:width], @results[:height] = m[1].to_i, m[2].to_i
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
results[key_to_sym(key)]
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse
|
24
|
+
result = {}
|
25
|
+
while true
|
26
|
+
line = @lines.shift
|
27
|
+
key, value = split(line)
|
28
|
+
if @lines.first && indent(line) < indent(@lines.first)
|
29
|
+
# The current line has a sub-section.
|
30
|
+
result[key] = parse()
|
31
|
+
else
|
32
|
+
result[key] = value
|
33
|
+
end
|
34
|
+
|
35
|
+
# Next line is indented less than the current line:
|
36
|
+
break if @lines.first.nil? || indent(line) > indent(@lines.first)
|
37
|
+
end
|
38
|
+
result
|
39
|
+
end
|
40
|
+
|
41
|
+
def indent(line)
|
42
|
+
/\S/.match(line).begin(0)
|
43
|
+
end
|
44
|
+
|
45
|
+
def split(line)
|
46
|
+
k, v = line.split(':', 2).map(&:strip)
|
47
|
+
[key_to_sym(k), v]
|
48
|
+
end
|
49
|
+
|
50
|
+
def key_to_sym(key)
|
51
|
+
key.is_a?(Symbol) ? key : key.strip.gsub(/[\b\W_-]+/, '_').downcase.to_sym
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module MicroMagick
|
5
|
+
class Image
|
6
|
+
attr_reader :input_file
|
7
|
+
|
8
|
+
def initialize(input_file)
|
9
|
+
@input_file = input_file
|
10
|
+
@input_options = []
|
11
|
+
@output_options = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](key)
|
15
|
+
identify[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def width
|
19
|
+
self[:width]
|
20
|
+
end
|
21
|
+
|
22
|
+
def height
|
23
|
+
self[:height]
|
24
|
+
end
|
25
|
+
|
26
|
+
def corrupt?
|
27
|
+
identify && @corrupt
|
28
|
+
end
|
29
|
+
|
30
|
+
# Strip the image of any profiles or comments.
|
31
|
+
# Note that this re-encodes the image, so it should only be used when downsampling
|
32
|
+
# (say, for a thumbnail)
|
33
|
+
# (ImageMagick has the -strip command, but GraphicsMagick doesn't.
|
34
|
+
# It turns out that ```+profile *``` does the same thing.)
|
35
|
+
def strip
|
36
|
+
add_output_option('+profile', '*')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Crop to a square, using the specified gravity.
|
40
|
+
def square_crop(gravity = 'Center')
|
41
|
+
gravity(gravity) unless gravity.nil?
|
42
|
+
d = [width, height].min
|
43
|
+
crop("#{d}x#{d}+0+0!")
|
44
|
+
end
|
45
|
+
|
46
|
+
# For normal options, like -resize or -flip, you can call .resize("32x32") or .flip().
|
47
|
+
# If you need to add an output option that starts with a '+', you can use this method.
|
48
|
+
def add_output_option(option_name, *args)
|
49
|
+
(@output_options ||= []).push(option_name)
|
50
|
+
args.each { |ea| @output_options.push(Shellwords.escape(ea.to_s)) }
|
51
|
+
|
52
|
+
# if we're a resize call, let's give the -size render hint to gm, but only when it's safe:
|
53
|
+
# * we don't have input options yet,
|
54
|
+
# * we're not cropping (because the -size will prevent the crop from working),
|
55
|
+
# * and we have dimensions in the form of NNNxNNN
|
56
|
+
if %w{-geometry -resize -sample -scale}.include?(option_name) &&
|
57
|
+
@input_options.empty? &&
|
58
|
+
!@output_options.include?('-crop')
|
59
|
+
dimensions = args.first
|
60
|
+
if dimensions.to_s =~ /\A(\d+x\d+)\z/
|
61
|
+
@input_options.push('-size', dimensions)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# Support call chaining:
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Runs "convert"
|
69
|
+
# See http://www.imagemagick.org/script/convert.php
|
70
|
+
def write(output_file)
|
71
|
+
MicroMagick.exec(command('convert', output_file))
|
72
|
+
ensure
|
73
|
+
@input_options.clear
|
74
|
+
@output_options.clear
|
75
|
+
end
|
76
|
+
|
77
|
+
# Runs "mogrify"
|
78
|
+
# See http://www.imagemagick.org/script/mogrify.php
|
79
|
+
def overwrite
|
80
|
+
MicroMagick.exec(command('mogrify'))
|
81
|
+
ensure
|
82
|
+
@input_options.clear
|
83
|
+
@output_options.clear
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def method_missing(method, *args, &block)
|
89
|
+
add_output_option("-#{method.to_s}", *args)
|
90
|
+
end
|
91
|
+
|
92
|
+
def command(command_name, output_file = nil)
|
93
|
+
cmd = [command_name]
|
94
|
+
cmd.push *@input_options
|
95
|
+
cmd.push Shellwords.escape(@input_file)
|
96
|
+
cmd.push *@output_options
|
97
|
+
cmd.push(Shellwords.escape(output_file)) if output_file
|
98
|
+
cmd
|
99
|
+
end
|
100
|
+
|
101
|
+
def identify
|
102
|
+
@identify || begin
|
103
|
+
cmd = ['identify', '-verbose', Shellwords.escape(input_file)]
|
104
|
+
@identify = IdentifyParser.new(MicroMagick.exec(cmd, true)).results
|
105
|
+
@corrupt = false
|
106
|
+
rescue CorruptImageError => e
|
107
|
+
@identify = {}
|
108
|
+
@corrupt = true
|
109
|
+
end
|
110
|
+
@identify
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Aliases to support < v0.0.6
|
115
|
+
Geometry = Convert = Image
|
116
|
+
end
|
data/lib/micro_magick/version.rb
CHANGED
data/test/corrupt.jpg
ADDED
Binary file
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe MicroMagick::IdentifyParser do
|
4
|
+
describe 'simple parsing functions' do
|
5
|
+
let(:ip) { MicroMagick::IdentifyParser.new('') }
|
6
|
+
it 'converts keys to expected symbols' do
|
7
|
+
ip.key_to_sym('Image').must_equal :image
|
8
|
+
ip.key_to_sym('Channel Depths').must_equal :channel_depths
|
9
|
+
ip.key_to_sym('JPEG-Quality').must_equal :jpeg_quality
|
10
|
+
ip.key_to_sym('Y Cb Cr Positioning').must_equal :y_cb_cr_positioning
|
11
|
+
ip.key_to_sym('Profile-EXIF').must_equal :profile_exif
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'determines line indent properly' do
|
15
|
+
ip.indent('Image: out.jpeg').must_equal 0
|
16
|
+
ip.indent(' Channel Depths:').must_equal 2
|
17
|
+
ip.indent(' Blue:').must_equal 4
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'with expected output' do
|
22
|
+
let(:ip) do
|
23
|
+
MicroMagick::IdentifyParser.new(<<-OUT)
|
24
|
+
Image: /tmp/input.jpg
|
25
|
+
Format: JPEG (Joint Photographic Experts Group JFIF format)
|
26
|
+
Mime type: image/jpeg
|
27
|
+
Class: DirectClass
|
28
|
+
Geometry: 1152x2048+0+0
|
29
|
+
Resolution: 72x72
|
30
|
+
Print size: 16x28.4444
|
31
|
+
Units: PixelsPerInch
|
32
|
+
Type: TrueColor
|
33
|
+
Endianess: Undefined
|
34
|
+
Colorspace: sRGB
|
35
|
+
Depth: 8-bit
|
36
|
+
Channel depth:
|
37
|
+
red: 8-bit
|
38
|
+
green: 8-bit
|
39
|
+
blue: 8-bit
|
40
|
+
Channel statistics:
|
41
|
+
Red:
|
42
|
+
min: 0 (0)
|
43
|
+
max: 255 (1)
|
44
|
+
mean: 133.986 (0.525437)
|
45
|
+
standard deviation: 54.1069 (0.212184)
|
46
|
+
kurtosis: -0.874804
|
47
|
+
skewness: -0.488666
|
48
|
+
Green:
|
49
|
+
min: 0 (0)
|
50
|
+
max: 255 (1)
|
51
|
+
mean: 119.681 (0.469337)
|
52
|
+
standard deviation: 64.5707 (0.253218)
|
53
|
+
kurtosis: -1.43104
|
54
|
+
skewness: -0.207503
|
55
|
+
Blue:
|
56
|
+
min: 0 (0)
|
57
|
+
max: 255 (1)
|
58
|
+
mean: 102.697 (0.402734)
|
59
|
+
standard deviation: 75.4283 (0.295797)
|
60
|
+
kurtosis: -1.69738
|
61
|
+
skewness: 0.0278036
|
62
|
+
Image statistics:
|
63
|
+
Overall:
|
64
|
+
min: 0 (0)
|
65
|
+
max: 255 (1)
|
66
|
+
mean: 118.788 (0.465836)
|
67
|
+
standard deviation: 65.2849 (0.256019)
|
68
|
+
kurtosis: -1.25648
|
69
|
+
skewness: -0.301872
|
70
|
+
Rendering intent: Perceptual
|
71
|
+
Gamma: 0.454545
|
72
|
+
Chromaticity:
|
73
|
+
red primary: (0.64,0.33)
|
74
|
+
green primary: (0.3,0.6)
|
75
|
+
blue primary: (0.15,0.06)
|
76
|
+
white point: (0.3127,0.329)
|
77
|
+
Background color: white
|
78
|
+
Border color: srgb(223,223,223)
|
79
|
+
Matte color: grey74
|
80
|
+
Transparent color: black
|
81
|
+
Interlace: None
|
82
|
+
Intensity: Undefined
|
83
|
+
Compose: Over
|
84
|
+
Page geometry: 1152x2048+0+0
|
85
|
+
Dispose: Undefined
|
86
|
+
Iterations: 0
|
87
|
+
Compression: JPEG
|
88
|
+
Quality: 94
|
89
|
+
Orientation: TopLeft
|
90
|
+
Properties:
|
91
|
+
date:create: 2014-02-09T08:59:26-08:00
|
92
|
+
date:modify: 2014-02-09T08:59:25-08:00
|
93
|
+
exif:ApertureValue: 228/100
|
94
|
+
exif:BrightnessValue: 105984/65536
|
95
|
+
exif:ColorSpace: 1
|
96
|
+
exif:ComponentsConfiguration: 1, 2, 3, 0
|
97
|
+
exif:Compression: 6
|
98
|
+
exif:DateTime: 2014:02:07 08:38:21
|
99
|
+
exif:DateTimeDigitized: 2014:02:07 08:38:21
|
100
|
+
exif:DateTimeOriginal: 2014:02:07 08:38:21
|
101
|
+
exif:ExifImageLength: 2048
|
102
|
+
exif:ExifImageWidth: 1152
|
103
|
+
exif:ExifOffset: 230
|
104
|
+
exif:ExifVersion: 48, 50, 50, 48
|
105
|
+
exif:ExposureBiasValue: 0/10
|
106
|
+
exif:ExposureMode: 0
|
107
|
+
exif:ExposureProgram: 2
|
108
|
+
exif:ExposureTime: 1/25
|
109
|
+
exif:Flash: 0
|
110
|
+
exif:FlashPixVersion: 48, 49, 48, 48
|
111
|
+
exif:FNumber: 220/100
|
112
|
+
exif:FocalLength: 420/100
|
113
|
+
exif:FocalLengthIn35mmFilm: 31
|
114
|
+
exif:GPSAltitude: 0/1000
|
115
|
+
exif:GPSAltitudeRef: 0
|
116
|
+
exif:GPSDateStamp: 2014:02:07
|
117
|
+
exif:GPSInfo: 722
|
118
|
+
exif:GPSLatitude: 37/1, 46/1, 341802/10000
|
119
|
+
exif:GPSLatitudeRef: N
|
120
|
+
exif:GPSLongitude: 122/1, 25/1, 6225/10000
|
121
|
+
exif:GPSLongitudeRef: W
|
122
|
+
exif:GPSProcessingMethod: ASCII
|
123
|
+
exif:GPSTimeStamp: 16/1, 38/1, 11/1
|
124
|
+
exif:GPSVersionID: 2, 2, 0, 0
|
125
|
+
exif:ImageLength: 1152
|
126
|
+
exif:ImageUniqueID: 9e6076155a3bd1ce0000000000000000
|
127
|
+
exif:ImageWidth: 2048
|
128
|
+
exif:InteroperabilityIndex: R98
|
129
|
+
exif:InteroperabilityOffset: 948
|
130
|
+
exif:InteroperabilityVersion: 48, 49, 48, 48
|
131
|
+
exif:ISOSpeedRatings: 125
|
132
|
+
exif:JPEGInterchangeFormat: 1072
|
133
|
+
exif:JPEGInterchangeFormatLength: 4729
|
134
|
+
exif:LightSource: 0
|
135
|
+
exif:Make: SAMSUNG
|
136
|
+
exif:MaxApertureValue: 228/100
|
137
|
+
exif:MeteringMode: 1
|
138
|
+
exif:Model: SCH-I545
|
139
|
+
exif:Orientation: 1
|
140
|
+
exif:ResolutionUnit: 2
|
141
|
+
exif:SceneCaptureType: 0
|
142
|
+
exif:SceneType: 1
|
143
|
+
exif:SensingMethod: 2
|
144
|
+
exif:ShutterSpeedValue: 304394/65536
|
145
|
+
exif:Software: Google
|
146
|
+
exif:thumbnail:ResolutionUnit: 2
|
147
|
+
exif:thumbnail:XResolution: 72/1
|
148
|
+
exif:thumbnail:YResolution: 72/1
|
149
|
+
exif:WhiteBalance: 0
|
150
|
+
exif:XResolution: 72/1
|
151
|
+
exif:YCbCrPositioning: 1
|
152
|
+
exif:YResolution: 72/1
|
153
|
+
jpeg:colorspace: 2
|
154
|
+
jpeg:sampling-factor: 2x2,1x1,1x1
|
155
|
+
signature: 2e969ff76481f84c85031e191ee902825bf6c2a12fb6083da57633326cde2bef
|
156
|
+
Profiles:
|
157
|
+
Profile-exif: 5808 bytes
|
158
|
+
Profile-xmp: 325 bytes
|
159
|
+
Artifacts:
|
160
|
+
filename: /Users/mrm/Downloads/20140207_083822.jpg
|
161
|
+
verbose: true
|
162
|
+
Tainted: False
|
163
|
+
Filesize: 227KB
|
164
|
+
Number pixels: 2.359M
|
165
|
+
Pixels per second: 29.49MB
|
166
|
+
User time: 0.070u
|
167
|
+
Elapsed time: 0:01.080
|
168
|
+
Version: ImageMagick 6.8.7-7 Q16 x86_64 2013-11-27 http://www.imagemagick.org
|
169
|
+
OUT
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'extracts width and depth from geometry' do
|
173
|
+
ip[:width].must_equal 1152
|
174
|
+
ip[:height].must_equal 2048
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'extracts sub-sections properly' do
|
178
|
+
ip[:channel_depth][:red].must_equal '8-bit'
|
179
|
+
ip[:channel_depth][:green].must_equal '8-bit'
|
180
|
+
ip[:channel_depth][:blue].must_equal '8-bit'
|
181
|
+
ip[:artifacts][:verbose].must_equal 'true'
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
data/test/image_tests.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module ImageTests
|
5
|
+
def self.included spec_class
|
6
|
+
spec_class.class_eval do
|
7
|
+
|
8
|
+
let(:img) { MicroMagick::Convert.new('test/480x270.jpg') }
|
9
|
+
|
10
|
+
let(:corrupt) { MicroMagick::Convert.new('test/corrupt.jpg') }
|
11
|
+
|
12
|
+
# By using the block format, the tempfile will be closed and deleted:
|
13
|
+
let(:outfile) { Tempfile.open(%w(out .jpg)) { |ea| ea.path } }
|
14
|
+
|
15
|
+
it 'extracts image geometry correctly' do
|
16
|
+
img.width.must_equal 270
|
17
|
+
img.height.must_equal 480
|
18
|
+
img.corrupt?.must_be_false
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'detects corrupt images properly' do
|
22
|
+
corrupt.width.must_be_nil
|
23
|
+
corrupt.height.must_be_nil
|
24
|
+
corrupt.corrupt?.must_be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'resizes to cropped square' do
|
28
|
+
img.resize('64x64')
|
29
|
+
command = img.write(outfile)
|
30
|
+
command.must_equal "#{MicroMagick.cmd_prefix}convert -size 64x64 test/480x270.jpg -resize 64x64 " + Shellwords.escape(outfile)
|
31
|
+
File.exist?(outfile).must_be_true
|
32
|
+
g = MicroMagick::Geometry.new(outfile)
|
33
|
+
g.width.must_equal 64 * 270 / 480
|
34
|
+
g.height.must_equal 64
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'removes exif headers from .strip' do
|
38
|
+
img.strip
|
39
|
+
command = img.write(outfile)
|
40
|
+
command.must_equal "#{MicroMagick.cmd_prefix}convert test/480x270.jpg +profile \\* " + Shellwords.escape(outfile)
|
41
|
+
i = MicroMagick::Image.new('test/480x270.jpg')
|
42
|
+
i['Profile-EXIF'].must_be_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'crops properly' do
|
46
|
+
command = img.quality(85).square_crop('North').resize('128x128').write(outfile)
|
47
|
+
command.must_equal "#{MicroMagick.cmd_prefix}convert test/480x270.jpg " +
|
48
|
+
"-quality 85 -gravity North -crop 270x270\\+0\\+0\\! -resize 128x128 " +
|
49
|
+
Shellwords.escape(outfile)
|
50
|
+
File.exist?(outfile).must_be_true
|
51
|
+
g = MicroMagick::Image.new(outfile)
|
52
|
+
g.width.must_equal 128
|
53
|
+
g.height.must_equal 128
|
54
|
+
|
55
|
+
# make sure calling previous arguments don't leak into new calls:
|
56
|
+
img.resize("64x64")
|
57
|
+
command = img.write(outfile)
|
58
|
+
command.must_equal "#{MicroMagick.cmd_prefix}convert -size 64x64 test/480x270.jpg -resize 64x64 " +
|
59
|
+
Shellwords.escape(outfile)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'raises errors from invalid parameters' do
|
63
|
+
img.boing
|
64
|
+
proc { img.write(outfile) }.must_raise MicroMagick::ArgumentError
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:enoent) { MicroMagick::Image.new('nonexistant-file.jpg') }
|
68
|
+
|
69
|
+
it 'raises NoSuchFile when fetching attributes' do
|
70
|
+
proc { enoent.width }.must_raise MicroMagick::NoSuchFile
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'raises NoSuchFile from write' do
|
74
|
+
proc { enoent.overwrite }.must_raise MicroMagick::NoSuchFile
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'raises NoSuchFile from overwrite' do
|
78
|
+
proc { enoent.overwrite }.must_raise MicroMagick::NoSuchFile
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/test/micro_gmagick_test.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
class MicroGmagickTest < MicroMagickTestBase
|
4
|
-
|
5
|
-
def setup
|
6
|
-
MicroMagick.use(:graphicsmagick)
|
7
|
-
super
|
8
|
-
end
|
1
|
+
require 'test_helper'
|
2
|
+
require 'image_tests'
|
9
3
|
|
4
|
+
describe 'Image tests under GraphicsMagick' do
|
5
|
+
before { MicroMagick.use_graphicsmagick }
|
6
|
+
include ImageTests
|
10
7
|
end
|
11
8
|
|
data/test/micro_imagick_test.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
class MicroImagickTest < MicroMagickTestBase
|
4
|
-
|
5
|
-
def setup
|
6
|
-
MicroMagick.use(:imagemagick)
|
7
|
-
super
|
8
|
-
end
|
1
|
+
require 'test_helper'
|
2
|
+
require 'image_tests'
|
9
3
|
|
4
|
+
describe 'Image tests under ImageMagick' do
|
5
|
+
before { MicroMagick.use_imagemagick }
|
6
|
+
include ImageTests
|
10
7
|
end
|
11
8
|
|
data/test/micro_magick_test.rb
CHANGED
@@ -1,34 +1,21 @@
|
|
1
|
-
require
|
2
|
-
require "micro_magick"
|
1
|
+
require 'test_helper'
|
3
2
|
|
4
|
-
|
3
|
+
describe MicroMagick do
|
5
4
|
|
6
|
-
|
7
|
-
#
|
8
|
-
MicroMagick.
|
9
|
-
|
10
|
-
# eh, ok, now we should stub out system
|
11
|
-
# and verify that the next call to cmd_prefix
|
12
|
-
# calls `hash gm`?
|
13
|
-
|
14
|
-
# Meh, let's just assume that the test running machine has gm in their PATH.
|
15
|
-
assert_equal MicroMagick.cmd_prefix, "gm"
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_use_invalid
|
19
|
-
assert_raise MicroMagick::InvalidArgument do
|
20
|
-
MicroMagick.use(:boink)
|
21
|
-
end
|
5
|
+
it 'uses GraphicsMagick by default' do
|
6
|
+
# Assume that the machine that's running the test has gm in it's PATH
|
7
|
+
MicroMagick.reset!
|
8
|
+
MicroMagick.cmd_prefix.must_equal 'gm '
|
22
9
|
end
|
23
10
|
|
24
|
-
|
25
|
-
MicroMagick.
|
26
|
-
|
11
|
+
it 'uses GraphicsMagick when set explicitly' do
|
12
|
+
MicroMagick.use_graphicsmagick
|
13
|
+
MicroMagick.cmd_prefix.must_equal 'gm '
|
27
14
|
end
|
28
15
|
|
29
|
-
|
30
|
-
MicroMagick.
|
31
|
-
|
16
|
+
it 'uses ImageMagick when set explicitly' do
|
17
|
+
MicroMagick.use_imagemagick
|
18
|
+
MicroMagick.cmd_prefix.must_equal ''
|
32
19
|
end
|
33
20
|
|
34
21
|
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: micro_magick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -24,6 +24,62 @@ dependencies:
|
|
24
24
|
- - ! '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-great_expectations
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-reporters
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
27
83
|
description: ''
|
28
84
|
email:
|
29
85
|
- matthew-github@mceachen.org
|
@@ -32,15 +88,17 @@ extensions: []
|
|
32
88
|
extra_rdoc_files: []
|
33
89
|
files:
|
34
90
|
- lib/micro_magick.rb
|
35
|
-
- lib/micro_magick/
|
36
|
-
- lib/micro_magick/
|
91
|
+
- lib/micro_magick/identify_parser.rb
|
92
|
+
- lib/micro_magick/image.rb
|
37
93
|
- lib/micro_magick/version.rb
|
38
94
|
- test/480x270.jpg
|
39
|
-
- test/
|
95
|
+
- test/corrupt.jpg
|
96
|
+
- test/identify_parser_test.rb
|
97
|
+
- test/image_tests.rb
|
40
98
|
- test/micro_gmagick_test.rb
|
41
99
|
- test/micro_imagick_test.rb
|
42
100
|
- test/micro_magick_test.rb
|
43
|
-
- test/
|
101
|
+
- test/test_helper.rb
|
44
102
|
homepage: https://github.com/mceachen/micro_magick
|
45
103
|
licenses: []
|
46
104
|
metadata: {}
|
@@ -60,14 +118,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
118
|
version: '0'
|
61
119
|
requirements: []
|
62
120
|
rubyforge_project:
|
63
|
-
rubygems_version: 2.0
|
121
|
+
rubygems_version: 2.2.0
|
64
122
|
signing_key:
|
65
123
|
specification_version: 4
|
66
|
-
summary:
|
124
|
+
summary: Simple and efficient ImageMagick/GraphicsMagick ruby wrapper
|
67
125
|
test_files:
|
68
126
|
- test/480x270.jpg
|
69
|
-
- test/
|
127
|
+
- test/corrupt.jpg
|
128
|
+
- test/identify_parser_test.rb
|
129
|
+
- test/image_tests.rb
|
70
130
|
- test/micro_gmagick_test.rb
|
71
131
|
- test/micro_imagick_test.rb
|
72
132
|
- test/micro_magick_test.rb
|
73
|
-
- test/
|
133
|
+
- test/test_helper.rb
|
134
|
+
has_rdoc:
|
data/lib/micro_magick/convert.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'shellwords'
|
2
|
-
|
3
|
-
module MicroMagick
|
4
|
-
class Convert
|
5
|
-
def initialize(input_file)
|
6
|
-
@input_file = input_file
|
7
|
-
@input_options = []
|
8
|
-
@output_options = []
|
9
|
-
end
|
10
|
-
|
11
|
-
def width
|
12
|
-
geometry.width
|
13
|
-
end
|
14
|
-
|
15
|
-
def height
|
16
|
-
geometry.height
|
17
|
-
end
|
18
|
-
|
19
|
-
def geometry
|
20
|
-
@geometry ||= MicroMagick::Geometry.new(@input_file)
|
21
|
-
end
|
22
|
-
|
23
|
-
# strip the image of any profiles or comments
|
24
|
-
# (ImageMagick has the -strip command, but GraphicsMagick doesn't.
|
25
|
-
# It turns out that ```+profile *``` does the same thing.)
|
26
|
-
def strip
|
27
|
-
add_output_option("+profile", "*")
|
28
|
-
end
|
29
|
-
|
30
|
-
# Crop to a square, using the specified gravity.
|
31
|
-
def square_crop(gravity = "Center")
|
32
|
-
gravity(gravity) unless gravity.nil?
|
33
|
-
d = [width, height].min
|
34
|
-
crop("#{d}x#{d}+0+0!")
|
35
|
-
end
|
36
|
-
|
37
|
-
# For normal options, like -resize or -flip, you can call .resize("32x32") or .flip().
|
38
|
-
# If you need to add an output option that starts with a '+', you can use this method.
|
39
|
-
def add_output_option(option_name, *args)
|
40
|
-
(@output_options ||= []) << option_name
|
41
|
-
@output_options += args.collect { |ea| Shellwords.escape(ea.to_s) }
|
42
|
-
|
43
|
-
# if we're a resize call, let's give the -size render hint to gm, but only when it's safe:
|
44
|
-
# * we don't have input options yet,
|
45
|
-
# * we're not cropping (because the -size will prevent the crop from working),
|
46
|
-
# * and we have dimensions in the form of NNNxNNN
|
47
|
-
if %w{-geometry -resize -sample -scale}.include?(option_name) &&
|
48
|
-
@input_options.empty? &&
|
49
|
-
!@output_options.include?("-crop")
|
50
|
-
if (dimensions = args.first)
|
51
|
-
if dimensions =~ /^(\d+x\d+)$/
|
52
|
-
@input_options << "-size #{dimensions}"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
# Executes `convert`, writing to output_file, and clearing all previously set input and output options.
|
60
|
-
# @return the command given to system()
|
61
|
-
def write(output_file)
|
62
|
-
@output_file = output_file
|
63
|
-
cmd = command()
|
64
|
-
MicroMagick.exec(cmd)
|
65
|
-
@input_options.clear
|
66
|
-
@output_options.clear
|
67
|
-
cmd
|
68
|
-
end
|
69
|
-
|
70
|
-
protected
|
71
|
-
|
72
|
-
def method_missing(method, *args, &block)
|
73
|
-
add_output_option("-#{method.to_s}", *args)
|
74
|
-
end
|
75
|
-
|
76
|
-
def command
|
77
|
-
([MicroMagick.cmd_prefix, "convert"] +
|
78
|
-
@input_options +
|
79
|
-
[Shellwords.escape(@input_file)] +
|
80
|
-
@output_options +
|
81
|
-
[Shellwords.escape(@output_file)]).compact.join(" ")
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'shellwords'
|
2
|
-
|
3
|
-
module MicroMagick
|
4
|
-
class Geometry
|
5
|
-
attr_reader :width, :height
|
6
|
-
|
7
|
-
def initialize(input_file)
|
8
|
-
cmd = [MicroMagick.cmd_prefix,
|
9
|
-
"identify",
|
10
|
-
"-format",
|
11
|
-
"%w:%h",
|
12
|
-
Shellwords.escape(input_file)
|
13
|
-
]
|
14
|
-
geometry = MicroMagick.exec(cmd.join(" "))
|
15
|
-
@width, @height = geometry.split(':').collect { |ea| ea.to_i }
|
16
|
-
end
|
17
|
-
|
18
|
-
def to_s
|
19
|
-
"#{width} x #{height}"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/test/geometry_test.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
require "test/unit"
|
2
|
-
require "shellwords"
|
3
|
-
require "micro_magick"
|
4
|
-
|
5
|
-
class MicroMagickTestBase < Test::Unit::TestCase
|
6
|
-
|
7
|
-
def setup
|
8
|
-
@img = MicroMagick::Convert.new("test/480x270.jpg")
|
9
|
-
tmp = Tempfile.new('out.jpg')
|
10
|
-
@outfile = tmp.path
|
11
|
-
tmp.close
|
12
|
-
tmp.delete
|
13
|
-
assert !File.exist?(@outfile)
|
14
|
-
end
|
15
|
-
|
16
|
-
def cmd_prefix
|
17
|
-
s = MicroMagick.cmd_prefix
|
18
|
-
s.nil? ? "" : s + " "
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_resize
|
22
|
-
@img.resize("64x64")
|
23
|
-
command = @img.write(@outfile)
|
24
|
-
assert_equal "#{cmd_prefix}convert -size 64x64 test/480x270.jpg -resize 64x64 " + Shellwords.escape(@outfile), command
|
25
|
-
assert File.exist?(@outfile)
|
26
|
-
g = MicroMagick::Geometry.new(@outfile)
|
27
|
-
assert_equal (64 * (270.0/480.0)).to_i, g.width
|
28
|
-
assert_equal 64, g.height
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_strip
|
32
|
-
@img.strip
|
33
|
-
command = @img.write(@outfile)
|
34
|
-
assert_equal "#{cmd_prefix}convert test/480x270.jpg +profile \\* " + Shellwords.escape(@outfile), command
|
35
|
-
end
|
36
|
-
|
37
|
-
def test_convert_with_crop
|
38
|
-
@img.quality(85)
|
39
|
-
@img.square_crop("North")
|
40
|
-
@img.resize("128x128")
|
41
|
-
command = @img.write(@outfile)
|
42
|
-
assert_equal "#{cmd_prefix}convert test/480x270.jpg " +
|
43
|
-
"-quality 85 -gravity North -crop 270x270\\+0\\+0\\! -resize 128x128 " +
|
44
|
-
Shellwords.escape(@outfile),
|
45
|
-
command
|
46
|
-
assert File.exist?(@outfile)
|
47
|
-
g = MicroMagick::Geometry.new(@outfile)
|
48
|
-
assert_equal 128, g.width
|
49
|
-
assert_equal 128, g.height
|
50
|
-
|
51
|
-
# make sure calling previous arguments don't leak into new calls:
|
52
|
-
@img.resize("64x64")
|
53
|
-
command = @img.write(@outfile)
|
54
|
-
assert_equal "#{cmd_prefix}convert -size 64x64 test/480x270.jpg -resize 64x64 " +
|
55
|
-
Shellwords.escape(@outfile),
|
56
|
-
command
|
57
|
-
end
|
58
|
-
|
59
|
-
def test_bad_args
|
60
|
-
@img.boing
|
61
|
-
assert_raise MicroMagick::InvalidArgument do
|
62
|
-
@img.write(@outfile)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|