Skip to content

Commit

Permalink
feat: support conic gradient
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Jun 22, 2021
1 parent f986a19 commit 850b6ee
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 15 deletions.
17 changes: 17 additions & 0 deletions __test__/draw.spec.ts
Expand Up @@ -274,6 +274,23 @@ test('createRadialGradient', async (t) => {
await snapshotImage(t)
})

test('createConicGradient', async (t) => {
const { ctx } = t.context
const gradient = ctx.createConicGradient(0, 100, 100)

// Add five color stops
gradient.addColorStop(0, 'red')
gradient.addColorStop(0.25, 'orange')
gradient.addColorStop(0.5, 'yellow')
gradient.addColorStop(0.75, 'green')
gradient.addColorStop(1, 'blue')

// Set the fill style and draw a rectangle
ctx.fillStyle = gradient
ctx.fillRect(20, 20, 200, 200)
await snapshotImage(t)
})

test('drawImage', async (t) => {
const { ctx } = t.context
const filePath = './snapshots/drawImage.png'
Expand Down
Binary file added __test__/snapshots/createConicGradient.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion index.d.ts
Expand Up @@ -239,6 +239,12 @@ export interface StrokeOptions {
}

export interface SKRSContext2D extends Omit<CanvasRenderingContext2D, 'drawImage' | 'createPattern' | 'getTransform'> {
/**
* @param startAngle The angle at which to begin the gradient, in radians. Angle measurements start vertically above the centre and move around clockwise.
* @param x The x-axis coordinate of the centre of the gradient.
* @param y The y-axis coordinate of the centre of the gradient.
*/
createConicGradient(startAngle: number, x: number, y: number): CanvasGradient
drawImage(image: Image, dx: number, dy: number): void
drawImage(image: Image, dx: number, dy: number, dw: number, dh: number): void
drawImage(
Expand Down Expand Up @@ -267,7 +273,11 @@ export interface SKRSContext2D extends Omit<CanvasRenderingContext2D, 'drawImage
}
}

export interface Canvas extends Omit<HTMLCanvasElement, 'getContext'> {
export interface Canvas
extends Omit<
HTMLCanvasElement,
'getContext' | 'transferControlToOffscreen' | 'addEventListener' | 'removeEventListener'
> {
getContext(contextType: '2d', contextAttributes?: { alpha: boolean }): SKRSContext2D
png(): Promise<Buffer>
jpeg(quality: number): Promise<Buffer>
Expand Down
39 changes: 38 additions & 1 deletion skia-c/skia_c.cpp
Expand Up @@ -788,7 +788,7 @@ extern "C"
}
}

skiac_shader *skiac_shader_make_two_point_conical_gradient(
skiac_shader *skiac_shader_make_radial_gradient(
skiac_point c_start_point,
float start_radius,
skiac_point c_end_point,
Expand Down Expand Up @@ -826,6 +826,43 @@ extern "C"
}
}

skiac_shader *skiac_shader_make_conic_gradient(
SkScalar cx,
SkScalar cy,
SkScalar radius,
const uint32_t *colors,
const float *positions,
int count,
int tile_mode,
uint32_t flags,
skiac_transform c_ts)
{
auto ts = conv_from_transform(c_ts);
// Skia's sweep gradient angles are relative to the x-axis, not the y-axis.
ts.preRotate(radius - 90.0, cx, cy);
auto shader = SkGradientShader::MakeSweep(
cx,
cy,
colors,
positions,
count,
(SkTileMode)tile_mode,
radius,
360.0,
flags,
&ts)
.release();

if (shader)
{
return reinterpret_cast<skiac_shader *>(shader);
}
else
{
return nullptr;
}
}

skiac_shader *skiac_shader_make_from_surface_image(
skiac_surface *c_surface,
skiac_transform c_ts,
Expand Down
12 changes: 11 additions & 1 deletion skia-c/skia_c.hpp
Expand Up @@ -265,7 +265,7 @@ extern "C"
int tile_mode,
uint32_t flags,
skiac_transform c_ts);
skiac_shader *skiac_shader_make_two_point_conical_gradient(
skiac_shader *skiac_shader_make_radial_gradient(
skiac_point start_point,
float start_radius,
skiac_point end_point,
Expand All @@ -276,6 +276,16 @@ extern "C"
int tile_mode,
uint32_t flags,
skiac_transform c_ts);
skiac_shader *skiac_shader_make_conic_gradient(
float cx,
float cy,
float radius,
const uint32_t *colors,
const float *positions,
int count,
int tile_mode,
uint32_t flags,
skiac_transform c_ts);
skiac_shader *skiac_shader_make_from_surface_image(
skiac_surface *c_surface,
skiac_transform c_ts,
Expand Down
10 changes: 10 additions & 0 deletions src/ctx.rs
Expand Up @@ -102,6 +102,7 @@ impl Context {
Property::new(env, "closePath")?.with_method(close_path),
Property::new(env, "createLinearGradient")?.with_method(create_linear_gradient),
Property::new(env, "createRadialGradient")?.with_method(create_radial_gradient),
Property::new(env, "createConicGradient")?.with_method(create_conic_gradient),
Property::new(env, "drawImage")?.with_method(draw_image),
Property::new(env, "getContextAttributes")?.with_method(get_context_attributes),
Property::new(env, "isPointInPath")?.with_method(is_point_in_path),
Expand Down Expand Up @@ -773,6 +774,15 @@ fn create_radial_gradient(ctx: CallContext) -> Result<JsObject> {
radial_gradient.into_js_instance(ctx.env)
}

#[js_function(3)]
fn create_conic_gradient(ctx: CallContext) -> Result<JsObject> {
let r: f64 = ctx.get::<JsNumber>(0)?.try_into()?;
let x: f64 = ctx.get::<JsNumber>(1)?.try_into()?;
let y: f64 = ctx.get::<JsNumber>(2)?.try_into()?;
let conic_gradient = CanvasGradient::create_conic_gradient(x as f32, y as f32, r as f32);
conic_gradient.into_js_instance(ctx.env)
}

#[js_function]
fn close_path(ctx: CallContext) -> Result<JsUndefined> {
let this = ctx.this_unchecked::<JsObject>();
Expand Down
54 changes: 46 additions & 8 deletions src/gradient.rs
Expand Up @@ -12,7 +12,8 @@ use crate::{error::SkError, sk::*};
#[derive(Debug, Clone)]
pub enum CanvasGradient {
Linear(LinearGradient),
Radial(TwoPointConicalGradient),
Radial(RadialGradient),
Conic(ConicGradient),
}

impl CanvasGradient {
Expand All @@ -35,8 +36,8 @@ impl CanvasGradient {
start_point: (x0, y0),
end_point: (x1, y1),
base: Gradient {
colors: Vec::with_capacity(0),
positions: Vec::with_capacity(0),
colors: Vec::new(),
positions: Vec::new(),
tile_mode: TileMode::Clamp,
transform: Transform::default(),
},
Expand All @@ -46,21 +47,35 @@ impl CanvasGradient {

#[inline(always)]
pub fn create_radial_gradient(x0: f32, y0: f32, r0: f32, x1: f32, y1: f32, r1: f32) -> Self {
let radial_gradient = TwoPointConicalGradient {
let radial_gradient = RadialGradient {
start: (x0, y0),
start_radius: r0,
end: (x1, y1),
end_radius: r1,
base: Gradient {
colors: Vec::with_capacity(0),
positions: Vec::with_capacity(0),
colors: Vec::new(),
positions: Vec::new(),
tile_mode: TileMode::Clamp,
transform: Transform::default(),
},
};
Self::Radial(radial_gradient)
}

#[inline(always)]
pub fn create_conic_gradient(x: f32, y: f32, r: f32) -> Self {
Self::Conic(ConicGradient {
center: (x, y),
radius: r,
base: Gradient {
colors: Vec::new(),
positions: Vec::new(),
tile_mode: TileMode::Clamp,
transform: Transform::default(),
},
})
}

#[inline(always)]
pub fn add_color_stop(&mut self, offset: f32, color: Color) {
let (stops, colors) = match self {
Expand All @@ -72,6 +87,10 @@ impl CanvasGradient {
&mut radial_gradient.base.positions,
&mut radial_gradient.base.colors,
),
Self::Conic(conic_gradient) => (
&mut conic_gradient.base.positions,
&mut conic_gradient.base.colors,
),
};
if let Ok(pos) = stops.binary_search_by(|o| o.partial_cmp(&offset).unwrap()) {
colors[pos] = color;
Expand Down Expand Up @@ -142,7 +161,7 @@ impl CanvasGradient {
let sr1 = r1 * scale_factor;
let sr2 = r2 * scale_factor;

let new_radial_gradient = TwoPointConicalGradient {
let new_radial_gradient = RadialGradient {
start: (sx1, sy1),
end: (sx2, sy2),
start_radius: sr1,
Expand All @@ -151,7 +170,26 @@ impl CanvasGradient {
};

Ok(
Shader::new_two_point_conical_gradient(&new_radial_gradient)
Shader::new_radial_gradient(&new_radial_gradient)
.ok_or_else(|| SkError::Generic("Get shader of radial gradient failed".to_owned()))?,
)
}
Self::Conic(ref conic_gradient) => {
let (x, y) = conic_gradient.center;
let r = conic_gradient.radius;

let sx = current_transform.a;
let sy = current_transform.d;
let scale_factor = (f32::abs(sx) + f32::abs(sy)) / 2f32;
let sr = r * scale_factor;
let new_conic_gradient = ConicGradient {
center: (x, y),
radius: sr,
base: conic_gradient.base.clone(),
};

Ok(
Shader::new_conic_gradient(&new_conic_gradient)
.ok_or_else(|| SkError::Generic("Get shader of radial gradient failed".to_owned()))?,
)
}
Expand Down
44 changes: 40 additions & 4 deletions src/sk.rs
Expand Up @@ -504,7 +504,7 @@ mod ffi {
ts: skiac_transform,
) -> *mut skiac_shader;

pub fn skiac_shader_make_two_point_conical_gradient(
pub fn skiac_shader_make_radial_gradient(
start_point: skiac_point,
start_radius: f32,
end_point: skiac_point,
Expand All @@ -517,6 +517,18 @@ mod ffi {
ts: skiac_transform,
) -> *mut skiac_shader;

pub fn skiac_shader_make_conic_gradient(
cx: f32,
cy: f32,
radius: f32,
colors: *const super::Color,
positions: *const f32,
count: i32,
tile_mode: i32,
flags: u32,
ts: skiac_transform,
) -> *mut skiac_shader;

pub fn skiac_shader_make_from_surface_image(
surface: *mut skiac_surface,
ts: skiac_transform,
Expand Down Expand Up @@ -2183,14 +2195,21 @@ pub struct LinearGradient {
}

#[derive(Debug, Clone)]
pub struct TwoPointConicalGradient {
pub struct RadialGradient {
pub start: (f32, f32),
pub start_radius: f32,
pub end: (f32, f32),
pub end_radius: f32,
pub base: Gradient,
}

#[derive(Debug, Clone)]
pub struct ConicGradient {
pub center: (f32, f32),
pub radius: f32,
pub base: Gradient,
}

#[derive(Debug, Clone)]
pub struct Shader(*mut ffi::skiac_shader);

Expand Down Expand Up @@ -2222,7 +2241,7 @@ impl Shader {
}

#[inline]
pub fn new_two_point_conical_gradient(grad: &TwoPointConicalGradient) -> Option<Shader> {
pub fn new_radial_gradient(grad: &RadialGradient) -> Option<Shader> {
let start_point = ffi::skiac_point {
x: grad.start.0,
y: grad.start.1,
Expand All @@ -2233,7 +2252,7 @@ impl Shader {
};

unsafe {
Self::from_ptr(ffi::skiac_shader_make_two_point_conical_gradient(
Self::from_ptr(ffi::skiac_shader_make_radial_gradient(
start_point,
grad.start_radius,
end_point,
Expand All @@ -2248,6 +2267,23 @@ impl Shader {
}
}

#[inline]
pub fn new_conic_gradient(grad: &ConicGradient) -> Option<Shader> {
unsafe {
Self::from_ptr(ffi::skiac_shader_make_conic_gradient(
grad.center.0,
grad.center.1,
grad.radius,
grad.base.colors.as_ptr(),
grad.base.positions.as_ptr(),
grad.base.colors.len() as i32,
grad.base.tile_mode as i32,
0_u32,
grad.base.transform.into(),
))
}
}

#[inline]
pub fn new_from_surface_image(
surface: &Surface,
Expand Down

1 comment on commit 850b6ee

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 850b6ee Previous: f986a19 Ratio
Draw house#@napi-rs/skia 29 ops/sec (±0.1%) 26 ops/sec (±0.05%) 0.90
Draw house#node-canvas 24 ops/sec (±0.25%) 21 ops/sec (±0.37%) 0.88
Draw gradient#@napi-rs/skia 28 ops/sec (±0.09%) 25 ops/sec (±0.18%) 0.89
Draw gradient#node-canvas 23 ops/sec (±1.1%) 20 ops/sec (±0.25%) 0.87

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.