rszr 0.5.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +137 -15
- data/ext/rszr/errors.c +8 -3
- data/ext/rszr/image.c +209 -9
- data/ext/rszr/rszr.h +1 -0
- data/lib/rszr/batch_transformation.rb +24 -0
- data/lib/rszr/buffered.rb +25 -0
- data/lib/rszr/color.rb +25 -0
- data/lib/rszr/identification.rb +60 -0
- data/lib/rszr/image.rb +233 -55
- data/lib/rszr/image_processing.rb +82 -0
- data/lib/rszr/orientation.rb +107 -0
- data/lib/rszr/stream.rb +61 -0
- data/lib/rszr/version.rb +1 -1
- data/lib/rszr.rb +21 -0
- metadata +16 -134
data/lib/rszr/image.rb
CHANGED
@@ -1,20 +1,50 @@
|
|
1
1
|
module Rszr
|
2
2
|
class Image
|
3
|
-
|
3
|
+
GRAVITIES = [true, :center, :n, :nw, :w, :sw, :s, :se, :e, :ne].freeze
|
4
|
+
|
5
|
+
extend Identification
|
6
|
+
include Buffered
|
7
|
+
include Orientation
|
8
|
+
|
4
9
|
class << self
|
5
|
-
|
6
|
-
def load(path,
|
10
|
+
|
11
|
+
def load(path, autorotate: Rszr.autorotate, **opts)
|
7
12
|
path = path.to_s
|
8
13
|
raise FileNotFound unless File.exist?(path)
|
9
|
-
_load(path)
|
14
|
+
image = _load(path)
|
15
|
+
autorotate(image, path) if autorotate
|
16
|
+
image
|
10
17
|
end
|
11
18
|
alias :open :load
|
12
19
|
|
20
|
+
def load_data(data, autorotate: Rszr.autorotate, **opts)
|
21
|
+
raise LoadError, 'Unknown format' unless format = identify(data)
|
22
|
+
with_tempfile(format, data) do |file|
|
23
|
+
load(file.path, autorotate: autorotate, **opts)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
13
27
|
end
|
14
28
|
|
15
29
|
def dimensions
|
16
30
|
[width, height]
|
17
31
|
end
|
32
|
+
|
33
|
+
def format
|
34
|
+
fmt = _format
|
35
|
+
fmt == 'jpg' ? 'jpeg' : fmt
|
36
|
+
end
|
37
|
+
|
38
|
+
def format=(fmt)
|
39
|
+
fmt = fmt.to_s if fmt.is_a?(Symbol)
|
40
|
+
self._format = fmt
|
41
|
+
end
|
42
|
+
|
43
|
+
def [](x, y)
|
44
|
+
if x >= 0 && x <= width - 1 && y >= 0 && y <= height - 1
|
45
|
+
Color::RGBA.new(*_pixel(x, y))
|
46
|
+
end
|
47
|
+
end
|
18
48
|
|
19
49
|
def inspect
|
20
50
|
fmt = format
|
@@ -22,30 +52,116 @@ module Rszr
|
|
22
52
|
"#<#{self.class.name}:0x#{object_id.to_s(16)} #{width}x#{height}#{fmt}>"
|
23
53
|
end
|
24
54
|
|
25
|
-
|
26
|
-
|
27
|
-
|
55
|
+
module Transformations
|
56
|
+
def resize(*args, **opts)
|
57
|
+
_resize(false, *calculate_size(*args, **opts))
|
58
|
+
end
|
28
59
|
|
29
|
-
|
30
|
-
|
31
|
-
|
60
|
+
def resize!(*args, **opts)
|
61
|
+
_resize(true, *calculate_size(*args, **opts))
|
62
|
+
end
|
32
63
|
|
33
|
-
|
34
|
-
|
35
|
-
|
64
|
+
def crop(x, y, width, height)
|
65
|
+
_crop(false, x, y, width, height)
|
66
|
+
end
|
36
67
|
|
37
|
-
|
38
|
-
|
39
|
-
|
68
|
+
def crop!(x, y, width, height)
|
69
|
+
_crop(true, x, y, width, height)
|
70
|
+
end
|
71
|
+
|
72
|
+
def turn(orientation)
|
73
|
+
dup.turn!(orientation)
|
74
|
+
end
|
40
75
|
|
41
|
-
|
42
|
-
|
43
|
-
|
76
|
+
def turn!(orientation)
|
77
|
+
orientation = orientation.abs + 2 if orientation.negative?
|
78
|
+
_turn!(orientation % 4)
|
79
|
+
end
|
80
|
+
|
81
|
+
def rotate(deg)
|
82
|
+
_rotate(false, deg.to_f * Math::PI / 180.0)
|
83
|
+
end
|
84
|
+
|
85
|
+
def rotate!(deg)
|
86
|
+
_rotate(true, deg.to_f * Math::PI / 180.0)
|
87
|
+
end
|
88
|
+
|
89
|
+
# horizontal
|
90
|
+
def flop
|
91
|
+
dup.flop!
|
92
|
+
end
|
93
|
+
|
94
|
+
# vertical
|
95
|
+
def flip
|
96
|
+
dup.flip!
|
97
|
+
end
|
98
|
+
|
99
|
+
def sharpen(radius)
|
100
|
+
dup.sharpen!(radius)
|
101
|
+
end
|
102
|
+
|
103
|
+
def sharpen!(radius)
|
104
|
+
raise ArgumentError, 'illegal radius' if radius < 0
|
105
|
+
_sharpen!(radius)
|
106
|
+
end
|
107
|
+
|
108
|
+
def blur(radius)
|
109
|
+
dup.blur!(radius)
|
110
|
+
end
|
111
|
+
|
112
|
+
def blur!(radius)
|
113
|
+
raise ArgumentError, 'illegal radius' if radius < 0
|
114
|
+
_sharpen!(-radius)
|
115
|
+
end
|
116
|
+
|
117
|
+
def filter(filter_expr)
|
118
|
+
dup.filter!(filter_expr)
|
119
|
+
end
|
120
|
+
|
121
|
+
def brighten!(value, r: nil, g: nil, b: nil, a: nil)
|
122
|
+
raise ArgumentError, 'illegal brightness' if value > 1 || value < -1
|
123
|
+
filter!("colormod(brightness=#{value.to_f});")
|
124
|
+
end
|
125
|
+
|
126
|
+
def brighten(*args, **opts)
|
127
|
+
dup.brighten!(*args, **opts)
|
128
|
+
end
|
129
|
+
|
130
|
+
def contrast!(value, r: nil, g: nil, b: nil, a: nil)
|
131
|
+
raise ArgumentError, 'illegal contrast (must be > 0)' if value < 0
|
132
|
+
filter!("colormod(contrast=#{value.to_f});")
|
133
|
+
end
|
134
|
+
|
135
|
+
def contrast(*args, **opts)
|
136
|
+
dup.contrast!(*args, **opts)
|
137
|
+
end
|
138
|
+
|
139
|
+
def gamma!(value, r: nil, g: nil, b: nil, a: nil)
|
140
|
+
#raise ArgumentError, 'illegal gamma (must be > 0)' if value < 0
|
141
|
+
filter!("colormod(gamma=#{value.to_f});")
|
142
|
+
end
|
143
|
+
|
144
|
+
def gamma(*args, **opts)
|
145
|
+
dup.gamma!(*args, **opts)
|
146
|
+
end
|
44
147
|
end
|
148
|
+
|
149
|
+
include Transformations
|
45
150
|
|
46
|
-
def save(path, format
|
47
|
-
format ||= format_from_filename(path) || 'jpg'
|
48
|
-
|
151
|
+
def save(path, format: nil, quality: nil)
|
152
|
+
format ||= format_from_filename(path) || self.format || 'jpg'
|
153
|
+
raise ArgumentError, "invalid quality #{quality.inspect}" if quality && !(0..100).cover?(quality)
|
154
|
+
ensure_path_is_writable(path)
|
155
|
+
_save(path.to_s, format.to_s, quality)
|
156
|
+
end
|
157
|
+
|
158
|
+
def save_data(format: nil, quality: nil)
|
159
|
+
format ||= self.format || 'jpg'
|
160
|
+
with_tempfile(format) do |file|
|
161
|
+
save(file.path, format: format, quality: quality)
|
162
|
+
file.rewind
|
163
|
+
file.read
|
164
|
+
end
|
49
165
|
end
|
50
166
|
|
51
167
|
private
|
@@ -56,53 +172,115 @@ module Rszr
|
|
56
172
|
# :auto, 300 auto width, fit height
|
57
173
|
# 400, 300, crop: :center_middle
|
58
174
|
# 400, 300, background: rgba
|
59
|
-
# 400, 300,
|
175
|
+
# 400, 300, skew: true
|
60
176
|
|
61
|
-
def calculate_size(*args)
|
62
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
63
|
-
assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
|
64
|
-
original_width, original_height = width, height
|
65
|
-
x, y, = 0, 0
|
177
|
+
def calculate_size(*args, crop: nil, skew: nil, inflate: true)
|
178
|
+
#options = args.last.is_a?(Hash) ? args.pop : {}
|
179
|
+
#assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
|
66
180
|
if args.size == 1
|
67
|
-
|
68
|
-
raise ArgumentError, "scale #{scale.inspect} out of range" unless scale > 0 && scale < 1
|
69
|
-
new_width = original_width.to_f * scale
|
70
|
-
new_height = original_height.to_f * scale
|
181
|
+
calculate_size_for_scale(args.first)
|
71
182
|
elsif args.size == 2
|
72
183
|
box_width, box_height = args
|
73
|
-
if
|
74
|
-
|
75
|
-
new_width = box_height.to_f / original_height.to_f * original_width.to_f
|
76
|
-
elsif box_width.is_a?(Numeric) && :auto == box_height
|
77
|
-
new_width = box_width
|
78
|
-
new_height = box_width.to_f / original_width.to_f * original_height.to_f
|
184
|
+
if args.include?(:auto)
|
185
|
+
calculate_size_for_auto(box_width, box_height)
|
79
186
|
elsif box_width.is_a?(Numeric) && box_height.is_a?(Numeric)
|
80
|
-
if
|
81
|
-
|
82
|
-
elsif
|
83
|
-
|
187
|
+
if not inflate and width <= box_width and height <= box_height
|
188
|
+
[0, 0, width, height, width, height]
|
189
|
+
elsif skew
|
190
|
+
calculate_size_for_skew(box_width, box_height)
|
191
|
+
elsif crop
|
192
|
+
calculate_size_for_crop(box_width, box_height, crop)
|
84
193
|
else
|
85
|
-
|
86
|
-
box_scale = box_width.to_f / box_height.to_f
|
87
|
-
if scale >= box_scale # wider
|
88
|
-
new_width = box_width
|
89
|
-
new_height = original_height.to_f * box_width.to_f / original_width.to_f
|
90
|
-
else # narrower
|
91
|
-
new_height = box_height
|
92
|
-
new_width = original_width.to_f * box_height.to_f / original_height.to_f
|
93
|
-
end
|
194
|
+
calculate_size_for_limit(box_width, box_height)
|
94
195
|
end
|
95
|
-
else
|
96
|
-
raise ArgumentError, "unconclusive arguments #{args.inspect} #{options.inspect}"
|
97
196
|
end
|
98
197
|
else
|
99
198
|
raise ArgumentError, "wrong number of arguments (#{args.size} for 1..2)"
|
100
199
|
end
|
101
|
-
[x, y, original_width, original_height, new_width.round, new_height.round]
|
102
200
|
end
|
103
201
|
|
202
|
+
def calculate_size_for_scale(factor)
|
203
|
+
raise ArgumentError, "scale factor #{factor.inspect} out of range" unless factor > 0 && factor < 1
|
204
|
+
[0, 0, width, height, (width.to_f * factor).round, (height.to_f * factor).round]
|
205
|
+
end
|
206
|
+
|
207
|
+
def calculate_size_for_skew(box_width, box_height)
|
208
|
+
[0, 0, width, height, box_width, box_height]
|
209
|
+
end
|
210
|
+
|
211
|
+
def calculate_size_for_auto(box_width, box_height)
|
212
|
+
if :auto == box_width && box_height.is_a?(Numeric)
|
213
|
+
new_height = box_height
|
214
|
+
new_width = (box_height.to_f / height.to_f * width.to_f).round
|
215
|
+
elsif box_width.is_a?(Numeric) && :auto == box_height
|
216
|
+
new_width = box_width
|
217
|
+
new_height = (box_width.to_f / width.to_f * height.to_f).round
|
218
|
+
else
|
219
|
+
raise ArgumentError, "unconclusive arguments #{box_width.inspect}, #{box_height.inspect}"
|
220
|
+
end
|
221
|
+
[0, 0, width, height, new_width, new_height]
|
222
|
+
end
|
223
|
+
|
224
|
+
def calculate_size_for_crop(box_width, box_height, crop)
|
225
|
+
raise ArgumentError, "invalid crop gravity" unless GRAVITIES.include?(crop)
|
226
|
+
aspect = width.to_f / height.to_f
|
227
|
+
box_aspect = box_width.to_f / box_height.to_f
|
228
|
+
if aspect >= box_aspect # wider than box
|
229
|
+
src_width = (box_width.to_f * height.to_f / box_height.to_f).round
|
230
|
+
src_height = height
|
231
|
+
x = crop_horizontally(src_width, crop)
|
232
|
+
y = 0
|
233
|
+
else # narrower than box
|
234
|
+
src_width = width
|
235
|
+
src_height = (box_height.to_f * width.to_f / box_width.to_f).round
|
236
|
+
x = 0
|
237
|
+
y = crop_vertically(src_height, crop)
|
238
|
+
end
|
239
|
+
[x, y, src_width, src_height, box_width, box_height]
|
240
|
+
end
|
241
|
+
|
242
|
+
def crop_horizontally(src_width, crop)
|
243
|
+
case crop
|
244
|
+
when :nw, :w, :sw then 0
|
245
|
+
when :ne, :e, :se then width - src_width
|
246
|
+
else
|
247
|
+
((width - src_width).to_f / 2.to_f).round
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def crop_vertically(src_height, crop)
|
252
|
+
case crop
|
253
|
+
when :nw, :n, :ne then 0
|
254
|
+
when :sw, :s, :se then height - src_height
|
255
|
+
else
|
256
|
+
((height - src_height).to_f / 2.to_f).round
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def calculate_size_for_limit(box_width, box_height)
|
261
|
+
scale = width.to_f / height.to_f
|
262
|
+
box_scale = box_width.to_f / box_height.to_f
|
263
|
+
if scale >= box_scale # wider
|
264
|
+
new_width = box_width
|
265
|
+
new_height = (height.to_f * box_width.to_f / width.to_f).round
|
266
|
+
else # narrower
|
267
|
+
new_height = box_height
|
268
|
+
new_width = (width.to_f * box_height.to_f / height.to_f).round
|
269
|
+
end
|
270
|
+
[0, 0, width, height, new_width, new_height]
|
271
|
+
end
|
272
|
+
|
104
273
|
def format_from_filename(path)
|
105
|
-
File.extname(path)[1..-1]
|
274
|
+
File.extname(path)[1..-1].to_s.downcase
|
275
|
+
end
|
276
|
+
|
277
|
+
def ensure_path_is_writable(path)
|
278
|
+
path = Pathname.new(path)
|
279
|
+
path.dirname.realpath.writable?
|
280
|
+
rescue Errno::ENOENT => e
|
281
|
+
raise SaveError, 'Non-existant path component'
|
282
|
+
rescue SystemCallError => e
|
283
|
+
raise SaveError, e.message
|
106
284
|
end
|
107
285
|
|
108
286
|
def assert_valid_keys(hsh, *valid_keys)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rszr'
|
2
|
+
require 'image_processing'
|
3
|
+
|
4
|
+
module ImageProcessing
|
5
|
+
module Rszr
|
6
|
+
extend Chainable
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Returns whether the given image file is processable.
|
11
|
+
def valid_image?(file)
|
12
|
+
::Rszr::Image.load(file).width
|
13
|
+
true
|
14
|
+
rescue ::Rszr::Error
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class Processor < ImageProcessing::Processor
|
21
|
+
accumulator :image, ::Rszr::Image
|
22
|
+
|
23
|
+
class << self
|
24
|
+
|
25
|
+
# Loads the image on disk into a Rszr::Image object
|
26
|
+
def load_image(path_or_image, **options)
|
27
|
+
if path_or_image.is_a?(::Rszr::Image)
|
28
|
+
path_or_image
|
29
|
+
else
|
30
|
+
::Rszr::Image.load(path_or_image)
|
31
|
+
end
|
32
|
+
# TODO: image = image.autorot if autorot && !options.key?(:autorotate)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Writes the image object to disk.
|
36
|
+
# Accepts additional options (quality, format).
|
37
|
+
def save_image(image, destination_path, **options)
|
38
|
+
image.save(destination_path, **options)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Calls the operation to perform the processing. If the operation is
|
42
|
+
# defined on the processor (macro), calls it. Otherwise calls the
|
43
|
+
# bang variant of the method directly on the Rszr image object.
|
44
|
+
def apply_operation(accumulator, (name, args, block))
|
45
|
+
return super if method_defined?(name)
|
46
|
+
accumulator.send("#{name}!", *args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Resizes the image to not be larger than the specified dimensions.
|
52
|
+
def resize_to_limit(width, height, **options)
|
53
|
+
width, height = default_dimensions(width, height)
|
54
|
+
thumbnail(width, height, inflate: false, **options)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Resizes the image to fit within the specified dimensions.
|
58
|
+
def resize_to_fit(width, height, **options)
|
59
|
+
width, height = default_dimensions(width, height)
|
60
|
+
thumbnail(width, height, **options)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Resizes the image to fill the specified dimensions, applying any
|
64
|
+
# necessary cropping.
|
65
|
+
def resize_to_fill(width, height, gravity: :center, **options)
|
66
|
+
thumbnail(width, height, crop: gravity, **options)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def thumbnail(width, height, **options)
|
72
|
+
image.resize!(width, height, **options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_dimensions(width, height)
|
76
|
+
raise Error, 'either width or height must be specified' unless width || height
|
77
|
+
[width || :auto, height || :auto]
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Rszr
|
2
|
+
module Orientation
|
3
|
+
ROTATIONS = { 5 => 1, 6 => 1, 3 => 2, 4 => 2, 7 => 3, 8 => 3 }
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.attr_reader :original_orientation
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def autorotate(image, path)
|
15
|
+
return unless %w[jpeg tiff].include?(image.format)
|
16
|
+
File.open(path) do |file|
|
17
|
+
if orientation = send("parse_#{image.format}_orientation", file) and (1..8).member?(orientation)
|
18
|
+
image.instance_variable_set :@original_orientation, orientation
|
19
|
+
image.flop! if [2, 4, 5, 7].include?(orientation)
|
20
|
+
image.turn!(ROTATIONS[orientation]) if ROTATIONS.key?(orientation)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_tiff_orientation(data)
|
26
|
+
exif_parse_orientation(Stream.new(data))
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_jpeg_orientation(data)
|
30
|
+
stream = Stream.new(data)
|
31
|
+
exif = nil
|
32
|
+
state = nil
|
33
|
+
loop do
|
34
|
+
state = case state
|
35
|
+
when nil
|
36
|
+
stream.skip(2)
|
37
|
+
:started
|
38
|
+
when :started
|
39
|
+
stream.read_byte == 0xFF ? :sof : :started
|
40
|
+
when :sof
|
41
|
+
case stream.read_byte
|
42
|
+
when 0xe1 # APP1
|
43
|
+
skip_chars = stream.read_int - 2
|
44
|
+
app1 = Stream.new(stream.read(skip_chars))
|
45
|
+
if app1.read(4) == 'Exif'
|
46
|
+
app1.skip(2)
|
47
|
+
orientation = exif_parse_orientation(app1.fast_forward)# rescue nil
|
48
|
+
return orientation
|
49
|
+
end
|
50
|
+
:started
|
51
|
+
when 0xe0..0xef
|
52
|
+
:skipframe
|
53
|
+
when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF
|
54
|
+
:readsize
|
55
|
+
when 0xFF
|
56
|
+
:sof
|
57
|
+
else
|
58
|
+
:skipframe
|
59
|
+
end
|
60
|
+
when :skipframe
|
61
|
+
skip_chars = stream.read_int - 2
|
62
|
+
stream.skip(skip_chars)
|
63
|
+
:started
|
64
|
+
when :readsize
|
65
|
+
# stream.skip(3)
|
66
|
+
# height = stream.read_int
|
67
|
+
# width = stream.read_int
|
68
|
+
return exif&.orientation
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def exif_byte_order(stream)
|
74
|
+
byte_order = stream.read(2)
|
75
|
+
case byte_order
|
76
|
+
when 'II'
|
77
|
+
%w[v V]
|
78
|
+
when 'MM'
|
79
|
+
%w[n N]
|
80
|
+
else
|
81
|
+
raise LoadError
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def exif_parse_ifd(stream, short)
|
86
|
+
tag_count = stream.read(2).unpack(short)[0]
|
87
|
+
tag_count.downto(1) do
|
88
|
+
type = stream.read(2).unpack(short)[0]
|
89
|
+
stream.read(6)
|
90
|
+
data = stream.read(2).unpack(short)[0]
|
91
|
+
return data if 0x0112 == type
|
92
|
+
stream.read(2)
|
93
|
+
end
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def exif_parse_orientation(stream)
|
98
|
+
short, long = exif_byte_order(stream)
|
99
|
+
stream.read(2) # 42
|
100
|
+
offset = stream.read(4).unpack(long)[0]
|
101
|
+
stream.skip(offset - 8)
|
102
|
+
exif_parse_ifd(stream, short)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
data/lib/rszr/stream.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Rszr
|
2
|
+
class Stream
|
3
|
+
attr_reader :pos, :data
|
4
|
+
protected :data
|
5
|
+
|
6
|
+
def initialize(data, start: 0)
|
7
|
+
raise ArgumentError, 'start must be > 0' if start < 0
|
8
|
+
@data = case data
|
9
|
+
when IO then data
|
10
|
+
when String then StringIO.new(data)
|
11
|
+
when Stream then data.data
|
12
|
+
else
|
13
|
+
raise ArgumentError, "data must be File or String, got #{data.class}"
|
14
|
+
end
|
15
|
+
@data.binmode
|
16
|
+
@data.seek(start)
|
17
|
+
@pos = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(n)
|
21
|
+
@data.read(n).tap { @pos += n }
|
22
|
+
end
|
23
|
+
|
24
|
+
def peek(n)
|
25
|
+
old_pos = @data.pos
|
26
|
+
@data.read(n)
|
27
|
+
ensure
|
28
|
+
@data.pos = old_pos
|
29
|
+
end
|
30
|
+
|
31
|
+
def skip(n)
|
32
|
+
@data.seek(n, IO::SEEK_CUR).tap { @pos += n }
|
33
|
+
end
|
34
|
+
|
35
|
+
def substream
|
36
|
+
self.class.new(self, @data.pos)
|
37
|
+
end
|
38
|
+
|
39
|
+
def fast_forward
|
40
|
+
@pos = 0
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_byte
|
45
|
+
read(1)[0].ord
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_int
|
49
|
+
read(2).unpack('n')[0]
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_string_int
|
53
|
+
value = []
|
54
|
+
while read(1) =~ /(\d)/
|
55
|
+
value << $1
|
56
|
+
end
|
57
|
+
value.join.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/rszr/version.rb
CHANGED
data/lib/rszr.rb
CHANGED
@@ -1,6 +1,27 @@
|
|
1
1
|
require 'rbconfig'
|
2
2
|
require 'pathname'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'stringio'
|
3
5
|
|
4
6
|
require 'rszr/rszr'
|
5
7
|
require 'rszr/version'
|
8
|
+
require 'rszr/stream'
|
9
|
+
require 'rszr/identification'
|
10
|
+
require 'rszr/orientation'
|
11
|
+
require 'rszr/buffered'
|
12
|
+
require 'rszr/color'
|
6
13
|
require 'rszr/image'
|
14
|
+
|
15
|
+
module Rszr
|
16
|
+
class << self
|
17
|
+
@@autorotate = nil
|
18
|
+
|
19
|
+
def autorotate
|
20
|
+
@@autorotate
|
21
|
+
end
|
22
|
+
|
23
|
+
def autorotate=(value)
|
24
|
+
@@autorotate = !!value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|