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.
Potentially problematic release.
This version of skia might be problematic. Click here for more details.
- 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
|