skia 0.1.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 +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/Rakefile +8 -0
- data/examples/avatar_generator.rb +74 -0
- data/examples/bar_chart.rb +89 -0
- data/examples/basic_drawing.rb +43 -0
- data/examples/gradient.rb +32 -0
- data/examples/pdf_output.rb +48 -0
- data/examples/picture_recording.rb +62 -0
- data/examples/progress_gauge.rb +103 -0
- data/examples/social_card.rb +121 -0
- data/examples/text_drawing.rb +40 -0
- data/lib/skia/base.rb +40 -0
- data/lib/skia/canvas.rb +177 -0
- data/lib/skia/color.rb +82 -0
- data/lib/skia/data.rb +47 -0
- data/lib/skia/document.rb +76 -0
- data/lib/skia/font.rb +74 -0
- data/lib/skia/image.rb +137 -0
- data/lib/skia/matrix.rb +163 -0
- data/lib/skia/native/callbacks.rb +6 -0
- data/lib/skia/native/functions.rb +223 -0
- data/lib/skia/native/types.rb +284 -0
- data/lib/skia/native.rb +50 -0
- data/lib/skia/paint.rb +100 -0
- data/lib/skia/path.rb +135 -0
- data/lib/skia/picture.rb +120 -0
- data/lib/skia/point.rb +94 -0
- data/lib/skia/rect.rb +179 -0
- data/lib/skia/shader.rb +107 -0
- data/lib/skia/surface.rb +151 -0
- data/lib/skia/typeface.rb +39 -0
- data/lib/skia/version.rb +5 -0
- data/lib/skia.rb +31 -0
- data/skia-ruby.gemspec +30 -0
- metadata +93 -0
data/lib/skia/point.rb
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Point
|
|
5
|
+
attr_accessor :x, :y
|
|
6
|
+
|
|
7
|
+
def initialize(x = 0.0, y = 0.0)
|
|
8
|
+
@x = x.to_f
|
|
9
|
+
@y = y.to_f
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.from_struct(struct)
|
|
13
|
+
new(struct[:x], struct[:y])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_struct
|
|
17
|
+
struct = Native::SKPoint.new
|
|
18
|
+
struct[:x] = @x
|
|
19
|
+
struct[:y] = @y
|
|
20
|
+
struct
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def +(other)
|
|
24
|
+
self.class.new(@x + other.x, @y + other.y)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def -(other)
|
|
28
|
+
self.class.new(@x - other.x, @y - other.y)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def *(other)
|
|
32
|
+
self.class.new(@x * other, @y * other)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def /(other)
|
|
36
|
+
self.class.new(@x / other, @y / other)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def -@
|
|
40
|
+
self.class.new(-@x, -@y)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def length
|
|
44
|
+
Math.sqrt(@x * @x + @y * @y)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def normalize
|
|
48
|
+
len = length
|
|
49
|
+
return self.class.new(0, 0) if len.zero?
|
|
50
|
+
|
|
51
|
+
self / len
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ==(other)
|
|
55
|
+
return false unless other.is_a?(Point)
|
|
56
|
+
|
|
57
|
+
@x == other.x && @y == other.y
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def to_a
|
|
61
|
+
[@x, @y]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class IPoint
|
|
66
|
+
attr_accessor :x, :y
|
|
67
|
+
|
|
68
|
+
def initialize(x = 0, y = 0)
|
|
69
|
+
@x = x.to_i
|
|
70
|
+
@y = y.to_i
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.from_struct(struct)
|
|
74
|
+
new(struct[:x], struct[:y])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_struct
|
|
78
|
+
struct = Native::SKIPoint.new
|
|
79
|
+
struct[:x] = @x
|
|
80
|
+
struct[:y] = @y
|
|
81
|
+
struct
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def ==(other)
|
|
85
|
+
return false unless other.is_a?(IPoint)
|
|
86
|
+
|
|
87
|
+
@x == other.x && @y == other.y
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def to_a
|
|
91
|
+
[@x, @y]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/skia/rect.rb
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Rect
|
|
5
|
+
attr_accessor :left, :top, :right, :bottom
|
|
6
|
+
|
|
7
|
+
def initialize(left = 0.0, top = 0.0, right = 0.0, bottom = 0.0)
|
|
8
|
+
@left = left.to_f
|
|
9
|
+
@top = top.to_f
|
|
10
|
+
@right = right.to_f
|
|
11
|
+
@bottom = bottom.to_f
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.from_xywh(x, y, width, height)
|
|
15
|
+
new(x, y, x + width, y + height)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.from_wh(width, height)
|
|
19
|
+
new(0, 0, width, height)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.from_struct(struct)
|
|
23
|
+
new(struct[:left], struct[:top], struct[:right], struct[:bottom])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_struct
|
|
27
|
+
struct = Native::SKRect.new
|
|
28
|
+
struct[:left] = @left
|
|
29
|
+
struct[:top] = @top
|
|
30
|
+
struct[:right] = @right
|
|
31
|
+
struct[:bottom] = @bottom
|
|
32
|
+
struct
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def width
|
|
36
|
+
@right - @left
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def height
|
|
40
|
+
@bottom - @top
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def center_x
|
|
44
|
+
(@left + @right) / 2.0
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def center_y
|
|
48
|
+
(@top + @bottom) / 2.0
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def center
|
|
52
|
+
Point.new(center_x, center_y)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def empty?
|
|
56
|
+
@left >= @right || @top >= @bottom
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def contains?(x, y)
|
|
60
|
+
x >= @left && x < @right && y >= @top && y < @bottom
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def contains_rect?(other)
|
|
64
|
+
@left <= other.left && @top <= other.top &&
|
|
65
|
+
@right >= other.right && @bottom >= other.bottom
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def intersects?(other)
|
|
69
|
+
@left < other.right && other.left < @right &&
|
|
70
|
+
@top < other.bottom && other.top < @bottom
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def intersect(other)
|
|
74
|
+
return nil unless intersects?(other)
|
|
75
|
+
|
|
76
|
+
self.class.new(
|
|
77
|
+
[@left, other.left].max,
|
|
78
|
+
[@top, other.top].min,
|
|
79
|
+
[@right, other.right].min,
|
|
80
|
+
[@bottom, other.bottom].max
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def union(other)
|
|
85
|
+
self.class.new(
|
|
86
|
+
[@left, other.left].min,
|
|
87
|
+
[@top, other.top].min,
|
|
88
|
+
[@right, other.right].max,
|
|
89
|
+
[@bottom, other.bottom].max
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def offset(dx, dy)
|
|
94
|
+
self.class.new(@left + dx, @top + dy, @right + dx, @bottom + dy)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def offset!(dx, dy)
|
|
98
|
+
@left += dx
|
|
99
|
+
@top += dy
|
|
100
|
+
@right += dx
|
|
101
|
+
@bottom += dy
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def inset(dx, dy)
|
|
106
|
+
self.class.new(@left + dx, @top + dy, @right - dx, @bottom - dy)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def ==(other)
|
|
110
|
+
return false unless other.is_a?(Rect)
|
|
111
|
+
|
|
112
|
+
@left == other.left && @top == other.top &&
|
|
113
|
+
@right == other.right && @bottom == other.bottom
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def to_a
|
|
117
|
+
[@left, @top, @right, @bottom]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class IRect
|
|
122
|
+
attr_accessor :left, :top, :right, :bottom
|
|
123
|
+
|
|
124
|
+
def initialize(left = 0, top = 0, right = 0, bottom = 0)
|
|
125
|
+
@left = left.to_i
|
|
126
|
+
@top = top.to_i
|
|
127
|
+
@right = right.to_i
|
|
128
|
+
@bottom = bottom.to_i
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.from_xywh(x, y, width, height)
|
|
132
|
+
new(x, y, x + width, y + height)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.from_wh(width, height)
|
|
136
|
+
new(0, 0, width, height)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.from_struct(struct)
|
|
140
|
+
new(struct[:left], struct[:top], struct[:right], struct[:bottom])
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def to_struct
|
|
144
|
+
struct = Native::SKIRect.new
|
|
145
|
+
struct[:left] = @left
|
|
146
|
+
struct[:top] = @top
|
|
147
|
+
struct[:right] = @right
|
|
148
|
+
struct[:bottom] = @bottom
|
|
149
|
+
struct
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def width
|
|
153
|
+
@right - @left
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def height
|
|
157
|
+
@bottom - @top
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def empty?
|
|
161
|
+
@left >= @right || @top >= @bottom
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def ==(other)
|
|
165
|
+
return false unless other.is_a?(IRect)
|
|
166
|
+
|
|
167
|
+
@left == other.left && @top == other.top &&
|
|
168
|
+
@right == other.right && @bottom == other.bottom
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def to_a
|
|
172
|
+
[@left, @top, @right, @bottom]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def to_rect
|
|
176
|
+
Rect.new(@left.to_f, @top.to_f, @right.to_f, @bottom.to_f)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
data/lib/skia/shader.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Shader < Base
|
|
5
|
+
def initialize(ptr)
|
|
6
|
+
super(ptr, :sk_shader_unref)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.wrap(ptr)
|
|
10
|
+
return nil if ptr.nil? || ptr.null?
|
|
11
|
+
|
|
12
|
+
new(ptr)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.linear_gradient(start_point, end_point, colors, positions = nil, tile_mode: :clamp, matrix: nil)
|
|
16
|
+
points = FFI::MemoryPointer.new(Native::SKPoint, 2)
|
|
17
|
+
start_struct = start_point.to_struct
|
|
18
|
+
end_struct = end_point.to_struct
|
|
19
|
+
points[0].write_bytes(start_struct.to_ptr.read_bytes(Native::SKPoint.size))
|
|
20
|
+
points[1].write_bytes(end_struct.to_ptr.read_bytes(Native::SKPoint.size))
|
|
21
|
+
|
|
22
|
+
color_values = colors.map { |c| c.is_a?(Color) ? c.to_i : c }
|
|
23
|
+
colors_ptr = FFI::MemoryPointer.new(:uint32, color_values.size)
|
|
24
|
+
colors_ptr.write_array_of_uint32(color_values)
|
|
25
|
+
|
|
26
|
+
positions_ptr = nil
|
|
27
|
+
if positions
|
|
28
|
+
positions_ptr = FFI::MemoryPointer.new(:float, positions.size)
|
|
29
|
+
positions_ptr.write_array_of_float(positions.map(&:to_f))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
matrix_ptr = matrix&.to_struct
|
|
33
|
+
|
|
34
|
+
ptr = Native.sk_shader_new_linear_gradient(
|
|
35
|
+
points,
|
|
36
|
+
colors_ptr,
|
|
37
|
+
positions_ptr,
|
|
38
|
+
color_values.size,
|
|
39
|
+
tile_mode,
|
|
40
|
+
matrix_ptr
|
|
41
|
+
)
|
|
42
|
+
raise Error, 'Failed to create linear gradient shader' if ptr.nil? || ptr.null?
|
|
43
|
+
|
|
44
|
+
new(ptr)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.radial_gradient(center, radius, colors, positions = nil, tile_mode: :clamp, matrix: nil)
|
|
48
|
+
center_struct = center.to_struct
|
|
49
|
+
|
|
50
|
+
color_values = colors.map { |c| c.is_a?(Color) ? c.to_i : c }
|
|
51
|
+
colors_ptr = FFI::MemoryPointer.new(:uint32, color_values.size)
|
|
52
|
+
colors_ptr.write_array_of_uint32(color_values)
|
|
53
|
+
|
|
54
|
+
positions_ptr = nil
|
|
55
|
+
if positions
|
|
56
|
+
positions_ptr = FFI::MemoryPointer.new(:float, positions.size)
|
|
57
|
+
positions_ptr.write_array_of_float(positions.map(&:to_f))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
matrix_ptr = matrix&.to_struct
|
|
61
|
+
|
|
62
|
+
ptr = Native.sk_shader_new_radial_gradient(
|
|
63
|
+
center_struct,
|
|
64
|
+
radius.to_f,
|
|
65
|
+
colors_ptr,
|
|
66
|
+
positions_ptr,
|
|
67
|
+
color_values.size,
|
|
68
|
+
tile_mode,
|
|
69
|
+
matrix_ptr
|
|
70
|
+
)
|
|
71
|
+
raise Error, 'Failed to create radial gradient shader' if ptr.nil? || ptr.null?
|
|
72
|
+
|
|
73
|
+
new(ptr)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.sweep_gradient(center, colors, positions = nil, tile_mode: :clamp, start_angle: 0.0, end_angle: 360.0,
|
|
77
|
+
matrix: nil)
|
|
78
|
+
center_struct = center.to_struct
|
|
79
|
+
|
|
80
|
+
color_values = colors.map { |c| c.is_a?(Color) ? c.to_i : c }
|
|
81
|
+
colors_ptr = FFI::MemoryPointer.new(:uint32, color_values.size)
|
|
82
|
+
colors_ptr.write_array_of_uint32(color_values)
|
|
83
|
+
|
|
84
|
+
positions_ptr = nil
|
|
85
|
+
if positions
|
|
86
|
+
positions_ptr = FFI::MemoryPointer.new(:float, positions.size)
|
|
87
|
+
positions_ptr.write_array_of_float(positions.map(&:to_f))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
matrix_ptr = matrix&.to_struct
|
|
91
|
+
|
|
92
|
+
ptr = Native.sk_shader_new_sweep_gradient(
|
|
93
|
+
center_struct,
|
|
94
|
+
colors_ptr,
|
|
95
|
+
positions_ptr,
|
|
96
|
+
color_values.size,
|
|
97
|
+
tile_mode,
|
|
98
|
+
start_angle.to_f,
|
|
99
|
+
end_angle.to_f,
|
|
100
|
+
matrix_ptr
|
|
101
|
+
)
|
|
102
|
+
raise Error, 'Failed to create sweep gradient shader' if ptr.nil? || ptr.null?
|
|
103
|
+
|
|
104
|
+
new(ptr)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/skia/surface.rb
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Surface < Base
|
|
5
|
+
attr_reader :width, :height
|
|
6
|
+
|
|
7
|
+
def initialize(ptr, width, height)
|
|
8
|
+
super(ptr, :sk_surface_unref)
|
|
9
|
+
@width = width
|
|
10
|
+
@height = height
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.make_raster(width, height, color_type: :rgba_8888, alpha_type: :premul)
|
|
14
|
+
info = Native::SKImageInfo.new
|
|
15
|
+
info[:width] = width
|
|
16
|
+
info[:height] = height
|
|
17
|
+
info[:colorType] = color_type
|
|
18
|
+
info[:alphaType] = alpha_type
|
|
19
|
+
info[:colorspace] = nil
|
|
20
|
+
|
|
21
|
+
ptr = Native.sk_surface_new_raster(info, 0, nil)
|
|
22
|
+
raise Error, 'Failed to create raster surface' if ptr.nil? || ptr.null?
|
|
23
|
+
|
|
24
|
+
new(ptr, width, height)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.make_raster_direct(width, height, pixels, row_bytes, color_type: :rgba_8888, alpha_type: :premul)
|
|
28
|
+
info = Native::SKImageInfo.new
|
|
29
|
+
info[:width] = width
|
|
30
|
+
info[:height] = height
|
|
31
|
+
info[:colorType] = color_type
|
|
32
|
+
info[:alphaType] = alpha_type
|
|
33
|
+
info[:colorspace] = nil
|
|
34
|
+
|
|
35
|
+
ptr = Native.sk_surface_new_raster_direct(info, pixels, row_bytes, nil, nil, nil)
|
|
36
|
+
raise Error, 'Failed to create direct raster surface' if ptr.nil? || ptr.null?
|
|
37
|
+
|
|
38
|
+
new(ptr, width, height)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def canvas
|
|
42
|
+
@canvas ||= Canvas.new(Native.sk_surface_get_canvas(@ptr))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def snapshot
|
|
46
|
+
ptr = Native.sk_surface_new_image_snapshot(@ptr)
|
|
47
|
+
raise Error, 'Failed to create image snapshot' if ptr.nil? || ptr.null?
|
|
48
|
+
|
|
49
|
+
Image.new(ptr)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def draw
|
|
53
|
+
yield canvas
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def encode(format = :png, quality = 100)
|
|
58
|
+
pixmap = Native.sk_pixmap_new
|
|
59
|
+
raise Error, 'Failed to create pixmap' if pixmap.nil? || pixmap.null?
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
raise Error, 'Failed to peek pixels from surface' unless Native.sk_surface_peek_pixels(@ptr, pixmap)
|
|
63
|
+
|
|
64
|
+
stream = Native.sk_dynamicmemorywstream_new
|
|
65
|
+
raise Error, 'Failed to create stream' if stream.nil? || stream.null?
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
success = case format
|
|
69
|
+
when :png
|
|
70
|
+
options = Native::SKPngEncoderOptions.new
|
|
71
|
+
options[:fFilterFlags] = :all
|
|
72
|
+
options[:fZLibLevel] = 6
|
|
73
|
+
options[:fComments] = nil
|
|
74
|
+
options[:fICCProfile] = nil
|
|
75
|
+
options[:fICCProfileDescription] = nil
|
|
76
|
+
Native.sk_pngencoder_encode(stream, pixmap, options)
|
|
77
|
+
when :jpeg, :jpg
|
|
78
|
+
options = Native::SKJpegEncoderOptions.new
|
|
79
|
+
options[:fQuality] = quality
|
|
80
|
+
options[:fDownsample] = :downsample_420
|
|
81
|
+
options[:fAlphaOption] = :ignore
|
|
82
|
+
options[:xmpMetadata] = nil
|
|
83
|
+
options[:fICCProfile] = nil
|
|
84
|
+
options[:fICCProfileDescription] = nil
|
|
85
|
+
Native.sk_jpegencoder_encode(stream, pixmap, options)
|
|
86
|
+
when :webp
|
|
87
|
+
options = Native::SKWebpEncoderOptions.new
|
|
88
|
+
options[:fCompression] = :lossy
|
|
89
|
+
options[:fQuality] = quality.to_f
|
|
90
|
+
options[:fICCProfile] = nil
|
|
91
|
+
options[:fICCProfileDescription] = nil
|
|
92
|
+
Native.sk_webpencoder_encode(stream, pixmap, options)
|
|
93
|
+
else
|
|
94
|
+
options = Native::SKPngEncoderOptions.new
|
|
95
|
+
options[:fFilterFlags] = :all
|
|
96
|
+
options[:fZLibLevel] = 6
|
|
97
|
+
options[:fComments] = nil
|
|
98
|
+
options[:fICCProfile] = nil
|
|
99
|
+
options[:fICCProfileDescription] = nil
|
|
100
|
+
Native.sk_pngencoder_encode(stream, pixmap, options)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
raise Error, "Failed to encode surface as #{format}" unless success
|
|
104
|
+
|
|
105
|
+
data_ptr = Native.sk_dynamicmemorywstream_detach_as_data(stream)
|
|
106
|
+
raise Error, 'Failed to get encoded data' if data_ptr.nil? || data_ptr.null?
|
|
107
|
+
|
|
108
|
+
Data.new(data_ptr)
|
|
109
|
+
ensure
|
|
110
|
+
Native.sk_dynamicmemorywstream_destroy(stream)
|
|
111
|
+
end
|
|
112
|
+
ensure
|
|
113
|
+
Native.sk_pixmap_destructor(pixmap)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def save(path, format: nil, quality: 100)
|
|
118
|
+
format ||= detect_format_from_path(path)
|
|
119
|
+
data = encode(format, quality)
|
|
120
|
+
File.binwrite(path, data.to_s)
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def save_png(path, quality = 100)
|
|
125
|
+
save(path, format: :png, quality: quality)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def save_jpeg(path, quality = 80)
|
|
129
|
+
save(path, format: :jpeg, quality: quality)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def save_webp(path, quality = 80)
|
|
133
|
+
save(path, format: :webp, quality: quality)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def detect_format_from_path(path)
|
|
139
|
+
case File.extname(path).downcase
|
|
140
|
+
when '.png'
|
|
141
|
+
:png
|
|
142
|
+
when '.jpg', '.jpeg'
|
|
143
|
+
:jpeg
|
|
144
|
+
when '.webp'
|
|
145
|
+
:webp
|
|
146
|
+
else
|
|
147
|
+
:png
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Typeface < Base
|
|
5
|
+
WEIGHT_NORMAL = 400
|
|
6
|
+
WEIGHT_BOLD = 700
|
|
7
|
+
WIDTH_NORMAL = 5
|
|
8
|
+
|
|
9
|
+
def initialize(ptr)
|
|
10
|
+
super(ptr, :sk_typeface_unref)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.from_name(name, weight: WEIGHT_NORMAL, width: WIDTH_NORMAL, slant: :upright)
|
|
14
|
+
style = Native.sk_fontstyle_new(weight, width, slant)
|
|
15
|
+
begin
|
|
16
|
+
ptr = Native.sk_typeface_create_from_name(name, style)
|
|
17
|
+
return nil if ptr.nil? || ptr.null?
|
|
18
|
+
|
|
19
|
+
new(ptr)
|
|
20
|
+
ensure
|
|
21
|
+
Native.sk_fontstyle_delete(style) if style && !style.null?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.from_file(path, index = 0)
|
|
26
|
+
ptr = Native.sk_typeface_create_from_file(path, index)
|
|
27
|
+
raise FileNotFoundError, "Failed to load typeface: #{path}" if ptr.nil? || ptr.null?
|
|
28
|
+
|
|
29
|
+
new(ptr)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.default
|
|
33
|
+
ptr = Native.sk_typeface_create_default
|
|
34
|
+
return nil if ptr.nil? || ptr.null?
|
|
35
|
+
|
|
36
|
+
new(ptr)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/skia/version.rb
ADDED
data/lib/skia.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
|
|
5
|
+
require_relative 'skia/version'
|
|
6
|
+
require_relative 'skia/native'
|
|
7
|
+
require_relative 'skia/base'
|
|
8
|
+
require_relative 'skia/color'
|
|
9
|
+
require_relative 'skia/point'
|
|
10
|
+
require_relative 'skia/rect'
|
|
11
|
+
require_relative 'skia/matrix'
|
|
12
|
+
require_relative 'skia/paint'
|
|
13
|
+
require_relative 'skia/path'
|
|
14
|
+
require_relative 'skia/data'
|
|
15
|
+
require_relative 'skia/image'
|
|
16
|
+
require_relative 'skia/surface'
|
|
17
|
+
require_relative 'skia/canvas'
|
|
18
|
+
require_relative 'skia/typeface'
|
|
19
|
+
require_relative 'skia/font'
|
|
20
|
+
require_relative 'skia/shader'
|
|
21
|
+
require_relative 'skia/document'
|
|
22
|
+
require_relative 'skia/picture'
|
|
23
|
+
|
|
24
|
+
module Skia
|
|
25
|
+
class Error < StandardError; end
|
|
26
|
+
class NullPointerError < Error; end
|
|
27
|
+
class EncodingError < Error; end
|
|
28
|
+
class DecodingError < Error; end
|
|
29
|
+
class FileNotFoundError < Error; end
|
|
30
|
+
class UnsupportedOperationError < Error; end
|
|
31
|
+
end
|
data/skia-ruby.gemspec
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/skia/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'skia'
|
|
7
|
+
spec.version = Skia::VERSION
|
|
8
|
+
spec.authors = ['Yudai Takada']
|
|
9
|
+
spec.email = ['t.yudai92@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Ruby bindings for Google Skia 2D graphics library'
|
|
12
|
+
spec.description = 'Ruby bindings for Skia, providing high-performance 2D graphics capabilities'
|
|
13
|
+
spec.homepage = 'https://github.com/ydah/skia-ruby'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = "#{spec.homepage}/tree/v#{spec.version}"
|
|
19
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md"
|
|
20
|
+
|
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
23
|
+
(File.expand_path(f) == __FILE__) ||
|
|
24
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile Dockerfile])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
|
|
29
|
+
spec.add_dependency 'ffi', '~> 1.15'
|
|
30
|
+
end
|