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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE +21 -0
  4. data/README.md +205 -0
  5. data/Rakefile +8 -0
  6. data/examples/advanced_features.rb +50 -0
  7. data/examples/avatar_generator.rb +74 -0
  8. data/examples/bar_chart.rb +89 -0
  9. data/examples/basic_drawing.rb +43 -0
  10. data/examples/gradient.rb +32 -0
  11. data/examples/pdf_output.rb +48 -0
  12. data/examples/pdf_stream.rb +23 -0
  13. data/examples/picture_recording.rb +62 -0
  14. data/examples/progress_gauge.rb +103 -0
  15. data/examples/runtime_effect.rb +26 -0
  16. data/examples/social_card.rb +121 -0
  17. data/examples/text_drawing.rb +40 -0
  18. data/lib/skia/base.rb +40 -0
  19. data/lib/skia/bitmap.rb +140 -0
  20. data/lib/skia/canvas.rb +239 -0
  21. data/lib/skia/color.rb +82 -0
  22. data/lib/skia/color_filter.rb +23 -0
  23. data/lib/skia/color_space.rb +44 -0
  24. data/lib/skia/data.rb +47 -0
  25. data/lib/skia/document.rb +222 -0
  26. data/lib/skia/font.rb +118 -0
  27. data/lib/skia/image.rb +216 -0
  28. data/lib/skia/image_filter.rb +29 -0
  29. data/lib/skia/image_info.rb +59 -0
  30. data/lib/skia/mask_filter.rb +26 -0
  31. data/lib/skia/matrix.rb +163 -0
  32. data/lib/skia/native/callbacks.rb +6 -0
  33. data/lib/skia/native/functions.rb +384 -0
  34. data/lib/skia/native/types.rb +400 -0
  35. data/lib/skia/native.rb +67 -0
  36. data/lib/skia/paint.rb +144 -0
  37. data/lib/skia/path.rb +166 -0
  38. data/lib/skia/path_effect.rb +30 -0
  39. data/lib/skia/picture.rb +120 -0
  40. data/lib/skia/pixmap.rb +109 -0
  41. data/lib/skia/point.rb +94 -0
  42. data/lib/skia/rect.rb +179 -0
  43. data/lib/skia/rrect.rb +139 -0
  44. data/lib/skia/runtime_effect.rb +88 -0
  45. data/lib/skia/shader.rb +145 -0
  46. data/lib/skia/surface.rb +272 -0
  47. data/lib/skia/text_blob.rb +47 -0
  48. data/lib/skia/typeface.rb +175 -0
  49. data/lib/skia/version.rb +5 -0
  50. data/lib/skia.rb +42 -0
  51. data/skia-ruby.gemspec +30 -0
  52. 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
@@ -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
@@ -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