Skip to content

Latest commit

 

History

36 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Head in Jar 🫙

Fit an image to a 3D model, then line it up with the object you're projecting onto.

Head in Jar is a desktop projection mapping editor. You can work with a still image or a live video feed, inspect the result in 3D, and send it to a separate display.

Electron · Three.js · JavaScript


In the editor

  • Import an OBJ, orbit around it, and check the surface in the studio-lit 3D preview.
  • Move, scale, and rotate an image. Use landmarks for a closer fit and masks to limit coverage.
  • Pair landmarks on the image and model to fit a front-facing texture.
  • Align projected model points with a physical object.
  • Bring in one WebRTC video stream, with optional audio, from the built-in sender page or a WHIP client.
  • Control the second display with Start projection, Hold, and Stop projection.
  • Save your project, recover an interrupted session, and undo or redo edits.

This is still early software. You can use static images or one live WebRTC source. The desktop setup has been tested on Linux with X11/XWayland. A macOS user has opened the unsigned package, but physical projector behavior on macOS still needs a real-device check.

Downloads

Versioned builds are published on the Releases page: AppImage and .deb for Linux x64, plus .dmg and .zip for both Intel and Apple Silicon Macs. Bring your own OBJ and image; no model or texture is bundled.

macOS first launch

The macOS packages are currently unsigned and unnotarized. Download the package for your Mac from the official Releases page, copy Head in Jar.app to Applications, and try opening it. If macOS offers Open Anyway in System Settings → Privacy & Security, follow Apple's instructions.

If macOS instead says the app is “damaged” and offers no Open Anyway button, a user has confirmed that clearing the download quarantine from this trusted copy allows it to open:

xattr -dr com.apple.quarantine "/Applications/Head in Jar.app"

This removes Gatekeeper's download check for that copy; it does not verify the package or sign the app. Use it only for an app you obtained directly from this project's Releases page and trust. If the .dmg itself will not open, download it again instead. Signing and notarization are needed to avoid this manual step in future releases.

Get started 🚀

Requires Node.js 22 or newer, npm, and a desktop session with WebGL support. Connect a projector or second monitor for dedicated output.

git clone git@github.com:kmrov/headinjar.git
cd headinjar/app
npm ci
npm start

npm start builds the renderer and opens the editor. Bring an OBJ mesh and a reference image of your own; the repository does not bundle either.

Start with the illustrated beginner tutorial. Its screenshots were captured with local assets; the OBJ and texture themselves are not distributed.

Your first projection 🎯

  1. Import a model and image. Load an OBJ and a reference image in the editor.
  2. Place the texture. Use Front mapping for an image on the OBJ's +Z side. Adjust Image position, or open Align and pair at least three non-collinear image and model landmarks. The texture fits automatically as valid pairs are added. To drag an image around the face and sides without model UVs, choose Wrap, use Place image, and refine it with Align. To place one image on another side, choose Surface, right-drag to orbit the model, then click or drag it with Place image.
  3. Refine coverage. Use Mask to limit coverage. Double-click to finish a mask contour.
  4. Choose an output display. Click Choose display in the bottom bar, select the projector, then click Start projection. Output opens black until explicitly started.
  5. Match the physical object. Open Projector calibration and drag its model points until their projected marks match the real surface, then click Apply.
  6. Save the project. Keep the reference image accessible at its original path.

For precise landmark placement in Front → Align, zoom the source image and model independently with the mouse wheel at the cursor. Middle-drag to pan either view. Fit source resets the image; Fit view resets the model. These view changes do not alter the saved texture placement or projector calibration.

Texture mapping modes

Mode Use it for
Front A front-facing image fitted with transforms, a control grid, masks, and landmarks. The image extends around the main connected surface, including its sides and underside.
Wrap A regular image dragged across the front and sides of a head. Scale, rotation, and image/model landmarks are saved separately from Front; the back stays neutral. No authored UV atlas is needed.
Surface One image or live video placed by clicking or dragging any visible model side. Right-drag to orbit while Place image stays active; scale and rotation are in the Surface inspector.
Model UV A texture atlas authored for the OBJ's original UV coordinates. Front placement controls do not apply in this mode.

Front starts on the OBJ's original +Z side; the editor camera starts there, and orbiting it does not change the mapping direction. The same image coordinates stretch across connected sides and undersides, so those areas show an approximation of the front image rather than their real appearance. Wrap uses a head-oriented curved mapping around the original vertical axis. In Place image, left-drag to move the image and right-drag horizontally to rotate it; turn off Place image to orbit the model. Align adds points on either the front or a visible side. Wrap fades near the back edge, and its settings do not replace Front alignment. A front-only photo still lacks real profile detail and may stretch along the sides. Surface follows the selected local face and clips at sharp corners. It places a single planar image, not a whole-model texture atlas. If an OBJ has UV coordinates, a random photo still will not match its texture atlas.

Physical landmark alignment

