Skip to content

Commit

Permalink
fix: image decode should be async (#872)
Browse files Browse the repository at this point in the history
* fix: image decode should be async

* set bitmap immutable
  • Loading branch information
Brooooooklyn committed Aug 15, 2024
1 parent 1666a33 commit 713ca40
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 133 deletions.
7 changes: 2 additions & 5 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,5 @@ rustflags = ["-C", "target-cpu=cortex-a57"]
rustflags = ["-C", "target-cpu=cortex-a7"]

[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = [
"-C",
"target-cpu=cortex-a57",
]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-cpu=cortex-a57"]
1 change: 1 addition & 0 deletions .taplo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ exclude = ["node_modules/**/*.toml", "skia/**/*.toml"]
align_entries = true
indent_tables = true
reorder_keys = true
column_width = 180
40 changes: 18 additions & 22 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,23 @@ version = "0.1.0"
crate-type = ["cdylib"]

[dependencies]
anyhow = "1"
base64 = "0.22"
cssparser = "0.29"
infer = "0.16"
libavif = { version = "0.14", default-features = false, features = [
"codec-aom",
] }
napi = { version = "2", default-features = false, features = [
"napi3",
"serde-json",
] }
napi-derive = { version = "2", default-features = false }
nom = "7"
num_cpus = "1"
once_cell = "1"
regex = "1"
rgb = "0.8"
serde = "1"
anyhow = "1"
base64 = "0.22"
base64-simd = "0.8"
cssparser = "0.29"
infer = "0.16"
libavif = { version = "0.14", default-features = false, features = ["codec-aom"] }
napi = { version = "3.0.0-alpha.8", default-features = false, features = ["napi3", "serde-json"] }
napi-derive = { version = "3.0.0-alpha.7", default-features = false }
nom = "7"
num_cpus = "1"
once_cell = "1"
regex = "1"
rgb = "0.8"
serde = "1"
serde_derive = "1"
serde_json = "1"
thiserror = "1"
serde_json = "1"
thiserror = "1"

[target.'cfg(not(target_os = "linux"))'.dependencies]
mimalloc = "0.1"
Expand All @@ -41,6 +37,6 @@ cc = "1"
napi-build = "2"

[profile.release]
lto = true
codegen-units = 1
strip = "symbols"
lto = true
strip = "symbols"
43 changes: 43 additions & 0 deletions __test__/draw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,12 @@ test('createPattern-no-transform', async (t) => {
const { ctx } = t.context
const imageSrc = await promises.readFile(join(__dirname, 'canvas_createpattern.png'))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = imageSrc
await promise
const pattern = ctx.createPattern(image, 'repeat')
ctx.fillStyle = pattern
ctx.fillRect(0, 0, 300, 300)
Expand All @@ -262,7 +267,12 @@ test('createPattern-with-transform', async (t) => {
const { ctx } = t.context
const imageSrc = await promises.readFile(join(__dirname, 'canvas_createpattern.png'))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = imageSrc
await promise
const pattern = ctx.createPattern(image, 'repeat')
const matrix = new DOMMatrix()
pattern.setTransform(matrix.rotate(-45).scale(1.5))
Expand Down Expand Up @@ -308,7 +318,12 @@ test('drawImage', async (t) => {
const filePath = './javascript.png'
const file = await promises.readFile(join(__dirname, filePath))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
ctx.drawImage(image, 0, 0)
await snapshotImage(t)
})
Expand All @@ -318,7 +333,12 @@ test('drawImage-svg', async (t) => {
const filePath = './mountain.svg'
const file = await promises.readFile(join(__dirname, filePath))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
ctx.drawImage(image, 0, 0)
await snapshotImage(t)
})
Expand All @@ -328,7 +348,12 @@ test('drawImage-svg-with-only-viewBox', async (t) => {
const filePath = './only-viewbox.svg'
const file = await promises.readFile(join(__dirname, filePath))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
ctx.drawImage(image, 0, 0)
await snapshotImage(t)
})
Expand All @@ -338,7 +363,12 @@ test('drawImage-svg-resize', async (t) => {
const filePath = './resize.svg'
const file = await promises.readFile(join(__dirname, filePath))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
image.width = 100
image.height = 100
ctx.drawImage(image, 0, 0)
Expand All @@ -360,7 +390,15 @@ test('drawImage-svg without width height should be empty image', async (t) => {
const filePath = './mountain.svg'
const svgContent = (await promises.readFile(join(__dirname, filePath))).toString('utf-8')
const image = new Image()
const { promise, resolve, reject } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.onerror = (err) => {
reject(err)
}
image.src = Buffer.from(svgContent.replace('width="128"', '').replace('height="128"', ''))
await promise
ctx.drawImage(image, 0, 0)
const output = await canvas.encode('png')
const outputData = png.decoders['image/png'](output)
Expand All @@ -372,7 +410,12 @@ test('draw-image-svg-noto-emoji', async (t) => {
const filePath = './notoemoji-person.svg'
const file = await promises.readFile(join(__dirname, filePath))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
ctx.drawImage(image, 0, 0)
await snapshotImage(t)
})
Expand Down
22 changes: 21 additions & 1 deletion __test__/filter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ const test = ava as TestFn<{

const FIREFOX = readFileSync(join(__dirname, 'fixtures', 'firefox-logo.svg'))
const FIREFOX_IMAGE = new Image(200, 206.433)
const { promise: firefoxImageLoad, resolve } = Promise.withResolvers<void>()
FIREFOX_IMAGE.onload = () => {
resolve()
}
FIREFOX_IMAGE.src = FIREFOX

test.beforeEach((t) => {
test.beforeEach(async (t) => {
const canvas = createCanvas(300, 300)
t.context.canvas = canvas
t.context.ctx = canvas.getContext('2d')
await firefoxImageLoad
})

test('filter-blur', async (t) => {
Expand All @@ -33,7 +38,12 @@ test('filter-brightness', async (t) => {
const { ctx } = t.context
ctx.filter = 'brightness(2)'
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = await fs.readFile(join(__dirname, 'fixtures', 'filter-brightness.jpg'))
await promise
ctx.drawImage(image, 0, 0)
await snapshotImage(t)
})
Expand All @@ -42,7 +52,12 @@ test('filter-contrast', async (t) => {
const { ctx } = t.context
ctx.filter = 'contrast(200%)'
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = await fs.readFile(join(__dirname, 'fixtures', 'filter-contrast.jpeg'))
await promise
ctx.drawImage(image, 0, 0)
await snapshotImage(t)
})
Expand Down Expand Up @@ -122,6 +137,11 @@ test('filter-save-restore', async (t) => {

async function createImage(name: string) {
const i = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
i.onload = () => {
resolve()
}
i.src = await fs.readFile(join(__dirname, 'fixtures', name))
await promise
return i
}
2 changes: 0 additions & 2 deletions __test__/image-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export async function snapshotImage<C>(
if (existedPixels.length !== imagePixels.length) {
await writeFailureImage()
t.fail('Image size is not equal')
return
}
let diffCount = 0
imagePixels.forEach((u8, index) => {
Expand All @@ -58,7 +57,6 @@ export async function snapshotImage<C>(
if (diffCount / existedPixels.length > differentRatio / 100) {
await writeFailureImage()
t.fail(`Image bytes is not equal, different ratio is ${((diffCount / existedPixels.length) * 100).toFixed(2)}%`)
return
}
t.pass('Image pixels is equal')
}
Expand Down
39 changes: 35 additions & 4 deletions __test__/image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,29 @@ test('should be able to create Image', (t) => {

test('should be able to set src with buffer', async (t) => {
const file = await loadImageFile()
t.notThrows(() => {
await t.notThrowsAsync(async () => {
const image = new Image()
const { promise, resolve, reject } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.onerror = (err) => {
reject(err)
}
image.src = file
await promise
})
})

test('width and height state should be ok', async (t) => {
const file = await loadImageFile()
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
t.is(image.width, 300)
t.is(image.height, 320)
t.is(image.naturalWidth, 300)
Expand All @@ -36,8 +49,13 @@ test('width and height state should be ok', async (t) => {
test('complete state should be ok', async (t) => {
const file = await loadImageFile()
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
t.is(image.complete, false)
image.src = file
await promise
t.is(image.complete, true)
})

Expand All @@ -51,15 +69,25 @@ test('alt state should be ok', (t) => {
test('with-exif image width and height should be correct', async (t) => {
const file = await fs.readFile(join(__dirname, 'fixtures', 'with-exif.jpg'))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
t.is(image.width, 450)
t.is(image.height, 600)
})

test('draw-image-exif', async (t) => {
const file = await fs.readFile(join(__dirname, 'fixtures', 'with-exif.jpg'))
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = file
await promise
const canvas = createCanvas(800, 800)
const ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0)
Expand All @@ -86,7 +114,12 @@ test('properties should be readonly', (t) => {

test('svg-transparent-background', async (t) => {
const image = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
image.onload = () => {
resolve()
}
image.src = await fs.readFile(join(__dirname, '..', 'example', 'resize-svg.svg'))
await promise

const w = 1000
const h = 1000
Expand Down Expand Up @@ -117,11 +150,9 @@ test('load invalid image should not throw if onerror is provided', async (t) =>
() =>
new Promise<void>((resolve) => {
const image = new Image()
image.onload = () => {
resolve()
}
image.onerror = (err) => {
t.is(err.message, 'Unsupported image type')
resolve()
}
image.src = broken
}),
Expand Down
5 changes: 5 additions & 0 deletions __test__/regression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,12 @@ test('draw-svg-with-text', async (t) => {
ctx.fillText(Title, 80, 100)

const Arrow = new Image()
const { promise, resolve } = Promise.withResolvers<void>()
Arrow.onload = () => {
resolve()
}
Arrow.src = await fs.readFile(join(__dirname, 'image-og.svg'))
await promise
ctx.drawImage(Arrow, 80, 60)
await snapshotImage(t, { ctx, canvas }, 'png', process.arch === 'x64' && process.platform !== 'darwin' ? 0.15 : 0.3)
})
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"canvaskit-wasm": "^0.39.1",
"colorette": "^2.0.20",
"conventional-changelog-cli": "^5.0.0",
"core-js": "^3.38.0",
"echarts": "^5.4.3",
"husky": "^9.0.10",
"lint-staged": "^15.2.1",
Expand All @@ -109,7 +110,8 @@
},
"ava": {
"require": [
"@swc-node/register"
"@swc-node/register",
"core-js/proposals/promise-with-resolvers.js"
],
"extensions": [
"ts"
Expand Down
2 changes: 2 additions & 0 deletions skia-c/skia_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1511,9 +1511,11 @@ extern "C"
canvas->setMatrix(matrix);
auto image = SkImages::RasterFromBitmap(*bitmap);
canvas->drawImage(image, 0, 0);
oriented_bitmap->setImmutable();
bitmap_info->bitmap = reinterpret_cast<skiac_bitmap *>(oriented_bitmap);
delete bitmap;
} else {
bitmap->setImmutable();
bitmap_info->bitmap = reinterpret_cast<skiac_bitmap *>(bitmap);
}
bitmap_info->width = width;
Expand Down
Loading

0 comments on commit 713ca40

Please sign in to comment.