oximg 0.18.1-aarch64-linux-gnu → 0.20.0-aarch64-linux-gnu

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4d3cb7265d61a0d89d9851f6e0938951ca773603a02dcdc19ad5b8abf3d68de
4
- data.tar.gz: 7cfa66df5ffac827bc245ddd387cc321054dabd6f97c97a13bf3e6b3e8ad50e5
3
+ metadata.gz: 18c7da6b14cbd76c0f403a39479b59e611e643a15f06a087012c42e7c86114f4
4
+ data.tar.gz: 0237a92850bd1259b6b1eb1182a2688d4c73ff2d2e3de14b1de6138212a80201
5
5
  SHA512:
6
- metadata.gz: 1be6c2171b1eb682a4d495d3130eca32b7e856db393a5319aba109ada1f5d5cdd1e636ebf8072243894b58d6801849aa0cee30cb34d91adee4f0ff4593bfeb53
7
- data.tar.gz: 5c2cf3b537d675e748cd62e3787a1008099ab2e16103ecdfee11d80a8e4b1535f81a5ebc3e3a7b5124410948042b230269f7ffae0dec5a75ea5f91770bfa6dbc
6
+ metadata.gz: cbcb4f7a737d982744d982a5cd489536964e394feae912e33166a659a4f8e4b49e311a229452b39f452f66da4e2fcfa21d65e549a354806b8e86b7331de6ccde
7
+ data.tar.gz: d4f54f32aabd7bd4dc7659bbe2b9f9462e04bc74f82e0c0092fed1af7881f63697af8d04abb328a1fe7da47af299c9ba8b351bd1a93de78220d6dc2d50ea0461
data/README.md CHANGED
@@ -20,7 +20,8 @@ Oximg.resize("in.jpg", "out.jpg", width: 750)
20
20
  Oximg.resize("in.jpg", "out.webp", width: 750, height: 500, quality: 80)
21
21
  Oximg.strip("upload.jpg", "clean.jpg")
22
22
  Oximg.probe("in.jpg")
23
- #=> {content_type: "image/jpeg", format: :jpeg, width: 4000, height: 3000}
23
+ #=> {content_type: "image/jpeg", format: :jpeg, width: 4000, height: 3000,
24
+ # orientation: 1, display_width: 4000, display_height: 3000}
24
25
  ```
25
26
 
26
27
  A GIF source is read and probed like the others (`format: :gif`), but
@@ -66,7 +67,7 @@ cannot be read as a flag.
66
67
  ## Stripping metadata
67
68
 
68
69
  ```ruby
69
- Oximg.strip(source, destination, icc: nil, env: nil)
70
+ Oximg.strip(source, destination, icc: nil, auto_rotate: nil, env: nil)
70
71
  ```
71
72
 
72
73
  Removes EXIF (GPS, camera, timestamps), XMP, IPTC and comments from a
@@ -78,6 +79,38 @@ still displays upright. `icc: :strip` also drops the colour profile.
78
79
  The default is `:keep`, or `OXIMG_ICC`. Dropping a CMYK JPEG's profile
79
80
  raises `Oximg::ProcessingError`, as do GIF and AVIF sources.
80
81
 
82
+ `auto_rotate: true` turns a rotated JPEG upright instead, still without
83
+ re-encoding: its compressed 8x8 blocks are moved, as `jpegtran
84
+ -perfect` does, so the stored and displayed sizes agree for every
85
+ reader, not only for ones that honor EXIF. That is only exact when the
86
+ edges it moves are whole 8 or 16 px blocks (a phone's 4032x3024 is);
87
+ otherwise, and for PNG and WebP, the file is stripped as without it,
88
+ orientation kept. It costs a full entropy decode and encode, about
89
+ 170 ms for a 12 MP photo.
90
+
91
+ `strip` returns an `Oximg::Stripped`, the binary's own account of the
92
+ run, so telling which happened needs no second read:
93
+
94
+ ```ruby
95
+ result = Oximg.strip(upload, clean, icc: :keep, auto_rotate: true)
96
+ result.upright? # no orientation tag left: stored size is displayed size
97
+ result.rotated? # the pixels were turned upright
98
+ result.orientation #=> 6, the tag kept, or nil
99
+ result.not_rotated&.code #=> :unaligned_edges, :corrupt, :over_limit or :not_jpeg
100
+ result.not_rotated&.message # for humans; the wording may change
101
+ ```
102
+
103
+ `not_rotated` is nil unless `auto_rotate: true` left a rotated source
104
+ tagged. The code is the contract (the table is in
105
+ [docs/features/paths.md](../../docs/features/paths.md)), so count
106
+ fallbacks by it. The result also stands in for the destination path
107
+ `strip` used to return: `to_s`, `to_path` and `to_str` are the path and
108
+ it compares equal to that String, so `File.size(Oximg.strip(...))`
109
+ still works. With an `oximg` older than 0.20, which has no `strip
110
+ --json`, the gem reads the summary line that binary prints instead:
111
+ `orientation` and `rotated?` are still right, `removed` is nil, and
112
+ the code is `:unknown` beside the binary's own reason.
113
+
81
114
  ## Knobs, per process or per call
82
115
 
83
116
  Everything the server tunes through `OXIMG_*` environment variables
@@ -106,12 +139,21 @@ Oximg.probe(Pathname("in.jpg")) # also a path
106
139
  Oximg.probe(upload.tempfile) # anything with #path
107
140
  Oximg.probe(File.open("in.jpg", "rb")) # an open File
108
141
  Oximg.probe(StringIO.new(bytes)) # an IO: bytes in memory
109
- #=> {content_type: "image/jpeg", format: :jpeg, width: 4000, height: 3000}
142
+ #=> {content_type: "image/jpeg", format: :jpeg, width: 4000, height: 3000,
143
+ # orientation: 1, display_width: 4000, display_height: 3000}
110
144
  ```
