distorted 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +661 -0
- data/README.md +5 -140
- data/bin/console +14 -0
- data/bin/distorted +6 -0
- data/bin/setup +8 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Less_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/More_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/less_more_perfect_dos_vga_437.html +52 -0
- data/font/1252/LICENSE/PerfectDOSVGA437/font-comment.php@file=perfect_dos_vga_437.html +5 -0
- data/font/1252/LessPerfectDOSVGA.ttf +0 -0
- data/font/1252/MorePerfectDOSVGA.ttf +0 -0
- data/font/1252/Perfect DOS VGA 437 Win.ttf +0 -0
- data/font/437/Perfect DOS VGA 437.ttf +0 -0
- data/font/437/dos437.txt +72 -0
- data/font/65001/Anonymous Pro B.ttf +0 -0
- data/font/65001/Anonymous Pro BI.ttf +0 -0
- data/font/65001/Anonymous Pro I.ttf +0 -0
- data/font/65001/Anonymous Pro.ttf +0 -0
- data/font/65001/LICENSE/AnonymousPro/FONTLOG.txt +45 -0
- data/font/65001/LICENSE/AnonymousPro/OFL-FAQ.txt +235 -0
- data/font/65001/LICENSE/AnonymousPro/OFL.txt +94 -0
- data/font/65001/LICENSE/AnonymousPro/README.txt +55 -0
- data/font/850/ProFont-Bold-01/LICENSE +22 -0
- data/font/850/ProFont-Bold-01/readme.txt +28 -0
- data/font/850/ProFontWindows-Bold.ttf +0 -0
- data/font/850/ProFontWindows.ttf +0 -0
- data/font/850/Profont/LICENSE +22 -0
- data/font/850/Profont/readme.txt +31 -0
- data/font/932/LICENSE/README-ttf.txt +213 -0
- data/font/932/mona.ttf +0 -0
- data/lib/distorted.rb +2 -0
- data/lib/distorted/checking_you_out.rb +219 -0
- data/lib/distorted/checking_you_out/README +4 -0
- data/lib/distorted/checking_you_out/application.yaml +33 -0
- data/lib/distorted/checking_you_out/font.yaml +29 -0
- data/lib/distorted/checking_you_out/image.yaml +108 -0
- data/lib/distorted/click_again.rb +333 -0
- data/lib/distorted/element_of_media.rb +2 -0
- data/lib/distorted/element_of_media/change.rb +119 -0
- data/lib/distorted/element_of_media/compound.rb +120 -0
- data/lib/distorted/error_code.rb +51 -0
- data/lib/distorted/floor.rb +17 -0
- data/lib/distorted/invoker.rb +97 -0
- data/lib/distorted/media_molecule.rb +58 -0
- data/lib/distorted/media_molecule/font.rb +195 -0
- data/lib/distorted/media_molecule/image.rb +33 -0
- data/lib/distorted/media_molecule/pdf.rb +44 -0
- data/lib/distorted/media_molecule/svg.rb +45 -0
- data/lib/distorted/media_molecule/text.rb +203 -0
- data/lib/distorted/media_molecule/video.rb +18 -0
- data/lib/distorted/modular_technology/gstreamer.rb +174 -0
- data/lib/distorted/modular_technology/pango.rb +90 -0
- data/lib/distorted/modular_technology/ttfunk.rb +48 -0
- data/lib/distorted/modular_technology/vips.rb +17 -0
- data/lib/distorted/modular_technology/vips/foreign.rb +489 -0
- data/lib/distorted/modular_technology/vips/load.rb +133 -0
- data/lib/distorted/modular_technology/vips/save.rb +161 -0
- data/lib/distorted/monkey_business/encoding.rb +317 -0
- data/lib/distorted/monkey_business/hash.rb +18 -0
- data/lib/distorted/monkey_business/set.rb +15 -0
- data/lib/distorted/monkey_business/string.rb +6 -0
- data/lib/distorted/triple_counter.rb +52 -0
- data/lib/distorted/version.rb +22 -0
- data/test/distorted_test.rb +11 -0
- data/test/test_helper.rb +4 -0
- metadata +130 -20
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'distorted/monkey_business/set'
|
3
|
+
|
4
|
+
require 'distorted/checking_you_out'
|
5
|
+
|
6
|
+
require 'distorted/modular_technology/gstreamer'
|
7
|
+
|
8
|
+
|
9
|
+
module Cooltrainer; end
|
10
|
+
module Cooltrainer::DistorteD; end
|
11
|
+
module Cooltrainer::DistorteD::Molecule; end
|
12
|
+
module Cooltrainer::DistorteD::Molecule::Video
|
13
|
+
|
14
|
+
LOWER_WORLD = CHECKING::YOU::IN('video/mp4').to_hash
|
15
|
+
|
16
|
+
include Cooltrainer::DistorteD::Technology::GStreamer
|
17
|
+
|
18
|
+
end # Video
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'distorted/checking_you_out'
|
4
|
+
|
5
|
+
require 'distorted/triple_counter'
|
6
|
+
GST_MINIMUM_VER = TripleCounter.new(1, 18, 0)
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'gst'
|
10
|
+
GST_AVAILABLE_VER = TripleCounter.new(*(Gst.version))
|
11
|
+
unless GST_AVAILABLE_VER >= GST_MINIMUM_VER
|
12
|
+
raise LoadError.new(
|
13
|
+
"DistorteD needs GStreamer #{GST_MINIMUM_VER}, but the available version is '#{Gst.version_string}'"
|
14
|
+
)
|
15
|
+
end
|
16
|
+
rescue LoadError => le
|
17
|
+
raise unless le.message =~ /libgst/
|
18
|
+
|
19
|
+
# Multiple OS help
|
20
|
+
help = <<~INSTALL
|
21
|
+
|
22
|
+
Please install the GStreamer library for your system, version #{GST_MINIMUM_VER} or later.
|
23
|
+
INSTALL
|
24
|
+
|
25
|
+
# Re-raise with install message
|
26
|
+
raise $!, "#{help}\n#{$!}", $!.backtrace
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
module Cooltrainer; end
|
31
|
+
module Cooltrainer::DistorteD; end
|
32
|
+
module Cooltrainer::DistorteD::Technology; end
|
33
|
+
module Cooltrainer::DistorteD::Technology::GStreamer
|
34
|
+
|
35
|
+
OUTER_LIMITS = CHECKING::YOU::IN(Set[
|
36
|
+
'application/dash+xml',
|
37
|
+
'application/vnd.apple.mpegurl',
|
38
|
+
'video/mp4',
|
39
|
+
])
|
40
|
+
|
41
|
+
|
42
|
+
def write_video_mp4(dest_root, change)
|
43
|
+
copy_file(change.paths(dest_root).first)
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_application_dash_xml(dest, *a, **k)
|
47
|
+
begin
|
48
|
+
segment_dest = File.join(File.dirname(dest), "#{basename}.dash", '/')
|
49
|
+
#segment_dest = segment_dest.sub("#{@base}/", '')
|
50
|
+
FileUtils.mkdir_p(segment_dest)
|
51
|
+
Jekyll.logger.debug(@tag_name, "Re-muxing #{path} to #{segment_dest}")
|
52
|
+
|
53
|
+
# https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description
|
54
|
+
# TODO: Convert this from parse_launch() pipeline notation to Element objects
|
55
|
+
# TODO: Get source video duration/resolution/etc and use it to compute a
|
56
|
+
# value for `target-duration`.
|
57
|
+
# TODO: Also support urldecodebin for remote media.
|
58
|
+
pipeline, error = Gst.parse_launch("dashsink name=mux filesrc name=src ! decodebin name=demux ! audioconvert ! avenc_aac ! mux.audio_0 demux. ! videoconvert ! x264enc ! mux.video_0")
|
59
|
+
|
60
|
+
if pipeline.nil?
|
61
|
+
Jekyll.logger.error(@tag_name, "Parse error: #{error.message}")
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
|
65
|
+
filesrc = pipeline.get_by_name('src')
|
66
|
+
filesrc.location = path
|
67
|
+
|
68
|
+
mux = pipeline.get_by_name('mux')
|
69
|
+
mux.mpd_filename = File.basename(dest)
|
70
|
+
mux.target_duration = 3
|
71
|
+
#mux.segment_tpl_path = "#{segment_dest}/#{basename}%05d.mp4"
|
72
|
+
mux.mpd_root_path = segment_dest
|
73
|
+
Jekyll.logger.warn('MPD ROOT PATH', mux.get_property('mpd-root-path'))
|
74
|
+
|
75
|
+
# typedef enum
|
76
|
+
# {
|
77
|
+
# GST_DASH_SINK_MUXER_TS = 0,
|
78
|
+
# GST_DASH_SINK_MUXER_MP4 = 1,
|
79
|
+
# } GstDashSinkMuxerType;
|
80
|
+
mux.muxer = 1
|
81
|
+
|
82
|
+
pipeline.play
|
83
|
+
|
84
|
+
# Play until End Of Stream
|
85
|
+
event_loop(pipeline)
|
86
|
+
|
87
|
+
pipeline.stop
|
88
|
+
|
89
|
+
rescue Gst::ParseError::NoSuchElement
|
90
|
+
raise
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def write_application_vnd_apple_mpegurl(dest, *a, **k)
|
95
|
+
begin
|
96
|
+
orig_dest = dest
|
97
|
+
orig_path = path
|
98
|
+
|
99
|
+
FileUtils.mkdir_p(File.dirname(orig_dest))
|
100
|
+
|
101
|
+
hls_dest = File.join(File.dirname(orig_dest), basename + '.hls')
|
102
|
+
FileUtils.mkdir_p(hls_dest)
|
103
|
+
Jekyll.logger.debug(@tag_name, "Re-muxing #{orig_path} to #{hls_dest}.")
|
104
|
+
|
105
|
+
#FileUtils.rm(orig_dest) if File.exist?(orig_dest)
|
106
|
+
if not File.file?(orig_dest)
|
107
|
+
FileUtils.cp(orig_path, orig_dest)
|
108
|
+
end
|
109
|
+
|
110
|
+
# https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description
|
111
|
+
# TODO: Convert this from parse_launch() pipeline notation to Element objects
|
112
|
+
# TODO: Get source video duration/resolution/etc and use it to compute a
|
113
|
+
# value for `target-duration`.
|
114
|
+
# TODO: Also support urldecodebin for remote media.
|
115
|
+
pipeline, error = Gst.parse_launch("filesrc name=src ! decodebin name=demux ! videoconvert ! x264enc ! queue2 ! h264parse ! queue2 ! mux.video hlssink2 name=mux max-files=0 playlist-length=0 target-duration=2 demux. ! audioconvert ! faac ! queue2 ! mux.audio")
|
116
|
+
|
117
|
+
if pipeline.nil?
|
118
|
+
Jekyll.logger.error(@tag_name, "Parse error: #{error.message}")
|
119
|
+
return false
|
120
|
+
end
|
121
|
+
|
122
|
+
filesrc = pipeline.get_by_name('src')
|
123
|
+
filesrc.location = orig_path
|
124
|
+
|
125
|
+
hls_playlist = "#{hls_dest}/#{basename}.m3u8"
|
126
|
+
hls = pipeline.get_by_name('mux')
|
127
|
+
hls.location = "#{hls_dest}/#{basename}%05d.ts"
|
128
|
+
hls.playlist_location = hls_playlist
|
129
|
+
|
130
|
+
# TODO: config option for absolute vs relative segment URIs in the playlist.
|
131
|
+
#hls.playlist_root = @url
|
132
|
+
|
133
|
+
# TODO: dashsink support once there is a stable GStreamer release including it:
|
134
|
+
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/merge_requests/704
|
135
|
+
|
136
|
+
pipeline.play
|
137
|
+
|
138
|
+
# Play until End Of Stream
|
139
|
+
event_loop(pipeline)
|
140
|
+
|
141
|
+
pipeline.stop
|
142
|
+
|
143
|
+
# HACK HACK HACK: Replace X-ALLOW-CACHE line in playlist with YES.
|
144
|
+
# This property does not seem to be exposed to the outside of hlssink:
|
145
|
+
# https://cgit.freedesktop.org/gstreamer/gst-plugins-bad/tree/ext/hls/gsthlssink.c
|
146
|
+
text = File.read(hls_playlist)
|
147
|
+
File.write(hls_playlist, text.gsub(/^#EXT-X-ALLOW-CACHE:NO$/, '#EXT-X-ALLOW-CACHE:YES'))
|
148
|
+
rescue Gst::ParseError::NoSuchElement
|
149
|
+
raise
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def event_loop(pipeline)
|
154
|
+
running = true
|
155
|
+
bus = pipeline.bus
|
156
|
+
|
157
|
+
while running
|
158
|
+
message = bus.poll(Gst::MessageType::ANY, -1)
|
159
|
+
|
160
|
+
case message.type
|
161
|
+
when Gst::MessageType::EOS
|
162
|
+
running = false
|
163
|
+
when Gst::MessageType::WARNING
|
164
|
+
warning, _debug = message.parse_warning
|
165
|
+
Jekyll.logger.warning(@tag_name, warning)
|
166
|
+
when Gst::MessageType::ERROR
|
167
|
+
error, _debug = message.parse_error
|
168
|
+
Jekyll.logger.error(@tag_name, error)
|
169
|
+
running = false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Cooltrainer
|
2
|
+
module DistorteD
|
3
|
+
module Technology
|
4
|
+
module Pango
|
5
|
+
|
6
|
+
# Escape text as necessary for Pango Markup, which is what Vips::Image.text()
|
7
|
+
# expects for its argv. This code should be in GLib but is unimplemented in Ruby's:
|
8
|
+
#
|
9
|
+
# https://ruby-gnome2.osdn.jp/hiki.cgi?Gtk%3A%3ALabel#Markup+%28styled+text%29
|
10
|
+
# "The markup passed to Gtk::Label#set_markup() must be valid; for example,
|
11
|
+
# literal </>/& characters must be escaped as <, >, and &.
|
12
|
+
# If you pass text obtained from the user, file, or a network to
|
13
|
+
# Gtk::Label#set_markup(), you'll want to escape it
|
14
|
+
# with GLib::Markup.escape_text?(not implemented yet)."
|
15
|
+
#
|
16
|
+
# Base my own implementation on the original C version found in gmarkup:
|
17
|
+
# https://gitlab.gnome.org/GNOME/glib/-/blob/master/glib/gmarkup.c
|
18
|
+
def g_markup_escape_text(text)
|
19
|
+
text.map{ |c| g_markup_escape_char(c) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a Pango-escaped Carriage Return.
|
23
|
+
# Use this for linebreaking Pango Markup output.
|
24
|
+
def cr
|
25
|
+
g_markup_escape_char(0x0D)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a Pango-escapped Line Feed.
|
29
|
+
# This isn't used/needed for anything with Pango
|
30
|
+
# but it felt weird to include CR and not LF lmao
|
31
|
+
def lf
|
32
|
+
g_markup_escape_char(0x0A)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a Pango'escaped CRLF pair.
|
36
|
+
# Also not needed for anything.
|
37
|
+
def crlf
|
38
|
+
cr << lf
|
39
|
+
end
|
40
|
+
|
41
|
+
# "Modified UTF-8" uses a normally-illegal byte sequence
|
42
|
+
# to encode the NULL character so 0x00 can exclusively
|
43
|
+
# be a string terminator.
|
44
|
+
def overlong_null
|
45
|
+
[0xC0, 0x80].pack('C*').force_encoding('UTF-8')
|
46
|
+
end
|
47
|
+
|
48
|
+
# The char-by-char actual function used by g_markup_escape_text
|
49
|
+
def g_markup_escape_char(c)
|
50
|
+
# I think a fully-working version of this function would
|
51
|
+
# be as simple as `sprintf('&#x%x;', c.ord)` ALL THE THINGS,
|
52
|
+
# but I want to copy the structure of the C implementation
|
53
|
+
# as closely as possible, which means using the named escape
|
54
|
+
# sequences for common characters and separating the
|
55
|
+
# Latin-1 Supplement range from the other
|
56
|
+
# the Unicode control characters (> 0x7f) even though three's no
|
57
|
+
# need to in Ruby.
|
58
|
+
case c.ord
|
59
|
+
when '&'.ord
|
60
|
+
'&'
|
61
|
+
when '<'.ord
|
62
|
+
'<'
|
63
|
+
when '>'.ord
|
64
|
+
'>'
|
65
|
+
when '\''.ord
|
66
|
+
'''
|
67
|
+
when '"'.ord
|
68
|
+
'"'
|
69
|
+
when 0x1..0x8, 0xb..0xc, 0xe..0x1f, 0x7f
|
70
|
+
sprintf('&#x%x;', c.ord)
|
71
|
+
when 0x80..0x84, 0x86..0x9f
|
72
|
+
# The original C implementation separates this range
|
73
|
+
# from the above range due to its need to handle the
|
74
|
+
# UTF control character bytes with gunichar:
|
75
|
+
# https://wiki.tcl-lang.org/page/UTF%2D8+bit+by+bit
|
76
|
+
# https://www.fileformat.info/info/unicode/utf8.htm
|
77
|
+
# Ruby has already done this for us here :)
|
78
|
+
sprintf('&#x%x;', c.ord)
|
79
|
+
when 0x0 # what's this…?
|
80
|
+
# Avoid a `ArgumentError: string contains null byte`
|
81
|
+
# by not printing one :)
|
82
|
+
else
|
83
|
+
c
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end # Pango
|
88
|
+
end # Tech
|
89
|
+
end # DistorteD
|
90
|
+
end # Cooltrainer
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ttfunk'
|
2
|
+
|
3
|
+
module Cooltrainer; end
|
4
|
+
module Cooltrainer::DistorteD; end
|
5
|
+
module Cooltrainer::DistorteD::Technology; end
|
6
|
+
module Cooltrainer::DistorteD::Technology::TTFunk
|
7
|
+
|
8
|
+
def to_ttfunk
|
9
|
+
# TODO: Check that src exists, because TTFunk won't and will just
|
10
|
+
# give us an unusable object instead.
|
11
|
+
@ttfunk_file ||= TTFunk::File.open(font_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns a boolean for whether or not this font is monospaced.
|
15
|
+
# true == monospace
|
16
|
+
# false == proportional
|
17
|
+
def font_spacing
|
18
|
+
# Monospace fonts will (read: should) have the same width
|
19
|
+
# for every glyph, so we can tell a monospace font by
|
20
|
+
# checking if a deduplicated widths table has size == 1:
|
21
|
+
# irb(main)> font.horizontal_metrics.widths.count
|
22
|
+
# => 256
|
23
|
+
# irb(main)> font.horizontal_metrics.widths.uniq.compact.length
|
24
|
+
# => 1
|
25
|
+
to_ttfunk.horizontal_metrics.widths.uniq.compact.length == 1 ? :monospace : :proportional
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the Family and Subfamily as one string suitable for libvips
|
29
|
+
def font_name
|
30
|
+
"#{to_ttfunk.name.font_family.first.encode('UTF-8')} #{to_ttfunk.name.font_subfamily.first.encode('UTF-8')}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the Pango-Markup-encoded UTF-8 String version + revision of the font
|
34
|
+
def font_version
|
35
|
+
g_markup_escape_text(to_ttfunk.name&.version&.first&.encode('UTF-8').to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the Pango-Markup-encoded UTF-8 String font file description
|
39
|
+
def font_description
|
40
|
+
g_markup_escape_text(to_ttfunk.name&.description&.first&.encode('UTF-8').to_s)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the Pango-Markup-encoded UTF-8 String copyright information of the font
|
44
|
+
def font_copyright
|
45
|
+
g_markup_escape_text(to_ttfunk.name&.copyright&.first&.encode('UTF-8').to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'distorted/checking_you_out'
|
4
|
+
|
5
|
+
require 'distorted/modular_technology/vips/load'
|
6
|
+
require 'distorted/modular_technology/vips/save'
|
7
|
+
|
8
|
+
|
9
|
+
module Cooltrainer; end
|
10
|
+
module Cooltrainer::DistorteD; end
|
11
|
+
module Cooltrainer::DistorteD::Technology; end
|
12
|
+
module Cooltrainer::DistorteD::Technology::Vips
|
13
|
+
|
14
|
+
include Cooltrainer::DistorteD::Technology::Vips::Save
|
15
|
+
include Cooltrainer::DistorteD::Technology::Vips::Load
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,489 @@
|
|
1
|
+
require 'vips'
|
2
|
+
|
3
|
+
require 'distorted/checking_you_out'
|
4
|
+
require 'distorted/element_of_media'
|
5
|
+
|
6
|
+
# Based on https://github.com/libvips/ruby-vips/issues/186#issuecomment-433691412
|
7
|
+
module Vips
|
8
|
+
attach_function :vips_class_find, [:string, :string], :pointer
|
9
|
+
attach_function :vips_object_summary_class, [:pointer, :pointer], :void
|
10
|
+
|
11
|
+
class BufStruct < FFI::Struct
|
12
|
+
layout :base, :pointer,
|
13
|
+
:mx, :int,
|
14
|
+
:i, :int,
|
15
|
+
:full, :bool,
|
16
|
+
:lasti, :int,
|
17
|
+
:dynamic, :bool
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module GObject
|
23
|
+
# Fundamental types not already defined in ruby-vips' `lib/vips.rb`
|
24
|
+
GBOXED_TYPE = g_type_from_name('GBoxed')
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module Cooltrainer; end
|
29
|
+
module Cooltrainer::DistorteD; end
|
30
|
+
module Cooltrainer::DistorteD::Technology; end
|
31
|
+
module Cooltrainer::DistorteD::Technology::Vips
|
32
|
+
|
33
|
+
|
34
|
+
# 🄵🄸🄽🄳 🅃🄷🄴 🄲🄾🄼🄿🅄🅃🄴🅁 🅁🄾🄾🄼
|
35
|
+
# 🄵🄸🄽🄳 🅃🄷🄴 🄲🄾🄼🄿🅄🅃🄴🅁 🅁🄾🄾🄼
|
36
|
+
# 🄵🄸🄽🄳 🅃🄷🄴 🄲🄾🄼🄿🅄🅃🄴🅁 🅁🄾🄾🄼
|
37
|
+
Vips::vips_vector_set_enabled(1)
|
38
|
+
|
39
|
+
|
40
|
+
# All of the actual Loader/Saver classes we need to interact with
|
41
|
+
# will be tree children of one of these top-level class categories:
|
42
|
+
TOP_LEVEL_LOADER = :VipsForeignLoad
|
43
|
+
TOP_LEVEL_SAVER = :VipsForeignSave
|
44
|
+
|
45
|
+
|
46
|
+
# This has got to be built in to Ruby-GLib somewhere, right?
|
47
|
+
# Remove this if an FFI method is possible to get this mapping.
|
48
|
+
G_TYPE_VALUES = {
|
49
|
+
:gboolean => [false, true],
|
50
|
+
:gchararray => String,
|
51
|
+
:gdouble => Float,
|
52
|
+
:gint => Integer,
|
53
|
+
}
|
54
|
+
|
55
|
+
# Aliases we want to support for consistency and accessibility.
|
56
|
+
VIPS_ALIASES = {
|
57
|
+
:Q => Set[:Q, :quality],
|
58
|
+
:colours => Set[:colours, :colors],
|
59
|
+
:centre => Set[:centre, :center], # America; FUCK YEAH!
|
60
|
+
}
|
61
|
+
|
62
|
+
# GEnum valid values are detectable, but I don't know how to do the same
|
63
|
+
# for the numeric parameters. Specify them here manually for now.
|
64
|
+
VIPS_VALID = {
|
65
|
+
:"page-height" => (0..Vips::MAX_COORD),
|
66
|
+
:"quant-table" => (0..8),
|
67
|
+
:Q => (0..100),
|
68
|
+
:colours => (2..256),
|
69
|
+
:dither => (0.0..1.0),
|
70
|
+
:compression => (0..9),
|
71
|
+
:"alpha-q" => (0..100),
|
72
|
+
:"reduction-effort" => (0..6),
|
73
|
+
:kmin => (0..0x7FFFFFFF), # https://en.wikipedia.org/wiki/2,147,483,647
|
74
|
+
:kmax => (0..0x7FFFFFFF),
|
75
|
+
:"tile-width" => (0..0x8000), # 32768
|
76
|
+
:"tile-height" => (0..0x8000),
|
77
|
+
:xres => (0.001..1e+06),
|
78
|
+
:yres => (0.001..1e+06),
|
79
|
+
}
|
80
|
+
|
81
|
+
# Same with default values for numeric parameters.
|
82
|
+
VIPS_DEFAULT = {
|
83
|
+
:Q => 75,
|
84
|
+
:colours => 256,
|
85
|
+
:compression => 6,
|
86
|
+
:"alpha-q" => 100,
|
87
|
+
:"reduction-effort" => 4,
|
88
|
+
:kmin => 0x7FFFFFFF - 1,
|
89
|
+
:kmax => 0x7FFFFFFF,
|
90
|
+
:"tile-width" => 128,
|
91
|
+
:"tile-height" => 128,
|
92
|
+
:xres => 1,
|
93
|
+
:yres => 1,
|
94
|
+
}
|
95
|
+
|
96
|
+
|
97
|
+
# Store FFI results where possible to minimize memory churn 'n' general fragility.
|
98
|
+
@@vips_foreign_types = Hash[]
|
99
|
+
@@vips_foreign_suffixes = Hash[]
|
100
|
+
@@vips_foreign_options = Hash[]
|
101
|
+
|
102
|
+
|
103
|
+
# Returns a String libvips Loader class name most appropriate for the given filename suffix.
|
104
|
+
# This is a workaround for the fact that the built-in Vips::vips_foreign_find_load
|
105
|
+
# requires access of a real image file, and we are here talking only of hypothetical ones.
|
106
|
+
# See this method's call site in 'vips/load' for more detailed comments on this.
|
107
|
+
#
|
108
|
+
# irb(main):234:0> Vips::vips_filename_get_filename('fart.jpg')
|
109
|
+
# => #<FFI::Pointer address=0x0000561efe3d08e0>
|
110
|
+
# irb(main):235:0> Vips::p2str(Vips::vips_filename_get_filename('fart.jpg'))
|
111
|
+
# => "fart.jpg"
|
112
|
+
# irb(main):236:0> File.extname(Vips::p2str(Vips::vips_filename_get_filename('fart.jpg')))
|
113
|
+
# => ".jpg"
|
114
|
+
# irb(main):237:0> Vips::vips_foreign_find_save(File.extname(Vips::p2str(Vips::vips_filename_get_filename('fart.jpg'))))
|
115
|
+
# => "VipsForeignSaveJpegFile"
|
116
|
+
#
|
117
|
+
# Here are the available Operations I have on my laptop with libvips 8.8:
|
118
|
+
# [okeeblow@emi#okeeblow] vips -l|grep VipsForeign|grep File
|
119
|
+
# VipsForeignLoadPdfFile (pdfload), load PDF with libpoppler (.pdf), priority=0, is_a, get_flags, get_flags_filename, header, load
|
120
|
+
# VipsForeignLoadSvgFile (svgload), load SVG with rsvg (.svg, .svgz, .svg.gz), priority=0, is_a, get_flags, get_flags_filename, header, load
|
121
|
+
# VipsForeignLoadGifFile (gifload), load GIF with giflib (.gif), priority=0, is_a, get_flags, get_flags_filename, header, load
|
122
|
+
# VipsForeignLoadJpegFile (jpegload), load jpeg from file (.jpg, .jpeg, .jpe), priority=50, is_a, get_flags, header, load
|
123
|
+
# VipsForeignLoadWebpFile (webpload), load webp from file (.webp), priority=0, is_a, get_flags, get_flags_filename, header, load
|
124
|
+
# VipsForeignLoadTiffFile (tiffload), load tiff from file (.tif, .tiff), priority=50, is_a, get_flags, get_flags_filename, header, load
|
125
|
+
# VipsForeignLoadMagickFile (magickload), load file with ImageMagick, priority=-100, is_a, get_flags, get_flags_filename, header
|
126
|
+
# VipsForeignSaveRadFile (radsave), save image to Radiance file (.hdr), priority=0, rgb
|
127
|
+
# VipsForeignSaveDzFile (dzsave), save image to deepzoom file (.dz), priority=0, any
|
128
|
+
# VipsForeignSavePngFile (pngsave), save image to png file (.png), priority=0, rgba
|
129
|
+
# VipsForeignSaveJpegFile (jpegsave), save image to jpeg file (.jpg, .jpeg, .jpe), priority=0, rgb-cmyk
|
130
|
+
# VipsForeignSaveWebpFile (webpsave), save image to webp file (.webp), priority=0, rgba-only
|
131
|
+
# VipsForeignSaveTiffFile (tiffsave), save image to tiff file (.tif, .tiff), priority=0, any
|
132
|
+
# VipsForeignSaveMagickFile (magicksave), save file with ImageMagick (.gif, .bmp), priority=-100, any
|
133
|
+
#
|
134
|
+
# You can notice differences such as a `dzsave` and `radsave` but no `dzload` or `radload`.
|
135
|
+
# This is why we can't assume that HAS_SAVER == HAS_LOADER across the board.
|
136
|
+
# Other differences are invisible here, like different formats supported silently by `magickload`,
|
137
|
+
# so that Operation is the catch-all fallback if we don't have any better idea.
|
138
|
+
#
|
139
|
+
# We can try taking a MIME::Type's `sub_type`, capitalizing it, and trying to find a Loader Operation by that name.
|
140
|
+
# irb(main):254:0> MIME::Types::type_for('.heif').last.sub_type.capitalize
|
141
|
+
# => "Heif"
|
142
|
+
# irb(main):255:0> MIME::Types::type_for('.jpg').last.sub_type.capitalize
|
143
|
+
# => "Jpeg"
|
144
|
+
#
|
145
|
+
## NOTE: I'm writing this on an old install that lacks HEIF support in its libvips 8.8 installation,
|
146
|
+
# so this failure to find 'VipsForeignLoadHeifFile' is expected and correct for me!
|
147
|
+
# It probably won't fail for you in the future, but I want to make sure to include
|
148
|
+
# some example of varying library capability and not assume capabilities based on libvips version:
|
149
|
+
#
|
150
|
+
# irb(main):257:0> GObject::g_type_from_name("VipsForeignLoad#{MIME::Types::type_for('.jpg').last.sub_type.capitalize}File")
|
151
|
+
# => 94691057380176
|
152
|
+
# irb(main):258:0> GObject::g_type_from_name("VipsForeignLoad#{MIME::Types::type_for('.heif').last.sub_type.capitalize}File")
|
153
|
+
# => 0
|
154
|
+
def self.vips_foreign_find_load_suffix(filename)
|
155
|
+
suffix = File.extname(Vips::p2str(Vips::vips_filename_get_filename('fart.jpg')))
|
156
|
+
guessed_loader = "VipsForeignLoad#{CHECKING::YOU::OUT(suffix).first.sub_type.capitalize}File"
|
157
|
+
return self::vips_foreign_valid_operation?(guessed_loader) ? guessed_loader : 'VipsForeignLoadMagickFile'.freeze
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Returns a Set of MIME::Types based on the "supported suffix" lists generated
|
162
|
+
# by libvips and our other functions here in this Module.
|
163
|
+
def self.vips_get_types(basename)
|
164
|
+
@@vips_foreign_types[basename.to_sym] ||= self::vips_get_suffixes(basename).each_with_object(Set[]) { |suffix, types|
|
165
|
+
types.merge(CHECKING::YOU::OUT(suffix))
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Returns a Set of String filename suffixes supported by a tree of libvips loader/saver classes.
|
171
|
+
#
|
172
|
+
# The Array returned from self::vips_get_nickname_suffixes will be overloaded
|
173
|
+
# with all duplicate suffix possibilities for each Type according to libvips.
|
174
|
+
# e.g.
|
175
|
+
# This is unrelated to MIME::Type#preferred_extension!!
|
176
|
+
def self.vips_get_suffixes(basename)
|
177
|
+
@@vips_foreign_suffixes[basename.to_sym] ||= self::vips_get_suffixes_per_nickname(basename).values.each_with_object(Set[]) {|s,n| n.merge(s)}
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# Returns a Hash[alias] of Compound attributes supported by a given libvips Loader/Saver class.
|
182
|
+
def self.vips_get_options(nickname)
|
183
|
+
return Hash if nickname.nil?
|
184
|
+
@@vips_foreign_options[nickname.to_sym] ||= self::vips_get_nickname_options(nickname)
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
protected
|
189
|
+
|
190
|
+
|
191
|
+
# Returns a Set of local MIME::Types supported by the given class and any of its children.
|
192
|
+
def self.vips_get_types_per_nickname(basename)
|
193
|
+
self::vips_get_suffixes_per_nickname(basename).transform_values{|s| CHECKING::YOU::OUT(s)}
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns a Hash[Type] of Set[String] class nicknames supporting that Type.
|
197
|
+
def self.vips_get_nicknames_per_type(basename)
|
198
|
+
self::vips_get_nickname_types(basename).each_with_object(Hash.new { |h,k| h[k] = Set[] }) { |(nickname,type_set), memo|
|
199
|
+
type_set.each{ |t|
|
200
|
+
memo[t] << nickname
|
201
|
+
}
|
202
|
+
}
|
203
|
+
end
|
204
|
+
|
205
|
+
# Returns a Hash[String] of Set[String]s containing the
|
206
|
+
# supported MediaType filename suffixes for all child classes of
|
207
|
+
# either VipsForeignSave or VipsForeignLoad.
|
208
|
+
#
|
209
|
+
# This is very similar to the built-in Vips::get_suffixes except
|
210
|
+
# also allows us to directly inspect Loaders — including Magick!
|
211
|
+
#
|
212
|
+
# Previously we had to take the Saver suffixes and just assume each had a matching Loader.
|
213
|
+
# This was very limiting MediaType support since OpenEXR/OpenSlide/Magick-supported
|
214
|
+
# Loader types would not have a Saver suffix and would have no way to be discovered!
|
215
|
+
# This also works around Loader type support bugs, e.g. the Magick-based BMP (MS Bitmap)
|
216
|
+
# Loader was missing prior to libvips version 8.9.1.,
|
217
|
+
# so we can stop checking versions and inserting manual workarounds for those corner cases!
|
218
|
+
#
|
219
|
+
# The FFI buffer reads will leave us with an overloaded Array containing
|
220
|
+
# duplicate suffixes for every supported suffix variation of a given type,
|
221
|
+
# e.g. ['.jpg', '.jpe', '.jpeg', '.png], '.gif', '.tif', '.tiff' … ]
|
222
|
+
def self.vips_get_suffixes_per_nickname(basename)
|
223
|
+
self::vips_get_child_class_nicknames(basename).each_with_object(Hash.new) { |nickname, nickname_suffixes|
|
224
|
+
# "Search below basename, return the first class whose name or nickname matches."
|
225
|
+
# VipsForeign is a basename for savers and loaders alike.
|
226
|
+
foreign_class = Vips::vips_class_find('VipsForeign'.freeze, nickname)
|
227
|
+
next if foreign_class.null?
|
228
|
+
|
229
|
+
buf_struct = Vips::BufStruct.new
|
230
|
+
buf_struct_string = FFI::MemoryPointer.new(:char, 2048)
|
231
|
+
buf_struct[:base] = buf_struct_string
|
232
|
+
buf_struct[:mx] = 2048
|
233
|
+
|
234
|
+
# Load the human-readable class summary into a given buffer.
|
235
|
+
Vips::vips_object_summary_class(foreign_class, buf_struct.pointer)
|
236
|
+
|
237
|
+
class_summary = buf_struct_string.read_string
|
238
|
+
|
239
|
+
suffixes = class_summary.scan(/\.\w+\.?\w+/)
|
240
|
+
nickname_suffixes.update({nickname => suffixes.to_set}) unless suffixes.empty?
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
# Returns a Set of String class names for libvips' Loaders/Savers.
|
245
|
+
def self.vips_get_child_class_nicknames(basename)
|
246
|
+
nicknames = Set[]
|
247
|
+
generate_class = Proc.new{ |gtype|
|
248
|
+
nickname = Vips::nickname_find(gtype)
|
249
|
+
nicknames << nickname if nickname
|
250
|
+
|
251
|
+
# https://libvips.github.io/libvips/API/current/VipsObject.html#vips-type-map
|
252
|
+
# "Map over a type's children. Stop when fn returns non-nil and return that value."
|
253
|
+
Vips::vips_type_map(gtype, generate_class, nil)
|
254
|
+
}
|
255
|
+
generate_class.call(GObject::g_type_from_name(basename))
|
256
|
+
nicknames
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns a Hash[alias] of attribute Compounds for every optional attribute of a libvips Loader/Saver class.
|
260
|
+
#
|
261
|
+
# The discarded 'required' attributes are things like filenames that we will handle ourselves in DD.
|
262
|
+
# irb> Vips::Introspect.get('jpegload').required_input
|
263
|
+
# => [{:arg_name=>"filename", :flags=>19, :gtype=>64}]
|
264
|
+
# irb> Vips::Introspect.new('jpegload').required_output
|
265
|
+
# => [{:arg_name=>"out", :flags=>35, :gtype=>94062772794288}]
|
266
|
+
#
|
267
|
+
## Example using :argument_map:
|
268
|
+
# irb> Vips::Operation.new('gifload').argument_map{|a,b,c| p "#{a[:name]} — #{a[:value_type]} — #{GObject::g_type_name(a[:value_type])}"}
|
269
|
+
# "filename — 64 — gchararray"
|
270
|
+
# "nickname — 64 — gchararray"
|
271
|
+
# "out — 94691057294304 — VipsImage"
|
272
|
+
# "description — 64 — gchararray"
|
273
|
+
# "page — 24 — gint"
|
274
|
+
# "n — 24 — gint"
|
275
|
+
# "flags — 94691059531296 — VipsForeignFlags"
|
276
|
+
# "memory — 20 — gboolean"
|
277
|
+
# "access — 94691057417952 — VipsAccess"
|
278
|
+
# "sequential — 20 — gboolean"
|
279
|
+
# "fail — 20 — gboolean"
|
280
|
+
# "disc — 20 — gboolean"
|
281
|
+
#
|
282
|
+
## Descriptions are obtained by passing the complete pspec to g_param_get_blurb:
|
283
|
+
# Example:
|
284
|
+
# irb> Vips::Operation.new('openexrload').argument_map{|a,b,c| p GObject::g_param_spec_get_blurb(a)}
|
285
|
+
# "Filename to load from"
|
286
|
+
# "Class nickname"
|
287
|
+
# "Output image"
|
288
|
+
# "Class description"
|
289
|
+
# "Flags for this file"
|
290
|
+
# "Force open via memory"
|
291
|
+
# "Required access pattern for this file"
|
292
|
+
# "Sequential read only"
|
293
|
+
# "Fail on first error"
|
294
|
+
# "Open to disc"
|
295
|
+
def self.vips_get_nickname_options(nickname)
|
296
|
+
options = Hash[]
|
297
|
+
Vips::Operation.new(nickname).argument_map{ |param_spec, argument_class, _argument_instance|
|
298
|
+
flags = argument_class[:flags]
|
299
|
+
if (flags & Vips::ARGUMENT_INPUT) != 0 # We only want "input" arguments
|
300
|
+
# …and we also only want optional non-deprecated arguments.
|
301
|
+
if (flags & Vips::ARGUMENT_REQUIRED) == 0 && (flags & Vips::ARGUMENT_DEPRECATED) == 0
|
302
|
+
# ParameterSpec name will be a String e.g. 'Q' or 'interlace' or 'page-height'
|
303
|
+
element = param_spec[:name].to_sym
|
304
|
+
|
305
|
+
# `magicksave` takes an argument `format` to choose one of its many supported types,
|
306
|
+
# but that selection in DistorteD-land is via our MIME::Types, so this option should be dropped.
|
307
|
+
# https://github.com/libvips/libvips/blob/4de9b56725862edf872ae503a3dfb4cf05da9e77/libvips/foreign/magicksave.c#L455~L460
|
308
|
+
next if element == :format
|
309
|
+
|
310
|
+
# GObject::g_type_name will return `nil` for an invalid :value_type,
|
311
|
+
# but these are coming straight from libvips so we know they're fine.
|
312
|
+
gtype_name = GObject::g_type_name(param_spec[:value_type]).to_sym
|
313
|
+
|
314
|
+
# Support aliasing options like 'Q' into 'quality' for consistency
|
315
|
+
# and 'colours' into 'colors' for accessibility.
|
316
|
+
isotopes = VIPS_ALIASES.dig(element) || Set[element]
|
317
|
+
|
318
|
+
# Keyword arguments to splat into our Compound
|
319
|
+
attributes = {
|
320
|
+
# Some libvips drivers seem to have mixed-leading-case options,
|
321
|
+
# like ppmsave and webp save for example:
|
322
|
+
# https://github.com/libvips/libvips/blob/4de9b56725862edf872ae503a3dfb4cf05da9e77/libvips/foreign/ppmsave.c#L396~L415
|
323
|
+
# https://github.com/libvips/libvips/blob/4de9b56725862edf872ae503a3dfb4cf05da9e77/libvips/foreign/webpsave.c#L152
|
324
|
+
# TODO: Inventory all of these and submit an upstream patch to capitaqlize them consistently.
|
325
|
+
# Until them (and for old versions), fix up the first letter manually.
|
326
|
+
# Avoid using just `blurb.capitalize` as that will lowercase everything after
|
327
|
+
# the first character, which is definitely worse than what I'm trying to fix lol
|
328
|
+
:blurb => GObject::g_param_spec_get_blurb(param_spec).tap{|blurb| blurb[0] = blurb[0].capitalize},
|
329
|
+
:default => self::vips_get_option_default(param_spec[:value_type]),
|
330
|
+
}
|
331
|
+
if GObject::g_type_fundamental(param_spec[:value_type]) == GObject::GENUM_TYPE
|
332
|
+
attributes[:valid] = self::vips_get_enum_values(param_spec[:value_type])
|
333
|
+
elsif VIPS_VALID.has_key?(element)
|
334
|
+
attributes[:valid] = VIPS_VALID[element]
|
335
|
+
elsif G_TYPE_VALUES.has_key?(gtype_name)
|
336
|
+
attributes[:valid] = G_TYPE_VALUES[gtype_name]
|
337
|
+
end
|
338
|
+
|
339
|
+
# Add the Compound for every alias
|
340
|
+
compound = Cooltrainer::Compound.new(isotopes, **attributes)
|
341
|
+
isotopes.each{ |isotope|
|
342
|
+
options.store(isotope, compound)
|
343
|
+
}
|
344
|
+
end
|
345
|
+
end
|
346
|
+
}
|
347
|
+
|
348
|
+
# This isn't really a 'Saver' Option — rather an argument to a separate
|
349
|
+
# :smartcrop or :thumbnail VIPS method we can call, but I want to offer
|
350
|
+
# this option on every Type and use it to control the method we call
|
351
|
+
# to write the image.
|
352
|
+
options.store(:crop, Cooltrainer::Compound.new(:crop,
|
353
|
+
blurb: 'Visual cropping method',
|
354
|
+
valid: self::vips_get_enum_values('VipsInteresting'.freeze),
|
355
|
+
default: self::vips_get_option_default('VipsInteresting'.freeze),
|
356
|
+
)) if nickname.include?('Save'.freeze) # Only for savers!!
|
357
|
+
|
358
|
+
# All done :)
|
359
|
+
options
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
# Returns the default value for any ruby-vips GObject::GValue
|
364
|
+
# based on its fundamental GType.
|
365
|
+
def self.vips_get_option_default(gtype)
|
366
|
+
gtype_id = gtype.is_a?(String) ? GObject::g_type_from_name(gtype) : gtype
|
367
|
+
# The `enum` method will actually work for several of these types,
|
368
|
+
# e.g. returns `false` for GBool, but let's skip it to avoid the whole,
|
369
|
+
# like, FFI/allocation thing.
|
370
|
+
case GObject::g_type_fundamental(gtype_id)
|
371
|
+
when GObject::GENUM_TYPE
|
372
|
+
return self.vips_get_enum_default(gtype_id)
|
373
|
+
when GObject::GBOOL_TYPE
|
374
|
+
return false
|
375
|
+
when GObject::GDOUBLE_TYPE
|
376
|
+
return 0.0
|
377
|
+
when GObject::GINT_TYPE
|
378
|
+
return 0
|
379
|
+
when GObject::GUINT64_TYPE
|
380
|
+
return 0
|
381
|
+
when GObject::GBOXED_TYPE
|
382
|
+
return self.vips_get_boxed_default(gtype_id)
|
383
|
+
else
|
384
|
+
return nil
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# Returns the default for a GEnum derivative by allocating, initializing,
|
389
|
+
# and getting the contents of a GValue.
|
390
|
+
#
|
391
|
+
## Example:
|
392
|
+
# irb> gvp = GObject::GValue.alloc
|
393
|
+
# irb> gvp
|
394
|
+
# => #<GObject::GValue:0x00005603ba9d4c70>
|
395
|
+
# irb> gvp.init(GObject::g_type_from_name('VipsAccess'))
|
396
|
+
# => nil
|
397
|
+
# irb> GObject::g_type_from_name 'VipsAccess'
|
398
|
+
# => 94574011156416
|
399
|
+
# irb> gvp.get
|
400
|
+
# => :random
|
401
|
+
def self.vips_get_enum_default(gtype)
|
402
|
+
begin
|
403
|
+
gtype_id = gtype.is_a?(String) ? GObject::g_type_from_name(gtype) : gtype
|
404
|
+
# Deallocation is automatic when `gvp` goes out of scope.
|
405
|
+
gvp = GObject::GValue.alloc
|
406
|
+
gvp.init(gtype)
|
407
|
+
out = gvp.get
|
408
|
+
gvp.unset
|
409
|
+
return out
|
410
|
+
rescue FFI::NullPointerError => e
|
411
|
+
# This is happening for VipsArrayDouble gtype 94691056795136
|
412
|
+
# end I don't feel like debugging it rn lololol
|
413
|
+
nil
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
|
418
|
+
# Returns a Set[Symbol] of supported enum values for a given g_type
|
419
|
+
def self.vips_get_enum_values(gtype)
|
420
|
+
begin
|
421
|
+
gtype_id = gtype.is_a?(String) ? GObject::g_type_from_name(gtype) : gtype
|
422
|
+
|
423
|
+
# HACK HACK HACK:
|
424
|
+
# There *has* to be a better/native way to get this, but for now I'm just going to
|
425
|
+
# parse them out of the error message you can access after trying an obviously-wrong value.
|
426
|
+
#
|
427
|
+
# irb> Vips::vips_error_clear
|
428
|
+
# => nil
|
429
|
+
# irb> GObject::g_type_from_name 'VipsForeignTiffCompression'
|
430
|
+
# => 94691059614768
|
431
|
+
# irb> Vips::vips_enum_from_nick 'DistorteD', 94691059614768, 'lolol'
|
432
|
+
# => -1
|
433
|
+
# irb> Vips::vips_error_buffer
|
434
|
+
# => "DistorteD: enum 'VipsForeignTiffCompression' has no member 'lolol', should be one of: none, jpeg, deflate, packbits, ccittfax4, lzw\n"
|
435
|
+
Vips::vips_enum_from_nick('DistorteD'.freeze, gtype_id, 'lolol'.freeze)
|
436
|
+
error_buffer = Vips::vips_error_buffer
|
437
|
+
if error_buffer.include?('should be one of: '.freeze)
|
438
|
+
Vips::vips_error_clear
|
439
|
+
# Parse the error into a Set of Symbol options
|
440
|
+
discovered = error_buffer.split('should be one of: '.freeze)[1][0..-2].split(', '.freeze).map(&:to_sym).to_set
|
441
|
+
# For any Options with aliases, merge in the aliases.
|
442
|
+
(discovered & self::VIPS_ALIASES.keys.to_set).each { |aliased|
|
443
|
+
discovered.merge(self::VIPS_ALIASES[aliased])
|
444
|
+
}
|
445
|
+
# We need to give this back as an Array because callers will want to call :join on it,
|
446
|
+
# and we should give it back sorted so aliased aren't all piled up at the end.
|
447
|
+
discovered.to_a.sort
|
448
|
+
else
|
449
|
+
return Array[]
|
450
|
+
end
|
451
|
+
rescue
|
452
|
+
return Array[]
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
|
457
|
+
# Returns a Array of the boxed type (Int, Double, etc)
|
458
|
+
def self.vips_get_boxed_default(gtype)
|
459
|
+
gtype_id = gtype.is_a?(String) ? GObject::g_type_from_name(gtype) : gtype
|
460
|
+
gtype_name = GObject::g_type_name(gtype_id)
|
461
|
+
# It's not really correct to explicitly return three values here,
|
462
|
+
# but the use of this for `background` colors are the only use rn.
|
463
|
+
case gtype_name
|
464
|
+
when 'VipsArrayDouble'.freeze
|
465
|
+
return [0.0, 0.0, 0.0]
|
466
|
+
when 'VipsArrayInt'.freeze
|
467
|
+
return [0, 0, 0]
|
468
|
+
else
|
469
|
+
return []
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
# Returns boolean validity for libvips class names,
|
475
|
+
# e.g. for validating that a desired Loader/Saver class actually exists!
|
476
|
+
def self.vips_foreign_valid_operation?(otra)
|
477
|
+
# This doesn't seem to raise any Exception on invalid g_type, just returns 0.
|
478
|
+
# Use this to cast to a boolean return value:
|
479
|
+
#
|
480
|
+
# irb(main):243:0> GObject::g_type_from_name('VipsForeignSaveJpegFile')
|
481
|
+
# => 94691057381120
|
482
|
+
# irb(main):244:0> GObject::g_type_from_name('VipsForeignLoadJpegFile')
|
483
|
+
# => 94691057380176
|
484
|
+
# irb(main):245:0> GObject::g_type_from_name('VipsForeignLoadJpegFilgfgfgfe')
|
485
|
+
# => 0
|
486
|
+
GObject::g_type_from_name(otra) == 0 ? false : true
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|