Class: Skia::Surface

Inherits:
Base
  • Object
show all
Defined in:
lib/skia/surface.rb

Instance Attribute Summary collapse

Attributes inherited from Base

#ptr

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#null?, release_callback, #to_ptr

Constructor Details

#initialize(ptr, width, height) ⇒ Surface

Returns a new instance of Surface.



7
8
9
10
11
12
# File 'lib/skia/surface.rb', line 7

def initialize(ptr, width, height)
  super(ptr, :sk_surface_unref)
  @width = width
  @height = height
  @pixel_storage = nil
end

Instance Attribute Details

#heightObject (readonly)

Returns the value of attribute height.



5
6
7
# File 'lib/skia/surface.rb', line 5

def height
  @height
end

#widthObject (readonly)

Returns the value of attribute width.



5
6
7
# File 'lib/skia/surface.rb', line 5

def width
  @width
end

Class Method Details

.build_image_info(width, height, image_info, color_type:, alpha_type:, color_space:) ⇒ Object

Raises:

  • (ArgumentError)


220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/skia/surface.rb', line 220

def self.build_image_info(width, height, image_info, color_type:, alpha_type:, color_space:)
  return image_info if image_info.is_a?(ImageInfo)
  raise ArgumentError, 'width and height are required when image_info is not provided' if width.nil? || height.nil?

  ImageInfo.new(
    width: width,
    height: height,
    color_type: color_type,
    alpha_type: alpha_type,
    color_space: color_space
  )
end

.coerce_pixels(pixels) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
# File 'lib/skia/surface.rb', line 233

def self.coerce_pixels(pixels)
  if pixels.is_a?(FFI::Pointer)
    [pixels, nil]
  elsif pixels.is_a?(String)
    storage = FFI::MemoryPointer.new(:uint8, pixels.bytesize)
    storage.write_bytes(pixels)
    [storage, storage]
  else
    raise ArgumentError, 'pixels must be FFI::Pointer or String'
  end
end

.coerce_pointer(value) ⇒ Object

Raises:

  • (ArgumentError)


245
246
247
248
249
250
# File 'lib/skia/surface.rb', line 245

def self.coerce_pointer(value)
  return value if value.is_a?(FFI::Pointer)
  return FFI::Pointer.new(value) if value.is_a?(Integer)

  raise ArgumentError, 'context must be an FFI::Pointer or integer address'
end

.ensure_native_function!(function_name, api_name) ⇒ Object



252
253
254
255
256
257
# File 'lib/skia/surface.rb', line 252

def self.ensure_native_function!(function_name, api_name)
  return if Native.function_available?(function_name)

  raise UnsupportedOperationError,
        "Surface#{api_name} is not supported by the current libSkiaSharp (missing #{function_name})"
end

.make_backend_render_target(context:, backend_render_target:, width:, height:, origin: :top_left, color_type: :rgba_8888, color_space: nil) ⇒ Object

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/skia/surface.rb', line 61

def self.make_backend_render_target(context:, backend_render_target:, width:, height:, origin: :top_left,
                                    color_type: :rgba_8888, color_space: nil)
  ensure_native_function!(:sk_surface_new_backend_render_target, '.make_backend_render_target')
  ptr = Native.sk_surface_new_backend_render_target(
    coerce_pointer(context),
    coerce_pointer(backend_render_target),
    origin,
    color_type,
    color_space&.ptr,
    nil
  )
  raise Error, 'Failed to create backend render target surface' if ptr.nil? || ptr.null?

  new(ptr, width, height)
end

.make_backend_texture(context:, backend_texture:, width:, height:, sample_count: 0, origin: :top_left, color_type: :rgba_8888, color_space: nil) ⇒ Object

Raises:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/skia/surface.rb', line 77

def self.make_backend_texture(context:, backend_texture:, width:, height:, sample_count: 0, origin: :top_left,
                              color_type: :rgba_8888, color_space: nil)
  ensure_native_function!(:sk_surface_new_backend_texture, '.make_backend_texture')
  ptr = Native.sk_surface_new_backend_texture(
    coerce_pointer(context),
    coerce_pointer(backend_texture),
    origin,
    sample_count.to_i,
    color_type,
    color_space&.ptr,
    nil
  )
  raise Error, 'Failed to create backend texture surface' if ptr.nil? || ptr.null?

  new(ptr, width, height)
end

.make_null(width, height) ⇒ Object