111
145
 
112
- `probe` reads the header only and decodes no pixels. The dimensions are
113
- the stored ones, before any EXIF rotation. A String is always a path,
114
- so bytes already in memory go in as a `StringIO`. An open `File` is
146
+ `probe` reads the header only and decodes no pixels. `width` and
147
+ `height` are the stored ones, before any EXIF rotation. `orientation`
148
+ is the EXIF value, 1..8 (1 when there is none or it is invalid, as
149
+ `resize` reads it), and `display_width` / `display_height` are what a
150
+ viewer honoring it shows: the axes exchanged for 5..8. A portrait
151
+ phone photo stored 4000x3000 with orientation 6, as `strip` leaves it,
152
+ lays out at 3000x4000. A WebP keeps its EXIF after the image data, so
153
+ probing one that has it reads the whole source. An `oximg` executable
154
+ older than this gem reports every source as orientation 1.
155
+
156
+ A String is always a path, so bytes already in memory go in as a `StringIO`. An open `File` is
115
157
  read from its start, its position untouched, and it is the file you
116
158
  hold that is read, even if its name has since been replaced, renamed,
117
159
  or was relative to a directory the process has left. A closed or
@@ -161,8 +203,8 @@ Oximg.in_process_probe? #=> true on a platform gem
161
203
 
162
204
  ```ruby
163
205
  Oximg.available? #=> true
164
- Oximg.executable #=> "/…/gems/oximg-0.18.1-arm64-darwin/exe/oximg"
165
- Oximg.version #=> "0.18.1" # the binary's version, not the gem's
206
+ Oximg.executable #=> "/…/gems/oximg-0.20.0-arm64-darwin/exe/oximg"
207
+ Oximg.version #=> "0.20.0" # the binary's version, not the gem's
166
208
  ```
167
209
 
168
210
  `resize` shells out to that binary — one process per image, which is
data/exe/oximg CHANGED
Binary file
data/lib/oximg/binary.rb CHANGED
@@ -76,11 +76,23 @@ module Oximg
76
76
  [out, err]
77
77
  end
78
78
 
79
+ # True once the executable refused `strip --json` (an oximg older
80
+ # than 0.20): Oximg.strip then runs without it and reads the
81
+ # summary line that binary prints. Memoized per executable, like
82
+ # the path, so a process
83
+ # pays the refused spawn once.
84
+ attr_accessor :legacy_strip
85
+
86
+ def legacy_strip?
87
+ @legacy_strip == true
88
+ end
89
+
79
90
  # Resets the memoized lookup. For tests, and for a process that
80
91
  # changes OXIMG_BIN after boot.
81
92
  def reset!
82
93
  @path = nil
