feat: translate some more posts

This commit is contained in:
max_richter 2025-02-16 15:50:34 +01:00
parent 4c3e35efbf
commit aa333eb2cd
4 changed files with 314 additions and 8 deletions

View File

@ -0,0 +1,63 @@
---
title: "Invoice"
date: 2023-08-21
cover: ./images/bg.jpg
icon: "/projects/invoice.svg"
tags: ["sveltekit", "unocss", "prisma", "sqlite"]
toc: true
links:
[
["live", "https://invoice.app.max-richter.dev"],
["git", "https://git.max-richter.dev/max/invoice"],
]
---
import bg from './images/bg.jpg'
import invoices from './images/invoices.png'
import customers from './images/customers.png'
import editCustomers from './images/edit-customer.png'
import editProfile from './images/edit-profile.png'
import overview from './images/overview.png'
import ImageSlider from '@components/ImageSlider.svelte'
import Image from '@components/Image.astro'
# Introduction
In my free time, I like to take on small jobs, such as deliveries, setups, and pickups for others.
An unavoidable part of these tasks is creating invoices in PDF format. Initially, I followed a manual process and created my first invoices in Figma. But as programmers often say:
> Why do something manually in 5 minutes when I can automate it in 24 hours?
This thought led to my latest hobby project **"Invoice."**
<ImageSlider title="Invoice Screens" client:load>
<Image src={invoices} alt="Invoices" />
<Image src={customers} alt="Customers" />
<Image src={editCustomers} alt="Edit Customers" />
<Image src={editProfile} alt="Edit Profile" />
<Image src={overview} alt="Overview" />
</ImageSlider>
# Development
During development, I always kept the principle 'K.I.S.S.' in mind: Keep it simple, stupid. For this project, that meant choosing "boring" but well-known technologies:
## [🚀 SvelteKit](https://kit.svelte.dev)
For an efficient and reactive user interface.
## [🎨 UNOcss](https://unocss.dev/)
The faster alternative to Tailwind.
## [🌍 TypesafeI18n](https://github.com/ivanhofer/typesafe-i18n)
To enable multilingual support without complex logic.
## [🛠️ Prisma](https://prisma.io)
As a database access layer for smooth data management.
## [🗃️ SQLite](https://www.sqlite.org/index.html)
As a reliable backend, ideal for small projects.
## [📄 Playwright](https://playwright.dev)
For generating PDFs.

View File

