feat(variant): image preview on variant creation with a real sample image
This commit is contained in:
1 parent
a1cdfce529
commit
04dc151ed7
9 files changed
+314
-40
No files matched your search
@@ -0,0 +1,98 @@
|
||||
import { onScopeDispose, ref, watch, type Ref } from 'vue'
|
||||
import sampleUrl from '@/assets/variant-sample.jpg'
|
||||
import { SAMPLE_SOURCE, type EstimateRequest } from '@/lib/variants/variantEstimate'
|
||||
import { outputSize } from '@/lib/variants/variantOutput'
|
||||
import { canRender, drawPlan, flattensTransparency, mimeFor } from '@/lib/variants/variantRender'
|
||||
|
||||
const RENDER_DELAY_MS = 150
|
||||
const PERCENT = 100
|
||||
|
||||
export interface RenderedVariant {
|
||||
url: string
|
||||
bytes: number
|
||||
}
|
||||
|
||||
let sampleImage: Promise<HTMLImageElement | null> | null = null
|
||||
|
||||
function loadSample() {
|
||||
sampleImage ??= new Promise((resolve) => {
|
||||
const image = new Image()
|
||||
image.onload = () => resolve(image)
|
||||
image.onerror = () => resolve(null)
|
||||
image.src = sampleUrl
|
||||
})
|
||||
return sampleImage
|
||||
}
|
||||
|
||||
function encode(canvas: HTMLCanvasElement, request: EstimateRequest) {
|
||||
const mime = mimeFor(request.format)
|
||||
const quality = mime === 'image/png' ? undefined : request.quality / PERCENT
|
||||
return new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, mime, quality))
|
||||
}
|
||||
|
||||
async function renderVariant(image: HTMLImageElement, request: EstimateRequest) {
|
||||
const size = outputSize(SAMPLE_SOURCE, request)
|
||||
if (size === null || !canRender(size)) {
|
||||
return null
|
||||
}
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = size.width
|
||||
canvas.height = size.height
|
||||
const context = canvas.getContext('2d')
|
||||
if (context === null) {
|
||||
return null
|
||||
}
|
||||
if (flattensTransparency(request.format)) {
|
||||
context.fillStyle = 'white'
|
||||
context.fillRect(0, 0, size.width, size.height)
|
||||
}
|
||||
const plan = drawPlan(SAMPLE_SOURCE, size, request.fit)
|
||||
context.drawImage(image, plan.sx, plan.sy, plan.sw, plan.sh, plan.dx, plan.dy, plan.dw, plan.dh)
|
||||
return encode(canvas, request)
|
||||
}
|
||||
|
||||
export function useVariantRender(request: Ref<EstimateRequest | null>) {
|
||||
const original = ref<string | null>(null)
|
||||
const variant = ref<RenderedVariant | null>(null)
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let token = 0
|
||||
|
||||
function release() {
|
||||
if (variant.value) {
|
||||
URL.revokeObjectURL(variant.value.url)
|
||||
variant.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function run(current: EstimateRequest | null, ticket: number) {
|
||||
const image = await loadSample()
|
||||
if (image === null || ticket !== token) {
|
||||
return
|
||||
}
|
||||
original.value = sampleUrl
|
||||
const blob = current === null ? null : await renderVariant(image, current)
|
||||
if (ticket !== token) {
|
||||
return
|
||||
}
|
||||
release()
|
||||
if (blob !== null) {
|
||||
variant.value = { url: URL.createObjectURL(blob), bytes: blob.size }
|
||||
}
|
||||
}
|
||||
|
||||
function schedule(current: EstimateRequest | null) {
|
||||
clearTimeout(timer)
|
||||
const ticket = ++token
|
||||
timer = setTimeout(() => void run(current, ticket), RENDER_DELAY_MS)
|
||||
}
|
||||
|
||||
watch(request, schedule, { immediate: true, deep: true })
|
||||
|
||||
onScopeDispose(() => {
|
||||
clearTimeout(timer)
|
||||
token++
|
||||
release()
|
||||
})
|
||||
|
||||
return { original, variant }
|
||||
}
|
||||
Reference in new issue
Block a user