feat: compress EVERYTHING!

This commit is contained in:
2023-11-13 14:09:38 +01:00
parent 9f55b88b50
commit 9c395d82f3
35 changed files with 462 additions and 97 deletions

View File

@ -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%;

View File

@ -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;
}

View File

@ -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
})
}

View File

@ -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}`
})
}

View File

@ -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)

View File

@ -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;

View File

@ -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"
/>