83
94
  @version = nil
95
+ @legacy_strip = nil
84
96
  end
85
97
 
86
98
  private
Binary file
data/lib/oximg/native.rb CHANGED
@@ -29,7 +29,7 @@ module Oximg
29
29
  BUNDLED = File.expand_path("native/#{LIBRARY}", __dir__)
30
30
 
31
31
  # The library's oximg_probe_abi_version this code was written for.
32
- ABI_VERSION = 1
32
+ ABI_VERSION = 2
33
33
 
34
34
  # Statuses (ext/src/lib.rs).
35
35
  OK = 0
@@ -110,7 +110,8 @@ module Oximg
110
110
  end
111
111
 
112
112
  def call
113
- dims = FFI::MemoryPointer.new(:uint64, 2)
113
+ # Stored width, stored height, orientation (ext/src/lib.rs).
114
+ dims = FFI::MemoryPointer.new(:uint64, 3)
114
115
  msg = FFI::MemoryPointer.new(:char, MESSAGE_BYTES)
115
116
  status = yield(dims, msg)
116
117
  return if status == READ
@@ -118,8 +119,8 @@ module Oximg
118
119
  text = msg.read_string.force_encoding(Encoding::UTF_8)
119
120
  raise ProcessingError.new(text, 1) unless status == OK
120
121
 
121
- width, height = dims.read_array_of_uint64(2)
122
- {content_type: text, format: CONTENT_TYPE_FORMATS[text], width: width, height: height}
122
+ width, height, orientation = dims.read_array_of_uint64(3)
123
+ Header.build(text, width, height, orientation)
123
124
  end
124
125
 
125
126
  # nil when there is no library to use. Memoized either way, so a
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Oximg
4
+ # What Oximg.strip did: where it wrote, and whether the output
5
+ # displays upright. Built from <tt>oximg strip --json</tt>, so it is
6
+ # the binary's own account of the run, not a second read of the file.
7
+ #
8
+ # It stands in for the destination path Oximg.strip returned before
9
+ # (#176): #to_s, #to_path and #to_str are the path, and it compares
10
+ # equal to that path as a String, so
11
+ # <tt>File.size(Oximg.strip(...))</tt>, <tt>File.join</tt> and
12
+ # <tt>assert_equal out, Oximg.strip(...)</tt> keep working.
13
+ #
14
+ # result = Oximg.strip(upload, clean, auto_rotate: true)
15
+ # result.rotated? #=> false
16
+ # result.orientation #=> 6, the tag the output keeps
17
+ # result.not_rotated.code #=> :unaligned_edges
18
+ # result.not_rotated.message #=> "1601x1203 does not divide into ..."
19
+ class Stripped
20
+ # Why <tt>auto_rotate: true</tt> left a rotated source tagged.
21
+ # +code+ is the contract, a Symbol from NOT_ROTATED_CODES (or
22
+ # :unknown, below); +message+ is for humans and may change.
23
+ NotRotated = Data.define(:code, :message)
24
+
25
+ # The binary's codes (docs/features/paths.md). A code missing here
26
+ # still comes through, as its Symbol: a newer binary may add one.
27
+ NOT_ROTATED_CODES = %i[unaligned_edges corrupt over_limit not_jpeg].freeze
28
+
29
+ # The destination, absolute.
30
+ attr_reader :path
31
+ # The output's content type ("image/jpeg", ...).
32
+ attr_reader :content_type
33
+ # Output size in bytes.
34
+ attr_reader :bytes
35
+ # What was dropped: <tt>[{name: "APP1 Exif", count: 1, bytes: 1204}, ...]</tt>;
36
+ # nil from an oximg older than 0.20.
37
+ attr_reader :removed
38
+ # The orientation tag the output keeps, 2..8, or nil when it
39
+ # displays as stored (upright, untagged, or rotated upright).
40
+ attr_reader :orientation
41
+ # The orientation turned into the pixels, 2..8, or nil.
42
+ attr_reader :rotated
43
+ # A NotRotated, or nil unless auto_rotate left a rotated source
44
+ # tagged. From an oximg older than 0.20, which has no --json and
45
+ # gives the reason only as a sentence, the code is :unknown.
46
+ attr_reader :not_rotated
47
+
48
+ # From the hash <tt>oximg strip --json</tt> prints.
49
+ def self.from_json(path, report)
50
+ why = report["not_rotated"]
51
+ new(
52
+ path: path,
53
+ content_type: report["content_type"],
54
+ bytes: report["bytes"],
55
+ removed: report["removed"]&.map { |r| {name: r["name"], count: r["count"], bytes: r["bytes"]} },
56
+ orientation: report["orientation"],
57
+ rotated: report["rotated"],
58
+ not_rotated: why && NotRotated.new(code: why["code"].to_sym, message: why["message"])
59
+ )
60
+ end
61
+
62
+ def initialize(path:, bytes:, content_type:, orientation:, rotated:, not_rotated:, removed: nil)
63
+ @path = path
64
+ @content_type = content_type
65
+ @bytes = bytes
66
+ @removed = removed
67
+ @orientation = orientation
68
+ @rotated = rotated
69
+ @not_rotated = not_rotated
70
+ freeze
71
+ end
72
+
73
+ # True when the pixels were turned upright (auto_rotate).
74
+ def rotated?
75
+ !@rotated.nil?
76
+ end
77
+
78
+ # True when the output displays as stored: no orientation tag left,
79
+ # so its stored width and height are the displayed ones.
80
+ def upright?
81
+ @orientation.nil?
82
+ end
83
+
84
+ def to_s
85
+ @path
86
+ end
87
+ alias_method :to_path, :to_s
88
+ alias_method :to_str, :to_s
89
+
90
+ # Equal to another result for the same run, or to the destination
91
+ # path as a String (String#== defers here for anything with to_str).
92
+ def ==(other)
93
+ case other
94
+ when Stripped then to_h == other.to_h
95
+ when String then @path == other
96
+ else false
97
+ end
98
+ end
99
+
100
+ def to_h
101
+ {path: @path, content_type: @content_type, bytes: @bytes, removed: @removed,
102
+ orientation: @orientation, rotated: @rotated, not_rotated: @not_rotated&.to_h}
103
+ end
104
+
105
+ def inspect
106
+ "#<#{self.class.name} #{@path} #{to_h.except(:path).compact.inspect}>"
107
+ end
108
+ end
109
+ end
data/lib/oximg/version.rb CHANGED
@@ -7,5 +7,5 @@ module Oximg
7
7
  # names a binary it does not carry. `Oximg.version` reports what the
