Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .claude/skills/prova-interfaccia/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: prova-interfaccia
description: Guarda l'interfaccia di DaProd Image senza Electron né motore (Chromium con un finto window.daprod), fa screenshot e stampa le richieste che l'app manderebbe al motore. Da usare dopo ogni modifica a pagine e componenti.
---

# Provare l'interfaccia senza motore

Nel container non ci sono la GPU né ComfyUI, ma l'interfaccia (`src/renderer`) gira in Chromium con un **finto `window.daprod`**
(`test/finto.js`: foto finte, un lavoro che "avanza", `testo.scrivi` che risponde con riquadri di prova).

```bash
npm run build # compila renderer, main e preload in out/
python3 -m http.server 8765 --directory out/renderer & # serve la build (se non sta già girando)
node test/interfaccia.mjs # foto in test/.out/ + prompt e dimensioni delle richieste
```

- `test/interfaccia.mjs` apre Modifica → Estrai, sceglie un oggetto e una persona (con l'omino) e stampa `larghezza × altezza` e il
`prompt` dell'ultima richiesta (`window.__ultima` è la richiesta accodata, `window.__emetti('lavoro', …)` fa finire un lavoro).
- Per provare un'altra pagina copia il file e cambia i passaggi: i selettori sono quelli dell'interfaccia (`.rotaia button:has-text("Crea")`,
`.segmenti button:has-text("…")`, `.colonna.destra textarea`).
- Per una funzione pura (es. `src/renderer/src/omino.ts`, `riquadri.ts`) basta `npx esbuild file.ts --bundle --format=iife` e una
pagina HTML col canvas; si guarda lo screenshot con Playwright (Chromium è in `PLAYWRIGHT_BROWSERS_PATH`, non scaricare niente).
- Le prove con l'app vera e il motore (Windows) sono `node test/setup.mjs` e `node test/foto.mjs`: non girano qui.

Non si può provare qui: la qualità delle immagini che il modello disegna. Di quella dillo chiaro nel messaggio finale.
21 changes: 21 additions & 0 deletions .claude/skills/rilascio/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: rilascio
description: Prepara una nuova versione di DaProd Image (numero di versione, CHANGELOG, README, CLAUDE.md, typecheck, build, PR). Da usare quando si finisce un giro di modifiche e va consegnato come vX.Y.Z.
---

# Rilascio di DaProd Image

Una versione = `version` in `package.json` (e `package-lock.json`, due punti in cima) + una voce in `CHANGELOG.md`. Unita su `main`,
la CI (`.github/workflows/app.yml`) compila l'installer e pubblica da sola la release.

1. **Scegli il numero**: 0.X.0 per una novità visibile, 0.X.Y per correzioni.
2. **Alza la versione** in `package.json` e nelle prime righe di `package-lock.json` (`"version"` due volte).
3. **CHANGELOG.md**: nuova sezione in cima, `## X.Y.Z — AAAA-MM-GG`, una frase di riassunto e poi elenco in grassetto di cosa cambia.
Si scrive **in italiano parlato**, per l'utente (non per gli sviluppatori).
4. **README.md** e **CLAUDE.md**: aggiorna le parti toccate (CLAUDE.md è la mappa del codice: file nuovi, nomi cambiati, regole).
5. Se è cambiato il **motore** (ComfyUI, pacchetti, nodi in `engine/`): alza `motore` in `engine/versioni.json`. Altrimenti no.
6. **Prima di consegnare**: `npm run typecheck` e `npm run build` devono passare. Se hai toccato l'interfaccia, guarda anche le foto di
`node test/interfaccia.mjs` (vedi la skill `prova-interfaccia`).
7. Commit sul ramo di lavoro, push, PR in bozza. Dopo il merge la release esce da sola.

Commenti, messaggi dell'interfaccia e testi per l'utente: sempre in italiano parlato. Nomi tecnici in inglese dove serve.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## 0.10.0 — 2026-10-15

Estrai persone **e oggetti**, una casella di testo come si deve, la testa dell'omino e le risoluzioni sempre in vista.

- **Estrai** (era «Personaggio»): il modello guarda **tutta la foto** e trova da solo persone e oggetti; clicchi quello che vuoi (si
illumina, il resto si abbassa; cliccando ancora nello stesso punto passi a quello più grande che lo contiene) o lo riquadri tu
trascinando. Niente più camera: la schermata è molto più pulita, con tre passi: **cosa estrarre**, **come lo vuoi**, **dimensioni e qualità**.
- Se è una **persona**: inquadratura (figura intera, fino alla vita, solo il volto) e posa (T-pose, A-pose, in piedi, come nella foto, disegnata con l'omino).
- Se è un **oggetto**: vista (come nella foto, davanti, ¾, dall'alto, di lato); se una parte è coperta o tagliata il modello la completa.
- Sfondo (bianco, grigio, trasparente, come la foto), ombra sul pavimento, note, e il pulsante «È una persona / È un oggetto» se il modello si sbaglia.
- **La casella dei prompt**: **tasto destro** con Incolla, Copia, Taglia, Annulla e Seleziona tutto in tutte le caselle di testo; il
**cestino** cancella tutto il testo in un colpo; la casella **si allarga da sola** col testo, senza barra di scorrimento (Crea,
Modifica, Laboratorio, editor delle ricette, preset, piani del modello).
- **L'omino**: la **testa** si regola (gira a sinistra/destra fino al profilo, su/giù, inclinata, con modi pronti) e va nel prompt a parole.
E la guida di posa non è più uno scheletro a linee (che il modello a volte lasciava nella foto): è un **manichino grigio pieno**, come
quello di legno degli artisti, con mani e dita; il prompt dice che è solo un riferimento da sostituire con la persona vera. Il
controllo automatico cerca anche tracce del manichino (parti grigie lisce) e, se le trova, le toglie. Un pulsante mostra il
manichino che vede il modello.
- **Le risoluzioni si vedono sempre**: in Modifica, Fusion, Esempio, Laboratorio ed Estrai c'è la riga «La foto esce a **1024 × 1376 px**»,
le scelte dei megapixel mostrano la misura vera in pixel sotto ogni voce (e non sono più nascoste in «Avanzate»), il pulsante
principale dice a che misura esce, Crea mostra larghezza × altezza accanto alla risoluzione. In «Solo una zona» dice che la foto resta com'è.
- **Skill di progetto** per chi sviluppa (`.claude/skills/`): `rilascio` (come si prepara una versione) e `prova-interfaccia` (come
vedere l'interfaccia senza motore, con `node test/interfaccia.mjs`).

## 0.9.1 — 2026-10-15

Lo scheletro dell'omino non resta più nella foto.
Expand Down
19 changes: 11 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ interfaccia**, installato e guidato dall'app.
`engine/versioni.json`: al primo avvio dopo l'aggiornamento l'app vede che il motore non è alla versione giusta e lo
aggiorna da sola (pagina di setup in modo "aggiorna", `mantieniModello`: niente scelta del modello, niente download).
- **Prima di consegnare**: `npm run typecheck` e `npm run build`. Le prove con l'app vera: `node test/setup.mjs`
(foto della pagina di installazione, `--installa` la fa davvero) e `node test/foto.mjs` (foto delle pagine in `test/.out/`).
(foto della pagina di installazione, `--installa` la fa davvero) e `node test/foto.mjs` (foto delle pagine in `test/.out/`). Senza Electron né motore (qui nel container): `node test/interfaccia.mjs` (Chromium + finto `window.daprod` in `test/finto.js`), vedi `.claude/skills/prova-interfaccia`.
Per preparare una versione c'è la skill `.claude/skills/rilascio`.

## Il motore (src/main/installa, src/main/motore)
- **Versioni fissate** in `engine/versioni.json`: ComfyUI a un commit preciso (zip da codeload, estratto col `tar.exe`
Expand Down Expand Up @@ -78,7 +79,7 @@ interfaccia**, installato e guidato dall'app.
- **Descrivi** (Migliora prompt, Foto → prompt): `TextGenerate` con lo stesso Qwen3-VL già caricato, niente modelli in più.

## Interfaccia (src/renderer)
- **Modifica** ha 6 modi: tutta la foto, zona, espandi, **Fusion**, **Esempio** e **Personaggio** (vedi sotto) (`componenti/Fusion.tsx`, richieste in `fusion.ts`: nessuna base, `m.riferimenti` sono le foto;
- **Modifica** ha 6 modi: tutta la foto, zona, espandi, **Fusion**, **Esempio** e **Estrai** (vedi sotto) (`componenti/Fusion.tsx`, richieste in `fusion.ts`: nessuna base, `m.riferimenti` sono le foto;
Fusion = `modalita: 'crea'` con `images.image_N`, Esempio = `modalita: 'modifica'` con l'esempio come `image_1`; i risultati stanno nel
componente, non diventano versioni; il confronto prima/dopo usa sempre `versioni[0]`);
`★ base` scambia base e riferimento. **Dimensioni** (`componenti/Misura.tsx`, `Richiesta.uscita`): auto / come l'originale / misura
Expand All @@ -94,10 +95,11 @@ interfaccia**, installato e guidato dall'app.
qualunque pagina la mandi (il `conParoleLora` del renderer fa la stessa cosa, non raddoppia). **Esempio** (`fusion.ts`): `pianificaEsempio` fa un foglio con l'esempio (image 1) e i riferimenti numerati e lo dà al Qwen3-VL (`descrivi`, `SISTEMA_PIANO`), che scrive l'istruzione di modifica decidendo da solo cosa sostituire con cosa (niente regole per persone o altro); il piano sta in `pianoEsempio` (con `chiaveEsempio`: foto + nota), si corregge nell'interfaccia, e `richiestaEsempio` lo manda come prompt con l'esempio come `image_1`. **Galleria**: tasto destro
(`MenuContestuale`), `galleria:elimina` con `definitivo`, `galleria:copia` con più id = file negli appunti con PowerShell `Set-Clipboard`. **Laboratorio**:
un `Ingresso` di tipo `camera` mostra il comando Camera (valore JSON, `leggiCamera`) e le ricette aggiungono il passo `VISTA_TUA`.
- **Casella** (`componenti/Casella.tsx`): la textarea dei prompt (cresce col testo, cestino); il tasto destro nei campi di testo è nel main (`index.ts`, evento `context-menu`).
**Risoluzioni**: `Misura.tsx` ha `dimMp()` (le stesse misure del motore), `ScegliMp` (megapixel con i pixel sotto) e `Uscita` (la riga «La foto esce a…»).
- **Omino** (`omino.ts` puro + `componenti/Omino.tsx`, `ominoImmagine.ts`): scheletro di 15 giunti in un riquadro 280×300 (`Dx`/`Sx` sono della persona: la destra sta a sinistra dello schermo);
`trascina()` ruota l'osso attorno al genitore (lunghezze fisse, si porta dietro i discendenti; il bacino sposta tutto); mani = 5 dita con piegamento 0–1 + divaricazione + polso
(`geometriaMano`); `disegnaOpenPose()` fa la mappa di posa (colori OpenPose, dita ad arcobaleno) e `descriviPosa()` la posa in inglese. In **Personaggio** posa `omino` (crop + scheletro come
`<image2>`); nel **Laboratorio** un `Ingresso` di tipo `posa` (JSON) e il passo `POSA_TUA` con `PassoWf.posa`: `richiestaPasso` (async) mette lo scheletro ultima immagine e sostituisce `{posa}` con il suo `<imageN>`.
(`geometriaMano`); `Posa.viso` (gira/su/inclina) è la testa; `disegnaManichino()` fa la guida di posa (manichino grigio pieno su fondo chiaro: niente linee da copiare) e `descriviPosa()` la posa in inglese. In **Estrai** posa `omino` (manichino come `<image1>`, ritaglio come `<image2>`); nel **Laboratorio** un `Ingresso` di tipo `posa` (JSON) e il passo `POSA_TUA` con `PassoWf.posa`: `richiestaPasso` (async) mette lo scheletro ultima immagine e sostituisce `{posa}` con il suo `<imageN>`.
`pulisciOmino.ts`: `controllaScheletro(lavoroId…)` — a lavoro finito il Qwen3-VL risponde YES/NO se resta lo scheletro, e se sì parte una modifica (`PROMPT_PULIZIA`) che lo toglie; `SENZA_SCHELETRO` chiude i prompt che usano l'omino.
**Idee** (`regista.ts`): le azioni rapide di Modifica sono idee a toggle (`idee`); `pianificaIdee` (Qwen3-VL, `SISTEMA_IDEE`, foto + eventuale foglio con i riferimenti) le mescola in un'istruzione sola
(`fantasia`: temperatura), piano in `pianoIdee` (con chiave). **Esempio**: il piano è JSON `{instruction, crops}` (attributi a parole + riquadri 0–1000 sul foglio, `leggiPiano`, `riquadroNellaFoto`,
Expand Down Expand Up @@ -128,10 +130,11 @@ interfaccia**, installato e guidato dall'app.
il vecchio `testo` del titolo si trasforma in elemento in `vecchioInNuovo()`. `costruisci.ts` raggruppa (densità aerea/normale/fitta) e sceglie
un modello fra i primi senza ripetere; `disegna.ts` disegna su canvas (schermo e stampa: cornice, ombra, z, elementi); `esporta.ts` JPEG/PDF
(`pdf.ts`, senza librerie). Le foto grandi non restano in cache.
- **Personaggio** (modo di Modifica, `componenti/Personaggio.tsx` + `personaggio.ts` + `riquadri.ts`): `trovaPersone` chiede al Qwen3-VL (`descrivi` con la foto)
un JSON `bbox_2d` 0–1000 (`leggiPersone`, tollera fence e JSON troncato); si può riquadrare a mano. `richiestaEstrai` ritaglia la persona (+12%) in un temp
e fa una **Crea** con quella come `<image1>` (posa T/A/in piedi, sfondo, proporzioni); `richiestaVista` fa una **Modifica** del risultato con
`componiCamera` (come il comando Camera, LoRA camera se c'è). I risultati stanno in `usaPersonaggio` (non nelle versioni).
- **Estrai** (modo di Modifica, `componenti/Estrai.tsx` + `estrai.ts` + `riquadri.ts`): a foto caricata `trovaElementi` chiede al Qwen3-VL (`descrivi` con la foto intera)
un JSON `{bbox_2d 0–1000, label, kind: person|object}` (`leggiElementi`, tollera fence e JSON troncato, toglie i riquadri doppi); si sceglie con un clic (più clic = il riquadro più grande
che contiene il punto) o riquadrando a mano; il tipo (persona/oggetto) si corregge. `richiestaEstrai` ritaglia la cosa (+12%) in un temp e fa una **Crea** con quella come `<image1>`:
**persona** = inquadratura (intera/vita/volto) + posa (T/A/in piedi/come/omino: manichino `<image1>`, ritaglio `<image2>`); **oggetto** = vista (come/davanti/¾/alto/lato), completa le parti coperte.
Proporzioni (`proporzioniEstrai`, auto = dal riquadro per gli oggetti) e `dimensioniEstrai` sono le stesse cifre che l'interfaccia mostra. I risultati stanno in `usaEstrai` (non nelle versioni).
- Le impostazioni di ogni immagine stanno **dentro il PNG** (chunk iTXt `daprod`, `src/main/png.ts`): la galleria si
ricostruisce dai file e un PNG trascinato in Galleria torna con prompt, seed e LoRA.
- LoRA: dall'intestazione safetensors si capisce se sono per Qwen 2.1 (`transformer_blocks.N.attn.to_q`/`img_mlp`)
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ a 300 dpi con l'abbondanza.

## L'omino

Un manichino con le ossa che muovi come vuoi (anche **mani e dita**, una per una) per dare una posa al personaggio: in Modifica →
Personaggio e nelle ricette del Laboratorio. Diventa una mappa di posa che il modello segue.
Un manichino con le ossa che muovi come vuoi (anche **mani e dita**, una per una, e la **testa**) per dare una posa al personaggio: in
Modifica → Estrai e nelle ricette del Laboratorio. Diventa un manichino grigio che il modello usa solo come guida di posa.

## Personaggio
## Estrai

In Modifica: una foto con più persone, clic su quella che vuoi (si illumina), **estraila a figura intera** (T-pose, A-pose…) e **muovi la
camera** attorno a lei: una vista o il giro completo.
In Modifica: dai una foto e il modello trova **persone e oggetti**; clicchi quello che vuoi (si illumina) e lo ottieni da solo su sfondo
pulito: una persona a figura intera (T-pose, A-pose, disegnata con l'omino), fino alla vita o il volto; un oggetto intero, da davanti,
di lato o dall'alto. Vedi sempre a che risoluzione esce.

## Laboratorio

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "daprod-image",
"productName": "DaProd Image",
"version": "0.9.1",
"version": "0.10.0",
"description": "DaProd Image: crea e modifica foto con Qwen-Image-2.1 sul tuo PC (CUDA, da 6 GB di VRAM). Turbo a 8 passi, preset per poster e infografiche, zone con anteprima dal vivo, LoRA con un clic, galleria.",
"author": "DaProd",
"license": "MIT",
Expand Down
19 changes: 18 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// DaProd Image — processo principale: finestra, protocollo daprod:// per le immagini, IPC verso l'interfaccia.
import { execFile } from 'node:child_process'
import { app, BrowserWindow, clipboard, ClipboardItem, dialog, ipcMain, nativeImage, net, protocol, shell, type IpcMainInvokeEvent } from 'electron'
import { app, BrowserWindow, clipboard, ClipboardItem, dialog, ipcMain, Menu, nativeImage, net, protocol, shell, type IpcMainInvokeEvent } from 'electron'
import { copyFileSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
import { basename, extname, join, normalize } from 'node:path'
import { pathToFileURL } from 'node:url'
Expand Down Expand Up @@ -60,6 +60,23 @@ function creaFinestra(): void {
}
})
finestra.once('ready-to-show', () => finestra?.show())
// tasto destro nei campi di testo (prompt, note, nomi): Electron non lo fa da solo
finestra.webContents.on('context-menu', (_e, p) => {
if (!p.isEditable && !p.selectionText) return
const voci: Electron.MenuItemConstructorOptions[] = p.isEditable
? [
{ label: 'Annulla', role: 'undo', enabled: p.editFlags.canUndo },
{ label: 'Ripeti', role: 'redo', enabled: p.editFlags.canRedo },
{ type: 'separator' },
{ label: 'Taglia', role: 'cut', enabled: p.editFlags.canCut },
{ label: 'Copia', role: 'copy', enabled: p.editFlags.canCopy },
{ label: 'Incolla', role: 'paste', enabled: p.editFlags.canPaste },
{ type: 'separator' },
{ label: 'Seleziona tutto', role: 'selectAll', enabled: p.editFlags.canSelectAll }
]
: [{ label: 'Copia', role: 'copy' }]
Menu.buildFromTemplate(voci).popup()
})
finestra.webContents.setWindowOpenHandler(({ url }) => {
if (/^https?:\/\//.test(url)) void shell.openExternal(url)
return { action: 'deny' }
Expand Down
58 changes: 58 additions & 0 deletions src/renderer/src/componenti/Casella.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// La casella di testo dei prompt: si allarga da sola quanto il testo (niente barra di scorrimento) e ha il cestino per cancellare tutto.
import { useLayoutEffect, useRef, type JSX, type Ref } from 'react'
import { I } from './Icone'

export function Casella(p: {
valore: string
cambia: (v: string) => void
placeholder?: string
/** altezza minima in px */
minimo?: number
disabled?: boolean
/** senza cestino (per i testi corti) */
senzaCestino?: boolean
inputRef?: Ref<HTMLTextAreaElement>
}): JSX.Element {
const el = useRef<HTMLTextAreaElement | null>(null)
const minimo = p.minimo ?? 96

const adatta = (): void => {
const t = el.current
if (!t) return
t.style.height = 'auto'
t.style.height = `${Math.max(minimo, t.scrollHeight + 2)}px`
}
// a ogni cambio di testo, e quando la colonna si allarga o si stringe (il testo va a capo in un altro punto)
useLayoutEffect(adatta, [p.valore, minimo])
useLayoutEffect(() => {
const t = el.current
if (!t || typeof ResizeObserver === 'undefined') return
let larg = t.clientWidth
const o = new ResizeObserver(() => {
if (t.clientWidth !== larg) {
larg = t.clientWidth
adatta()
}
})
o.observe(t)
return () => o.disconnect()
}, [])

const aggancia = (t: HTMLTextAreaElement | null): void => {
el.current = t
const r = p.inputRef
if (typeof r === 'function') r(t)
else if (r) (r as { current: HTMLTextAreaElement | null }).current = t
}

return (
<div className="casella">
<textarea ref={aggancia} value={p.valore} placeholder={p.placeholder} disabled={p.disabled} onChange={(e) => p.cambia(e.target.value)} style={{ minHeight: minimo }} />
{p.valore && !p.disabled && !p.senzaCestino && (
<button type="button" className="casella-x" title="Cancella tutto il testo" onClick={() => { p.cambia(''); el.current?.focus() }}>
<I.cestino />
</button>
)}
</div>
)
}
Loading
Loading