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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +135 -0
- data/LICENSE.txt +22 -0
- data/README.md +308 -0
- data/ext/webp_ffi/Rakefile +3 -0
- data/ext/webp_ffi/build_config.rb +119 -0
- data/ext/webp_ffi/jpegdec.c +174 -0
- data/ext/webp_ffi/jpegdec.h +20 -0
- data/ext/webp_ffi/metadata.c +138 -0
- data/ext/webp_ffi/metadata.h +49 -0
- data/ext/webp_ffi/pngdec.c +389 -0
- data/ext/webp_ffi/pngdec.h +29 -0
- data/ext/webp_ffi/tiffdec.c +361 -0
- data/ext/webp_ffi/tiffdec.h +31 -0
- data/ext/webp_ffi/util.c +216 -0
- data/ext/webp_ffi/util.h +63 -0
- data/ext/webp_ffi/webp_ffi.c +618 -0
- data/ext/webp_ffi/webp_ffi.h +110 -0
- data/ext/webp_ffi/x86_64-linux/libwebp_ffi.so +0 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libjpeg-turbo-LICENSE.md +147 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libjpeg-turbo-README.ijg +260 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libpng-LICENSE +134 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libtiff-LICENSE.md +49 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libwebp-COPYING +30 -0
- data/ext/webp_ffi/x86_64-linux/licenses/libwebp-PATENTS +23 -0
- data/ext/webp_ffi/x86_64-linux/licenses/zlib-LICENSE +22 -0
- data/ext/webp_ffi/x86_64-linux/versions.txt +5 -0
- data/lib/webp/c.rb +101 -0
- data/lib/webp/error.rb +71 -0
- data/lib/webp/info.rb +13 -0
- data/lib/webp/options.rb +171 -0
- data/lib/webp/version.rb +3 -0
- data/lib/webp/webp.rb +179 -0
- data/lib/webp-ffi.rb +1 -0
- data/lib/webp_ffi.rb +16 -0
- data/sig/webp.rbs +74 -0
- metadata +145 -0
|
@@ -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
|
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
|
data/lib/webp/options.rb
ADDED
|
@@ -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
|
data/lib/webp/version.rb
ADDED
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"
|