feat: compress EVERYTHING!
This commit is contained in:
@ -1,20 +1,56 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let src: string;
|
||||
export let alt = '';
|
||||
const u = new URL(src);
|
||||
const filename = u.pathname.split('/').pop() || '9a8sda';
|
||||
const int = (parseInt(filename?.slice(0, 8), 16) % 7) + 1;
|
||||
const frame = `/frames/frame_0${int}`;
|
||||
|
||||
let handlingError = false;
|
||||
function handleError() {
|
||||
if (handlingError) return;
|
||||
handlingError = true;
|
||||
console.log('error', { src });
|
||||
const oldSrc = src;
|
||||
src = '';
|
||||
setTimeout(() => {
|
||||
src = oldSrc;
|
||||
handlingError = false;
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="frame" style="--frame: url(/frames/frame_0{int}.png)">
|
||||
<img src="/hang.png" class="hang" />
|
||||
<picture>
|
||||
<picture class="painting-frame">
|
||||
<!-- Load AVIF format -->
|
||||
<source srcset="{frame}.avif" type="image/avif" />
|
||||
|
||||
<!-- If AVIF is not supported, load WebP format -->
|
||||
<source srcset="{frame}.webp" type="image/webp" />
|
||||
|
||||
<!-- If neither AVIF nor WebP are supported, load PNG format -->
|
||||
<img src="{frame}.png" alt="Painting Frame" />
|
||||
</picture>
|
||||
|
||||
<img src="/hang.png" class="hang" alt="painting hanging cord" />
|
||||
<picture class="person">
|
||||
<source srcset={src.replace('.png', '.avif')} type="image/avif" />
|
||||
<source srcset={src.replace('.png', '.webp')} type="image/webp" />
|
||||
<source srcset={src.replace('.png', '.jpg')} type="image/jpeg" />
|
||||
<img {src} {alt} />
|
||||
<img {src} {alt} on:error={() => handleError()} />
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.painting-frame > * {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0px 0px 10px black) brightness(0.6) contrast(1.1);
|
||||
}
|
||||
.hang {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
@ -34,13 +70,13 @@
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--frame);
|
||||
/* background: var(--frame); */
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
filter: drop-shadow(0px 0px 20px black);
|
||||
}
|
||||
img {
|
||||
.person img {
|
||||
z-index: -1;
|
||||
max-width: 70%;
|
||||
max-height: 70%;
|
||||
|
@ -22,6 +22,7 @@
|
||||
portraitHairType?: 'straight' | 'curly' | 'bald';
|
||||
portraitHairColor?: 'red' | 'brown' | 'blonde' | 'black' | 'grey' | 'white';
|
||||
portraitHairLength?: 'short' | 'medium' | 'long';
|
||||
portraitSkinColor?: 'very light' | 'light' | 'medium' | 'dark' | 'very dark';
|
||||
portraitAccepted?: boolean;
|
||||
portraitPublic?: boolean;
|
||||
|
||||
@ -62,7 +63,8 @@
|
||||
body: JSON.stringify({
|
||||
hairType: $data.portraitHairType,
|
||||
hairColor: $data.portraitHairColor,
|
||||
hairLength: $data.portraitHairLength
|
||||
hairLength: $data.portraitHairLength,
|
||||
skinColor: $data.portraitSkinColor
|
||||
})
|
||||
});
|
||||
|
||||
@ -224,6 +226,14 @@
|
||||
{#if $data.providePortrait && !loadingPortrait && !$data.portraitUrl}
|
||||
<p>
|
||||
Wir werden {$data.adelsTitel || $data.name} mit
|
||||
<select placeholder="Typ" bind:value={$data.portraitSkinColor}>
|
||||
<option value="very light">sehr heller</option>
|
||||
<option value="light">heller</option>
|
||||
<option value="medium">medium</option>
|
||||
<option value="dark">dunkler</option>
|
||||
<option value="very dark">sehr dunkler</option>
|
||||
</select>
|
||||
Haut und
|
||||
<select placeholder="Typ" bind:value={$data.portraitHairType}>
|
||||
<option value="straight">glatten</option>
|
||||
<option value="curly">lockigen</option>
|
||||
@ -297,21 +307,17 @@
|
||||
margin-bottom: 200px;
|
||||
}
|
||||
|
||||
.portrait-frame {
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
.portrait-frame.loaded {
|
||||
margin-top: 100px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
border-radius: 2px;
|
||||
padding: 5px 9px;
|
||||
margin-right: 10px;
|
||||
background: #866831;
|
||||
box-shadow: 3px 3px 8px #e1b45f inset;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ export async function getPublicPortraits() {
|
||||
})).items
|
||||
}
|
||||
|
||||
export function createPerson({ name, confidence, portrait, portrait_public, noble_name, hair_color, hair_type, hair_length }: { name: string, portrait: string, portrait_public: boolean, hair_type: string, hair_length: string, hair_color: string, confidence: number, noble_name: string }) {
|
||||
export function createPerson({ name, confidence, portrait, portrait_public, noble_name, hair_color, hair_type, hair_length, skin_color }: { name: string, portrait: string, portrait_public: boolean, hair_type: string, hair_length: string, hair_color: string, confidence: number, noble_name: string, skin_color: string }) {
|
||||
return pb.collection("invites").create({
|
||||
name,
|
||||
confidence,
|
||||
@ -18,6 +18,7 @@ export function createPerson({ name, confidence, portrait, portrait_public, nobl
|
||||
noble_name,
|
||||
hair_type,
|
||||
hair_length,
|
||||
hair_color
|
||||
hair_color,
|
||||
skin_color
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,43 @@
|
||||
import { json } from "@sveltejs/kit";
|
||||
import Jimp from "jimp";
|
||||
import type { RequestHandler } from "./$types";
|
||||
import { putObject } from "$lib/helpers/minio";
|
||||
import { generateImage } from "$lib/helpers/stability";
|
||||
import sharp from "sharp";
|
||||
|
||||
|
||||
async function compressImage(imageName: string, imageBuffer: Buffer) {
|
||||
|
||||
const ja = performance.now()
|
||||
const jpgBuffer = await sharp(imageBuffer)
|
||||
.jpeg({ quality: 70 })
|
||||
.withMetadata()
|
||||
.toBuffer();
|
||||
|
||||
await putObject(imageName.replace(".png", ".jpg"), jpgBuffer, { "Content-Type": "image/jpeg" });
|
||||
|
||||
const jb = performance.now() - ja;
|
||||
console.log(`[AI] JPG compression took ${jb}ms`)
|
||||
|
||||
const wa = performance.now()
|
||||
const webpBuffer = await sharp(imageBuffer)
|
||||
.webp({ quality: 70 })
|
||||
.withMetadata()
|
||||
.toBuffer()
|
||||
|
||||
await putObject(imageName.replace(".png", ".webp"), webpBuffer, { "Content-Type": "image/webp" });
|
||||
const wb = performance.now() - wa;
|
||||
console.log(`[AI] WebP compression took ${wb}ms`)
|
||||
|
||||
const aa = performance.now()
|
||||
const aviBuffer = await sharp(imageBuffer)
|
||||
.avif({ quality: 70 })
|
||||
.withMetadata()
|
||||
.toBuffer()
|
||||
|
||||
await putObject(imageName.replace(".png", ".avif"), aviBuffer, { "Content-Type": "image/avif" });
|
||||
const ab = performance.now() - aa;
|
||||
console.log(`[AI] AVIF compression took ${ab}ms`)
|
||||
}
|
||||
|
||||
export const POST: RequestHandler = async ({ params, request }) => {
|
||||
|
||||
@ -14,33 +49,39 @@ export const POST: RequestHandler = async ({ params, request }) => {
|
||||
throw new Error("Name too long");
|
||||
}
|
||||
|
||||
const { hairType, hairColor, hairLength } = await request.json();
|
||||
console.log(hairType, hairColor, hairLength)
|
||||
const { hairType, hairColor, hairLength, skinColor } = await request.json();
|
||||
console.log(`[AI] Generating image for ${inputName} ${JSON.stringify({ hairType, hairColor, hairLength, skinColor })}`)
|
||||
|
||||
if (!hairType || !hairColor || !hairLength) {
|
||||
throw new Error("Missing hairType, hairColor or hairLength");
|
||||
}
|
||||
|
||||
const prompt = `realistic portrait oil painting of a masked ${inputName}, baroque, in the style of Charles Vess, masked ball attire, opulence, mystery, elegance, ${hairLength} ${hairType} ${hairColor} hair, darker skin`;
|
||||
const prompt = `realistic portrait oil painting of a masked ${inputName}, baroque, in the style of Charles Vess, masked ball attire, opulence, mystery, elegance, ${hairLength} ${hairType} ${hairColor} hair, ${skinColor} skin`;
|
||||
const negativePrompt = "blurry, multiple persons, picture frame"
|
||||
|
||||
const a = performance.now()
|
||||
// #const image = await openai.image(prompt);
|
||||
const image = await generateImage(prompt, negativePrompt);
|
||||
const duration = performance.now() - a;
|
||||
const d = performance.now() - a;
|
||||
console.log(`[AI] Image generation took ${d}ms`)
|
||||
|
||||
|
||||
const imageName = `${Math.random().toString(16).substring(3, 10)}-${inputName.toLowerCase().split(" ").slice(0, 5).join("-").slice(0, 25)}.png`
|
||||
|
||||
await putObject(imageName, Buffer.from(image.base64, 'base64'), { "Content-Type": "image/png" });
|
||||
const imageBuffer = Buffer.from(image.base64, 'base64');
|
||||
|
||||
const img = await Jimp.read(Buffer.from(image.base64, "base64"));
|
||||
const jpgBuffer = await img.quality(70).getBufferAsync(Jimp.MIME_JPEG);
|
||||
const a2 = performance.now()
|
||||
const pngBuffer = await sharp(imageBuffer)
|
||||
.png({ compressionLevel: 9, adaptiveFiltering: true, force: true })
|
||||
.withMetadata()
|
||||
.toBuffer()
|
||||
|
||||
await putObject(imageName.replace(".png", ".jpg"), jpgBuffer, { "Content-Type": "image/jpeg" });
|
||||
await putObject(imageName, pngBuffer, { "Content-Type": "image/png" });
|
||||
const d2 = performance.now() - a2;
|
||||
console.log(`[AI] PNG compression took ${d2}ms`)
|
||||
|
||||
await compressImage(imageName, imageBuffer)
|
||||
|
||||
return json({
|
||||
duration,
|
||||
url: `https://s3-api.app.max-richter.dev/silvester23/${imageName}`
|
||||
url: `https://s3.max-richter.dev/silvester23/${imageName}`
|
||||
})
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
hair_type: body.portraitHairType,
|
||||
hair_color: body.portraitHairColor,
|
||||
portrait_public: body.portraitPublic,
|
||||
skin_color: body.portraitSkinColor,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
@ -93,6 +93,7 @@
|
||||
:global(html) {
|
||||
background-image: url(/pattern.jpg) !important;
|
||||
backdrop-filter: brightness(0.5) !important;
|
||||
background-size: 25%;
|
||||
}
|
||||
.wrapper {
|
||||
max-width: 1300px;
|
||||
|
@ -1,7 +0,0 @@
|
||||
<script lang="ts">
|
||||
import ImageFrame from '$lib/components/ImageFrame.svelte';
|
||||
</script>
|
||||
|
||||
<ImageFrame
|
||||
src="https://s3-api.app.max-richter.dev/silvester23/13770f0-earl-maximus-of-richterla.png"
|
||||
/>
|
Reference in New Issue
Block a user