skia 1.0.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 +205 -0
- data/Rakefile +8 -0
- data/examples/advanced_features.rb +50 -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/pdf_stream.rb +23 -0
- data/examples/picture_recording.rb +62 -0
- data/examples/progress_gauge.rb +103 -0
- data/examples/runtime_effect.rb +26 -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/bitmap.rb +140 -0
- data/lib/skia/canvas.rb +239 -0
- data/lib/skia/color.rb +82 -0
- data/lib/skia/color_filter.rb +23 -0
- data/lib/skia/color_space.rb +44 -0
- data/lib/skia/data.rb +47 -0
- data/lib/skia/document.rb +222 -0
- data/lib/skia/font.rb +118 -0
- data/lib/skia/image.rb +216 -0
- data/lib/skia/image_filter.rb +29 -0
- data/lib/skia/image_info.rb +59 -0
- data/lib/skia/mask_filter.rb +26 -0
- data/lib/skia/matrix.rb +163 -0
- data/lib/skia/native/callbacks.rb +6 -0
- data/lib/skia/native/functions.rb +384 -0
- data/lib/skia/native/types.rb +400 -0
- data/lib/skia/native.rb +67 -0
- data/lib/skia/paint.rb +144 -0
- data/lib/skia/path.rb +166 -0
- data/lib/skia/path_effect.rb +30 -0
- data/lib/skia/picture.rb +120 -0
- data/lib/skia/pixmap.rb +109 -0
- data/lib/skia/point.rb +94 -0
- data/lib/skia/rect.rb +179 -0
- data/lib/skia/rrect.rb +139 -0
- data/lib/skia/runtime_effect.rb +88 -0
- data/lib/skia/shader.rb +145 -0
- data/lib/skia/surface.rb +272 -0
- data/lib/skia/text_blob.rb +47 -0
- data/lib/skia/typeface.rb +175 -0
- data/lib/skia/version.rb +5 -0
- data/lib/skia.rb +42 -0
- data/skia-ruby.gemspec +30 -0
- metadata +107 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/skia'
|
|
5
|
+
|
|
6
|
+
def draw_circular_progress(canvas, cx, cy, radius, progress, label, color)
|
|
7
|
+
paint = Skia::Paint.new
|
|
8
|
+
paint.antialias = true
|
|
9
|
+
paint.style = :stroke
|
|
10
|
+
paint.stroke_width = 12
|
|
11
|
+
paint.stroke_cap = :round
|
|
12
|
+
|
|
13
|
+
paint.color = Skia::Color.rgb(230, 230, 230)
|
|
14
|
+
canvas.draw_circle(cx, cy, radius, paint)
|
|
15
|
+
|
|
16
|
+
paint.color = color
|
|
17
|
+
rect = Skia::Rect.from_xywh(cx - radius, cy - radius, radius * 2, radius * 2)
|
|
18
|
+
|
|
19
|
+
path = Skia::Path.new
|
|
20
|
+
start_angle = -90
|
|
21
|
+
sweep_angle = progress * 360 / 100.0
|
|
22
|
+
path.arc_to_with_oval(rect, start_angle, sweep_angle, true)
|
|
23
|
+
canvas.draw_path(path, paint)
|
|
24
|
+
|
|
25
|
+
paint.style = :fill
|
|
26
|
+
paint.color = Skia::Color.rgb(50, 50, 50)
|
|
27
|
+
font_value = Skia::Font.new(nil, radius * 0.5)
|
|
28
|
+
value_text = "#{progress.to_i}%"
|
|
29
|
+
text_width, = font_value.measure_text(value_text)
|
|
30
|
+
canvas.draw_text(value_text, cx - text_width / 2, cy + radius * 0.15, font_value, paint)
|
|
31
|
+
|
|
32
|
+
font_label = Skia::Font.new(nil, radius * 0.25)
|
|
33
|
+
paint.color = Skia::Color.rgb(120, 120, 120)
|
|
34
|
+
label_width, = font_label.measure_text(label)
|
|
35
|
+
canvas.draw_text(label, cx - label_width / 2, cy + radius + 25, font_label, paint)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def draw_linear_progress(canvas, x, y, width, height, progress, label, color)
|
|
39
|
+
paint = Skia::Paint.new
|
|
40
|
+
paint.antialias = true
|
|
41
|
+
|
|
42
|
+
font = Skia::Font.new(nil, 14.0)
|
|
43
|
+
paint.color = Skia::Color.rgb(80, 80, 80)
|
|
44
|
+
canvas.draw_text(label, x, y - 10, font, paint)
|
|
45
|
+
|
|
46
|
+
percent_text = "#{progress.to_i}%"
|
|
47
|
+
text_width, = font.measure_text(percent_text)
|
|
48
|
+
canvas.draw_text(percent_text, x + width - text_width, y - 10, font, paint)
|
|
49
|
+
|
|
50
|
+
paint.color = Skia::Color.rgb(230, 230, 230)
|
|
51
|
+
bg_rect = Skia::Rect.from_xywh(x, y, width, height)
|
|
52
|
+
canvas.draw_round_rect(bg_rect, height / 2, paint)
|
|
53
|
+
|
|
54
|
+
paint.color = color
|
|
55
|
+
progress_width = width * progress / 100.0
|
|
56
|
+
return unless progress_width > height
|
|
57
|
+
|
|
58
|
+
progress_rect = Skia::Rect.from_xywh(x, y, progress_width, height)
|
|
59
|
+
canvas.draw_round_rect(progress_rect, height / 2, paint)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
WIDTH = 800
|
|
63
|
+
HEIGHT = 500
|
|
64
|
+
|
|
65
|
+
surface = Skia::Surface.make_raster(WIDTH, HEIGHT)
|
|
66
|
+
|
|
67
|
+
surface.draw do |canvas|
|
|
68
|
+
canvas.clear(Skia::Color.rgb(245, 247, 250))
|
|
69
|
+
|
|
70
|
+
paint = Skia::Paint.new
|
|
71
|
+
paint.antialias = true
|
|
72
|
+
|
|
73
|
+
font_title = Skia::Font.new(nil, 28.0)
|
|
74
|
+
paint.color = Skia::Color.rgb(50, 50, 50)
|
|
75
|
+
canvas.draw_text('System Dashboard', 30, 45, font_title, paint)
|
|
76
|
+
|
|
77
|
+
draw_circular_progress(canvas, 130, 180, 70, 78, 'CPU Usage', Skia::Color.rgb(59, 130, 246))
|
|
78
|
+
draw_circular_progress(canvas, 310, 180, 70, 45, 'Memory', Skia::Color.rgb(16, 185, 129))
|
|
79
|
+
draw_circular_progress(canvas, 490, 180, 70, 92, 'Disk', Skia::Color.rgb(245, 158, 11))
|
|
80
|
+
draw_circular_progress(canvas, 670, 180, 70, 23, 'Network', Skia::Color.rgb(139, 92, 246))
|
|
81
|
+
|
|
82
|
+
y_start = 320
|
|
83
|
+
bar_height = 16
|
|
84
|
+
bar_spacing = 55
|
|
85
|
+
|
|
86
|
+
tasks = [
|
|
87
|
+
{ label: 'Project Alpha', progress: 85, color: Skia::Color.rgb(59, 130, 246) },
|
|
88
|
+
{ label: 'Project Beta', progress: 60, color: Skia::Color.rgb(16, 185, 129) },
|
|
89
|
+
{ label: 'Project Gamma', progress: 35, color: Skia::Color.rgb(245, 158, 11) }
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
tasks.each_with_index do |task, i|
|
|
93
|
+
draw_linear_progress(
|
|
94
|
+
canvas,
|
|
95
|
+
50, y_start + (i * bar_spacing),
|
|
96
|
+
WIDTH - 100, bar_height,
|
|
97
|
+
task[:progress], task[:label], task[:color]
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
surface.save_png('dashboard.png')
|
|
103
|
+
puts 'Saved to dashboard.png'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/skia'
|
|
5
|
+
|
|
6
|
+
sksl = <<~SKSL
|
|
7
|
+
half4 main(float2 coord) {
|
|
8
|
+
half r = coord.x / 640.0;
|
|
9
|
+
half g = coord.y / 360.0;
|
|
10
|
+
return half4(r, g, 0.35, 1.0);
|
|
11
|
+
}
|
|
12
|
+
SKSL
|
|
13
|
+
|
|
14
|
+
effect = Skia::RuntimeEffect.make_for_shader(sksl)
|
|
15
|
+
shader = effect.make_shader
|
|
16
|
+
|
|
17
|
+
surface = Skia::Surface.make_raster(640, 360)
|
|
18
|
+
paint = Skia::Paint.new
|
|
19
|
+
paint.shader = shader
|
|
20
|
+
|
|
21
|
+
surface.draw do |canvas|
|
|
22
|
+
canvas.draw_rect(Skia::Rect.from_wh(640, 360), paint)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
surface.save_png('runtime_effect.png')
|
|
26
|
+
puts 'Saved runtime_effect.png'
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/skia'
|
|
5
|
+
|
|
6
|
+
def generate_social_card(
|
|
7
|
+
title:,
|
|
8
|
+
author: nil,
|
|
9
|
+
site_name: nil,
|
|
10
|
+
tags: [],
|
|
11
|
+
output: 'social_card.png'
|
|
12
|
+
)
|
|
13
|
+
width = 1200
|
|
14
|
+
height = 630
|
|
15
|
+
|
|
16
|
+
surface = Skia::Surface.make_raster(width, height)
|
|
17
|
+
|
|
18
|
+
surface.draw do |canvas|
|
|
19
|
+
paint = Skia::Paint.new
|
|
20
|
+
paint.antialias = true
|
|
21
|
+
|
|
22
|
+
gradient = Skia::Shader.linear_gradient(
|
|
23
|
+
Skia::Point.new(0, 0),
|
|
24
|
+
Skia::Point.new(width, height),
|
|
25
|
+
[
|
|
26
|
+
Skia::Color.rgb(102, 126, 234),
|
|
27
|
+
Skia::Color.rgb(118, 75, 162)
|
|
28
|
+
]
|
|
29
|
+
)
|
|
30
|
+
paint.shader = gradient
|
|
31
|
+
canvas.draw_rect(Skia::Rect.from_xywh(0, 0, width, height), paint)
|
|
32
|
+
paint.shader = nil
|
|
33
|
+
|
|
34
|
+
paint.color = Skia::Color.argb(30, 255, 255, 255)
|
|
35
|
+
canvas.draw_circle(100, 100, 200, paint)
|
|
36
|
+
canvas.draw_circle(1100, 500, 250, paint)
|
|
37
|
+
canvas.draw_circle(900, 50, 100, paint)
|
|
38
|
+
|
|
39
|
+
paint.color = Skia::Color::WHITE
|
|
40
|
+
content_rect = Skia::Rect.from_xywh(60, 60, width - 120, height - 120)
|
|
41
|
+
canvas.draw_round_rect(content_rect, 20, paint)
|
|
42
|
+
|
|
43
|
+
paint.color = Skia::Color.rgb(30, 30, 30)
|
|
44
|
+
font_title = Skia::Font.new(nil, 52.0)
|
|
45
|
+
|
|
46
|
+
max_chars_per_line = 28
|
|
47
|
+
title_lines = []
|
|
48
|
+
words = title.split
|
|
49
|
+
current_line = ''
|
|
50
|
+
|
|
51
|
+
words.each do |word|
|
|
52
|
+
test_line = current_line.empty? ? word : "#{current_line} #{word}"
|
|
53
|
+
if test_line.length > max_chars_per_line && !current_line.empty?
|
|
54
|
+
title_lines << current_line
|
|
55
|
+
current_line = word
|
|
56
|
+
else
|
|
57
|
+
current_line = test_line
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
title_lines << current_line unless current_line.empty?
|
|
61
|
+
|
|
62
|
+
title_y = 160
|
|
63
|
+
title_lines.each_with_index do |line, i|
|
|
64
|
+
canvas.draw_text(line, 100, title_y + (i * 70), font_title, paint)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if tags.any?
|
|
68
|
+
font_tag = Skia::Font.new(nil, 24.0)
|
|
69
|
+
tag_y = height - 180
|
|
70
|
+
tag_x = 100
|
|
71
|
+
|
|
72
|
+
tags.each do |tag|
|
|
73
|
+
tag_text = "##{tag}"
|
|
74
|
+
text_width, = font_tag.measure_text(tag_text)
|
|
75
|
+
|
|
76
|
+
paint.color = Skia::Color.rgb(102, 126, 234)
|
|
77
|
+
tag_rect = Skia::Rect.from_xywh(tag_x - 10, tag_y - 25, text_width + 20, 35)
|
|
78
|
+
canvas.draw_round_rect(tag_rect, 17, paint)
|
|
79
|
+
|
|
80
|
+
paint.color = Skia::Color::WHITE
|
|
81
|
+
canvas.draw_text(tag_text, tag_x, tag_y, font_tag, paint)
|
|
82
|
+
|
|
83
|
+
tag_x += text_width + 30
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if author
|
|
88
|
+
font_author = Skia::Font.new(nil, 28.0)
|
|
89
|
+
paint.color = Skia::Color.rgb(100, 100, 100)
|
|
90
|
+
canvas.draw_text(author, 100, height - 100, font_author, paint)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if site_name
|
|
94
|
+
font_site = Skia::Font.new(nil, 24.0)
|
|
95
|
+
paint.color = Skia::Color.rgb(150, 150, 150)
|
|
96
|
+
text_width, = font_site.measure_text(site_name)
|
|
97
|
+
canvas.draw_text(site_name, width - 100 - text_width, height - 100, font_site, paint)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
surface.save_png(output)
|
|
102
|
+
puts "Saved to #{output}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
generate_social_card(
|
|
106
|
+
title: 'Building Ruby Bindings for Skia Graphics Library with FFI',
|
|
107
|
+
author: '@ruby_dev',
|
|
108
|
+
site_name: 'tech.blog',
|
|
109
|
+
tags: %w[Ruby FFI Graphics],
|
|
110
|
+
output: 'social_card_blog.png'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
generate_social_card(
|
|
114
|
+
title: 'Conference Session: High Performance 2D Graphics in Ruby',
|
|
115
|
+
author: 'Speaker Name',
|
|
116
|
+
site_name: 'RubyKaigi',
|
|
117
|
+
tags: %w[RubyKaigi Conference],
|
|
118
|
+
output: 'social_card_event.png'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
puts "\nSocial cards generated!"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/skia"
|
|
5
|
+
|
|
6
|
+
surface = Skia::Surface.make_raster(640, 480)
|
|
7
|
+
|
|
8
|
+
surface.draw do |canvas|
|
|
9
|
+
canvas.clear(Skia::Color::WHITE)
|
|
10
|
+
|
|
11
|
+
font = Skia::Font.new(nil, 48.0)
|
|
12
|
+
|
|
13
|
+
paint = Skia::Paint.new
|
|
14
|
+
paint.antialias = true
|
|
15
|
+
paint.color = Skia::Color::BLACK
|
|
16
|
+
|
|
17
|
+
canvas.draw_text("Hello, Skia!", 50, 100, font, paint)
|
|
18
|
+
|
|
19
|
+
paint.color = Skia::Color::RED
|
|
20
|
+
canvas.draw_text("Red Text", 50, 180, font, paint)
|
|
21
|
+
|
|
22
|
+
paint.color = Skia::Color::BLUE
|
|
23
|
+
canvas.draw_text("Blue Text", 50, 260, font, paint)
|
|
24
|
+
|
|
25
|
+
small_font = Skia::Font.new(nil, 24.0)
|
|
26
|
+
paint.color = Skia::Color::GREEN
|
|
27
|
+
canvas.draw_text("Smaller green text", 50, 320, small_font, paint)
|
|
28
|
+
|
|
29
|
+
width, bounds = font.measure_text("Measured Text")
|
|
30
|
+
paint.color = Skia::Color.argb(100, 255, 200, 0)
|
|
31
|
+
paint.style = :fill
|
|
32
|
+
measured_rect = Skia::Rect.from_xywh(50, 380 + bounds.top, width, bounds.height)
|
|
33
|
+
canvas.draw_rect(measured_rect, paint)
|
|
34
|
+
|
|
35
|
+
paint.color = Skia::Color::BLACK
|
|
36
|
+
canvas.draw_text("Measured Text", 50, 380, font, paint)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
surface.save_png("text_output.png")
|
|
40
|
+
puts "Saved to text_output.png"
|
data/lib/skia/base.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Base
|
|
5
|
+
attr_reader :ptr
|
|
6
|
+
|
|
7
|
+
def initialize(ptr, release_method = nil)
|
|
8
|
+
raise NullPointerError, 'Pointer cannot be nil' if ptr.nil? || ptr.null?
|
|
9
|
+
|
|
10
|
+
@ptr = ptr
|
|
11
|
+
@release_method = release_method
|
|
12
|
+
setup_release if @release_method
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_ptr
|
|
16
|
+
@ptr
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def null?
|
|
20
|
+
@ptr.nil? || @ptr.null?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.release_callback(release_method, ptr)
|
|
24
|
+
proc { Native.send(release_method, ptr) unless ptr.null? }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def setup_release
|
|
30
|
+
ObjectSpace.define_finalizer(self, self.class.release_callback(@release_method, @ptr))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def release!
|
|
34
|
+
return unless @release_method && @ptr && !@ptr.null?
|
|
35
|
+
|
|
36
|
+
Native.send(@release_method, @ptr)
|
|
37
|
+
@ptr = nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/skia/bitmap.rb
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Bitmap < Base
|
|
5
|
+
def initialize(ptr = nil)
|
|
6
|
+
super(ptr || Native.sk_bitmap_new, nil)
|
|
7
|
+
@pixel_storage = nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def info
|
|
11
|
+
info_struct = Native::SKImageInfo.new
|
|
12
|
+
Native.sk_bitmap_get_info(@ptr, info_struct)
|
|
13
|
+
ImageInfo.from_struct(info_struct)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def width
|
|
17
|
+
info.width
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def height
|
|
21
|
+
info.height
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def row_bytes
|
|
25
|
+
Native.sk_bitmap_get_row_bytes(@ptr)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def byte_count
|
|
29
|
+
Native.sk_bitmap_get_byte_count(@ptr)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def pixels_ptr
|
|
33
|
+
Native.sk_bitmap_get_pixels(@ptr, nil)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def pixels
|
|
37
|
+
ptr = pixels_ptr
|
|
38
|
+
return nil if ptr.nil? || ptr.null?
|
|
39
|
+
|
|
40
|
+
ptr.read_bytes(byte_count)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def allocated?
|
|
44
|
+
!Native.sk_bitmap_is_null(@ptr)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def immutable?
|
|
48
|
+
Native.sk_bitmap_is_immutable(@ptr)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def immutable=(value)
|
|
52
|
+
return unless value
|
|
53
|
+
|
|
54
|
+
Native.sk_bitmap_set_immutable(@ptr)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def alloc_pixels(image_info, flags: nil)
|
|
58
|
+
info = coerce_image_info(image_info)
|
|
59
|
+
row_bytes = info.min_row_bytes
|
|
60
|
+
@pixel_storage = nil
|
|
61
|
+
return Native.sk_bitmap_try_alloc_pixels_with_flags(@ptr, info.to_struct, flags.to_i) if flags
|
|
62
|
+
|
|
63
|
+
Native.sk_bitmap_try_alloc_pixels(@ptr, info.to_struct, row_bytes)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def erase(color, rect: nil)
|
|
67
|
+
color_value = color.is_a?(Color) ? color.to_i : color
|
|
68
|
+
if rect
|
|
69
|
+
irect = coerce_irect(rect)
|
|
70
|
+
Native.sk_bitmap_erase_rect(@ptr, color_value, irect.to_struct)
|
|
71
|
+
else
|
|
72
|
+
Native.sk_bitmap_erase(@ptr, color_value)
|
|
73
|
+
end
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def pixel_color(x, y)
|
|
78
|
+
Color.new(Native.sk_bitmap_get_pixel_color(@ptr, x.to_i, y.to_i))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def peek_pixels
|
|
82
|
+
pixmap = Pixmap.new
|
|
83
|
+
return nil unless Native.sk_bitmap_peek_pixels(@ptr, pixmap.ptr)
|
|
84
|
+
|
|
85
|
+
pixmap
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def extract_subset(rect)
|
|
89
|
+
irect = coerce_irect(rect)
|
|
90
|
+
subset = self.class.new
|
|
91
|
+
success = Native.sk_bitmap_extract_subset(subset.ptr, @ptr, irect.to_struct)
|
|
92
|
+
return nil unless success
|
|
93
|
+
|
|
94
|
+
subset
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def make_shader(tile_x: :clamp, tile_y: :clamp, matrix: nil)
|
|
98
|
+
matrix_struct = matrix&.to_struct
|
|
99
|
+
sampling = Native::SKSamplingOptions.new
|
|
100
|
+
sampling[:fMaxAniso] = 0
|
|
101
|
+
sampling[:fUseCubic] = 0
|
|
102
|
+
sampling[:fCubicB] = 0.0
|
|
103
|
+
sampling[:fCubicC] = 0.0
|
|
104
|
+
sampling[:fFilter] = :nearest
|
|
105
|
+
sampling[:fMipmap] = :none
|
|
106
|
+
|
|
107
|
+
ptr = Native.sk_bitmap_make_shader(@ptr, tile_x, tile_y, sampling, matrix_struct)
|
|
108
|
+
raise Error, 'Failed to create shader from bitmap' if ptr.nil? || ptr.null?
|
|
109
|
+
|
|
110
|
+
Shader.new(ptr)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def to_image
|
|
114
|
+
ptr = Native.sk_image_new_from_bitmap(@ptr)
|
|
115
|
+
raise Error, 'Failed to create image from bitmap' if ptr.nil? || ptr.null?
|
|
116
|
+
|
|
117
|
+
Image.new(ptr)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def reset
|
|
121
|
+
Native.sk_bitmap_reset(@ptr)
|
|
122
|
+
@pixel_storage = nil
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def coerce_image_info(image_info)
|
|
129
|
+
return image_info if image_info.is_a?(ImageInfo)
|
|
130
|
+
|
|
131
|
+
raise ArgumentError, 'image_info must be a Skia::ImageInfo'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def coerce_irect(rect)
|
|
135
|
+
return rect if rect.is_a?(IRect)
|
|
136
|
+
|
|
137
|
+
raise ArgumentError, 'rect must be a Skia::IRect'
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/skia/canvas.rb
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Canvas < Base
|
|
5
|
+
def initialize(ptr)
|
|
6
|
+
super(ptr, nil)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def save
|
|
10
|
+
Native.sk_canvas_save(@ptr)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def save_layer(bounds = nil, paint = nil)
|
|
14
|
+
bounds_ptr = bounds&.to_struct
|
|
15
|
+
paint_ptr = paint&.ptr
|
|
16
|
+
Native.sk_canvas_save_layer(@ptr, bounds_ptr, paint_ptr)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def restore
|
|
20
|
+
Native.sk_canvas_restore(@ptr)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def restore_to_count(count)
|
|
24
|
+
Native.sk_canvas_restore_to_count(@ptr, count)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def save_count
|
|
28
|
+
Native.sk_canvas_get_save_count(@ptr)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def translate(dx, dy)
|
|
32
|
+
Native.sk_canvas_translate(@ptr, dx.to_f, dy.to_f)
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def scale(sx, sy = sx)
|
|
37
|
+
Native.sk_canvas_scale(@ptr, sx.to_f, sy.to_f)
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def rotate(degrees, px = nil, py = nil)
|
|
42
|
+
if px && py
|
|
43
|
+
translate(px, py)
|
|
44
|
+
Native.sk_canvas_rotate_degrees(@ptr, degrees.to_f)
|
|
45
|
+
translate(-px, -py)
|
|
46
|
+
else
|
|
47
|
+
Native.sk_canvas_rotate_degrees(@ptr, degrees.to_f)
|
|
48
|
+
end
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def rotate_radians(radians)
|
|
53
|
+
Native.sk_canvas_rotate_radians(@ptr, radians.to_f)
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def skew(sx, sy)
|
|
58
|
+
Native.sk_canvas_skew(@ptr, sx.to_f, sy.to_f)
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def concat(matrix)
|
|
63
|
+
matrix_struct = matrix.to_struct44
|
|
64
|
+
Native.sk_canvas_concat(@ptr, matrix_struct)
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def matrix=(matrix)
|
|
69
|
+
matrix_struct = matrix.to_struct44
|
|
70
|
+
Native.sk_canvas_set_matrix(@ptr, matrix_struct)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def matrix
|
|
74
|
+
m = Native::SKMatrix44.new
|
|
75
|
+
Native.sk_canvas_get_matrix(@ptr, m)
|
|
76
|
+
Matrix.from_struct44(m)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def reset_matrix
|
|
80
|
+
Native.sk_canvas_reset_matrix(@ptr)
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def clip_rect(rect, op = :intersect, antialias: false)
|
|
85
|
+
rect_struct = rect.to_struct
|
|
86
|
+
Native.sk_canvas_clip_rect_with_operation(@ptr, rect_struct, op, antialias)
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def clip_path(path, op = :intersect, antialias: false)
|
|
91
|
+
Native.sk_canvas_clip_path_with_operation(@ptr, path.ptr, op, antialias)
|
|
92
|
+
self
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def clip_rrect(rrect, op = :intersect, antialias: false)
|
|
96
|
+
Native.sk_canvas_clip_rrect_with_operation(@ptr, rrect.ptr, op, antialias)
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def clear(color = Color::TRANSPARENT)
|
|
101
|
+
color_value = color.is_a?(Color) ? color.to_i : color
|
|
102
|
+
Native.sk_canvas_clear(@ptr, color_value)
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def draw_color(color, blend_mode = :src_over)
|
|
107
|
+
color_value = color.is_a?(Color) ? color.to_i : color
|
|
108
|
+
Native.sk_canvas_draw_color(@ptr, color_value, blend_mode)
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def draw_paint(paint)
|
|
113
|
+
Native.sk_canvas_draw_paint(@ptr, paint.ptr)
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def draw_rect(rect, paint)
|
|
118
|
+
rect_struct = rect.to_struct
|
|
119
|
+
Native.sk_canvas_draw_rect(@ptr, rect_struct, paint.ptr)
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def draw_rrect(rrect, paint)
|
|
124
|
+
Native.sk_canvas_draw_rrect(@ptr, rrect.ptr, paint.ptr)
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def draw_round_rect(rect, radius, paint)
|
|
129
|
+
rect_struct = rect.to_struct
|
|
130
|
+
Native.sk_canvas_draw_round_rect(@ptr, rect_struct, radius.to_f, radius.to_f, paint.ptr)
|
|
131
|
+
self
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def draw_arc(oval, start_angle, sweep_angle, use_center, paint)
|
|
135
|
+
oval_struct = oval.to_struct
|
|
136
|
+
Native.sk_canvas_draw_arc(@ptr, oval_struct, start_angle.to_f, sweep_angle.to_f, use_center, paint.ptr)
|
|
137
|
+
self
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def draw_circle(cx, cy, radius, paint)
|
|
141
|
+
Native.sk_canvas_draw_circle(@ptr, cx.to_f, cy.to_f, radius.to_f, paint.ptr)
|
|
142
|
+
self
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def draw_oval(rect, paint)
|
|
146
|
+
rect_struct = rect.to_struct
|
|
147
|
+
Native.sk_canvas_draw_oval(@ptr, rect_struct, paint.ptr)
|
|
148
|
+
self
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def draw_path(path, paint)
|
|
152
|
+
Native.sk_canvas_draw_path(@ptr, path.ptr, paint.ptr)
|
|
153
|
+
self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def draw_picture(picture, matrix = nil, paint = nil)
|
|
157
|
+
matrix_struct = matrix&.to_struct
|
|
158
|
+
Native.sk_canvas_draw_picture(@ptr, picture.ptr, matrix_struct, paint&.ptr)
|
|
159
|
+
self
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def draw_line(x1, y1, x2, y2, paint)
|
|
163
|
+
Native.sk_canvas_draw_line(@ptr, x1.to_f, y1.to_f, x2.to_f, y2.to_f, paint.ptr)
|
|
164
|
+
self
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def draw_point(x, y, paint)
|
|
168
|
+
Native.sk_canvas_draw_point(@ptr, x.to_f, y.to_f, paint.ptr)
|
|
169
|
+
self
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def draw_points(points, paint, mode: :points)
|
|
173
|
+
return self if points.empty?
|
|
174
|
+
|
|
175
|
+
point_structs = points.map { |point| coerce_point(point).to_struct }
|
|
176
|
+
points_ptr = FFI::MemoryPointer.new(Native::SKPoint, point_structs.length)
|
|
177
|
+
|
|
178
|
+
point_structs.each_with_index do |point_struct, index|
|
|
179
|
+
destination = Native::SKPoint.new(points_ptr + (index * Native::SKPoint.size))
|
|
180
|
+
destination[:x] = point_struct[:x]
|
|
181
|
+
destination[:y] = point_struct[:y]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
Native.sk_canvas_draw_points(@ptr, mode, point_structs.length, points_ptr, paint.ptr)
|
|
185
|
+
self
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def draw_image(image, x, y, paint = nil)
|
|
189
|
+
Native.sk_canvas_draw_image(@ptr, image.ptr, x.to_f, y.to_f, paint&.ptr)
|
|
190
|
+
self
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def draw_image_rect(image, src_rect, dst_rect, paint = nil)
|
|
194
|
+
src_struct = src_rect&.to_struct
|
|
195
|
+
dst_struct = dst_rect.to_struct
|
|
196
|
+
Native.sk_canvas_draw_image_rect(@ptr, image.ptr, src_struct, dst_struct, paint&.ptr)
|
|
197
|
+
self
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def draw_text(text, x, y, font, paint)
|
|
201
|
+
text_bytes = text.encode('UTF-8')
|
|
202
|
+
Native.sk_canvas_draw_simple_text(@ptr, text_bytes, text_bytes.bytesize, :utf8, x.to_f, y.to_f, font.ptr,
|
|
203
|
+
paint.ptr)
|
|
204
|
+
self
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def draw_text_blob(blob, x, y, paint)
|
|
208
|
+
Native.sk_canvas_draw_text_blob(@ptr, blob.ptr, x.to_f, y.to_f, paint.ptr)
|
|
209
|
+
self
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def with_save
|
|
213
|
+
count = save
|
|
214
|
+
begin
|
|
215
|
+
yield self
|
|
216
|
+
ensure
|
|
217
|
+
restore_to_count(count)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def with_save_layer(bounds = nil, paint = nil)
|
|
222
|
+
count = save_layer(bounds, paint)
|
|
223
|
+
begin
|
|
224
|
+
yield self
|
|
225
|
+
ensure
|
|
226
|
+
restore_to_count(count)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private
|
|
231
|
+
|
|
232
|
+
def coerce_point(point)
|
|
233
|
+
return point if point.is_a?(Point)
|
|
234
|
+
return Point.new(point[0], point[1]) if point.is_a?(Array) && point.length == 2
|
|
235
|
+
|
|
236
|
+
raise ArgumentError, 'point must be Skia::Point or [x, y]'
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|