webp-ffi 1.0.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 +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/aarch64-linux/libwebp_ffi.so +0 -0
- data/ext/webp_ffi/aarch64-linux/licenses/libjpeg-turbo-LICENSE.md +147 -0
- data/ext/webp_ffi/aarch64-linux/licenses/libjpeg-turbo-README.ijg +260 -0
- data/ext/webp_ffi/aarch64-linux/licenses/libpng-LICENSE +134 -0
- data/ext/webp_ffi/aarch64-linux/licenses/libtiff-LICENSE.md +49 -0
- data/ext/webp_ffi/aarch64-linux/licenses/libwebp-COPYING +30 -0
- data/ext/webp_ffi/aarch64-linux/licenses/libwebp-PATENTS +23 -0
- data/ext/webp_ffi/aarch64-linux/licenses/zlib-LICENSE +22 -0
- data/ext/webp_ffi/aarch64-linux/versions.txt +5 -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/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,110 @@
|
|
|
1
|
+
#ifndef _WEBP_FFI_H_
|
|
2
|
+
#define _WEBP_FFI_H_
|
|
3
|
+
|
|
4
|
+
#if defined(__cplusplus) || defined(c_plusplus)
|
|
5
|
+
extern "C" {
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
typedef struct {
|
|
9
|
+
int lossless; // Lossless encoding (0=lossy(default), 1=lossless).
|
|
10
|
+
int near_lossless; // use near-lossless image preprocessing (0..100=off)
|
|
11
|
+
float quality; // between 0 (smallest file) and 100 (biggest)
|
|
12
|
+
int method; // quality/speed trade-off (0=fast, 6=slower-better)
|
|
13
|
+
|
|
14
|
+
// Parameters related to lossy compression only:
|
|
15
|
+
int target_size; // if non-zero, set the desired target size in bytes.
|
|
16
|
+
// Takes precedence over the 'compression' parameter.
|
|
17
|
+
float target_PSNR; // if non-zero, specifies the minimal distortion to
|
|
18
|
+
// try to achieve. Takes precedence over target_size.
|
|
19
|
+
int segments; // maximum number of segments to use, in [1..4]
|
|
20
|
+
int sns_strength; // Spatial Noise Shaping. 0=off, 100=maximum.
|
|
21
|
+
int filter_strength; // range: [0 = off .. 100 = strongest]
|
|
22
|
+
int filter_sharpness; // range: [0 = off .. 7 = least sharp]
|
|
23
|
+
int filter_type; // filtering type: 0 = simple, 1 = strong (only used
|
|
24
|
+
// if filter_strength > 0 or autofilter > 0)
|
|
25
|
+
int autofilter; // Auto adjust filter's strength [0 = off, 1 = on]
|
|
26
|
+
int alpha_compression; // Algorithm for encoding the alpha plane (0 = none,
|
|
27
|
+
// 1 = compressed with WebP lossless). Default is 1.
|
|
28
|
+
int alpha_filtering; // Predictive filtering method for alpha plane.
|
|
29
|
+
// 0: none, 1: fast, 2: best. Default if 1.
|
|
30
|
+
int alpha_quality; // Between 0 (smallest size) and 100 (lossless).
|
|
31
|
+
// Default is 100.
|
|
32
|
+
int pass; // number of entropy-analysis passes (in [1..10]).
|
|
33
|
+
|
|
34
|
+
int show_compressed; // if true, export the compressed picture back.
|
|
35
|
+
// In-loop filtering is not applied.
|
|
36
|
+
int preprocessing; // preprocessing filter (0=none, 1=segment-smooth)
|
|
37
|
+
int partitions; // log2(number of token partitions) in [0..3]
|
|
38
|
+
// Default is set to 0 for easier progressive decoding.
|
|
39
|
+
int partition_limit; // quality degradation allowed to fit the 512k limit on
|
|
40
|
+
// prediction modes coding (0: no degradation,
|
|
41
|
+
// 100: maximum possible degradation).
|
|
42
|
+
int width, height;
|
|
43
|
+
int crop_x, crop_y, crop_w, crop_h;
|
|
44
|
+
int resize_w, resize_h;
|
|
45
|
+
// -1 keeps the libwebp default for the options below
|
|
46
|
+
int preset; // WebPPreset, applied before the other options
|
|
47
|
+
int image_hint; // WebPImageHint
|
|
48
|
+
int exact; // keep RGB values under transparent areas
|
|
49
|
+
int use_sharp_yuv; // sharper (and slower) RGB to YUV conversion
|
|
50
|
+
int thread_level; // multi-threaded encoding
|
|
51
|
+
int low_memory; // less memory, more CPU
|
|
52
|
+
int emulate_jpeg_size; // quality comparable to JPEG at the same size
|
|
53
|
+
int lossless_level; // 0..9, WebPConfigLosslessPreset (turns on lossless)
|
|
54
|
+
int metadata; // METADATA_* flags: what to copy from the input
|
|
55
|
+
} FfiWebpEncodeConfig;
|
|
56
|
+
|
|
57
|
+
typedef struct {
|
|
58
|
+
OutputFileFormat output_format;
|
|
59
|
+
int bypass_filtering; // if true, skip the in-loop filtering
|
|
60
|
+
int no_fancy_upsampling; // if true, use faster pointwise upsampler
|
|
61
|
+
int crop_x, crop_y, crop_w, crop_h;
|
|
62
|
+
int resize_w, resize_h;
|
|
63
|
+
int use_threads; // if true, use multi-threaded decoding
|
|
64
|
+
} FfiWebpDecodeConfig;
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
typedef struct {
|
|
68
|
+
int width, height;
|
|
69
|
+
int has_alpha, has_animation;
|
|
70
|
+
int format; // 0 = mixed or unknown, 1 = lossy, 2 = lossless
|
|
71
|
+
} FfiWebpFeatures;
|
|
72
|
+
|
|
73
|
+
// Versions are packed as 0xMMmmpp (major, minor, patch).
|
|
74
|
+
int decoder_version(void);
|
|
75
|
+
int encoder_version(void);
|
|
76
|
+
int webp_get_info(const uint8_t* data, size_t data_size, int* width, int* height);
|
|
77
|
+
// Returns a VP8StatusCode, 0 when 'features' was filled.
|
|
78
|
+
int webp_get_features(const uint8_t* data, size_t data_size, FfiWebpFeatures* features);
|
|
79
|
+
|
|
80
|
+
// All functions below return 0 on success or an error code (see
|
|
81
|
+
// lib/webp/error.rb). On failure '*detail' holds libwebp's own reason when
|
|
82
|
+
// there is one: a WebPEncodingError when encoding, a VP8StatusCode when
|
|
83
|
+
// decoding, otherwise 0.
|
|
84
|
+
int webp_encode(const char *in_file, const char *out_file,
|
|
85
|
+
const FfiWebpEncodeConfig *encode_config, int* detail);
|
|
86
|
+
int webp_decode(const char *in_file, const char *out_file,
|
|
87
|
+
const FfiWebpDecodeConfig *decode_config, int* detail);
|
|
88
|
+
|
|
89
|
+
// In-memory versions: on success '*output' holds 'output_size' bytes that the
|
|
90
|
+
// caller frees with webp_free_data. On failure '*output' is NULL.
|
|
91
|
+
int webp_encode_data(const uint8_t* data, size_t data_size,
|
|
92
|
+
uint8_t** output, size_t* output_size,
|
|
93
|
+
const FfiWebpEncodeConfig* encode_config, int* detail);
|
|
94
|
+
int webp_decode_data(const uint8_t* data, size_t data_size,
|
|
95
|
+
uint8_t** output, size_t* output_size,
|
|
96
|
+
const FfiWebpDecodeConfig* decode_config, int* detail);
|
|
97
|
+
// Raw pixels: 'rgba' holds width * height * 4 bytes, row after row.
|
|
98
|
+
int webp_encode_rgba(const uint8_t* rgba, int width, int height,
|
|
99
|
+
uint8_t** output, size_t* output_size,
|
|
100
|
+
const FfiWebpEncodeConfig* encode_config, int* detail);
|
|
101
|
+
int webp_decode_rgba(const uint8_t* data, size_t data_size,
|
|
102
|
+
uint8_t** output, size_t* output_size, int* width, int* height,
|
|
103
|
+
const FfiWebpDecodeConfig* decode_config, int* detail);
|
|
104
|
+
void webp_free_data(uint8_t* data);
|
|
105
|
+
|
|
106
|
+
#if defined(__cplusplus) || defined(c_plusplus)
|
|
107
|
+
} // extern "C"
|
|
108
|
+
#endif
|
|
109
|
+
|
|
110
|
+
#endif /* _WEBP_FFI_H_ */
|
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"
|
data/lib/webp_ffi.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'ffi'
|
|
2
|
+
require 'ffi-compiler/loader'
|
|
3
|
+
|
|
4
|
+
module WebP
|
|
5
|
+
module C
|
|
6
|
+
extend FFI::Library
|
|
7
|
+
ffi_lib FFI::Compiler::Loader.find('webp_ffi')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require "webp/options"
|
|
12
|
+
require "webp/c"
|
|
13
|
+
require "webp/error"
|
|
14
|
+
require "webp/info"
|
|
15
|
+
require "webp/webp"
|
|
16
|
+
require "webp/version"
|