8
8
  # resolved executable actually is, which can differ when it comes
9
9
  # from PATH.
10
- VERSION = "0.18.1"
10
+ VERSION = "0.20.0"
11
11
  end
data/lib/oximg.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fcntl"
4
+ require "json"
4
5
  require "tempfile"
5
6
  require_relative "oximg/version"
6
7
  require_relative "oximg/binary"
7
8
  require_relative "oximg/native"
9
+ require_relative "oximg/stripped"
8
10
 
9
11
  # Ruby bindings for oximg (https://github.com/oximg/oximg):
10
12
  # high-performance image compression and resizing, in-process work for a
@@ -102,8 +104,12 @@ module Oximg
102
104
  # The line `oximg probe` prints (src/cli.rs):
103
105
  #
104
106
  # in.gif: image/gif 120x90 (10800 stored pixels), 3 frames, 1500ms, looping forever
107
+ # in.jpg: image/jpeg 3000x2000 (6000000 stored pixels), orientation 6 (displayed 2000x3000)
105
108
  #
106
- # The three fields the gem reports are pinned exactly. Everything after
109
+ # The three fields the gem reports are pinned exactly, and so is the
110
+ # orientation, which a non-upright source prints first after the
111
+ # comma (its absence means 1; the displayed size is derived here from
112
+ # it, not read). Everything after
107
113
  # the comma — the animation summary, printed for an animated source
108
114
  # only — is information the gem does not expose, and is deliberately
109
115
  # left unparsed rather than matched: a pattern that stopped at
@@ -113,7 +119,25 @@ module Oximg
113
119
  # a field missing, a continuation without the comma, or anything
114
120
  # printed before or after it. stderr is captured separately, so a
115
121
  # warning never lands here.
