Skip to content

Repository files navigation

Expression Studio

Upload a few photos of yourself, pick a facial expression, and get photorealistic portraits of you performing it.

Expression Studio is an identity-preserving image generator. Your photos supply the identity; a chosen expression reference (or your own text description) supplies the expression; and the model renders fresh portraits that keep your likeness while adopting the new expression. It's built on Nano Banana Pro (Google gemini-3-pro-image), accessed through OpenRouter.

Expression Studio — app overview


About

Expression Studio is a small, self-hosted web app for generating photorealistic portraits of yourself in a chosen emotion. You bring your own reference photos and your own OpenRouter API key; the app pairs your likeness with one of 25 hand-tuned expression references (or a free-text description you write) and asks Nano Banana Pro (Google gemini-3-pro-image) to render new, identity-preserving portraits.

It's handy for things like profile-picture sets, avatars, expression sheets for illustration or reference, and mood variations of a headshot — all from a handful of photos. It is not a face-swap or deepfake tool for impersonating other people: the intended use is generating images of yourself, from photos you have the right to use. See the License & usage terms section for what that means in practice.


Features

  • 🎭 25 built-in expressions — from happy and shocked to skeptical, flirty, and pondering, each described in muscle-level detail for accurate results.
  • ✍️ Describe your own — skip the presets and type a free-text expression; identity is always preserved.
  • 🖼️ 1–4 variants per generation — pick how many portraits to render at once, then regenerate for a fresh set.
  • 👤 Multi-photo identity — upload up to 5 reference photos (multiple angles improve likeness). Images are downscaled in the browser before upload.
  • 🪟 Transparent background — optionally run server-side segmentation to get cut-out PNGs with a real alpha channel.
  • 🧠 Headroom safety net — a post-generation pass re-frames portraits whose heads sit too close to the top edge, without distorting the subject.
  • 🔒 Privacy-conscious — your API key stays server-side; photos are sent only to your own server and to OpenRouter.
Pick an expression Describe your own Results
Expression grid Custom prompt Results grid

How it works

Browser (page.tsx)
  └─ POST /api/generate
        { images: [dataURL...], count, transparent,
          expression: "shocked"   // preset mode
          | customPrompt: "..."   // custom mode (no reference image)
        }
        └─ route.ts (Node runtime)
             ├─ preset: reads facialExpressions/<expression>.png as the reference
             ├─ custom: no reference; uses the free-text prompt directly
             └─ lib/imageGen.ts → `count` parallel OpenRouter chat-completion calls
                   model: google/gemini-3-pro-image, modalities: ["image","text"]
                   prompt: "images 1..n are the SAME person — keep their identity;
                            (preset) the LAST image is an expression reference —
                            copy ONLY the expression, not identity"

This is identity-preserving generation, not pixel-level compositing. The model renders new portraits rather than pasting an expression onto your photo.


Getting started

Prerequisites

  • Node.js 18.17 or newer (Node 20 LTS recommended). Check with node -v.
  • An OpenRouter API key whose account has access to image models (google/gemini-3-pro-image).
  • An internet connection for the first install — the native image dependencies (sharp, onnxruntime-node) download prebuilt binaries during install.

1. Clone

# HTTPS
git clone https://github.com/SonapSav/ExpressionStudio.git

# or SSH
git clone git@github.com:SonapSav/ExpressionStudio.git

cd ExpressionStudio

2. Install dependencies

The steps are the same on Windows, macOS, and Linux — npm downloads the prebuilt native binaries for your platform automatically.

npm install

macOS / Linux: no extra steps. Windows: no extra steps either — just make sure you're on Node 18.17+. If a native module (sharp, onnxruntime-node) ever fails to install, it's almost always a missing internet connection during install — reconnect and re-run npm install.

3. Add your API key

Copy the example env file and add your OpenRouter key:

# macOS / Linux
cp .env.example .env.local

# Windows (PowerShell)
Copy-Item .env.example .env.local

Then edit .env.local:

OPENROUTER_API_KEY=your_openrouter_api_key_here

4. Run

npm run dev

