The Golden Ratio Coder: Build an AI-Powered Design Critic with Quarkus
Blend art and code. Learn how to use Quarkus, LangChain4j, and Ollama to critique images with AI and overlay the Golden Ratio.
I’ve always been fascinated by graphic design. When I was younger, I even dreamed of becoming a designer. Truth be told, I didn’t have the raw artistic talent for it, so I ended up as what we’d call in German a “Reprohersteller/in”, something like a “prepress technician” in English. My job was to prepare layouts for print, making sure everything looked sharp, aligned, and ready for production. It wasn’t glamorous, but it taught me a deep respect for precision in visual work.
That passion for visuals eventually pushed me into web design, and from there into programming. I still love illustrations, typography, and images. And I’ve always asked myself: why do we find some layouts beautiful and others awkward?
One answer lies in mathematics. For centuries, artists and architects have used the Golden Ratio, a proportion of about 1.618, to create shapes and layouts that feel naturally pleasing to the human eye. Think of the spiral of a seashell, the curves of a sunflower, or the facades of Renaissance buildings. The same ratio shows up everywhere. Psychologists suggest that we’re drawn to these patterns because they reflect growth processes in nature, triggering a sense of balance and harmony.
In this tutorial, we’ll explore this intersection of art and code. We’ll build an AI-Powered Design Critic: a Quarkus application that accepts an image, uses a local AI vision model to analyze its composition, and overlays the Golden Ratio grid so we can see whether the picture follows this timeless rule of beauty.
We’ll combine:
Quarkus for the REST API and frontend.
LangChain4j for clean AI integration.
Ollama for running a local vision model.
Java AWT/Graphics2D for image overlays.
By the end, you’ll have a working service with a simple web UI that critiques and annotates your images in real time.
Setting Up the Foundation
Install and Run Ollama
First, we need a local model runner. Ollama lets you run open-source LLMs and vision models on your machine. You can also fall back to Quarkus Dev Services and run the model in a container locally. Whatever you prefer.
ollama pull gemma3:latest
This downloads the Gemma3 model, a good choice for vision tasks. Keep Ollama running in the background.
Bootstrap the Quarkus Project
Create a new Quarkus project:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=ai-design-critic \
-DclassName="org.acme.critic.ImageAnalysisResource" \
-Dpath="/critic" \
-Dextensions="rest-jackson,langchain4j-ollama,quarkus-awt"
cd ai-design-critic
The source code is on my Github repository, as usual.
Configure LangChain4j
Tell Quarkus how to connect to Ollama in src/main/resources/application.properties
:
quarkus.langchain4j.ollama.chat-model.model-name=gemma3:latest
quarkus.langchain4j.ollama.timeout=60s
Handling Uploads
Let’s build a clean upload endpoint. Replace the code of src/main/java/org/acme/critic/ImageAnalysisResource.java
with the following:
package org.acme.critic;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.List;
import org.acme.critic.ai.CompositionCritic;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;
import dev.langchain4j.data.image.Image;
import io.quarkus.logging.Log;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/critic")
public class ImageAnalysisResource {
@Inject
CompositionCritic critic;
@POST
@Path("/extract")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response uploadAndAnalyze(@RestForm("image") FileUpload file) throws IOException {
try {
// 0. Check if we received a file
if (file == null) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("{\"error\":\"Error: No file uploaded.\"}").build();
}
// 1. Get the MIME type of the uploaded image
String mimeType = file.contentType();
if (mimeType == null || (!mimeType.equals("image/png") && !mimeType.equals("image/jpeg"))) {
// Add more supported types if needed by your model
return Response.status(Response.Status.BAD_REQUEST)
.entity("{\"error\":\"Only PNG and JPEG images are supported. Uploaded type: " + mimeType
+ "\"}")
.build();
}
Log.info(mimeType + " image received for description.");
// 2. Read image bytes from the uploaded file
try (InputStream inputStream = Files.newInputStream(file.uploadedFile())) {
// ToDo Process Image
return Response.ok("File Uploaded").build();
}
} catch (Exception e) {
Log.error("Error processing the image", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("{\"error\":\"Could not process the image.\"}").build();
}
}
public static class FileUploadInput {
@FormParam("file")
public List<FileUpload> file;
}
}
AI Integration
Now let’s get the critique from our vision model. Create src/main/java/org.acme/critic/ai/CompositionCritic.java
package org.acme.critic.ai;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
@RegisterAiService
public interface CompositionCritic {
@SystemMessage("""
You are a master art critic with an expert eye for visual composition.
Your task is to provide a concise yet insightful critique of the provided image's structure.
Focus entirely on the principles of composition.
Do not analyze the subject matter, emotional tone, or color palette,
except where they directly impact visual weight and flow.
In your analysis, please evaluate:
- **Balance and Visual Weight:** Is the composition balanced? Symmetrically or asymmetrically?
- **Leading Lines and Flow:** Where does the eye travel? Is the path clear and intentional?
- **Rule of Thirds & Golden Ratio:** Are key elements placed effectively according to these principles?
- **Framing and Negative Space:** How are the edges of the frame and empty spaces used to enhance the composition?
Please structure your output into two brief sections:
1. **Compositional Strengths:**
2. **Areas for Improvement:**
""")
@UserMessage("Describe this image.")
String critiqueComposition(Image image);
}
Inject and use it in the src/main/java/org/acme/critic/ImageAnalysisResource.java
:
//..
@Inject
CompositionCritic critic;
//..
@POST
@Path("/extract")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response uploadAndAnalyze(@RestForm("image") FileUpload file) throws IOException {
//...
// 2. Read image bytes from the uploaded file
try (InputStream inputStream = Files.newInputStream(file.uploadedFile())) {
byte[] imageBytes = inputStream.readAllBytes();
Image image = Image.builder() .base64Data(java.util.Base64.getEncoder().encodeToString(imageBytes))
.build();
return Response.ok(critic.critiqueComposition(image)).build();
}
//..
}
Send an image,
curl -X POST \
-F "image=@file.png" \
http://localhost:8080/critic/extract
and you’ll get back a text critique:
While the asymmetry is effective, it leans toward a slightly unbalanced feeling. The figure's dominant placement could be strengthened with a more precise application of the Golden Ratio. The horizon line, though present, feels somewhat haphazardly placed, disrupting the potential for a more harmonious flow. The negative space, primarily the upper right quadrant, feels somewhat empty, perhaps benefiting from a subtle variation in brushstroke density or a strategically placed element to subtly redirect the viewer's eye. A more considered use of the edge of the frame – perhaps by introducing a slightly darker tone or a textured element – could help to better define the composition and reduce the feeling of the painting simply ‘ending’ at the upper right corner.
Well, It was a pretty well known Munch that I used here: The Scream. I wouldn’t dare to criticize him that hard. Anyway.
Golden Ratio Overlay
Text is nice, but visuals matter. Let’s draw the Golden Ratio grid. Add the following method to src/main/java/org/acme/critic/ImageAnalysisResource.java
@POST
@Path("/overlay")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response createGoldenGridOverlay(@RestForm("image") FileUpload file) throws IOException {
try (InputStream inputStream = Files.newInputStream(file.uploadedFile())) {
BufferedImage img = ImageIO.read(inputStream);
// Draw the Golden Ratio Circle as Overlay
ImageOverlay.draw(img);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(img, "png", baos);
return Response.ok(baos.toByteArray())
.type(MediaType.APPLICATION_OCTET_STREAM)
.build();
}
}
I spare you the ImageOverlay.java.
Just grab it from my Github repository. It took a while to get there.
Test it:
curl -X POST \
-F "image=@file.png" \
http://localhost:8080/critic/overlay \
--output overlay_output.png
Open the resulting file and see the golden ratio grid on it.
Oh ja. Before someone is screaming “copyright”! I took this image myself. So nothing to worry about. And yes, of course. There is more to be desired from the drawing algorithm. But I really didn’t want to dig into this further here.
Next Steps
You now have a working AI Design Critic:
Add confidence scores to the critique.
Batch process multiple images.
Export annotated images.
This project blends AI reasoning with Java image processing, all running locally and securely. It’s a powerful pattern for building AI-infused Java applications.
Happy coding!