116
- PROBE_LINE = /\A[^\n]*: (\S+) (\d+)x(\d+) \(\d+ stored pixels\)(?:,[^\n]*)?\n?\z/
122
+ PROBE_LINE = /\A[^\n]*: (\S+) (\d+)x(\d+) \(\d+ stored pixels\)(?:, orientation ([1-8]) \(displayed \d+x\d+\))?(?:,[^\n]*)?\n?\z/
123
+
124
+ # The hash probe returns, built in one place for both of its paths
125
+ # (the in-process library and the spawned executable).
126
+ module Header # :nodoc:
127
+ def self.build(content_type, width, height, orientation)
128
+ # Orientations 5-8 exchange the axes on display.
129
+ swapped = orientation >= 5
130
+ {
131
+ content_type: content_type,
132
+ format: CONTENT_TYPE_FORMATS[content_type],
133
+ width: width,
134
+ height: height,
135
+ orientation: orientation,
136
+ display_width: swapped ? height : width,
137
+ display_height: swapped ? width : height
138
+ }
139
+ end
140
+ end
117
141
 
118
142
  class << self
119
143
  # Absolute path to the executable in use; raises
@@ -176,17 +200,49 @@ module Oximg
176
200
  # a rotated photo keeps only its EXIF orientation, so it still
177
201
  # displays upright. +icc:+ is :keep or :strip (default: the
178
202
  # binary's OXIMG_ICC, else keep); dropping a CMYK JPEG's profile is
179
- # refused with a ProcessingError. Returns the destination path.
203
+ # refused with a ProcessingError.
204
+ #
205
+ # Returns an Oximg::Stripped: the binary's own account of the run
206
+ # (orientation kept, rotated?, not_rotated with a stable code). It
207
+ # stands in for the destination path this returned before:
208
+ # #to_path, #to_str and == against a String are the path.
180
209
  #
181
210
  # This is what <tt>saver(strip: true)</tt> / <tt>-strip</tt> did
182
211
  # under libvips or ImageMagick, minus the re-encode.
183
212
  #
213
+ # <tt>auto_rotate: true</tt> turns a rotated JPEG upright instead,
214
+ # still without re-encoding: the compressed 8x8 blocks are moved
215
+ # (what <tt>jpegtran -perfect</tt> does), so its stored and displayed
216
+ # dimensions agree for every reader and no orientation tag is left.
217
+ # That is only exact when the edges it moves are whole 8 or 16 px
218
+ # blocks; otherwise, and for PNG and WebP, the orientation is kept
219
+ # as without it, and the result says why:
220
+ #
221
+ # result = Oximg.strip(upload, clean, auto_rotate: true)
222
+ # unless result.upright?
223
+ # result.not_rotated.code #=> :unaligned_edges, :corrupt, :over_limit, :not_jpeg
224
+ # end
225
+ #
184
226
  # +env:+ is per-call knobs, as for resize.
185
- def strip(source, destination, icc: nil, env: nil)
227
+ def strip(source, destination, icc: nil, auto_rotate: nil, env: nil)
186
228
  output = expand(destination, "destination")
187
- argv = strip_argv(source, output, icc: icc)
188
- Binary.run(*argv, env: knob_env(env))
189
- output
229
+ argv = strip_argv(source, output, icc: icc, auto_rotate: auto_rotate)
230
+ child_env = knob_env(env)
231
+ unless Binary.legacy_strip?
232
+ begin
233
+ out, = Binary.run(*argv, "--json", env: child_env)
234
+ return Stripped.from_json(output, JSON.parse(out))
235
+ rescue ProcessingError => e
236
+ # An oximg older than 0.20 refuses the flag at parse time,
237
+ # before it reads or writes anything: remember that, and run
238
+ # the strip it does know.
239
+ raise unless e.status == 2 && e.message.include?('unknown option "--json"')
240
+
241
+ Binary.legacy_strip = true
242
+ end
243
+ end
244
+ _, err = Binary.run(*argv, env: child_env)
245
+ legacy_stripped(output, err)
190
246
  end
191
247
 
192
248
  # The child environment an +env:+ turns into: String names and
@@ -211,17 +267,24 @@ module Oximg
211
267
  end
212
268
  end
213
269
 
214
- # The argv `strip` would run, minus the executable; public for the
215
- # same reason as resize_argv.
216
- def strip_argv(source, destination, icc: nil)
270
+ # The argv `strip` would run, minus the executable and the --json it
271
+ # adds to read the outcome; public for the same reason as
272
+ # resize_argv.
273
+ def strip_argv(source, destination, icc: nil, auto_rotate: nil)
217
274
  argv = ["strip", expand(source, "source"), expand(destination, "destination")]