@ -0,0 +1,129 @@
---
title: "K.A.R.L"
date: 2021-04-01
cover: ./images/Indicatrices_of_Distortion.png
license: "CC-BY-SA:4.0"
featured: true
toc: true
icon: /projects/karl/favicon.png
links:
[
["live", "https://max-richter.dev/karl"],
["git", "https://git.max-richter.dev/max/karl"],
]
---
import Crosswalk from "./images/crosswalk.jpg"
import CrosswalkMask from "./images/crosswalk_mask.png"
import Image from "@components/Image.astro"
import Distorion from "./images/Indicatrices_of_Distortion.png"
import ImageGallery from "@components/ImageGallery.svelte"
<ImageGallery client:load/>
*[Header by Justin Kunimune - Own work, CC BY-SA 4.0](https://commons.wikimedia.org/w/index.php?curid=66467577*)*
> K.A.R.L is a web app that helps to divide 360-degree panoramas into sections (sky, ground, trees, etc.) and then determine the proportion of each section in the overall image.
# Introduction
The project emerged from a collaboration with two friends. One of them is in the conceptual phase of his bachelor's thesis (Geography), which deals with the impact of vegetation on urban climate. To this end, he conducted albedo measurements at various locations in Cologne—essentially measuring "how much light comes from the sky, and how much of it is reflected by the ground." To put these measurements into context, he took 360-degree panoramas at each measurement location, which look something like this:
<Image src={Crosswalk} alt="Image of a crosswalk" caption="Image from hdrihaven.com" />
He then needed data on what percentage of the view was occupied by vegetation, sky, and ground. To measure this, he manually created a segmentation map in Gimp, which looks something like this:
<Image src={CrosswalkMask} alt="Segmentation map" />
# Problem Statement
If we naively count the pixels of each color and use that to calculate a percentage distribution, we encounter the classic distortion problem that humanity has faced for centuries with maps. Spheres do not like being represented in two dimensions, which always leads to distortions, as visualized in the following image.
<Image src={Distorion} alt="Indicatrices of Distortion" caption="by Justin Kunimune - Own work, CC BY-SA 4.0" />
Fortunately, this distortion only occurs in width, so we need a formula that provides a weight for the height of a pixel to compensate for this distortion. After many attempts, we arrived at this formula:
```javascript
/*
height: height of the image in pixels
calibrationFactor: 1.333, somehow this works, don't ask why, we don't either
y: y position of the pixel
*/
const pixelValue = Math.cos(
(((360 / height ** 2) * y ** 2 + (-360 / height) * y + 90) / 360) *
(2 + calibrationFactor) *
Math.PI
)
```
This formula is actually designed to calculate the distance between two longitudes for a given latitude, but it works very well for our purposes.
Here are some of the first attempts in Desmos (fantastic tool, by the way):
<iframe src="https://www.desmos.com/calculator/52ph4thjah?embed"/>
# Technologies
I wanted to build a web app that theoretically works completely offline. Therefore, after the initial page load, no further data is sent; everything runs in the browser. This is challenging for an application that processes many pixels and requires a lot of performance since browser resources are limited. The following technologies helped to keep the user experience reasonably smooth:
## Canvas
When working with pixels in the browser, you can't really avoid Canvas2D. Its programmatic interface makes it ideal for this job.
## Web Workers
Normally, all operations of a website run in a single thread in the browser. This means that a computationally intensive task can bring the entire site to a halt. Web Workers provide a solution by allowing arbitrary code to run in a separate thread, though communication with the main thread can be tricky.
K.A.R.L uses two Web Workers: the `pixel-worker` handles segmentation map analysis and the fill tool, while the `ai-worker` executes TensorFlow code.
## TensorFlow
Why manually color segments when the computer can do it automatically? That was my thought, so I integrated TensorFlow with the [ADE20K](https://groups.csail.mit.edu/vision/datasets/ADE20K/) dataset. This network is excellent at recognizing trees, ground, concrete, and sky. In the editor view, this function is hidden under the "AI" button on the right.
## IndexDB
When it comes to storing large amounts of data (especially images) locally in the browser, IndexedDB is the best option *(yes, localStorage with Base64 images could work too, but it's limited to 5MB in some browsers)*. However, the IndexedDB API is one of the [most confusing browser APIs out there](https://nolanlawson.github.io/offlinefirst-2016-03/#/27), so I use [idb](npmjs.com/package/idb), a fantastic small wrapper library for IndexedDB that also supports Promises.
# Interesting Features...
## Flood Fill Algorithm
After manually segmenting several panoramas using the tools, I noticed that I often just painted regions with similar colors. This gave me the idea to build a fill tool similar to Photoshop, but much better.
The tool works roughly as follows:
1. The user clicks on the image with the fill tool.
2. The x/y coordinates of the click and all pixels of the image are sent to a Web Worker.
3. The Web Worker creates a grayscale image where each pixel value represents the spatial and color distance to the clicked region.
4. The image is sent back.
5. The canvas code then determines whether individual pixels should be filled based on their values.
## Svelte Bindings
I always found it complicated to manage state across multiple components. A good example is the editor, which consists of three separate components (Toolbar, Topbar, PaintArea), all of which need to know which color and tool are currently active. One could store this state globally and let each component access it, but in reality, the active tool is only relevant in the editor context. So now, this state resides in the editor component and is passed down to its subcomponents via binding. It looks something like this:
```svelte
<!-- Editor.svelte -->
<script lang="ts">
import TopBar from "./TopBar.svelte";
let activeColor = "ff0000";
let _activeColor = localStorage.getItem("activeColor");
if (_activeColor) {
activeColor = _activeColor;
}
</script>
<TopBar bind:activeColor/>
<!-- TopBar.svelte -->
<script>
export let activeColor = "ff0000";
</script>
{{activeColor}}
```
## Svelte Stores
For some other cases, stores work really well, such as for toasts/modals. This allows the toast state (the small messages at the bottom left) to be managed in a single component without having to distribute it across multiple components.

View File

@ -1,18 +1,133 @@
---
title: "Plantarium"
date: 2022-08-31
date: 2022-08-31T20:46:26+02:00
cover: ./images/plantarium.png
featured: true
links: [["website", "https://plant.max-richter.com"], ["git", "https://github.com/jim-fx/plantarium"]]
draft: true
links: [["website", "https://plant.max-richter.dev"], ["git", "https://github.com/jim-fx/plantarium"]]
tags: ["Web", "3D", "Svelte", "Node Systems"]
icon: /projects/plantarium/favicon.svg
draft: false
toc: true
---
One of the projects I spent the most time with. As a Waldorf student and village kid, a bit cliché, I know, but I've always been passionate about plants, their structure and aesthetics. I'm also an absolute [Blender](https://blender.org) nerd and from these two interests Plantarium was born.
# Introduction
Plantarium is a tool that allows users to generate procedural plants. The first prototype was finished within two weeks of intensive work and looked something like this:
Plantarium is the intersection of two things I find fascinating: plants and 3D modeling.
It is a web app that allows users to create and export plants.
Users define the plants appearance through a node system, and Plantarium generates a 3D model from it.
<img src="images/page01-0.jpg"/>
<Image src={plantarium_screenshot} alt="Plantarium Screenshot"/>
# The Beginnings and Challenges
The interface was divided into 4 different levels, "Stems", "Branches", "Leaves" and "Import/Export". The idea was not bad because parts of the interface belonged to parts of the plant and you could quickly find the right settings if you wanted to change something. This way of designing the interface limits the freedom of the user(s) to generate the plants they want. Also, the interface code was unattractive, to say the least. It worked but it was not fun to work on. Also, I was mixing UI code with code that generated the plant.
The first prototype was ready within two weeks of intense work and looked something like this:
import ImageSlider from "@components/ImageSlider.svelte"
import Leaves from "./_components/Leaves.svelte"
import Image from "@components/Image.astro"
import page01_0 from "./images/page01-0.jpg"
import page01_1 from "./images/page01-1.jpg"
import page01_2 from "./images/page01-2.jpg"
import page01_3 from "./images/page01-3.jpg"
import page01_5 from "./images/page01-5.jpg"
import page01_6 from "./images/page01-6.jpg"
import screenshot_geometry_nodes from "./images/screenshot-geometry-nodes.jpg"
import screenshot_houdini from "./images/screenshot-houdini.jpg"
import screenshot_unreal from "./images/screenshot-unreal.jpg"
import screenshot_davinci from "./images/screenshot-davinci.jpg"
import plantarium_screenshot from "./images/plantarium-screenshot.png"
import Plantarium from "./images/plantarium.png"
import ImageGallery from "@components/ImageGallery.svelte"
<ImageGallery client:load/>
<Leaves client:load/>
<ImageSlider title="First Prototype" client:load>
<Image src={page01_0} alt="Stem Page"/>
<Image src={page01_1} alt="Branches Page"/>
<Image src={page01_2} alt="Leaves Page"/>
<Image src={page01_3} alt="Import/Export Page"/>
<Image src={page01_5} alt="Design of UI Components"/>
<Image src={page01_6} alt="Data Flow inside App"/>
</ImageSlider>
Not bad, but as with prototypes, there was still a lot to improve.
So heres a brief history of the biggest changes so far:
# Refactors
## Svelte Rewrite
The first prototype was handwritten, untested JavaScript.
Thats like trying to build a car out of toothpicks and duct tape.
You might get it to work, and you might even be proud of your creation,
but if you revisit it a week later—or worse, try to improve it—it quickly becomes a nightmare.
So, it was time for every web developers favorite activity:
figuring out whats currently the hottest thing in web development.
Once you pick one of the thousands of possible technologies,
you make it your personal religion and defend it with fanatical devotion.
Easy, right?
So, I went with the simplest, fastest, and overall best framework: ["Svelte"](https://svelte.dev).
In essence, frameworks help turn an apps data (e.g., usernames, login status) into HTML and CSS,
which the browser then displays.
### Node Systems
The interface was divided into four different sections: "Stem," "Branches," "Leaves," and "Import/Export."
The idea wasnt bad since each part of the plant had its own view,
making it easy to find the right settings when making changes.
However, this structure severely limited users.
You could only build plants following the Stem → Branch → Leaf model.
But what if you wanted multiple layers of branches or different leaves for stems and branches?
This model didnt allow for that.
So, as they say:
"Back to the drawing board."
I already had a solution in mind,
but I wasnt sure if I had the skills to implement it.
The solution? **Node Systems!**
Hmm, whats that?
<div class="center my-4">
![Node Systems](./images/nodes.svg)
</div>
Node systems consist of nodes and connections.
Nodes have inputs and outputs, which can be connected.
In the example graphic, we have two `input-color` nodes that each produce a color,
and a `mix-color` node that blends them.
The cool thing is that this system is highly flexible.
You can program nodes like `generate-stem`, `generate-branches`, or `add-leaves`.
<ImageSlider title="Examples of Node Systems" client:load>
<Image src={screenshot_geometry_nodes} alt="Blender uses nodes to create geometry"/>
<Image src={screenshot_houdini} alt="Houdini uses nodes for VFX/simulations"/>
<Image src={screenshot_unreal} alt="Unreal uses nodes for game logic"/>
<Image src={screenshot_davinci} alt="DaVinci uses nodes for VFX"/>
</ImageSlider>
### Svelte-Kit Rewrite
# Architecture
After experimenting with different approaches, I settled on an event-based architecture.
This fits well with node systems since individual components can communicate via events.
<div class="center my-4">
![Architecture](./images/architecture.svg)
</div>
# Design
# Future
# Conclusion
Currently, the architecture looks something like this:

View File

@ -19,7 +19,6 @@ export async function getStaticPaths() {
const pages = await getCollection("photos");
const page = filterCollection(pages, locale).find((page) => {
const [slug] = parseSlug(page.id);
console.log({ slug, id: page.id, params: Astro.params.slug, locale });
return slug === Astro.params.slug;
});