mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
perf: add cache for images
This commit is contained in:
@@ -168,6 +168,14 @@
|
|||||||
форматирование по колонкам??? для hex\bytes\rgba - пример в placeholder
|
форматирование по колонкам??? для hex\bytes\rgba - пример в placeholder
|
||||||
- [ ] прикрутить кастомный color picker, добавить прозрачность (range?)
|
- [ ] прикрутить кастомный color picker, добавить прозрачность (range?)
|
||||||
- [ ] обновить страницу kit или сделать storybook
|
- [ ] обновить страницу kit или сделать storybook
|
||||||
|
- [ ] **проверить `cachedDataUrl` (WeakMap-кэш превью) в реальной работе** — GC
|
||||||
|
срабатывает недетерминированно, поэтому освобождение base64-строк (≈1.3×
|
||||||
|
бинарного размера картинки на каждый тайл) не гарантировано своевременно.
|
||||||
|
Проверить на длинной сессии с большими картинками (несколько ранов подряд,
|
||||||
|
смена инструментов): растёт ли heap в DevTools → Memory после повторных
|
||||||
|
ранов и уходит ли после принудительной сборки. Если записи копятся —
|
||||||
|
заменить на ограниченный LRU или явную инвалидацию при
|
||||||
|
`resetForHost`/новом ране (`web/src/lib/core/io.ts`, `cachedDataUrl`)
|
||||||
- [ ] `PreviewTile`: высота плитки задана только `min-height: clamp(...)`,
|
- [ ] `PreviewTile`: высота плитки задана только `min-height: clamp(...)`,
|
||||||
поэтому она не следует за пропорциями результата — широкий результат
|
поэтому она не следует за пропорциями результата — широкий результат
|
||||||
уезжает в узкую полосу, а высокий упирается в `max-height: 100vh`. Нужно,
|
уезжает в узкую полосу, а высокий упирается в `max-height: 100vh`. Нужно,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toDataUrl } from "$lib/core/io";
|
import { cachedDataUrl } from "$lib/core/io";
|
||||||
import type { ToolImageFile } from "$lib/registry";
|
import type { ToolImageFile } from "$lib/registry";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
let { files }: Props = $props();
|
let { files }: Props = $props();
|
||||||
|
|
||||||
const parts = $derived(
|
const parts = $derived(
|
||||||
files.map((file) => ({ name: file.name, url: toDataUrl(file.image) })),
|
files.map((file) => ({ name: file.name, url: cachedDataUrl(file.image) })),
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toDataUrl } from "$lib/core/io";
|
import { cachedDataUrl } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import type { FileResult, ResultKind, ResultNote } from "$lib/registry";
|
import type { FileResult, ResultKind, ResultNote } from "$lib/registry";
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const shown = $derived(maskOn && mask ? mask : result);
|
const shown = $derived(maskOn && mask ? mask : result);
|
||||||
const resultUrl = $derived(shown ? toDataUrl(shown) : null);
|
const resultUrl = $derived(shown ? cachedDataUrl(shown) : null);
|
||||||
const resultDims = $derived(
|
const resultDims = $derived(
|
||||||
resultKind === "image" && result
|
resultKind === "image" && result
|
||||||
? `${result.width} × ${result.height}`
|
? `${result.width} × ${result.height}`
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toDataUrl } from "$lib/core/io";
|
import { cachedDataUrl } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import type { InputMode, ToolImageFile } from "$lib/registry";
|
import type { InputMode, ToolImageFile } from "$lib/registry";
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
onuploadmany,
|
onuploadmany,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const sourceUrl = $derived(source ? toDataUrl(source) : null);
|
const sourceUrl = $derived(source ? cachedDataUrl(source) : null);
|
||||||
const sourceDims = $derived(
|
const sourceDims = $derived(
|
||||||
source
|
source
|
||||||
? `${source.width} × ${source.height}` +
|
? `${source.width} × ${source.height}` +
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { Star } from "@lucide/svelte";
|
import { Star } from "@lucide/svelte";
|
||||||
import { DragDropProvider } from "@dnd-kit/svelte";
|
import { DragDropProvider } from "@dnd-kit/svelte";
|
||||||
import { debounce } from "$lib/core/debounce";
|
import { debounce } from "$lib/core/debounce";
|
||||||
import { toDataUrl } from "$lib/core/io";
|
import { cachedDataUrl } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import {
|
import {
|
||||||
chainStepTitle,
|
chainStepTitle,
|
||||||
@@ -301,7 +301,7 @@
|
|||||||
{#if inSet.length > 1}
|
{#if inSet.length > 1}
|
||||||
<PartsGrid files={inSet} />
|
<PartsGrid files={inSet} />
|
||||||
{:else if input}
|
{:else if input}
|
||||||
<img src={toDataUrl(input)} alt="" />
|
<img src={cachedDataUrl(input)} alt="" />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="empty">{t("resultCard.noResult")}</span>
|
<span class="empty">{t("resultCard.noResult")}</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -344,7 +344,7 @@
|
|||||||
<PartsGrid files={outSet} />
|
<PartsGrid files={outSet} />
|
||||||
{:else if out}
|
{:else if out}
|
||||||
<img
|
<img
|
||||||
src={toDataUrl(
|
src={cachedDataUrl(
|
||||||
masks.maskOn[i] && masks.masks[i]
|
masks.maskOn[i] && masks.masks[i]
|
||||||
? (masks.masks[i] as PixelImage)
|
? (masks.masks[i] as PixelImage)
|
||||||
: out,
|
: out,
|
||||||
|
|||||||
@@ -82,6 +82,21 @@ export function toBase64(img: PixelImage): string {
|
|||||||
return toDataUrl(img).slice("data:image/png;base64,".length);
|
return toDataUrl(img).slice("data:image/png;base64,".length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dataUrlCache = new WeakMap<PixelImage, string>();
|
||||||
|
|
||||||
|
// Display helper: PNG-encode on the main thread is expensive and templates
|
||||||
|
// re-render often with the same image (layout branch swaps, toggles). Results
|
||||||
|
// are treated as immutable -- a re-run produces new objects -- so an
|
||||||
|
// identity-keyed cache cannot go stale.
|
||||||
|
export function cachedDataUrl(img: PixelImage): string {
|
||||||
|
let url = dataUrlCache.get(img);
|
||||||
|
if (url === undefined) {
|
||||||
|
url = toDataUrl(img);
|
||||||
|
dataUrlCache.set(img, url);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
export async function decodeTextImage(text: string): Promise<PixelImage> {
|
export async function decodeTextImage(text: string): Promise<PixelImage> {
|
||||||
const cleaned = text.trim().replace(/^data:[^,]*,/, "");
|
const cleaned = text.trim().replace(/^data:[^,]*,/, "");
|
||||||
if (cleaned.length === 0) {
|
if (cleaned.length === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user