package imagegen import "context" // FaceSwapRequest transfers an identity from Source into Target. // // This is a DIFFERENT OPERATION from Edit, not a better-tuned one. Measured // against the instruction-edit models on 2026-07-31, asking a diffusion model // to put a specific person's face into a photo does not work by any route — // by name, by description, or by supplying the portrait as a reference image. // Face swapping is a dedicated detect/align/blend pipeline; a provider that // cannot do it should not pretend Edit is a substitute. type FaceSwapRequest struct { // Target is the photo to edit — the pose, expression, lighting and // everything outside the face are preserved from it. Target Image // Source is a photo of the face to put in. Only the identity travels; // the source's own pose and expression do not. Source Image // Index selects WHICH face in Target, in the provider's documented // ordering (llamaswap: left to right by box centre, as reported by // ListFaces). nil = the largest face, which is right for a portrait and // wrong for a group — enumerate first when it matters. Index *int // All swaps every detected face and ignores Index. All bool } // FaceSwapOption mutates a FaceSwapRequest before it is sent. type FaceSwapOption func(*FaceSwapRequest) // WithFaceIndex selects which face in the target to replace. func WithFaceIndex(i int) FaceSwapOption { return func(r *FaceSwapRequest) { r.Index = &i } } // WithAllFaces swaps every detected face. func WithAllFaces() FaceSwapOption { return func(r *FaceSwapRequest) { r.All = true } } // Apply returns a copy of the request with all options applied. func (r FaceSwapRequest) Apply(opts ...FaceSwapOption) FaceSwapRequest { for _, opt := range opts { opt(&r) } return r } // DetectedFace is one face located in an image, in PIXEL coordinates. type DetectedFace struct { // Index is the face's position in the provider's stable ordering, and // the value FaceSwapRequest.Index expects. Index int // Box is [x0, y0, x1, y1]. Box [4]int // Score is the detector's confidence, 0-1. Score float64 // Yaw is how far the head is turned from camera, in degrees, or nil when // the provider does not report pose. Exposed on ENUMERATION, not just // after the fact, because it is how a caller picks a face a swap will // actually work on: past roughly ±45° the features carrying identity are // edge-on, and the result reads as a generic person however good the // transfer is. A bounding box cannot show this. Yaw *float64 } // Size returns the box dimensions. Derived rather than stored: carrying // width/height alongside Box is two sources of truth for one fact, and the // pair can disagree after any transform. func (f DetectedFace) Size() (w, h int) { return f.Box[2] - f.Box[0], f.Box[3] - f.Box[1] } // SwappedFace is the MEASURED outcome for one face the provider replaced. // // It exists because "the call returned an image" and "the likeness // transferred" are different claims that look identical from outside, and a // caller that cannot tell them apart will go looking for another way to // check. The one it reaches for — asking a vision model who the result looks // like — is wrong in exactly the cases that matter: a VLM shown a jogger in a // Georgetown cap holding McDonald's cups answers "Bill Clinton" whoever's // face is on him, so it reports failure on a correct swap. type SwappedFace struct { // Index is the face's position in the provider's left-to-right ordering. Index int // Width, Height are the replaced face's pixel size in the TARGET. // Meaningful only against ImageWidth/ImageHeight: a 138px face is large // in a 400px picture and nearly invisible in a 2000px one, and it is the // ratio, not the absolute size, that decides whether a person notices. Width, Height int // ImageWidth, ImageHeight are the target image's dimensions, repeated on // every entry so a single face is self-describing without the caller // holding onto the rest of the response. ImageWidth, ImageHeight int // Yaw is how far the head is turned from camera, in degrees, or nil when // the provider does not report pose. The best single predictor of whether // a swap will READ as the source person: past roughly ±45° the features // carrying identity are edge-on and the result looks like a generic // person rather than a specific one. Yaw *float64 // IdentitySimilarity is cosine similarity between the source face and the // face actually present in the result, 0-1, or nil when the provider // could not measure it. Above ~0.5 the identity transferred; a LOW value // is the only evidence that a swap genuinely failed. IdentitySimilarity *float64 } // FractionOfImage is the swapped face's width as a share of the image's, 0-1. // The number that predicts whether a person will SEE the change: the swap // that prompted all this replaced a 138px face in a 1010px-wide photo — 14%, // correct by every measure and invisible at a glance — while the same code on // a 168px face in a 385px picture (44%) is unmistakable. Returns 0 when the // dimensions are unknown. func (f SwappedFace) FractionOfImage() float64 { if f.ImageWidth <= 0 || f.Width <= 0 { return 0 } return float64(f.Width) / float64(f.ImageWidth) } // FaceSwapper is the optional face-transfer surface. Separate interface so // existing providers keep compiling; callers type-assert. type FaceSwapper interface { // ListFaces enumerates the faces in an image, in the SAME ordering // FaceSwapRequest.Index uses. Exposed because a caller asked to change // "the man on the right" needs a way to name one face and to check its // own choice against pixel boxes. ListFaces(ctx context.Context, img Image) ([]DetectedFace, error) // FaceSwap transfers Source's identity into Target. FaceSwap(ctx context.Context, req FaceSwapRequest, opts ...FaceSwapOption) (*Result, error) }