Open http://localhost:3011.

For a production build:

npm run build
npm run start

Usage

  1. Upload a clear, front-facing photo (up to 5; multiple angles improve likeness).
  2. Pick an expression from the grid, or switch to Describe your own and type a free-text description.
  3. Choose how many variants (1–4) you want.
  4. (Optional) Tick Transparent background for cut-out PNGs with a real alpha channel instead of a photographic background.
  5. Generate — variants arrive in ~15–30s (a few seconds longer with a transparent background). Download any of them, or Regenerate for a fresh set.

The first transparent-background generation downloads the segmentation model weights (~40 MB, one time).


Configuration

All configuration lives in .env.local (see .env.example):

Variable Required Description
OPENROUTER_API_KEY Your OpenRouter key with image-model access.
OPENROUTER_IMAGE_MODEL Override the image model (default google/gemini-3-pro-image).
OPENROUTER_IMAGE_RESOLUTION Force output resolution as WIDTHxHEIGHT (e.g. 1024x1280 for a 4:5 portrait). Also drives the requested aspect ratio.
OPENROUTER_SITE_URL Site URL sent as HTTP-Referer for OpenRouter rankings.

Customizing the expression set

Edit lib/expressions.ts. Each entry needs a file present in both facialExpressions/ (server-side reference) and public/expressions/ (served to the browser grid), plus a prompt describing the expression in muscle-level detail.


Project structure

Path Purpose
app/page.tsx Main UI.
app/api/generate/route.ts Validates the request, loads the reference PNG, returns variants.
lib/expressions.ts The 25 expressions and their text descriptions.
lib/imageGen.ts OpenRouter request + prompt construction (generateVariants).
lib/removeBackground.ts Server-side background removal (makeTransparent).
lib/headroom.ts Post-generation reframing safety net (ensureHeadroom).
components/ PhotoUploader, ExpressionGrid, ResultsGrid.
facialExpressions/ Source reference PNGs (server-side).
public/expressions/ The same PNGs, served to the browser grid.

Notes

  • Cost: each "Generate" makes one image-generation call per variant, billed through your OpenRouter account. Regenerate makes another batch.
  • Watermark: outputs carry Google's invisible SynthID watermark.
  • Transparent background: Gemini image models can't emit alpha, so this option runs a segmentation model (@imgly/background-removal-node) on the server to cut the subject out. The native deps (onnxruntime-node, sharp) are marked as server-external in next.config.mjs.
  • Privacy: the API key stays server-side; photos are sent only to your server and to OpenRouter (which routes to Google for generation).

Tech stack


License & usage terms

Expression Studio's own source code is released under the MIT License — see LICENSE. MIT © 2026 Panos Vasilopoulos.

The MIT license covers this application's code only. It does not grant any rights over the AI model, the images the model generates, or the third-party services this app depends on. Those are governed by separate terms that you are responsible for reviewing and complying with:

Nano Banana Pro (Google gemini-3-pro-image), via OpenRouter

  • The model is proprietary to Google. Generating images with it is subject to Google's generative-AI terms and usage policies, and to OpenRouter's terms — because every request is routed through your OpenRouter account and, from there, to Google.
  • Commercial use. Neither this app nor its author grants you any rights to the model or to the images it produces. Whether you may use generated images commercially depends entirely on Google's and OpenRouter's current terms for the model and plan you use. Review them before any paid or commercial use:
  • SynthID watermark. Every generated image carries Google's invisible SynthID watermark identifying it as AI-generated. Do not present outputs as unedited photographs.
  • Likeness & consent. This app creates realistic images of real people from their photos. You are responsible for having the right to use every photo you upload and for complying with applicable law (consent, publicity/likeness rights, and data protection) — particularly for images of anyone other than yourself.

This section points you to the relevant terms; it is not legal advice. Terms change — when in doubt, consult the current Google and OpenRouter terms, and a lawyer for commercial use.

About

Self-hosted web app that generates photorealistic, identity-preserving portraits of you in 25+ facial expressions — powered by Nano Banana Pro (Google gemini-3-pro-image) via OpenRouter.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages