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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/skia'
|
|
5
|
+
|
|
6
|
+
bounds = Skia::Rect.from_xywh(0, 0, 200, 200)
|
|
7
|
+
picture = Skia::Picture.record(bounds) do |canvas|
|
|
8
|
+
paint = Skia::Paint.new
|
|
9
|
+
paint.antialias = true
|
|
10
|
+
|
|
11
|
+
paint.color = Skia::Color::RED
|
|
12
|
+
paint.style = :fill
|
|
13
|
+
canvas.draw_circle(100, 100, 80, paint)
|
|
14
|
+
|
|
15
|
+
paint.color = Skia::Color::BLUE
|
|
16
|
+
paint.style = :stroke
|
|
17
|
+
paint.stroke_width = 5
|
|
18
|
+
canvas.draw_circle(100, 100, 80, paint)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts 'Recorded picture:'
|
|
22
|
+
puts " Unique ID: #{picture.unique_id}"
|
|
23
|
+
puts " Cull rect: #{picture.cull_rect.inspect}"
|
|
24
|
+
puts " Approximate op count: #{picture.approximate_op_count}"
|
|
25
|
+
puts " Approximate bytes used: #{picture.approximate_bytes_used}"
|
|
26
|
+
|
|
27
|
+
data = picture.serialize
|
|
28
|
+
puts " Serialized size: #{data.size} bytes"
|
|
29
|
+
|
|
30
|
+
loaded_picture = Skia::Picture.from_data(data)
|
|
31
|
+
puts " Loaded picture unique ID: #{loaded_picture.unique_id}"
|
|
32
|
+
|
|
33
|
+
surface = Skia::Surface.make_raster(640, 480)
|
|
34
|
+
surface.draw do |canvas|
|
|
35
|
+
canvas.clear(Skia::Color::WHITE)
|
|
36
|
+
|
|
37
|
+
canvas.with_save do
|
|
38
|
+
canvas.translate(50, 50)
|
|
39
|
+
picture.playback(canvas)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
canvas.with_save do
|
|
43
|
+
canvas.translate(300, 50)
|
|
44
|
+
canvas.scale(0.5, 0.5)
|
|
45
|
+
picture.playback(canvas)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
canvas.with_save do
|
|
49
|
+
canvas.translate(50, 280)
|
|
50
|
+
canvas.scale(1.5, 1.5)
|
|
51
|
+
picture.playback(canvas)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
canvas.with_save do
|
|
55
|
+
canvas.translate(400, 280)
|
|
56
|
+
canvas.rotate(45, 100, 100)
|
|
57
|
+
picture.playback(canvas)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
surface.save_png('picture_output.png')
|
|
62
|
+
puts 'Saved to picture_output.png'
|
|
@@ -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,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/canvas.rb
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
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 clip_rect(rect, op = :intersect, antialias: false)
|
|
80
|
+
rect_struct = rect.to_struct
|
|
81
|
+
Native.sk_canvas_clip_rect_with_operation(@ptr, rect_struct, op, antialias)
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def clip_path(path, op = :intersect, antialias: false)
|
|
86
|
+
Native.sk_canvas_clip_path_with_operation(@ptr, path.ptr, op, antialias)
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def clear(color = Color::TRANSPARENT)
|
|
91
|
+
color_value = color.is_a?(Color) ? color.to_i : color
|
|
92
|
+
Native.sk_canvas_clear(@ptr, color_value)
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def draw_color(color, blend_mode = :src_over)
|
|
97
|
+
color_value = color.is_a?(Color) ? color.to_i : color
|
|
98
|
+
Native.sk_canvas_draw_color(@ptr, color_value, blend_mode)
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def draw_paint(paint)
|
|
103
|
+
Native.sk_canvas_draw_paint(@ptr, paint.ptr)
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def draw_rect(rect, paint)
|
|
108
|
+
rect_struct = rect.to_struct
|
|
109
|
+
Native.sk_canvas_draw_rect(@ptr, rect_struct, paint.ptr)
|
|
110
|
+
self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def draw_round_rect(rect, radius, paint)
|
|
114
|
+
rect_struct = rect.to_struct
|
|
115
|
+
Native.sk_canvas_draw_round_rect(@ptr, rect_struct, radius.to_f, radius.to_f, paint.ptr)
|
|
116
|
+
self
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def draw_circle(cx, cy, radius, paint)
|
|
120
|
+
Native.sk_canvas_draw_circle(@ptr, cx.to_f, cy.to_f, radius.to_f, paint.ptr)
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def draw_oval(rect, paint)
|
|
125
|
+
rect_struct = rect.to_struct
|
|
126
|
+
Native.sk_canvas_draw_oval(@ptr, rect_struct, paint.ptr)
|
|
127
|
+
self
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def draw_path(path, paint)
|
|
131
|
+
Native.sk_canvas_draw_path(@ptr, path.ptr, paint.ptr)
|
|
132
|
+
self
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def draw_line(x1, y1, x2, y2, paint)
|
|
136
|
+
Native.sk_canvas_draw_line(@ptr, x1.to_f, y1.to_f, x2.to_f, y2.to_f, paint.ptr)
|
|
137
|
+
self
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def draw_image(image, x, y, paint = nil)
|
|
141
|
+
Native.sk_canvas_draw_image(@ptr, image.ptr, x.to_f, y.to_f, paint&.ptr)
|
|
142
|
+
self
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def draw_image_rect(image, src_rect, dst_rect, paint = nil)
|
|
146
|
+
src_struct = src_rect&.to_struct
|
|
147
|
+
dst_struct = dst_rect.to_struct
|
|
148
|
+
Native.sk_canvas_draw_image_rect(@ptr, image.ptr, src_struct, dst_struct, paint&.ptr)
|
|
149
|
+
self
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def draw_text(text, x, y, font, paint)
|
|
153
|
+
text_bytes = text.encode('UTF-8')
|
|
154
|
+
Native.sk_canvas_draw_simple_text(@ptr, text_bytes, text_bytes.bytesize, :utf8, x.to_f, y.to_f, font.ptr,
|
|
155
|
+
paint.ptr)
|
|
156
|
+
self
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def with_save
|
|
160
|
+
count = save
|
|
161
|
+
begin
|
|
162
|
+
yield self
|
|
163
|
+
ensure
|
|
164
|
+
restore_to_count(count)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def with_save_layer(bounds = nil, paint = nil)
|
|
169
|
+
count = save_layer(bounds, paint)
|
|
170
|
+
begin
|
|
171
|
+
yield self
|
|
172
|
+
ensure
|
|
173
|
+
restore_to_count(count)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/skia/color.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Color
|
|
5
|
+
attr_reader :value
|
|
6
|
+
|
|
7
|
+
def initialize(r_or_value = 0, g = nil, b = nil, a = 255)
|
|
8
|
+
@value = if g.nil? && b.nil?
|
|
9
|
+
r_or_value.is_a?(Integer) ? r_or_value : 0xFF000000
|
|
10
|
+
else
|
|
11
|
+
((a & 0xFF) << 24) | ((r_or_value & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.argb(a, r, g, b)
|
|
16
|
+
new(r, g, b, a)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.rgb(r, g, b)
|
|
20
|
+
new(r, g, b, 255)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.from_hex(hex_string)
|
|
24
|
+
hex = hex_string.delete_prefix('#')
|
|
25
|
+
case hex.length
|
|
26
|
+
when 6
|
|
27
|
+
new(hex.to_i(16) | 0xFF000000)
|
|
28
|
+
when 8
|
|
29
|
+
new(hex.to_i(16))
|
|
30
|
+
else
|
|
31
|
+
raise ArgumentError, "Invalid hex color format: #{hex_string}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def alpha
|
|
36
|
+
(@value >> 24) & 0xFF
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def red
|
|
40
|
+
(@value >> 16) & 0xFF
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def green
|
|
44
|
+
(@value >> 8) & 0xFF
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def blue
|
|
48
|
+
@value & 0xFF
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_i
|
|
52
|
+
@value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_hex
|
|
56
|
+
format('#%08X', @value)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def with_alpha(a)
|
|
60
|
+
self.class.new(red, green, blue, a)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def ==(other)
|
|
64
|
+
return false unless other.is_a?(Color)
|
|
65
|
+
|
|
66
|
+
@value == other.value
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
TRANSPARENT = new(0x00000000)
|
|
70
|
+
BLACK = new(0xFF000000)
|
|
71
|
+
WHITE = new(0xFFFFFFFF)
|
|
72
|
+
RED = new(0xFFFF0000)
|
|
73
|
+
GREEN = new(0xFF00FF00)
|
|
74
|
+
BLUE = new(0xFF0000FF)
|
|
75
|
+
YELLOW = new(0xFFFFFF00)
|
|
76
|
+
CYAN = new(0xFF00FFFF)
|
|
77
|
+
MAGENTA = new(0xFFFF00FF)
|
|
78
|
+
GRAY = new(0xFF808080)
|
|
79
|
+
LIGHT_GRAY = new(0xFFC0C0C0)
|
|
80
|
+
DARK_GRAY = new(0xFF404040)
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/skia/data.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skia
|
|
4
|
+
class Data < Base
|
|
5
|
+
def initialize(ptr_or_bytes = nil)
|
|
6
|
+
case ptr_or_bytes
|
|
7
|
+
when FFI::Pointer
|
|
8
|
+
super(ptr_or_bytes, :sk_data_unref)
|
|
9
|
+
when String
|
|
10
|
+
ptr = Native.sk_data_new_with_copy(ptr_or_bytes, ptr_or_bytes.bytesize)
|
|
11
|
+
raise Error, 'Failed to create data from bytes' if ptr.nil? || ptr.null?
|
|
12
|
+
|
|
13
|
+
super(ptr, :sk_data_unref)
|
|
14
|
+
when nil
|
|
15
|
+
raise ArgumentError, 'Data requires bytes or pointer'
|
|
16
|
+
else
|
|
17
|
+
raise ArgumentError, "Invalid argument type: #{ptr_or_bytes.class}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.from_file(path)
|
|
22
|
+
ptr = Native.sk_data_new_from_file(path)
|
|
23
|
+
raise FileNotFoundError, "Failed to load file: #{path}" if ptr.nil? || ptr.null?
|
|
24
|
+
|
|
25
|
+
new(ptr)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.from_bytes(bytes)
|
|
29
|
+
new(bytes)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def size
|
|
33
|
+
Native.sk_data_get_size(@ptr)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def empty?
|
|
37
|
+
size == 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_s
|
|
41
|
+
data_ptr = Native.sk_data_get_data(@ptr)
|
|
42
|
+
data_ptr.read_bytes(size)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias to_bytes to_s
|
|
46
|
+
end
|
|
47
|
+
end
|