Raises:



35
36
37
38
39
40
# File 'lib/skia/surface.rb', line 35

def self.make_null(width, height)
  ptr = Native.sk_surface_new_null(width.to_i, height.to_i)
  raise Error, 'Failed to create null surface' if ptr.nil? || ptr.null?

  new(ptr, width, height)
end

.make_raster(width = nil, height = nil, image_info: nil, color_type: :rgba_8888, alpha_type: :premul, color_space: nil) ⇒ Object

Raises:



14
15
16
17
18
19
20
21
# File 'lib/skia/surface.rb', line 14

def self.make_raster(width = nil, height = nil, image_info: nil, color_type: :rgba_8888, alpha_type: :premul,
                     color_space: nil)
  info = build_image_info(width, height, image_info, color_type: color_type, alpha_type: alpha_type, color_space: color_space)
  ptr = Native.sk_surface_new_raster(info.to_struct, 0, nil)
  raise Error, 'Failed to create raster surface' if ptr.nil? || ptr.null?

  new(ptr, info.width, info.height)
end

.make_raster_direct(width = nil, height = nil, pixels:, row_bytes:, image_info: nil, color_type: :rgba_8888, alpha_type: :premul, color_space: nil) ⇒ Object

Raises:



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/skia/surface.rb', line 23

def self.make_raster_direct(width = nil, height = nil, pixels:, row_bytes:, image_info: nil, color_type: :rgba_8888,
                            alpha_type: :premul, color_space: nil)
  info = build_image_info(width, height, image_info, color_type: color_type, alpha_type: alpha_type, color_space: color_space)
  pixel_ptr, storage = coerce_pixels(pixels)
  ptr = Native.sk_surface_new_raster_direct(info.to_struct, pixel_ptr, row_bytes, nil, nil, nil)
  raise Error, 'Failed to create direct raster surface' if ptr.nil? || ptr.null?

  surface = new(ptr, info.width, info.height)
  surface.instance_variable_set(:@pixel_storage, storage) if storage
  surface
end

.make_render_target(context:, width: nil, height: nil, image_info: nil, budgeted: true, sample_count: 0, origin: :top_left, create_mips: false, color_type: :rgba_8888, alpha_type: :premul, color_space: nil) ⇒ Object

Raises:



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/skia/surface.rb', line 42

def self.make_render_target(context:, width: nil, height: nil, image_info: nil, budgeted: true, sample_count: 0,
                            origin: :top_left, create_mips: false, color_type: :rgba_8888, alpha_type: :premul,
                            color_space: nil)
  ensure_native_function!(:sk_surface_new_render_target, '.make_render_target')
  info = build_image_info(width, height, image_info, color_type: color_type, alpha_type: alpha_type, color_space: color_space)
  ptr = Native.sk_surface_new_render_target(
    coerce_pointer(context),
    budgeted,
    info.to_struct,
    sample_count.to_i,
    origin,
    nil,
    create_mips
  )
  raise Error, 'Failed to create render target surface' if ptr.nil? || ptr.null?

  new(ptr, info.width, info.height)
end

Instance Method Details

#canvasObject



94
95
96
# File 'lib/skia/surface.rb', line 94

def canvas
  @canvas ||= Canvas.new(Native.sk_surface_get_canvas(@ptr))
end

#draw {|canvas| ... } ⇒ Object

Yields:



134
135
136
137
# File 'lib/skia/surface.rb', line 134

def draw
  yield canvas
  self
end

#encode(format = :png, quality = 100) ⇒ Object

Raises:



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/skia/surface.rb', line 139

def encode(format = :png, quality = 100)
  pixmap = Native.sk_pixmap_new
  raise Error, 'Failed to create pixmap' if pixmap.nil? || pixmap.null?

  begin
    raise Error, 'Failed to peek pixels from surface' unless Native.sk_surface_peek_pixels(@ptr, pixmap)

    stream = Native.sk_dynamicmemorywstream_new
    raise Error, 'Failed to create stream' if stream.nil? || stream.null?

    begin
      success = case format
                when :png
                  options = Native::SKPngEncoderOptions.new
                  options[:fFilterFlags] = :all
                  options[:fZLibLevel] = 6
                  options[:fComments] = nil
                  options[:fICCProfile] = nil
                  options[:fICCProfileDescription] = nil
                  Native.sk_pngencoder_encode(stream, pixmap, options)
                when :jpeg, :jpg
                  options = Native::SKJpegEncoderOptions.new
                  options[:fQuality] = quality
                  options[:fDownsample] = :downsample_420
                  options[:fAlphaOption] = :ignore
                  options[:xmpMetadata] = nil
                  options[:fICCProfile] = nil
                  options[:fICCProfileDescription] = nil
                  Native.sk_jpegencoder_encode(stream, pixmap, options)
                when :webp
                  options = Native::SKWebpEncoderOptions.new
                  options[:fCompression] = :lossy
                  options[:fQuality] = quality.to_f
                  options[:fICCProfile] = nil
                  options[:fICCProfileDescription] = nil
                  Native.sk_webpencoder_encode(stream, pixmap, options)
                else
                  options = Native::SKPngEncoderOptions.new
                  options[:fFilterFlags] = :all
                  options[:fZLibLevel] = 6
                  options[:fComments] = nil
                  options[:fICCProfile] = nil
                  options[:fICCProfileDescription] = nil
                  Native.sk_pngencoder_encode(stream, pixmap, options)
                end

      raise Error, "Failed to encode surface as #{format}" unless success

      data_ptr = Native.sk_dynamicmemorywstream_detach_as_data(stream)
      raise Error, 'Failed to get encoded data' if data_ptr.nil? || data_ptr.null?

      Data.new(data_ptr)
    ensure
      Native.sk_dynamicmemorywstream_destroy(stream)
    end
  ensure
    Native.sk_pixmap_destructor(pixmap)
  end
end

#peek_pixelsObject



110
111
112
113
114
115
# File 'lib/skia/surface.rb', line 110

def peek_pixels
  pixmap = Pixmap.new
  return nil unless Native.sk_surface_peek_pixels(@ptr, pixmap.ptr)

  pixmap
end

#read_pixels(width: @width, height: @height, src_x: 0, src_y: 0, row_bytes: nil, image_info: nil, color_type: :rgba_8888, alpha_type: :premul, color_space: nil) ⇒ Object

Raises:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/skia/surface.rb', line 117

def read_pixels(width: @width, height: @height, src_x: 0, src_y: 0, row_bytes: nil, image_info: nil, color_type: :rgba_8888,
                alpha_type: :premul, color_space: nil)
  info = if image_info
           image_info
         else
           ImageInfo.new(width: width, height: height, color_type: color_type, alpha_type: alpha_type, color_space: color_space)
         end
  row_bytes ||= info.min_row_bytes
  byte_size = row_bytes * info.height
  pixels = FFI::MemoryPointer.new(:uint8, byte_size)

  ok = Native.sk_surface_read_pixels(@ptr, info.to_struct, pixels, row_bytes, src_x.to_i, src_y.to_i)
  raise Error, 'Failed to read pixels from surface' unless ok

  pixels.read_bytes(byte_size)
end

#save(path, format: nil, quality: 100) ⇒ Object



199
200
201
202
203
204
# File 'lib/skia/surface.rb', line 199

def save(path, format: nil, quality: 100)
  format ||= detect_format_from_path(path)
  data = encode(format, quality)
  File.binwrite(path, data.to_s)
  self
end

#save_jpeg(path, quality = 80) ⇒ Object



210
211
212
# File 'lib/skia/surface.rb', line 210

def save_jpeg(path, quality = 80)
  save(path, format: :jpeg, quality: quality)
end

#save_png(path, quality = 100) ⇒ Object



206
207
208
# File 'lib/skia/surface.rb', line 206

def save_png(path, quality = 100)
  save(path, format: :png, quality: quality)
end

#save_webp(path, quality = 80) ⇒ Object



214
215
216
# File 'lib/skia/surface.rb', line 214

def save_webp(path, quality = 80)
  save(path, format: :webp, quality: quality)
end

#snapshot(crop: nil) ⇒ Object

Raises:



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/skia/surface.rb', line 98

def snapshot(crop: nil)
  ptr = if crop
          irect = crop.is_a?(IRect) ? crop : IRect.from_xywh(crop.left, crop.top, crop.width, crop.height)
          Native.sk_surface_new_image_snapshot_with_crop(@ptr, irect.to_struct)
        else
          Native.sk_surface_new_image_snapshot(@ptr)
        end
  raise Error, 'Failed to create image snapshot' if ptr.nil? || ptr.null?

  Image.new(ptr)
end