-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Compare with PSD.js (fetched from CDN) and ag-psd (bundled) - Checkbox to apply layer opacity when decoding (skipped by default) (it's supported by @webtoon/psd only and is very slow) - Show stacked bar chart using Chart.js - Show spinner while running benchmark (improves UX since progressbar is generally static) Run `npm run start:benchmark` to launch the local development server, and `npm run build:benchmark` to build (for publishing to GitHub pages). Also, running `npm run deploy` will build both the browser demo and the benchmark pages and deploy them to GitHub pages.
- Loading branch information
1 parent
d16c6ab
commit 9bc26f9
Showing
14 changed files
with
1,716 additions
and
1 deletion.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<!DOCTYPE html> | ||
<!-- | ||
@webtoon/psd | ||
Copyright 2021-present NAVER WEBTOON | ||
MIT License | ||
--> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/x-icon" href="favicon.ico" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>PSD Benchmark Runner - @webtoon/psd</title> | ||
</head> | ||
<body> | ||
<noscript>You need JavaScript to run benchmarks</noscript> | ||
<main id="app" class="app"> | ||
<header class="app__section app-header"> | ||
<h1> | ||
Benchmark runner for | ||
<span class="webtoon-psd-name">@webtoon/psd</span> and other parsers | ||
</h1> | ||
<p> | ||
Compare the performance of | ||
<span class="webtoon-psd-name">@webtoon/psd</span> and other | ||
JavaScript PSD parsers (<a | ||
target="_blank" | ||
href="https://github.com/meltingice/psd.js" | ||
rel="noopener" | ||
>PSD.js</a | ||
>, | ||
<a | ||
target="_blank" | ||
href="https://github.com/Agamnentzar/ag-psd" | ||
rel="noopener" | ||
>ag-psd</a | ||
>) by selecting a PSD file on your device. | ||
</p> | ||
<p> | ||
(Note: Some features from the PSD file spec are not supported by all | ||
parsers. As such, this page should be treated as a rough indicator of | ||
performance.) | ||
</p> | ||
</header> | ||
<div class="app__section control-panel"> | ||
<div> | ||
<label> | ||
Select a PSD file: | ||
<input id="file-input" type="file" accept=".psd,.psb" /> | ||
</label> | ||
</div> | ||
<div> | ||
<label> | ||
# of times to parse the file: | ||
<input | ||
id="repeat-count-input" | ||
type="number" | ||
min="0" | ||
max="1000" | ||
value="3" | ||
/> | ||
</label> | ||
</div> | ||
<div> | ||
<label> | ||
<input id="apply-opacity-checkbox" type="checkbox" /> | ||
Apply opacity when decoding (supported by | ||
<span class="webtoon-psd-name">@webtoon/psd</span> only) | ||
</label> | ||
</div> | ||
</div> | ||
<div class="app__section progress-panel"> | ||
<label class="progress-panel__description" for="progressbar" | ||
>Select a file</label | ||
> | ||
<progress | ||
class="progress-panel__progressbar" | ||
id="progressbar" | ||
></progress> | ||
</div> | ||
<div class="app__section result-panel" style="display: none"> | ||
<div class="result-panel__spinner-container"> | ||
<div class="progress-spinner" data-state="spinning"></div> | ||
</div> | ||
<div class="result-panel__table-container"></div> | ||
<canvas id="result-chart-canvas" width="400" height="100"> | ||
Cannot display chart because your browser does not support canvas | ||
</canvas> | ||
</div> | ||
<div class="app__section error-panel" style="display: none"> | ||
<div class="error-panel__message"></div> | ||
</div> | ||
</main> | ||
<!-- | ||
The browser bundle of PSD.js is available only through Bower and GitHub. | ||
Since I don't want to use Bower, let's hotlink directly to the latest | ||
version of the bundle on GitHub instead. | ||
--> | ||
<script src="https://cdn.jsdelivr.net/gh/meltingice/psd.js@master/dist/psd.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// @webtoon/psd | ||
// Copyright 2021-present NAVER WEBTOON | ||
// MIT License | ||
|
||
import * as AgPsd from "ag-psd"; | ||
import Psd from "../../../src"; | ||
|
||
// Use the require() function provided by the browser bundle of PSD.js | ||
// Because TypeScript prevents us from calling `require()` inside an ESM module, | ||
// we must call `globalThis.require()` instead | ||
const PsdJs = globalThis.require("psd") as typeof import("./psd-js"); | ||
|
||
export interface BenchmarkResult { | ||
parseTime: number; | ||
imageRenderTime: number; | ||
layerRenderTime: number; | ||
} | ||
|
||
export function benchmarkPsdJs(arrayBuffer: ArrayBuffer): BenchmarkResult { | ||
const parseBegin = performance.now(); | ||
const psd = new PsdJs(new Uint8Array(arrayBuffer)); | ||
psd.parse(); | ||
psd.tree().export(); // trigger all getters | ||
const parseEnd = performance.now(); | ||
|
||
const imageRenderBegin = performance.now(); | ||
psd.image.pixelData; // trigger getter | ||
const imageRenderEnd = performance.now(); | ||
|
||
const layerRenderBegin = performance.now(); | ||
// trigger getters | ||
psd | ||
.tree() | ||
.descendants() | ||
.forEach((node) => node.type === "layer" && node.layer.image.pixelData); | ||
const layerRenderEnd = performance.now(); | ||
|
||
return { | ||
parseTime: parseEnd - parseBegin, | ||
imageRenderTime: imageRenderEnd - imageRenderBegin, | ||
layerRenderTime: layerRenderEnd - layerRenderBegin, | ||
}; | ||
} | ||
|
||
export function benchmarkPsdTs( | ||
arrayBuffer: ArrayBuffer, | ||
options: {applyOpacity: boolean} | ||
): BenchmarkResult { | ||
const parseBegin = performance.now(); | ||
const psd = Psd.parse(arrayBuffer); | ||
const parseEnd = performance.now(); | ||
|
||
const imageRenderBegin = performance.now(); | ||
psd.composite(options.applyOpacity); | ||
const imageRenderEnd = performance.now(); | ||
|
||
const layerRenderBegin = performance.now(); | ||
psd.layers.forEach((layer) => layer.composite(options.applyOpacity)); | ||
const layerRenderEnd = performance.now(); | ||
|
||
return { | ||
parseTime: parseEnd - parseBegin, | ||
imageRenderTime: imageRenderEnd - imageRenderBegin, | ||
layerRenderTime: layerRenderEnd - layerRenderBegin, | ||
}; | ||
} | ||
|
||
export function benchmarkAgPsd(arrayBuffer: ArrayBuffer): BenchmarkResult { | ||
const parseBegin = performance.now(); | ||
AgPsd.readPsd(arrayBuffer, { | ||
skipCompositeImageData: true, | ||
skipLayerImageData: true, | ||
skipThumbnail: true, | ||
useImageData: true, | ||
}); | ||
const parseEnd = performance.now(); | ||
|
||
// ag-psd does not provide APIs for decoding the merged image separately. | ||
// Instead, we measure (parse time + merged image decoding time) and subtract | ||
// the parse time. This is probably inaccurate. | ||
const parseAndImageRenderBegin = performance.now(); | ||
AgPsd.readPsd(arrayBuffer, { | ||
skipLayerImageData: true, | ||
skipThumbnail: true, | ||
useImageData: true, | ||
}); | ||
const parseAndImageRenderEnd = performance.now(); | ||
|
||
// ag-psd does not provide APIs for decoding each layer image separately. | ||
// Instead, we measure (parse time + all layer decoding time) and subtract | ||
// the parse time. This is probably inaccurate. | ||
const parseAndLayerRenderBegin = performance.now(); | ||
AgPsd.readPsd(arrayBuffer, { | ||
skipCompositeImageData: true, | ||
skipThumbnail: true, | ||
useImageData: true, | ||
}); | ||
const parseAndLayerRenderEnd = performance.now(); | ||
|
||
const parseTime = parseEnd - parseBegin; | ||
|
||
return { | ||
parseTime, | ||
imageRenderTime: | ||
parseAndImageRenderEnd - parseAndImageRenderBegin - parseTime, | ||
layerRenderTime: | ||
parseAndLayerRenderEnd - parseAndLayerRenderBegin - parseTime, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// @webtoon/psd | ||
// Copyright 2021-present NAVER WEBTOON | ||
// MIT License | ||
|
||
import { | ||
BarController, | ||
BarElement, | ||
CategoryScale, | ||
Chart, | ||
Legend, | ||
LinearScale, | ||
} from "chart.js"; | ||
|
||
// Initialize Chart.js | ||
Chart.register(BarController, BarElement, CategoryScale, Legend, LinearScale); | ||
|
||
const COLOR_SCHEME = { | ||
backgroundColor: [ | ||
"rgba(245, 121, 58, 0.5)", | ||
"rgba(169, 90, 161, 0.5)", | ||
"rgba(133, 192, 249, 0.5)", | ||
], | ||
borderColor: [ | ||
"rgba(245, 121, 58, 1)", | ||
"rgba(169, 90, 161, 1)", | ||
"rgba(133, 192, 249, 1)", | ||
], | ||
}; | ||
|
||
export function drawChart( | ||
canvas: HTMLCanvasElement, | ||
{ | ||
categories, | ||
measurements, | ||
}: { | ||
categories: string[]; | ||
measurements: {parserName: string; values: number[]}[]; | ||
} | ||
) { | ||
/** Library names */ | ||
const parserNames = measurements.map(({parserName}) => parserName); | ||
const datasets = categories.map((category, categoryIndex) => ({ | ||
label: category, | ||
data: measurements.map(({values}) => values[categoryIndex]), | ||
backgroundColor: COLOR_SCHEME.backgroundColor[categoryIndex], | ||
borderColor: COLOR_SCHEME.borderColor[categoryIndex], | ||
borderWidth: 1, | ||
barPercentage: 0.6, | ||
})); | ||
|
||
const chart = Chart.getChart(canvas); | ||
if (!chart) { | ||
new Chart(canvas, { | ||
type: "bar", | ||
data: { | ||
labels: parserNames, | ||
datasets, | ||
}, | ||
options: { | ||
indexAxis: "y", | ||
plugins: { | ||
legend: { | ||
display: true, | ||
}, | ||
}, | ||
scales: { | ||
x: { | ||
stacked: true, | ||
}, | ||
y: { | ||
stacked: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
} else { | ||
chart.data.labels = parserNames; | ||
chart.data.datasets = datasets; | ||
chart.update(); | ||
} | ||
} |
Oops, something went wrong.