DEV Community

sophie bella
sophie bella

Posted on

Two Image Edits, Two Contracts: Perspective and Identity

The hidden problem behind “edit this image”

An image editor can expose many controls, but two edits deserve separate contracts: changing where the viewer appears to stand, and changing who appears in the frame. The first is a perspective edit. The second is an identity edit. They overlap visually, yet they fail in different ways and need different review questions.

The public PhotoGenerator AI tools make this distinction useful to discuss at the interface level. This is a design memo based on visible workflows, not a claim about private APIs, model routing, benchmarks, or implementation details.

Make the job type explicit

Avoid representing both actions as an untyped edit request. A small discriminated union gives the frontend a place to preserve intent:

type ImageEdit =
  | {
      kind: "perspective";
      sourceId: string;
      horizontalDegrees: number;
      verticalDegrees: number;
      lens: "default" | "wide-angle" | "close-up";
      process: "fast" | "ultra";
    }
  | {
      kind: "identity";
      targetImageId: string;
      faceImageId: string;
      mode: "face" | "head";
      prompt: string;
    };
Enter fullscreen mode Exit fullscreen mode

The exact values above are a proposal for a discussable UI contract. The public Camera Angle Control screen visibly separates an uploaded source, horizontal and vertical rotation, lens type, and process mode. That is enough evidence to model the user’s intent, but not enough evidence to claim a backend schema.

The contract prevents a common UX error: showing a face-specific review message after a perspective edit, or asking for lens settings when the user only supplied a face source and a target image.

Preserve inputs through asynchronous states

Both edit types may take the user from input to processing to review. A failed request should not destroy the source selection or the explanation of what the user wanted:

type EditState<T> =
  | { status: "ready"; input: T }
  | { status: "processing"; input: T; requestId: string }
  | { status: "review"; input: T; outputUrl: string }
  | { status: "failed"; input: T; message: string };
Enter fullscreen mode Exit fullscreen mode

This model supports a useful retry rule: retry keeps the same input by default, while “start over” is an explicit action. It also lets the review screen show the source beside the output instead of asking the reviewer to remember the original.

Use different acceptance criteria

For a perspective edit, test:

  • whether the intended camera direction is visible;
  • whether the subject’s scale and silhouette remain usable;
  • whether hidden surfaces, text, reflections, and accessories drift;
  • whether the selected lens changes the message as expected;
  • whether the output still fits the destination crop.

For an identity edit, test:

  • whether the target face follows the target gaze and head angle;
  • whether expression and posture still communicate the original moment;
  • whether light direction, skin tone, jawline, hair, and occlusion agree;
  • whether the source person and usage context are authorized;
  • whether the output is labeled or disclosed when the context requires it.

The public Face Swap flow shows two image inputs and Swap Face/Swap Head modes. Its visible copy also calls out gaze, head angle, posture, expression, lighting, and skin tone. These are good review fields; they are not proof that every input will preserve each property.

Test the UI contract, not a guessed model score

Selectors below are illustrative and must be replaced with the application’s verified DOM:

test("keeps perspective inputs after a failed request", async ({ page }) => {
  await page.getByLabel(/choose an image/i).setInputFiles("fixtures/catalog.png");
  await page.getByRole("button", { name: /generate/i }).click();
  await expect(page.getByTestId("source-preview")).toBeVisible();
  await expect(page.getByRole("button", { name: /retry/i })).toBeVisible();
});

test("requires both identity inputs before a face edit", async ({ page }) => {
  await page.getByLabel(/target image/i).setInputFiles("fixtures/scene.png");
  await expect(page.getByRole("button", { name: /generate/i })).toBeDisabled();
});
Enter fullscreen mode Exit fullscreen mode

The tests assert recoverability and input completeness. They do not assert that an output is aesthetically good. Visual review or a defined evaluation dataset is required for that question.

Treat comparison as a product feature

Review is easier when the interface does more than show the latest output. Keep the original source visible, expose the active job type, and show the parameters that matter for the current edit. A perspective reviewer needs to see the selected horizontal and vertical direction and lens. An identity reviewer needs to see which target and face images were submitted, plus the selected mode.

This also improves observability. Events can distinguish perspective_started, perspective_reviewed, identity_started, and identity_reviewed. A retry should be associated with the same input record, while a deliberate change should create a new revision. The names are examples, not a claim about an existing analytics implementation.

Do not hide policy in a generic success state. If an image uses a real person, the review surface can remind the operator to confirm consent and usage rights before export. If an angle edit creates a new still image rather than a real photograph from another camera, the copy should make that expectation clear. Good product copy reduces the number of unsupported assumptions a user brings to the result.

For a team workflow, store a short human-readable handoff note with the revision: “changed viewpoint to make the package label easier to read; confirm the right edge,” or “tested an alternate approved talent reference; verify consent before campaign use.” This is more useful than a status such as complete, because it explains what another reviewer is supposed to decide.

Keep policy in the workflow

Face replacement is not only a rendering operation. The product flow should make room for consent, rights, and disclosure decisions, especially for real people, public figures, advertising, or profile images. A technically coherent output can still be an inappropriate asset.

Perspective changes have a different boundary: they create a new still-image viewpoint, but they do not provide a real second camera capture or guarantee recovery of unseen geometry. The interface should say enough for users to form that expectation.

The implementation rule is simple: model perspective and identity as different jobs, preserve both inputs across failure, and give each job its own review checklist. Clear contracts make the UI easier to build and make the final human decision easier to defend.

Top comments (0)