218
275
  argv.push("--icc", token(icc, ICC_MODES, "icc")) unless icc.nil?
276
+ # Off is strip's own default, so false adds nothing.
277
+ argv.push("--auto-rotate") if !auto_rotate.nil? && boolean(auto_rotate, "auto_rotate")
219
278
  argv
220
279
  end
221
280
 
222
281
  # Header-only inspection — no pixels are decoded. Returns
223
- # <tt>{content_type:, format:, width:, height:}</tt>, where the
224
- # dimensions are the stored ones (an EXIF rotation is not applied).
282
+ # <tt>{content_type:, format:, width:, height:, orientation:,
283
+ # display_width:, display_height:}</tt>. +width+ and +height+ are the
284
+ # stored ones; +orientation+ is the EXIF value, 1..8 (1 when there is
285
+ # none: the rule resize's auto-rotate applies and strip keeps), and
286
+ # the display size is what a viewer honoring it shows, with the axes
287
+ # exchanged for 5..8.
225
288
  #
226
289
  # +source+ is a path — a String, a Pathname, or anything with #path
227
290
  # such as a Tempfile — an open File, or a stream read from its
@@ -363,12 +426,31 @@ module Oximg
363
426
  match = output.match(PROBE_LINE)
364
427
  raise ProcessingError, "unparsable probe output: #{output.inspect}" unless match
365
428
 
366
- {
367
- content_type: match[1],
368
- format: CONTENT_TYPE_FORMATS[match[1]],
369
- width: Integer(match[2]),
370
- height: Integer(match[3])
371
- }
429
+ Header.build(match[1], Integer(match[2]), Integer(match[3]), match[4] ? Integer(match[4]) : 1)
430
+ end
431
+
432
+ # The summary every oximg since 0.18 ends a strip with, after
433
+ # "oximg: wrote <out> " (src/cli.rs). Those releases are frozen, so
434
+ # their wording is a fixed format here, though it is not a contract
435
+ # for later ones (they have --json). Removed block names are JPEG
436
+ # segment names or 4-byte chunk types, so the tail is unambiguous.
437
+ LEGACY_STRIP_SUMMARY = /\A\((\d+) bytes, ([^;\s]+); (.*?)(?:; kept orientation ([2-8])(?:, not rotated: (.*))?|; rotated orientation ([2-8]) upright)?\)\z/m
438
+
439
+ # The result from an oximg without --json, read from that summary
440
+ # rather than by probing: probe stops looking for the orientation
441
+ # after 1 MiB of metadata and needs a header libjpeg reads, where
442
+ # strip does neither. The reason a rotation was not made has no
443
+ # code before 0.20, so it is :unknown, with the binary's sentence.
444
+ def legacy_stripped(output, stderr)
445
+ prefix = "oximg: wrote #{output} "
446
+ line = stderr.lines.map(&:chomp).reverse.find { |l| l.start_with?(prefix) }
447
+ match = line && line.delete_prefix(prefix).match(LEGACY_STRIP_SUMMARY)
448
+ raise ProcessingError, "unparsable strip summary: #{stderr.strip.inspect}" unless match
449
+
450
+ kept = match[4] && Integer(match[4])
451
+ why = match[5] && Stripped::NotRotated.new(code: :unknown, message: match[5])
452
+ Stripped.new(path: output, bytes: Integer(match[1]), content_type: match[2],
453
+ orientation: kept, rotated: match[6] && Integer(match[6]), not_rotated: why)
372
454
  end
373
455
 
374
456
  # Absolute paths throughout: a relative name beginning with "-"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oximg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.1
4
+ version: 0.20.0
5
5
  platform: aarch64-linux-gnu
6
6
  authors:
7
7
  - oximg contributors
@@ -37,6 +37,7 @@ files:
37
37
  - lib/oximg/binary.rb
38
38
  - lib/oximg/native.rb
39
39
  - lib/oximg/native/liboximg_probe.so
40
+ - lib/oximg/stripped.rb
40
41
  - lib/oximg/version.rb
41
42
  homepage: https://github.com/oximg/oximg
42
43
  licenses: