webp-ffi 1.0.0-x86_64-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.
@@ -0,0 +1,49 @@
1
+ # LibTIFF license
2
+
3
+ Copyright © 1988-1997 Sam Leffler\
4
+ Copyright © 1991-1997 Silicon Graphics, Inc.
5
+
6
+ Permission to use, copy, modify, distribute, and sell this software and
7
+ its documentation for any purpose is hereby granted without fee, provided
8
+ that (i) the above copyright notices and this permission notice appear in
9
+ all copies of the software and related documentation, and (ii) the names of
10
+ Sam Leffler and Silicon Graphics may not be used in any advertising or
11
+ publicity relating to the software without the specific, prior written
12
+ permission of Sam Leffler and Silicon Graphics.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
16
+ WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
17
+
18
+ IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
19
+ ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
20
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
21
+ WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
22
+ LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
23
+ OF THIS SOFTWARE.
24
+
25
+ # Lempel-Ziv & Welch Compression (tif_lzw.c) license
26
+ The code of tif_lzw.c is derived from the compress program whose code is
27
+ derived from software contributed to Berkeley by James A. Woods,
28
+ derived from original work by Spencer Thomas and Joseph Orost.
29
+
30
+ The original Berkeley copyright notice appears below in its entirety:
31
+
32
+ Copyright (c) 1985, 1986 The Regents of the University of California.
33
+ All rights reserved.
34
+
35
+ This code is derived from software contributed to Berkeley by
36
+ James A. Woods, derived from original work by Spencer Thomas
37
+ and Joseph Orost.
38
+
39
+ Redistribution and use in source and binary forms are permitted
40
+ provided that the above copyright notice and this paragraph are
41
+ duplicated in all such forms and that any documentation,
42
+ advertising materials, and other materials related to such
43
+ distribution and use acknowledge that the software was developed
44
+ by the University of California, Berkeley. The name of the
45
+ University may not be used to endorse or promote products derived
46
+ from this software without specific prior written permission.
47
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
48
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
49
+ WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
@@ -0,0 +1,30 @@
1
+ Copyright (c) 2010, Google Inc. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+
7
+ * Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in
12
+ the documentation and/or other materials provided with the
13
+ distribution.
14
+
15
+ * Neither the name of Google nor the names of its contributors may
16
+ be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
@@ -0,0 +1,23 @@
1
+ Additional IP Rights Grant (Patents)
2
+ ------------------------------------
3
+
4
+ "These implementations" means the copyrightable works that implement the WebM
5
+ codecs distributed by Google as part of the WebM Project.
6
+
7
+ Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
8
+ royalty-free, irrevocable (except as stated in this section) patent license to
9
+ make, have made, use, offer to sell, sell, import, transfer, and otherwise
10
+ run, modify and propagate the contents of these implementations of WebM, where
11
+ such license applies only to those patent claims, both currently owned by
12
+ Google and acquired in the future, licensable by Google that are necessarily
13
+ infringed by these implementations of WebM. This grant does not include claims
14
+ that would be infringed only as a consequence of further modification of these
15
+ implementations. If you or your agent or exclusive licensee institute or order
16
+ or agree to the institution of patent litigation or any other patent
17
+ enforcement activity against any entity (including a cross-claim or
18
+ counterclaim in a lawsuit) alleging that any of these implementations of WebM
19
+ or any code incorporated within any of these implementations of WebM
20
+ constitute direct or contributory patent infringement, or inducement of
21
+ patent infringement, then any patent rights granted to you under this License
22
+ for these implementations of WebM shall terminate as of the date such
23
+ litigation is filed.
@@ -0,0 +1,22 @@
1
+ Copyright notice:
2
+
3
+ (C) 1995-2026 Jean-loup Gailly and Mark Adler
4
+
5
+ This software is provided 'as-is', without any express or implied
6
+ warranty. In no event will the authors be held liable for any damages
7
+ arising from the use of this software.
8
+
9
+ Permission is granted to anyone to use this software for any purpose,
10
+ including commercial applications, and to alter it and redistribute it
11
+ freely, subject to the following restrictions:
12
+
13
+ 1. The origin of this software must not be misrepresented; you must not
14
+ claim that you wrote the original software. If you use this software
15
+ in a product, an acknowledgment in the product documentation would be
16
+ appreciated but is not required.
17
+ 2. Altered source versions must be plainly marked as such, and must not be
18
+ misrepresented as being the original software.
19
+ 3. This notice may not be removed or altered from any source distribution.
20
+
21
+ Jean-loup Gailly Mark Adler
22
+ jloup@gzip.org madler@alumni.caltech.edu
@@ -0,0 +1,5 @@
1
+ libwebp 1.6.0
2
+ libpng 1.6.59
3
+ libjpeg-turbo 3.2.1
4
+ libtiff 4.7.2
5
+ zlib 1.3.2
data/lib/webp/c.rb ADDED
@@ -0,0 +1,101 @@
1
+ module WebP
2
+ module C
3
+ # enum
4
+ OutputFileFormat = enum( :png, 0,
5
+ :pam,
6
+ :ppm,
7
+ :pgm,
8
+ :bmp,
9
+ :tiff,
10
+ :yuv,
11
+ :alpha_plane_only )
12
+ # struct
13
+ class FfiWebpEncodeConfig < FFI::Struct
14
+ layout :lossless, :int,
15
+ :near_lossless, :int,
16
+ :quality, :float,
17
+ :method, :int,
18
+ :target_size, :int,
19
+ :target_PSNR, :float,
20
+ :segments, :int,
21
+ :sns_strength, :int,
22
+ :filter_strength, :int,
23
+ :filter_sharpness, :int,
24
+ :filter_type, :int,
25
+ :autofilter, :int,
26
+ :alpha_compression, :int,
27
+ :alpha_filtering, :int,
28
+ :alpha_quality, :int,
29
+ :pass, :int,
30
+ :show_compressed, :int,
31
+ :preprocessing, :int,
32
+ :partitions, :int,
33
+ :partition_limit, :int,
34
+ # image
35
+ :width, :int,
36
+ :height, :int,
37
+ :crop_x, :int,
38
+ :crop_y, :int,
39
+ :crop_w, :int,
40
+ :crop_h, :int,
41
+ :resize_w, :int,
42
+ :resize_h, :int,
43
+ # -1 keeps the libwebp default
44
+ :preset, :int,
45
+ :image_hint, :int,
46
+ :exact, :int,
47
+ :use_sharp_yuv, :int,
48
+ :thread_level, :int,
49
+ :low_memory, :int,
50
+ :emulate_jpeg_size, :int,
51
+ :lossless_level, :int,
52
+ :metadata, :int
53
+ end
54
+
55
+ class FfiWebpFeatures < FFI::Struct
56
+ layout :width, :int,
57
+ :height, :int,
58
+ :has_alpha, :int,
59
+ :has_animation, :int,
60
+ :format, :int
61
+ end
62
+
63
+ class FfiWebpDecodeConfig < FFI::Struct
64
+ layout :output_format, OutputFileFormat,
65
+ :bypass_filtering, :int,
66
+ :no_fancy_upsampling, :int,
67
+ :crop_x, :int,
68
+ :crop_y, :int,
69
+ :crop_w, :int,
70
+ :crop_h, :int,
71
+ :resize_w, :int,
72
+ :resize_h, :int,
73
+ :use_threads, :int
74
+ end
75
+
76
+ # webp lib functions
77
+ # libwebp versions packed as 0xMMmmpp
78
+ attach_function :decoder_version, [], :int
79
+ attach_function :encoder_version, [], :int
80
+ attach_function :webp_get_info, [:buffer_in, :size_t, :pointer, :pointer], :int
81
+ attach_function :webp_get_features, [:buffer_in, :size_t, :pointer], :int
82
+ # blocking: true releases Ruby's Global VM Lock while C works, so other
83
+ # threads keep running. The C code never calls back into Ruby.
84
+ # The last argument receives libwebp's reason when a call fails.
85
+ attach_function :webp_encode, [:string, :string, :pointer, :pointer], :int, blocking: true
86
+ attach_function :webp_decode, [:string, :string, :pointer, :pointer], :int, blocking: true
87
+ # in-memory versions: (data, data size, output pointer, output size, options, reason)
88
+ attach_function :webp_encode_data, [:pointer, :size_t, :pointer, :pointer, :pointer, :pointer], :int, blocking: true
89
+ attach_function :webp_decode_data, [:pointer, :size_t, :pointer, :pointer, :pointer, :pointer], :int, blocking: true
90
+ # raw RGBA pixels: (pixels, width, height, output pointer, output size, options, reason)
91
+ attach_function :webp_encode_rgba, [:pointer, :int, :int, :pointer, :pointer, :pointer, :pointer], :int, blocking: true
92
+ # (data, data size, output pointer, output size, width, height, options, reason)
93
+ attach_function :webp_decode_rgba, [:pointer, :size_t, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int, blocking: true
94
+ attach_function :webp_free_data, [:pointer], :void
95
+
96
+ # internal: image size check shared by the PNG, JPEG and TIFF readers
97
+ attach_function :check_image_size, :UtilCheckImageSize, [:uint64, :uint64, :uint64, :pointer, :pointer], :int
98
+ # internal: version string of the libtiff in use
99
+ attach_function :tiff_version, :UtilTIFFVersion, [], :string
100
+ end
101
+ end
data/lib/webp/error.rb ADDED
@@ -0,0 +1,71 @@
1
+ module WebP
2
+
3
+ ENCODER_ERRORS = [
4
+ "Version mismatch",
5
+ "Invalid configuration",
6
+ "Cannot read input picture file",
7
+ "Cannot open output file",
8
+ "Cannot crop picture",
9
+ "Cannot resize picture",
10
+ "Cannot encode picture as WebP",
11
+ "Cannot allocate memory"]
12
+
13
+ DECODER_ERRORS = [
14
+ "Version mismatch",
15
+ "Invalid webp image",
16
+ "Invalid output format",
17
+ "Decoding failed",
18
+ "Cannot read input file",
19
+ "Cannot write output file",
20
+ "Cannot allocate memory"]
21
+
22
+ # encode_data and encode_rgba have no input file, so the read error says "data"
23
+ ENCODER_DATA_ERRORS = ENCODER_ERRORS.dup.tap { |messages| messages[2] = "Cannot read input picture data" }.freeze
24
+
25
+ # error codes that point at the input or the output file
26
+ ENCODER_FILE_ERRORS = { 3 => :input, 4 => :output }.freeze
27
+ DECODER_FILE_ERRORS = { 2 => :input, 4 => :input, 5 => :input, 6 => :output }.freeze
28
+
29
+ # libwebp's own reasons: WebPEncodingError values when encoding
30
+ ENCODER_REASONS = {
31
+ 1 => [:out_of_memory, "out of memory"],
32
+ 2 => [:bitstream_out_of_memory, "out of memory while writing the bitstream"],
33
+ 3 => [:null_parameter, "a required parameter is missing"],
34
+ 4 => [:invalid_configuration, "the configuration is invalid"],
35
+ 5 => [:bad_dimension, "the picture size is not supported, WebP allows up to 16383 x 16383 pixels"],
36
+ 6 => [:partition0_overflow, "partition 0 is bigger than 512 KiB, try fewer segments or a lower partition_limit"],
37
+ 7 => [:partition_overflow, "a partition is bigger than 16 MiB"],
38
+ 8 => [:bad_write, "cannot write the output"],
39
+ 9 => [:file_too_big, "the file is bigger than 4 GiB"],
40
+ 10 => [:user_abort, "aborted"]
41
+ }.freeze
42
+
43
+ # ...and VP8StatusCode values when decoding
44
+ DECODER_REASONS = {
45
+ 1 => [:out_of_memory, "out of memory"],
46
+ 2 => [:invalid_param, "invalid parameter, for example a crop outside the picture"],
47
+ 3 => [:bitstream_error, "the data is not a valid WebP bitstream"],
48
+ 4 => [:unsupported_feature, "unsupported feature, for example an animated image"],
49
+ 5 => [:suspended, "decoding was suspended"],
50
+ 6 => [:user_abort, "aborted"],
51
+ 7 => [:not_enough_data, "not enough data, the image may be truncated"]
52
+ }.freeze
53
+
54
+ # Base class for every error this gem raises.
55
+ class Error < StandardError
56
+ # the gem's error code from C, or nil
57
+ attr_reader :code
58
+ # libwebp's reason as a Symbol (for example :bad_dimension), or nil
59
+ attr_reader :reason
60
+
61
+ def initialize(message = nil, code: nil, reason: nil)
62
+ super(message)
63
+ @code = code
64
+ @reason = reason
65
+ end
66
+ end
67
+
68
+ class InvalidImageFormatError < Error; end
69
+ class EncoderError < Error; end
70
+ class DecoderError < Error; end
71
+ end
data/lib/webp/info.rb ADDED
@@ -0,0 +1,13 @@
1
+ module WebP
2
+ # Returned by WebP.info.
3
+ Info = Data.define(:width, :height, :format, :alpha, :animated, :metadata) do
4
+ # :lossy, :lossless or :mixed (animations can mix both)
5
+ def lossless? = format == :lossless
6
+ def alpha? = alpha
7
+ def animated? = animated
8
+ end
9
+
10
+ # Returned by WebP.decode_rgba: 'rgba' holds width * height * 4 bytes, row
11
+ # after row, as a binary String.
12
+ RGBAImage = Data.define(:rgba, :width, :height)
13
+ end
@@ -0,0 +1,171 @@
1
+ module WebP
2
+ class Options
3
+
4
+ # integer options; -1 tells C to keep the libwebp default
5
+ ENCODE_INT_OPTIONS = [:lossless, :near_lossless, :method, :target_size, :segments,
6
+ :sns_strength, :filter_strength, :filter_sharpness,
7
+ :filter_type, :autofilter, :alpha_compression, :alpha_filtering,
8
+ :alpha_quality, :pass, :show_compressed, :preprocessing, :partitions,
9
+ :partition_limit, :width, :height,
10
+ :exact, :use_sharp_yuv, :thread_level, :low_memory, :emulate_jpeg_size].freeze
11
+ ENCODE_FLOAT_OPTIONS = [:quality, :target_PSNR].freeze
12
+ CROP_RESIZE_OPTIONS = [:crop_x, :crop_y, :crop_w, :crop_h, :resize_w, :resize_h].freeze
13
+ # options that take a name or a list of names
14
+ ENCODE_NAMED_OPTIONS = [:preset, :image_hint, :level, :metadata].freeze
15
+ ENCODE_OPTIONS = (ENCODE_INT_OPTIONS + ENCODE_FLOAT_OPTIONS + CROP_RESIZE_OPTIONS + ENCODE_NAMED_OPTIONS).freeze
16
+ # raw pixels come with their own size and have no metadata
17
+ ENCODE_RGBA_OPTIONS = (ENCODE_OPTIONS - [:width, :height, :metadata]).freeze
18
+
19
+ DECODE_FLAGS = [:bypass_filtering, :no_fancy_upsampling, :use_threads].freeze
20
+ DECODE_OPTIONS = ([:output_format] + DECODE_FLAGS + CROP_RESIZE_OPTIONS).freeze
21
+ DECODE_RGBA_OPTIONS = (DECODE_OPTIONS - [:output_format]).freeze
22
+ OUTPUT_FORMATS = [:png, :pam, :ppm, :pgm, :bmp, :tiff, :yuv, :alpha_plane_only].freeze
23
+
24
+ # libwebp's WebPPreset, WebPImageHint and the METADATA_* flags in metadata.h
25
+ PRESETS = { default: 0, picture: 1, photo: 2, drawing: 3, icon: 4, text: 5 }.freeze
26
+ IMAGE_HINTS = { default: 0, picture: 1, photo: 2, graph: 3 }.freeze
27
+ METADATA = { exif: 1, icc: 2, xmp: 4 }.freeze
28
+
29
+ def initialize(options)
30
+ @user_options = options
31
+ end
32
+
33
+ def encode_pointer(known = ENCODE_OPTIONS)
34
+ check_unknown_options(known, "encode")
35
+ options_pointer = FFI::MemoryPointer.new :char, C::FfiWebpEncodeConfig.size, true
36
+ options_struct = C::FfiWebpEncodeConfig.new options_pointer
37
+ (ENCODE_INT_OPTIONS + [:target_PSNR]).each do |key|
38
+ value = number_option(key)
39
+ options_struct[key] = value.nil? ? -1 : value
40
+ end
41
+ encode_default(options_struct)
42
+ CROP_RESIZE_OPTIONS.each do |key|
43
+ value = number_option(key)
44
+ options_struct[key] = value unless value.nil?
45
+ end
46
+ level = level_option
47
+ quality = number_option(:quality)
48
+ # a lossless level picks its own quality, so only override it when asked
49
+ options_struct[:quality] = quality.nil? ? (level >= 0 ? -1 : 100) : quality
50
+ options_struct[:lossless_level] = level
51
+ options_struct[:preset] = named_option(:preset, PRESETS)
52
+ options_struct[:image_hint] = named_option(:image_hint, IMAGE_HINTS)
53
+ options_struct[:metadata] = metadata_option
54
+ options_pointer
55
+ end
56
+
57
+ def decode_pointer(known = DECODE_OPTIONS)
58
+ check_unknown_options(known, "decode")
59
+ options_pointer = FFI::MemoryPointer.new :char, C::FfiWebpDecodeConfig.size, true
60
+ options_struct = C::FfiWebpDecodeConfig.new options_pointer
61
+ decode_default(options_struct)
62
+ options_struct[:output_format] = C::OutputFileFormat[output_format] if @user_options[:output_format]
63
+ # set every flag explicitly, so C never reads a field we did not write
64
+ DECODE_FLAGS.each do |key|
65
+ options_struct[key] = flag_option(key)
66
+ end
67
+ CROP_RESIZE_OPTIONS.each do |key|
68
+ value = number_option(key)
69
+ options_struct[key] = value unless value.nil?
70
+ end
71
+ options_pointer
72
+ end
73
+
74
+ private
75
+
76
+ # A typo like :cropt_y used to be ignored silently, so unknown options raise.
77
+ def check_unknown_options(known, operation)
78
+ unknown = @user_options.keys - known
79
+ return if unknown.empty?
80
+ details = unknown.map { |key| "#{key.inspect}#{suggestion(key, known)}" }
81
+ raise ArgumentError, "webp-ffi: unknown #{operation} option#{'s' if unknown.size > 1} #{details.join(', ')}"
82
+ end
83
+
84
+ def suggestion(key, known)
85
+ return "" unless defined?(DidYouMean::SpellChecker)
86
+ match = DidYouMean::SpellChecker.new(dictionary: known).correct(key).first
87
+ match ? " (did you mean #{match.inspect}?)" : ""
88
+ end
89
+
90
+ # numbers pass through, true and false become 1 and 0, nil keeps the default
91
+ def number_option(key)
92
+ value = @user_options[key]
93
+ case value
94
+ when nil then nil
95
+ when true then 1
96
+ when false then 0
97
+ when Numeric then value
98
+ else
99
+ raise ArgumentError, "webp-ffi: option #{key.inspect} must be a number or true/false, got #{value.inspect}"
100
+ end
101
+ end
102
+
103
+ def flag_option(key)
104
+ value = @user_options[key]
105
+ case value
106
+ when nil, false, 0 then 0
107
+ when true, 1 then 1
108
+ else
109
+ raise ArgumentError, "webp-ffi: option #{key.inspect} must be true or false, got #{value.inspect}"
110
+ end
111
+ end
112
+
113
+ def output_format
114
+ value = @user_options[:output_format]
115
+ format = value.respond_to?(:to_sym) ? value.to_sym : value
116
+ return format if OUTPUT_FORMATS.include?(format)
117
+ raise ArgumentError, "webp-ffi: unknown output_format #{value.inspect}, use one of #{OUTPUT_FORMATS.map(&:inspect).join(', ')}"
118
+ end
119
+
120
+ # a Symbol or String from 'names', or -1 for the default
121
+ def named_option(key, names)
122
+ value = @user_options[key]
123
+ return -1 if value.nil?
124
+ name = value.respond_to?(:to_sym) ? value.to_sym : value
125
+ names.fetch(name) do
126
+ raise ArgumentError, "webp-ffi: unknown #{key} #{value.inspect}, use one of #{names.keys.map(&:inspect).join(', ')}"
127
+ end
128
+ end
129
+
130
+ # lossless compression effort from 0 (fast) to 9 (small), or -1
131
+ def level_option
132
+ value = @user_options[:level]
133
+ return -1 if value.nil?
134
+ return value if value.is_a?(Integer) && value.between?(0, 9)
135
+ raise ArgumentError, "webp-ffi: option :level must be an integer from 0 to 9, got #{value.inspect}"
136
+ end
137
+
138
+ # :all, :none, or one or more of :exif, :icc and :xmp
139
+ def metadata_option
140
+ value = @user_options[:metadata]
141
+ case value
142
+ when nil, false, :none, "none" then 0
143
+ when true, :all, "all" then METADATA.values.sum
144
+ else
145
+ Array(value).sum do |name|
146
+ METADATA.fetch(name.respond_to?(:to_sym) ? name.to_sym : name) do
147
+ raise ArgumentError, "webp-ffi: unknown metadata #{name.inspect}, use :all, :none or some of :exif, :icc, :xmp"
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ def encode_default(options_struct)
154
+ options_struct[:quality] = 100
155
+ similar_default(options_struct)
156
+ end
157
+
158
+ def decode_default(options_struct)
159
+ # default format is png
160
+ options_struct[:output_format] = C::OutputFileFormat[:png]
161
+ similar_default(options_struct)
162
+ end
163
+
164
+ def similar_default(options_struct)
165
+ options_struct[:crop_x] = options_struct[:crop_y] = 0
166
+ options_struct[:crop_w] = options_struct[:crop_h] = 0
167
+ options_struct[:resize_w] = options_struct[:resize_h] = 0
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,3 @@
1
+ module WebP
2
+ VERSION = "1.0.0"
3
+ end
data/lib/webp/webp.rb ADDED
@@ -0,0 +1,179 @@
1
+ require "webp/options"
2
+ require "webp/c"
3
+
4
+ module WebP
5
+ # WebPBitstreamFeatures format values
6
+ FORMATS = { 0 => :mixed, 1 => :lossy, 2 => :lossless }.freeze
7
+ # flags in the VP8X header of extended WebP files
8
+ METADATA_FLAGS = { icc: 0x20, exif: 0x08, xmp: 0x04 }.freeze
9
+
10
+ class << self
11
+
12
+ def decoder_version
13
+ format_version(C.decoder_version)
14
+ end
15
+
16
+ def encoder_version
17
+ format_version(C.encoder_version)
18
+ end
19
+
20
+ # Width and height of WebP image data (a String or an IO), or nil for nil.
21
+ def webp_size(data)
22
+ return nil if data.nil?
23
+ data = data_string(data)
24
+ width, height = FFI::MemoryPointer.new(:int), FFI::MemoryPointer.new(:int)
25
+ unless 0 == C.webp_get_info(data, data.bytesize, width, height)
26
+ raise InvalidImageFormatError, "invalid WebP image data"
27
+ end
28
+ [width.read_int, height.read_int]
29
+ end
30
+
31
+ # Details about WebP image data (a String or an IO) as a WebP::Info, or nil
32
+ # for nil: size, format (:lossy, :lossless or :mixed), alpha, animation and
33
+ # the metadata it carries (:icc, :exif, :xmp).
34
+ def info(data)
35
+ return nil if data.nil?
36
+ data = data_string(data)
37
+ features = C::FfiWebpFeatures.new
38
+ status = C.webp_get_features(data, data.bytesize, features)
39
+ unless status.zero?
40
+ reason, text = DECODER_REASONS[status]
41
+ raise InvalidImageFormatError.new(["invalid WebP image data", text].compact.join(": "), reason: reason)
42
+ end
43
+ Info.new(width: features[:width], height: features[:height],
44
+ format: FORMATS.fetch(features[:format], :mixed),
45
+ alpha: features[:has_alpha] == 1, animated: features[:has_animation] == 1,
46
+ metadata: metadata_in(data))
47
+ end
48
+
49
+ # encode
50
+ def encode(input_file, output_file, options = {})
51
+ input_file, output_file = file_path(input_file), file_path(output_file)
52
+ options_pointer = init_options_object(options).encode_pointer
53
+ detail = FFI::MemoryPointer.new(:int)
54
+ code = C.webp_encode(input_file, output_file, options_pointer, detail)
55
+ unless code.zero?
56
+ raise build_error(EncoderError, ENCODER_ERRORS, code, detail.read_int, ENCODER_FILE_ERRORS, input_file, output_file)
57
+ end
58
+ true
59
+ end
60
+
61
+ # decode
62
+ def decode(input_file, output_file, options = {})
63
+ input_file, output_file = file_path(input_file), file_path(output_file)
64
+ options_pointer = init_options_object(options).decode_pointer
65
+ detail = FFI::MemoryPointer.new(:int)
66
+ code = C.webp_decode(input_file, output_file, options_pointer, detail)
67
+ unless code.zero?
68
+ raise build_error(DecoderError, DECODER_ERRORS, code, detail.read_int, DECODER_FILE_ERRORS, input_file, output_file)
69
+ end
70
+ true
71
+ end
72
+
73
+ # Encodes PNG, JPEG or TIFF image data (or raw YUV data, with the :width and
74
+ # :height options) to WebP. 'data' is a String or an IO. Returns the WebP
75
+ # image as a binary String.
76
+ def encode_data(data, options = {})
77
+ options_pointer = init_options_object(options).encode_pointer
78
+ with_output(EncoderError, ENCODER_DATA_ERRORS, data) do |input, size, output, output_size, detail|
79
+ C.webp_encode_data(input, size, output, output_size, options_pointer, detail)
80
+ end
81
+ end
82
+
83
+ # Decodes WebP image data (a String or an IO). Returns the image in the
84
+ # :output_format (PNG by default) as a binary String.
85
+ def decode_data(data, options = {})
86
+ options_pointer = init_options_object(options).decode_pointer
87
+ with_output(DecoderError, DECODER_ERRORS, data) do |input, size, output, output_size, detail|
88
+ C.webp_decode_data(input, size, output, output_size, options_pointer, detail)
89
+ end
90
+ end
91
+
92
+ # Encodes raw RGBA pixels: 'pixels' holds width * height * 4 bytes, row
93
+ # after row. Returns the WebP image as a binary String.
94
+ def encode_rgba(pixels, width, height, options = {})
95
+ width, height = Integer(width), Integer(height)
96
+ pixels = data_string(pixels)
97
+ unless width.positive? && height.positive? && pixels.bytesize == width * height * 4
98
+ raise ArgumentError, "webp-ffi: expected #{width} x #{height} x 4 bytes of RGBA pixels, got #{pixels.bytesize}"
99
+ end
100
+ options_pointer = init_options_object(options).encode_pointer(Options::ENCODE_RGBA_OPTIONS)
101
+ with_output(EncoderError, ENCODER_DATA_ERRORS, pixels) do |input, _size, output, output_size, detail|
102
+ C.webp_encode_rgba(input, width, height, output, output_size, options_pointer, detail)
103
+ end
104
+ end
105
+
106
+ # Decodes WebP image data (a String or an IO) to raw RGBA pixels. Returns a
107
+ # WebP::RGBAImage with the pixels, width and height.
108
+ def decode_rgba(data, options = {})
109
+ options_pointer = init_options_object(options).decode_pointer(Options::DECODE_RGBA_OPTIONS)
110
+ width, height = FFI::MemoryPointer.new(:int), FFI::MemoryPointer.new(:int)
111
+ rgba = with_output(DecoderError, DECODER_ERRORS, data) do |input, size, output, output_size, detail|
112
+ C.webp_decode_rgba(input, size, output, output_size, width, height, options_pointer, detail)
113
+ end
114
+ RGBAImage.new(rgba: rgba, width: width.read_int, height: height.read_int)
115
+ end
116
+
117
+ private
118
+
119
+ def format_version(version)
120
+ [version >> 16, version >> 8, version].map { |part| part & 0xff }.join(".")
121
+ end
122
+
123
+ # Accepts a String, a Pathname or anything with #to_path. Returns a new
124
+ # string, because C reads it while other Ruby threads keep running.
125
+ def file_path(file)
126
+ String.new(File.path(file))
127
+ end
128
+
129
+ # Accepts a String (or anything with #to_str) or an IO to read from.
130
+ def data_string(data)
131
+ data = data.read if data.respond_to?(:read) && !data.is_a?(String)
132
+ String.try_convert(data) or raise TypeError, "webp-ffi: expected image data as a String or an IO, got #{data.class}"
133
+ end
134
+
135
+ def metadata_in(data)
136
+ return [] unless data.bytesize >= 21 && data.byteslice(12, 4) == "VP8X"
137
+ flags = data.getbyte(20)
138
+ METADATA_FLAGS.select { |_name, flag| flags & flag != 0 }.keys
139
+ end
140
+
141
+ # Copies the input to native memory (C reads it while other Ruby threads
142
+ # keep running), yields the arguments for the C call, and returns the output
143
+ # bytes. Raises error_class when C reports an error.
144
+ def with_output(error_class, messages, data)
145
+ data = data_string(data)
146
+ input = FFI::MemoryPointer.new(:uint8, [data.bytesize, 1].max)
147
+ input.put_bytes(0, data)
148
+ output = FFI::MemoryPointer.new(:pointer)
149
+ output_size = FFI::MemoryPointer.new(:size_t)
150
+ detail = FFI::MemoryPointer.new(:int)
151
+ code = yield(input, data.bytesize, output, output_size, detail)
152
+ raise build_error(error_class, messages, code, detail.read_int) unless code.zero?
153
+
154
+ buffer = output.read_pointer
155
+ begin
156
+ buffer.read_bytes(output_size.read(:size_t))
157
+ ensure
158
+ C.webp_free_data(buffer)
159
+ end
160
+ end
161
+
162
+ def build_error(error_class, messages, code, detail, file_errors = {}, input_file = nil, output_file = nil)
163
+ message = code.between?(1, messages.size) ? messages[code - 1] : "Unknown error (code #{code})"
164
+ case file_errors[code]
165
+ when :input then message = "#{message}: #{input_file}"
166
+ when :output then message = "#{message}: #{output_file}"
167
+ end
168
+ reasons = error_class == EncoderError ? ENCODER_REASONS : DECODER_REASONS
169
+ reason, text = reasons[detail]
170
+ message = "#{message} (#{text})" if text
171
+ error_class.new(message, code: code, reason: reason)
172
+ end
173
+
174
+ def init_options_object(options)
175
+ Options.new options
176
+ end
177
+
178
+ end
179
+ end
data/lib/webp-ffi.rb ADDED
@@ -0,0 +1 @@
1
+ require "webp_ffi"