Texture alignment fits the image to the digital model. Physical alignment corrects the final projector frame to match a real object.

  1. Fix the projector and object in place. Visible landmarks from Image placement → Align appear automatically in Projector calibration. If there are none, use Add point to pick a place on the model.
  2. Click Choose display in the bottom bar, select the projector, then click Start projection.
  3. Select a numbered point. Its cross appears on the real surface. Drag the orange point in the preview until the projected cross matches that place.
  4. Repeat for at least three non-collinear points, up to twelve, then click Apply.

After Apply, the projected cross disappears; select a point again to continue adjusting it. Invalid or folded corrections are rejected. Changing the mesh or output dimensions resets physical calibration; changing the reference image preserves it.

This is operator-guided 2D frame correction. It does not detect the object or automatically solve its 3D pose.

Live WebRTC input 📡

Under Source type, choose Live video · WebRTC and Start connection server. The image controls give way to the video preview and connection controls. The server listens on 127.0.0.1:19840 and stays off until started. You can connect and inspect live video in the editor before opening an output display or importing a mesh. After importing a mesh, the same live frame appears on the model and in Front → Align. Freeze preview holds the editor and alignment view while the stream keeps running; it does not freeze the projector. The server provides two connection methods:

  • Built-in sender: use Copy link and open the link on the same computer. The page at /sender creates a test canvas video track and optional synthetic audio, then exchanges the offer and answer automatically. The link contains a per-start token in its URL fragment; keep it private.
  • WHIP client: use Copy WHIP URL and Copy Bearer token. Send a complete ICE-gathered SDP offer as POST /whip with Content-Type: application/sdp and Authorization: Bearer <token>. The 201 response contains SDP answer and a session Location. Apply the answer, then send authenticated DELETE to that exact Location when finished. Trickle ICE, PATCH, and ICE restart are not supported in this version.

For a client on the same computer, the default WHIP endpoint is http://127.0.0.1:19840/whip. To use HTTPS, set both HEADINJAR_TLS_CERT and HEADINJAR_TLS_KEY to certificate and key file paths before starting the app. The certificate must cover 127.0.0.1 and be trusted by the client. A missing or unreadable certificate or key prevents the server from starting. The app does not expose the endpoint on a LAN interface.

Live input does not turn on the projector. Select a display and click Start projection after the stream connects. Hold freezes the last projected frame and mutes audio; Stop projection returns to black and mutes audio. Closing or reopening Output keeps the WebRTC source connected. A connection loss, video resize, or decoded-frame stall disarms output and requires another explicit Start projection. Switching back to Reference image replaces the live editor preview with the saved reference image.

Output controls

Control Effect
Choose display Select or change the output display. Output opens black.
Start projection / Stop projection Start live rendering or return to black.
Hold Freeze the current output frame.

On Linux, the launcher selects X11/XWayland so the output window can be positioned on the selected monitor. The display chooser shows physical screen pixels separately from logical desktop dimensions. Selecting a display automatically sets the project's render resolution to that screen's physical resolution. If the resolution changes, projector calibration resets and must be checked before projection; higher resolutions may reduce frame rate. XRandR supplies exact pixel dimensions on Linux when available; without it, the app estimates them from Electron's logical bounds and scale factor, which can be off by a pixel under fractional scaling.

Project files

Projects are saved as JSON with mesh geometry, placement, landmarks, and projector settings. Reference images remain external files addressed by path; keep them alongside your own working assets and update the reference if you move them. Recovery files support restoring newer interrupted work.

Development 🛠️

Run these commands from app/:

npm test        # Mapping, project, output, IPC, WebRTC, signaling, and WHIP tests
npm run build   # Bundle the editor and projector renderers
npm run smoke   # Launch Electron and exercise the desktop workflow

The smoke test requires a working desktop display and WebGL. It creates temporary synthetic assets and writes screenshots to the ignored app/test-results/ directory. It checks import, editing, save/recovery, IPC isolation, and output window behavior. It does not validate optical calibration or sustained projector performance.

app/
  electron/     Desktop windows, IPC, and project lifecycle
  renderer/     Editor interface, 3D preview, and projector rendering
  src/          Mapping math, project storage, history, and output state
  scripts/      Build, launch, and desktop smoke test
  test/         Automated unit and integration tests

The renderer uses Three.js; desktop integration uses Electron. Fonts and icons are supplied by Inter and Phosphor npm packages. Dependencies retain their respective licenses. No open-source license is granted for this project at this time.

Publish a release

The release workflow runs when a version tag such as v0.1.0 is pushed. It sets the package version from the tag, runs tests, builds Linux and macOS packages, and publishes the GitHub Release only after all six packages are available. To publish the next version from the public checkout:

git tag v0.2.0
git push origin v0.2.0

Use a new vMAJOR.MINOR.PATCH tag for each release. Regular pushes to main build all three platforms and keep packages as temporary Actions artifacts; they do not create releases. To check Linux packaging locally, run npm run build and npm run package -- --linux --x64 from app/; package files appear in ignored app/release/. macOS packaging runs on GitHub's macOS runners. Installing a Developer ID certificate and enabling signing/notarization is a separate release step; the current macOS packages do not claim those protections.

About

A 3d projection mapping tool for putting heads in jars

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages