Skip to content

Commit e1ee357

Browse files
authored
fixes, updates (#25)
1 parent ce6130f commit e1ee357

File tree

12 files changed

+303
-10
lines changed

12 files changed

+303
-10
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ For non-standard APIs, refer to the API docs mentioned above.
6868
- `Fonts` - provides utility to manage fonts used by Skia
6969
- `PdfDocument` - create PDF documents using 2D Canvas API
7070
- `SvgCanvas` - like `Canvas` but creates an SVG as output instead
71+
- Several additional methods in `Path2D` object such as `toSVGString`,
72+
`simplify`, `difference`, `xor`, etc.
7173

7274
## Benchmarks
7375

native/include/path2d.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#pragma once
22

3+
#include "include/core/SkString.h"
34
#include "include/core/SkPath.h"
5+
#include "include/core/SkPaint.h"
46
#include "include/utils/SkParsePath.h"
57
#include "include/core/SkMatrix.h"
8+
#include "include/pathops/SkPathOps.h"
69
#include "include/common.hpp"
710

811
extern "C" {
@@ -20,5 +23,12 @@ extern "C" {
2023
SKIA_EXPORT void sk_path_arc(SkPath* path, float x, float y, float radius, float startAngle, float endAngle, bool clockwise);
2124
SKIA_EXPORT void sk_path_bezier_curve_to(SkPath* path, float cp1x, float cp1y, float cp2x, float cp2y, float x, float y);
2225
SKIA_EXPORT void sk_path_quadratic_curve_to(SkPath* path, float cpx, float cpy, float x, float y);
26+
SKIA_EXPORT int sk_path_is_point_in_path(SkPath* path, float x, float y, int rule);
27+
SKIA_EXPORT int sk_path_is_point_in_stroke(SkPath* path, float x, float y, float strokeWidth);
28+
SKIA_EXPORT SkString* sk_path_to_svg(SkPath* path, const char** outString, int* outSize);
29+
SKIA_EXPORT void sk_free_string(SkString* str);
30+
SKIA_EXPORT int sk_path_simplify(SkPath* path);
31+
SKIA_EXPORT int sk_path_as_winding(SkPath* path);
32+
SKIA_EXPORT int sk_path_op(SkPath* p1, SkPath* p2, int op);
2333
SKIA_EXPORT void sk_path_destroy(SkPath* path);
2434
}

native/src/context2d.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -863,14 +863,13 @@ extern "C" {
863863
// Context.isPointInPath()
864864
int sk_context_is_point_in_path(sk_context* context, float x, float y, SkPath* path, int rule) {
865865
if (path == nullptr) path = context->path;
866-
path->setFillType(rule == 1 ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding);
867-
return (int) path->contains(x, y);
866+
return sk_path_is_point_in_path(path, x, y, rule);
868867
}
869868

870869
// Context.isPointInStroke()
871870
int sk_context_is_point_in_stroke(sk_context* context, float x, float y, SkPath* path) {
872871
if (path == nullptr) path = context->path;
873-
return (int) path->contains(x, y);
872+
return sk_path_is_point_in_stroke(path, x, y, context->state->paint->getStrokeWidth());
874873
}
875874

876875
/// Transformations
@@ -1085,6 +1084,10 @@ extern "C" {
10851084
context->state->paint,
10861085
SkCanvas::kFast_SrcRectConstraint
10871086
);
1087+
1088+
if (canvas != nullptr) {
1089+
image->unref();
1090+
}
10881091
}
10891092

10901093
/// Pixel manipulation

native/src/path2d.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,55 @@ extern "C" {
114114
path->quadTo(cpx, cpy, x, y);
115115
}
116116

117+
int sk_path_is_point_in_path(SkPath* path, float x, float y, int rule) {
118+
auto prev = path->getFillType();
119+
path->setFillType(rule == 1 ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding);
120+
auto result = (int) path->contains(x, y);
121+
path->setFillType(prev);
122+
return result;
123+
}
124+
125+
int sk_path_is_point_in_stroke(SkPath* path, float x, float y, float strokeWidth) {
126+
auto prev = path->getFillType();
127+
path->setFillType(SkPathFillType::kWinding);
128+
SkPaint paint;
129+
paint.setStyle(SkPaint::kStroke_Style);
130+
paint.setStrokeWidth(strokeWidth);
131+
SkPath traced;
132+
int result;
133+
if (paint.getFillPath(*path, &traced, nullptr, 0.3)) {
134+
result = (int)traced.contains(x, y);
135+
} else {
136+
result = (int)path->contains(x, y);
137+
}
138+
path->setFillType(prev);
139+
return result;
140+
}
141+
142+
SkString* sk_path_to_svg(SkPath* path, const char** outString, int* outSize) {
143+
auto string = new SkString();
144+
SkParsePath::ToSVGString(*path, string);
145+
*outString = string->c_str();
146+
*outSize = string->size();
147+
return string;
148+
}
149+
150+
int sk_path_simplify(SkPath* path) {
151+
return (int) Simplify(*path, path);
152+
}
153+
154+
int sk_path_as_winding(SkPath* path) {
155+
return (int) AsWinding(*path, path);
156+
}
157+
158+
int sk_path_op(SkPath* p1, SkPath* p2, int op) {
159+
return (int) Op(*p1, *p2, (SkPathOp)op, p1);
160+
}
161+
162+
void sk_free_string(SkString* str) {
163+
delete str;
164+
}
165+
117166
void sk_path_destroy(SkPath* path) {
118167
delete path;
119168
}

src/canvas.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,18 @@ export class Canvas {
132132

133133
const size = OUT_SIZE[0];
134134
const ptr = OUT_DATA[0];
135-
const buffer = new Uint8Array(getBuffer(bufptr, size));
135+
const buffer = new Uint8Array(getBuffer(bufptr, 0, size));
136136
SK_DATA_FINALIZER.register(buffer, ptr);
137137
return buffer;
138138
}
139139

140+
toDataURL(format: ImageFormat = "png", quality = 100) {
141+
const buffer = this.encode(format, quality);
142+
return `data:image/${format};base64,${
143+
(Deno as any).core.ops.op_base64_encode(buffer)
144+
}`;
145+
}
146+
140147
/**
141148
* Read pixels from the canvas into a buffer.
142149
*/

src/context2d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,9 @@ export class CanvasRenderingContext2D {
970970
asw?: number,
971971
ash?: number,
972972
) {
973+
if (image instanceof Image && image._unsafePointer === 0) {
974+
return;
975+
}
973976
const dx = asx ?? adx;
974977
const dy = asy ?? ady;
975978
const dw = asw ?? adw ?? image.width;

src/ffi.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,41 @@ const SYMBOLS = {
833833
parameters: ["pointer", "i32", "i32"],
834834
result: "void",
835835
},
836+
837+
sk_path_is_point_in_path: {
838+
parameters: ["pointer", "f32", "f32", "i32"],
839+
result: "i32",
840+
},
841+
842+
sk_path_is_point_in_stroke: {
843+
parameters: ["pointer", "f32", "f32", "f32"],
844+
result: "i32",
845+
},
846+
847+
sk_path_to_svg: {
848+
parameters: ["pointer", "buffer", "buffer"],
849+
result: "pointer",
850+
},
851+
852+
sk_free_string: {
853+
parameters: ["pointer"],
854+
result: "void",
855+
},
856+
857+
sk_path_simplify: {
858+
parameters: ["pointer"],
859+
result: "i32",
860+
},
861+
862+
sk_path_as_winding: {
863+
parameters: ["pointer"],
864+
result: "i32",
865+
},
866+
867+
sk_path_op: {
868+
parameters: ["pointer", "pointer", "i32"],
869+
result: "i32",
870+
},
836871
} as const;
837872

838873
const LOCAL_BUILD = Deno.env.get("DENO_SKIA_LOCAL") === "1";
@@ -919,7 +954,11 @@ const {
919954
op_ffi_get_buf,
920955
}: {
921956
op_ffi_cstr_read: (ptr: Deno.PointerValue) => string;
922-
op_ffi_get_buf: (ptr: Deno.PointerValue, size: number) => ArrayBuffer;
957+
op_ffi_get_buf: (
958+
ptr: Deno.PointerValue,
959+
offset: number,
960+
size: number,
961+
) => ArrayBuffer;
923962
} = (Deno as any).core.ops;
924963

925964
export function cstr(str: string) {

src/image.ts

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,120 @@ const SK_IMAGE_FINALIZER = new FinalizationRegistry(
1414
},
1515
);
1616

17-
export class Image {
18-
#ptr: Deno.PointerValue;
17+
export type ImageSource = Uint8Array | string;
18+
19+
export class Image extends EventTarget {
20+
#token: { ptr: Deno.PointerValue } = { ptr: 0 };
21+
#ptr: Deno.PointerValue = 0;
22+
#src?: ImageSource;
1923

2024
get _unsafePointer() {
2125
return this.#ptr;
2226
}
2327

24-
constructor(data: Uint8Array | string) {
28+
constructor(data?: ImageSource) {
29+
super();
30+
this.src = data;
31+
}
32+
33+
get src() {
34+
return this.#src;
35+
}
36+
37+
set src(data: ImageSource | undefined) {
38+
if (this.#ptr !== 0) {
39+
sk_image_destroy(this.#ptr);
40+
SK_IMAGE_FINALIZER.unregister(this.#token);
41+
this.#ptr = 0;
42+
}
43+
44+
if (data === undefined) {
45+
this.#src = undefined;
46+
this.#ptr = 0;
47+
this.#token.ptr = 0;
48+
return;
49+
}
50+
51+
if (typeof data === "string") {
52+
if (data.match(/^\s*https?:\/\//)) {
53+
fetch(data.trim())
54+
.then((res) => res.arrayBuffer())
55+
.then((buffer) => {
56+
this.src = new Uint8Array(buffer);
57+
})
58+
.catch((error) => {
59+
this.dispatchEvent(
60+
new ErrorEvent("error", {
61+
error,
62+
}),
63+
);
64+
});
65+
return;
66+
} else if (data.match(/^\s*data:/)) {
67+
const comma = data.indexOf(",");
68+
const isBase64 = data.lastIndexOf("base64", comma) !== -1;
69+
const content = data.slice(comma + 1);
70+
const buffer = isBase64
71+
? (Deno as any).core.ops.op_base64_decode(content)
72+
: new TextEncoder().encode(content);
73+
this.src = buffer;
74+
return;
75+
}
76+
}
77+
2578
this.#ptr = data instanceof Uint8Array
2679
? sk_image_from_encoded(data, data.byteLength)
2780
: sk_image_from_file(cstr(data));
81+
2882
if (this.#ptr === 0) {
29-
throw new Error("Failed to load image");
83+
const error = new Error("Failed to load image");
84+
queueMicrotask(() => {
85+
this.dispatchEvent(
86+
new ErrorEvent("error", {
87+
error,
88+
}),
89+
);
90+
});
91+
throw error;
92+
}
93+
94+
this.#token.ptr = this.#ptr;
95+
this.#src = data;
96+
97+
if (this.#ptr !== 0) {
98+
SK_IMAGE_FINALIZER.register(this, this.#ptr, this.#token);
99+
}
100+
101+
queueMicrotask(() => {
102+
this.dispatchEvent(new Event("load"));
103+
});
104+
}
105+
106+
#onload?: EventListenerOrEventListenerObject;
107+
#onerror?: EventListenerOrEventListenerObject;
108+
109+
get onload() {
110+
return this.#onload;
111+
}
112+
113+
get onerror() {
114+
return this.#onerror;
115+
}
116+
117+
set onload(fn: EventListenerOrEventListenerObject | undefined) {
118+
if (this.#onload) {
119+
this.removeEventListener("load", this.#onload);
30120
}
31-
SK_IMAGE_FINALIZER.register(this, this.#ptr);
121+
this.#onload = fn;
122+
if (fn) this.addEventListener("load", fn);
123+
}
124+
125+
set onerror(fn: EventListenerOrEventListenerObject | undefined) {
126+
if (this.#onerror) {
127+
this.removeEventListener("error", this.#onerror);
128+
}
129+
this.#onerror = fn;
130+
if (fn) this.addEventListener("error", fn);
32131
}
33132

34133
static async load(path: string | URL) {
@@ -49,14 +148,19 @@ export class Image {
49148
}
50149

51150
get width() {
151+
if (this._unsafePointer === 0) return 0;
52152
return sk_image_width(this.#ptr);
53153
}
54154

55155
get height() {
156+
if (this._unsafePointer === 0) return 0;
56157
return sk_image_height(this.#ptr);
57158
}
58159

59160
[Symbol.for("Deno.customInspect")]() {
161+
if (this._unsafePointer === 0) {
162+
return `Image { pending, src: ${Deno.inspect(this.src)} }`;
163+
}
60164
return `Image { width: ${this.width}, height: ${this.height} }`;
61165
}
62166
}

0 commit comments

Comments
 (0)