diff --git a/index.d.ts b/index.d.ts
index 4fbbd66d..d458dae8 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,5 +1,8 @@
///
+// Clear all type of caches in Skia
+export function clearAllCache(): void
+
export interface DOMMatrix2DInit {
a: number
b: number
diff --git a/index.js b/index.js
index 06c8bf69..17804dd7 100644
--- a/index.js
+++ b/index.js
@@ -2,6 +2,7 @@ const { platform, homedir } = require('os')
const { join } = require('path')
const {
+ clearAllCache,
CanvasRenderingContext2D,
CanvasElement,
createContext,
@@ -195,6 +196,7 @@ if (!process.env.DISABLE_SYSTEM_FONTS_LOAD) {
}
module.exports = {
+ clearAllCache,
Canvas,
createCanvas,
Path2D,
diff --git a/js-binding.js b/js-binding.js
index 3eb6f14b..4256ead5 100644
--- a/js-binding.js
+++ b/js-binding.js
@@ -196,6 +196,7 @@ if (!nativeBinding) {
}
const {
+ clearAllCache,
CanvasRenderingContext2D,
CanvasElement,
createContext,
@@ -212,6 +213,7 @@ const {
StrokeCap,
} = nativeBinding
+module.exports.clearAllCache = clearAllCache
module.exports.CanvasRenderingContext2D = CanvasRenderingContext2D
module.exports.CanvasElement = CanvasElement
module.exports.createContext = createContext
diff --git a/package.json b/package.json
index fcbcf482..57ca6a5f 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
"benny": "^3.7.1",
"canvas": "^2.9.1",
"canvaskit-wasm": "^0.35.0",
- "colorette": "^2.0.16",
+ "colorette": "^2.0.19",
"conventional-changelog-cli": "^2.2.2",
"echarts": "^5.3.2",
"eslint": "^8.16.0",
@@ -91,7 +91,9 @@
"pinst": "^3.0.0",
"png.js": "^0.2.1",
"prettier": "^2.6.2",
+ "pretty-bytes": "^6.0.0",
"skia-canvas": "^1.0.0",
+ "table": "^6.8.0",
"typescript": "^4.7.2"
},
"lint-staged": {
diff --git a/scripts/debug-memory.mjs b/scripts/debug-memory.mjs
new file mode 100644
index 00000000..94f44928
--- /dev/null
+++ b/scripts/debug-memory.mjs
@@ -0,0 +1,66 @@
+import { join } from 'node:path'
+import { createRequire } from 'node:module'
+import { setTimeout } from 'node:timers/promises'
+
+import { whiteBright, red, green, gray } from 'colorette'
+import prettyBytes from 'pretty-bytes'
+import { table } from 'table'
+
+import { createCanvas, Path2D, clearAllCache } from '../index.js'
+
+function paint() {
+ const require = createRequire(import.meta.url)
+ const tiger = require('../example/tiger.json')
+ const canvas = createCanvas(6016, 3384)
+ const ctx = canvas.getContext('2d')
+ for (const pathObject of tiger) {
+ const p = new Path2D(pathObject.d)
+ ctx.fillStyle = pathObject.fillStyle
+ ctx.strokeStyle = pathObject.strokeStyle
+ if (pathObject.lineWidth) {
+ ctx.lineWidth = parseInt(pathObject.lineWidth, 10)
+ }
+ ctx.stroke(p)
+ ctx.fill(p)
+ }
+}
+
+const initial = process.memoryUsage()
+
+async function main() {
+ for (const [index, _] of Array.from({ length: 100 }).entries()) {
+ displayMemoryUsageFromNode(initial)
+ await setTimeout(100)
+ global?.gc?.()
+ await paint()
+ }
+}
+
+main().then(() => {
+ displayMemoryUsageFromNode(initial)
+ clearAllCache()
+ global?.gc?.()
+ setInterval(() => {
+ displayMemoryUsageFromNode(initial)
+ }, 2000)
+})
+
+function displayMemoryUsageFromNode(initialMemoryUsage) {
+ const finalMemoryUsage = process.memoryUsage()
+ const titles = Object.keys(initialMemoryUsage).map((k) => whiteBright(k))
+ const tableData = [titles]
+ const diffColumn = []
+ for (const [key, value] of Object.entries(initialMemoryUsage)) {
+ const diff = finalMemoryUsage[key] - value
+ const prettyDiff = prettyBytes(diff, { signed: true })
+ if (diff > 0) {
+ diffColumn.push(red(prettyDiff))
+ } else if (diff < 0) {
+ diffColumn.push(green(prettyDiff))
+ } else {
+ diffColumn.push(gray(prettyDiff))
+ }
+ }
+ tableData.push(diffColumn)
+ console.info(table(tableData))
+}
diff --git a/scripts/utils.js b/scripts/utils.js
index 6387fbe4..84be2c12 100644
--- a/scripts/utils.js
+++ b/scripts/utils.js
@@ -7,7 +7,7 @@ const REPO = 'canvas'
const [FULL_HASH] =
process.env.NODE_ENV === 'ava' ? ['000000'] : execSync(`git submodule status skia`).toString('utf8').trim().split(' ')
-const SHORT_HASH = FULL_HASH.substr(0, 8)
+const SHORT_HASH = FULL_HASH.substring(0, 8)
const TAG = `skia-${SHORT_HASH}`
diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp
index ee0074a8..69d77018 100644
--- a/skia-c/skia_c.cpp
+++ b/skia-c/skia_c.cpp
@@ -56,6 +56,11 @@ extern "C"
};
}
+ void skiac_clear_all_cache()
+ {
+ SkGraphics::PurgeAllCaches();
+ }
+
// Surface
static SkSurface *skiac_surface_create(int width, int height, SkAlphaType alphaType, uint8_t cs)
diff --git a/skia-c/skia_c.hpp b/skia-c/skia_c.hpp
index 09886328..fbb48a43 100644
--- a/skia-c/skia_c.hpp
+++ b/skia-c/skia_c.hpp
@@ -211,7 +211,7 @@ struct skiac_mapped_point
extern "C"
{
-
+ void skiac_clear_all_cache();
// Surface
skiac_surface *skiac_surface_create_rgba_premultiplied(int width, int height, uint8_t cs);
void skiac_surface_create_svg(skiac_svg_surface *c_surface, int width, int height, int alphaType, uint32_t flag, uint8_t cs);
diff --git a/src/ctx.rs b/src/ctx.rs
index 7ea470e2..df0c40e2 100644
--- a/src/ctx.rs
+++ b/src/ctx.rs
@@ -42,6 +42,7 @@ impl ImageOrCanvas {
}
pub struct Context {
+ env: Env,
pub(crate) surface: Surface,
path: Path,
pub alpha: bool,
@@ -53,6 +54,17 @@ pub struct Context {
pub stream: Option,
}
+impl Drop for Context {
+ fn drop(&mut self) {
+ if let Err(e) = self
+ .env
+ .adjust_external_memory(-((self.width * self.height * 4) as i64))
+ {
+ eprintln!("{e}");
+ }
+ }
+}
+
impl Context {
pub fn create_js_class(env: &Env) -> Result {
env.define_class(
@@ -242,6 +254,7 @@ impl Context {
}
pub fn new_svg(
+ env: Env,
width: u32,
height: u32,
svg_export_flag: SvgExportFlag,
@@ -256,6 +269,7 @@ impl Context {
)
.ok_or_else(|| Error::from_reason("Create skia svg surface failed".to_owned()))?;
Ok(Context {
+ env,
surface,
alpha: true,
path: Path::new(),
@@ -268,10 +282,11 @@ impl Context {
})
}
- pub fn new(width: u32, height: u32, color_space: ColorSpace) -> Result {
+ pub fn new(env: Env, width: u32, height: u32, color_space: ColorSpace) -> Result {
let surface = Surface::new_rgba_premultiplied(width, height, color_space)
.ok_or_else(|| Error::from_reason("Create skia surface failed".to_owned()))?;
Ok(Context {
+ env,
surface,
alpha: true,
path: Path::new(),
@@ -845,12 +860,21 @@ fn context_2d_constructor(ctx: CallContext) -> Result {
let mut this = ctx.this_unchecked::();
let context_2d = if ctx.length == 3 {
- Context::new(width, height, color_space)?
+ Context::new(*ctx.env, width, height, color_space)?
} else {
// SVG Canvas
let flag = ctx.get::(3)?.get_uint32()?;
- Context::new_svg(width, height, SvgExportFlag::try_from(flag)?, color_space)?
+ Context::new_svg(
+ *ctx.env,
+ width,
+ height,
+ SvgExportFlag::try_from(flag)?,
+ color_space,
+ )?
};
+ ctx
+ .env
+ .adjust_external_memory((width * height * 4) as i64)?;
ctx.env.wrap(&mut this, context_2d)?;
ctx.env.get_undefined()
}
diff --git a/src/lib.rs b/src/lib.rs
index 1f68e473..b2e3015f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -417,3 +417,8 @@ impl SVGCanvas {
}
}
}
+
+#[napi]
+pub fn clear_all_cache() {
+ unsafe { sk::ffi::skiac_clear_all_cache() };
+}
diff --git a/src/sk.rs b/src/sk.rs
index 54d0fb6a..712f67ee 100644
--- a/src/sk.rs
+++ b/src/sk.rs
@@ -13,7 +13,7 @@ use crate::error::SkError;
use crate::font::{FontStretch, FontStyle};
use crate::image::ImageData;
-mod ffi {
+pub mod ffi {
use std::ffi::c_void;
use std::os::raw::c_char;
@@ -247,6 +247,8 @@ mod ffi {
#[link(name = "skiac", kind = "static", cfg(target_os = "windows"))]
extern "C" {
+ pub fn skiac_clear_all_cache();
+
pub fn skiac_surface_create_rgba_premultiplied(
width: i32,
height: i32,
diff --git a/yarn.lock b/yarn.lock
index 4205d64a..0e5607e3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -244,7 +244,7 @@ __metadata:
benny: ^3.7.1
canvas: ^2.9.1
canvaskit-wasm: ^0.35.0
- colorette: ^2.0.16
+ colorette: ^2.0.19
conventional-changelog-cli: ^2.2.2
echarts: ^5.3.2
eslint: ^8.16.0
@@ -259,7 +259,9 @@ __metadata:
pinst: ^3.0.0
png.js: ^0.2.1
prettier: ^2.6.2
+ pretty-bytes: ^6.0.0
skia-canvas: ^1.0.0
+ table: ^6.8.0
typescript: ^4.7.2
languageName: unknown
linkType: soft
@@ -928,6 +930,18 @@ __metadata:
languageName: node
linkType: hard
+"ajv@npm:^8.0.1":
+ version: 8.11.0
+ resolution: "ajv@npm:8.11.0"
+ dependencies:
+ fast-deep-equal: ^3.1.1
+ json-schema-traverse: ^1.0.0
+ require-from-string: ^2.0.2
+ uri-js: ^4.2.2
+ checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef
+ languageName: node
+ linkType: hard
+
"ansi-escapes@npm:^4.3.0":
version: 4.3.2
resolution: "ansi-escapes@npm:4.3.2"
@@ -1579,7 +1593,7 @@ __metadata:
languageName: node
linkType: hard
-"colorette@npm:^2.0.16, colorette@npm:^2.0.17":
+"colorette@npm:^2.0.16, colorette@npm:^2.0.17, colorette@npm:^2.0.19":
version: 2.0.19
resolution: "colorette@npm:2.0.19"
checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427
@@ -3548,6 +3562,13 @@ __metadata:
languageName: node
linkType: hard
+"json-schema-traverse@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "json-schema-traverse@npm:1.0.0"
+ checksum: 02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad
+ languageName: node
+ linkType: hard
+
"json-stable-stringify-without-jsonify@npm:^1.0.1":
version: 1.0.1
resolution: "json-stable-stringify-without-jsonify@npm:1.0.1"
@@ -3781,6 +3802,13 @@ __metadata:
languageName: node
linkType: hard
+"lodash.truncate@npm:^4.4.2":
+ version: 4.4.2
+ resolution: "lodash.truncate@npm:4.4.2"
+ checksum: b463d8a382cfb5f0e71c504dcb6f807a7bd379ff1ea216669aa42c52fc28c54e404bfbd96791aa09e6df0de2c1d7b8f1b7f4b1a61f324d38fe98bc535aeee4f5
+ languageName: node
+ linkType: hard
+
"lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.17.4":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
@@ -4836,6 +4864,13 @@ __metadata:
languageName: node
linkType: hard
+"pretty-bytes@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "pretty-bytes@npm:6.0.0"
+ checksum: 0bb9f95e617236404b29a8392c6efd82d65805f622f5e809ecd70068102be857d4e3276c86d2a32fa2ef851cc29472e380945dab7bec83ec79bd57a19a10faf7
+ languageName: node
+ linkType: hard
+
"pretty-ms@npm:^7.0.1":
version: 7.0.1
resolution: "pretty-ms@npm:7.0.1"
@@ -5025,6 +5060,13 @@ __metadata:
languageName: node
linkType: hard
+"require-from-string@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "require-from-string@npm:2.0.2"
+ checksum: a03ef6895445f33a4015300c426699bc66b2b044ba7b670aa238610381b56d3f07c686251740d575e22f4c87531ba662d06937508f0f3c0f1ddc04db3130560b
+ languageName: node
+ linkType: hard
+
"resolve-cwd@npm:^3.0.0":
version: 3.0.0
resolution: "resolve-cwd@npm:3.0.0"
@@ -5649,6 +5691,19 @@ __metadata:
languageName: node
linkType: hard
+"table@npm:^6.8.0":
+ version: 6.8.0
+ resolution: "table@npm:6.8.0"
+ dependencies:
+ ajv: ^8.0.1
+ lodash.truncate: ^4.4.2
+ slice-ansi: ^4.0.0
+ string-width: ^4.2.3
+ strip-ansi: ^6.0.1
+ checksum: 5b07fe462ee03d2e1fac02cbb578efd2e0b55ac07e3d3db2e950aa9570ade5a4a2b8d3c15e9f25c89e4e50b646bc4269934601ee1eef4ca7968ad31960977690
+ languageName: node
+ linkType: hard
+
"tar@npm:^6.1.11, tar@npm:^6.1.2":
version: 6.1.11
resolution: "tar@npm:6.1.11"