<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Voor AI</title>
    <description>The latest articles on DEV Community by Voor AI (@voorai).</description>
    <link>https://dev.to/voorai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3753035%2F25b654f3-79cc-4a51-aff8-56a4d15b2141.png</url>
      <title>DEV Community: Voor AI</title>
      <link>https://dev.to/voorai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/voorai"/>
    <language>en</language>
    <item>
      <title>Build a Brand-Image Contract Before Generating Campaign Variants</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Wed, 12 Aug 2026 02:52:42 +0000</pubDate>
      <link>https://dev.to/voorai/build-a-brand-image-contract-before-generating-campaign-variants-15m4</link>
      <guid>https://dev.to/voorai/build-a-brand-image-contract-before-generating-campaign-variants-15m4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhvwzus7qssz08dfb78yz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhvwzus7qssz08dfb78yz.jpg" alt="Real Banana Pro AI Studio product and campaign examples" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“Keep the brand consistent” is not an executable requirement. A useful image workflow separates invariants—things that may not change—from variables that make each campaign asset new.&lt;/p&gt;

&lt;p&gt;This tutorial uses the current &lt;a href="https://voor.ai/banana-pro-ai-studio/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=banana_pro_brand_contract&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;Banana Pro AI Studio page on Voor&lt;/a&gt;. The exact route exposes a Prompt, optional Image Input, Aspect Ratio including Match Input Image, Public visibility, Reset, and Generate. It presents real product-studio and campaign examples. The live form supplies the current model and credit estimate before submission; no credits were spent here.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Encode the invariants
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"product_shape"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"short cylindrical bottle with rounded shoulder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"matte black, same height and diameter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cream field, centered two-line wordmark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logo_geometry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unchanged"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"materials"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bottle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amber glass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uncoated paper"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"must_not_change"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"label spelling"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"cap proportions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bottle silhouette"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use observable nouns and proportions. “Premium” is a direction; “matte black cap with the same height and diameter” is a constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep campaign variables separate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"campaign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cool stone shelf after rain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lighting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"soft overcast window light"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"single eucalyptus stem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"crop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4:5 social hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"negative"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"extra bottles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"floating text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"new logo"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can change environment, lighting, prop, and crop without rewriting the product identity. Version both JSON blocks with the final asset.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Assemble a constraint-first prompt
&lt;/h2&gt;

&lt;p&gt;Put invariants before style:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Preserve the exact bottle silhouette, matte-black cap proportions, cream label, and centered two-line wordmark from the input. Place one bottle on a cool stone shelf after rain, soft overcast window light, one eucalyptus stem, 4:5 crop. No extra bottles, no new copy, no logo redesign.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Upload the approved product master, select Match Input Image when geometry is the test, then switch aspect ratio only for a documented channel requirement. Check Public before using unreleased packaging. Read the displayed credit estimate before Generate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjik7ojnn60uaat0bsqn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjik7ojnn60uaat0bsqn.jpg" alt="Real current-route examples used to separate preserved identity from campaign variables" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Validate with code
&lt;/h2&gt;

&lt;p&gt;A small contract checker prevents missing review fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo_geometry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateBrief&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Missing invariants: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing campaign crop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not inspect pixels; it makes the human review checklist deterministic. Pair it with a contact sheet showing the product master beside every variant.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Reject identity drift before polishing
&lt;/h2&gt;

&lt;p&gt;At 200% zoom, check silhouette, cap, wordmark letters, label margins, glass tint, and highlights. Then review at thumbnail size for campaign hierarchy. Reject a beautiful scene if the product is no longer the product.&lt;/p&gt;

&lt;p&gt;The two images above are real examples on the current Banana Pro AI Studio route. They illustrate separate production directions; they are not a promise that arbitrary packaging text will always be exact.&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://voor.ai/banana-pro-ai-studio/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=banana_pro_brand_contract&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;exact Banana Pro AI Studio workspace&lt;/a&gt; to test one controlled variable at a time, and keep the invariant contract beside every approved export.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to Edit Body Shape in a Photo Without Bending the Background</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Tue, 11 Aug 2026 03:13:31 +0000</pubDate>
      <link>https://dev.to/voorai/how-to-edit-body-shape-in-a-photo-without-bending-the-background-43n7</link>
      <guid>https://dev.to/voorai/how-to-edit-body-shape-in-a-photo-without-bending-the-background-43n7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxt6i54fta7gqzdx7tmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxt6i54fta7gqzdx7tmz.png" alt="Real consented body-edit before and after pair with stable background geometry" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Generative body edits often fail outside the body. A waist changes—and a doorframe curves. A shoulder narrows—and wallpaper slides. The most useful QA artifact is therefore not a beauty score; it is a change mask over the full frame.&lt;/p&gt;

&lt;p&gt;This workflow uses a consented photo and the current &lt;a href="https://voor.ai/ai-body-editor/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=body_edit_background_grid&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;Voor AI Body Editor&lt;/a&gt;, whose live Seedream 4.5 workspace exposes an image input, prompt, aspect ratios, Public, and Generate. The before/after pair in this post is a real example from that exact route.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Set an edit boundary
&lt;/h2&gt;

&lt;p&gt;Write the request as a small delta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make a subtle, anatomically plausible adjustment to the jacket waist only. Preserve the person's face, identity, hands, pose, clothing texture, camera crop, floor line, wall edges, and every background object. No skin exposure and no new accessories.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do not ask for a vague “perfect body.” It has no measurable boundary and encourages the model to redesign the whole photograph.&lt;/p&gt;

&lt;p&gt;Check consent and the Public control. During verification, the current default one-image request estimated 14 credits; inspect the live estimate before Generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Normalize the images
&lt;/h2&gt;

&lt;p&gt;Save the source as &lt;code&gt;before.png&lt;/code&gt; and the candidate as &lt;code&gt;after.png&lt;/code&gt;. They should have the same dimensions. This Python script creates an amplified change mask and a checker overlay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageChops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;

&lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Compare equal-size images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageChops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change-mask.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
&lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grid-review.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mask answers “where did pixels change?” The grid answers “did straight structures stay straight?” Neither decides whether an edit is appropriate; they expose drift for a human reviewer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyldy9g42r0hmjlvt2mx9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyldy9g42r0hmjlvt2mx9.png" alt="Real edit pair prepared as a change-mask and straight-line grid review" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Define the allowed region
&lt;/h2&gt;

&lt;p&gt;Create a rectangle around the intended edit and calculate how much difference lies outside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageChops&lt;/span&gt;

&lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImageChops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Replace with the permitted x1, y1, x2, y2 region.
&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;260&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;760&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;820&lt;/span&gt;
&lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="n"&gt;outside_ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;changed pixels outside allowed region: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;outside_ratio&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This threshold is diagnostic, not universal. Compression, relighting, and resampling can change many pixels. Compare several candidates under the same settings rather than declaring one magic pass/fail value.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Run a human geometry review
&lt;/h2&gt;

&lt;p&gt;At 200% zoom, follow every nearby straight edge through the edited zone: doorframes, shelves, tiles, horizons, table legs. Then inspect hands, elbows, garment seams, repeated textures, shadows, and contact with furniture.&lt;/p&gt;

&lt;p&gt;Finally, blink between before and after at fit-to-screen size. If the entire scene appears to “breathe,” the edit escaped its boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Reject, narrow, or disclose
&lt;/h2&gt;

&lt;p&gt;If background changes dominate, reject the candidate. If one edge drifts, tighten the prompt around the exact garment region. Keep the untouched source and label the exported version as edited. Never use body edits to bully, sexualize, or deceive a subject.&lt;/p&gt;

&lt;p&gt;The real pair shown here demonstrates the correct review target: the intended change is only one part of the frame; stable background geometry is the acceptance criterion.&lt;/p&gt;

&lt;p&gt;Try the method in the &lt;a href="https://voor.ai/ai-body-editor/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=body_edit_background_grid&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;exact Voor body editor&lt;/a&gt;, then let the change mask—not wishful viewing—tell you where the model wandered.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How to Add Depth of Field to a Photo With an Edge-Safe Focus Map</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Mon, 10 Aug 2026 06:51:24 +0000</pubDate>
      <link>https://dev.to/voorai/how-to-add-depth-of-field-to-a-photo-with-an-edge-safe-focus-map-fj0</link>
      <guid>https://dev.to/voorai/how-to-add-depth-of-field-to-a-photo-with-an-edge-safe-focus-map-fj0</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fpneo7tqbp07m3650km.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fpneo7tqbp07m3650km.png" alt="A real Voor Depth Of Field result" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adding synthetic depth of field is a segmentation problem before it is a blur problem. Define what must stay sharp, add a protected edge band around it, and reject any result that blurs across the subject boundary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdczuwi1iosn21xzfrr42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdczuwi1iosn21xzfrr42.png" alt="Before and after from the stored Voor Depth Of Field example" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This comparison uses the app's stored first-party input and result. No new generation was submitted for this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the focus map
&lt;/h2&gt;

&lt;p&gt;Use the visible skateboarder as the foreground subject and split the image into three regions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sharp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transition&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blur&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FocusMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;protectedEdges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;blurIntent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ultra-shallow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shallow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moderate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FocusMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;face&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hair&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shirt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shorts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;legs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skateboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;protectedEdges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hair silhouette&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fingers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shirt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;legs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;board edge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tree line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;road&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;distant grass&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;blurIntent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ultra-shallow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transition region matters most. A uniform blur mask will either leave the background too sharp or cut into hair and fingers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the direct Depth Of Field app
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://voor.ai/app/depth-of-field/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=depth_field_edge_safe_map&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;current Depth Of Field workspace&lt;/a&gt;. The live page describes FLUX.1 Kontext [pro], requires an Input Image, currently defaults Blur Effect to &lt;code&gt;ultra shallow depth of field&lt;/code&gt;, offers Match Input Image and PNG, exposes Seed under Advanced Settings, and shows Public plus Generate.&lt;/p&gt;

&lt;p&gt;Use a source with a clear foreground subject and enough background distance to make the requested optics plausible. Keep Match Input Image so edge comparisons remain aligned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translate the map into a narrow instruction
&lt;/h2&gt;

&lt;p&gt;Avoid merely saying “cinematic bokeh.” Name the plane, protected boundary and background behavior:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep the skateboarder, face, hair, shirt, arms, legs and board completely sharp. Add ultra-shallow depth of field only behind the outer body and skateboard edges. Use a soft graduated transition; blur the sky, tree line, road and distant grass into natural optical bokeh. Preserve pose, crop, lighting and foreground edges. No halo, cutout edge or radial blur.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the app's Blur Effect control already expresses the strength, use the prompt to describe placement and exclusions rather than repeating decorative adjectives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run an edge validator
&lt;/h2&gt;

&lt;p&gt;Review at 200% zoom. Sample pixels on both sides of difficult boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;EdgeSample&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subjectDetailRetained&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;backgroundSoftened&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;haloVisible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validEdge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EdgeSample&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subjectDetailRetained&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundSoftened&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;haloVisible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test flyaway hair, glasses, fingers, translucent fabric and reflective edges. Then zoom back to 100% and check whether the blur direction and intensity feel like one lens rather than a painted mask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate optical errors from content errors
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Halo:&lt;/strong&gt; widen the transition band and ask for gradual optical falloff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blurred hair:&lt;/strong&gt; name the full hair silhouette as sharp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharp holes in the background:&lt;/strong&gt; enumerate visible background objects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radial smear:&lt;/strong&gt; request lens-like bokeh, not motion blur.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changed face or clothes:&lt;/strong&gt; reduce the change set to background defocus only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Execution constraints
&lt;/h2&gt;

&lt;p&gt;The live credit estimate can depend on account state and completed inputs. Inspect it immediately before Generate rather than hard-coding a number. Keep Public only if the uploaded image may be publicly visible. Use photos you own or are authorized to edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Physical limitations
&lt;/h2&gt;

&lt;p&gt;An AI blur is not a recovered depth map or a substitute for optical capture. Fine transparent edges, reflections and overlapping objects may be inferred incorrectly. Keep the original and disclose synthetic editing when documentary context matters.&lt;/p&gt;

&lt;p&gt;Once every protected boundary is listed, run the edit in the &lt;a href="https://voor.ai/app/depth-of-field/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=depth_field_edge_safe_map&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;exact Voor depth-of-field app&lt;/a&gt; and make the edge validator—not aesthetic excitement—the release gate.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Build a Camera-Movement Prompt Contract for AI Video</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Sun, 09 Aug 2026 04:12:18 +0000</pubDate>
      <link>https://dev.to/voorai/how-to-build-a-camera-movement-prompt-contract-for-ai-video-4ld4</link>
      <guid>https://dev.to/voorai/how-to-build-a-camera-movement-prompt-contract-for-ai-video-4ld4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqcxm27pieu1pgijhs0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqcxm27pieu1pgijhs0r.png" alt="Camera-movement prompt contract cover" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“Make it cinematic” is not a camera plan. A reproducible AI-video brief needs a move type, path, speed, subject lock, horizon rule, and stop condition. Voor’s current &lt;strong&gt;3D Camera Transform&lt;/strong&gt; page exposes a direct form with Seedance 1.5 Pro, image and optional last-frame inputs, a five-second default, 16:9, Public visibility, and Generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Represent the move as data
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CameraMove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;move&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orbit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dolly-in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dolly-out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crane&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;constant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;closing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opening&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;horizon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;preserve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CameraMove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;move&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orbit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;centered electric coupe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;constant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;horizon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;preserve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vehicle geometry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wheel count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;studio lighting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;background layout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compile the contract into a prompt
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CameraMove&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;degrees&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-degree &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; around the &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; camera distance`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;horizon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; horizon`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`preserve &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preserve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`one continuous move, no cuts, no zoom, no subject motion`&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The negative constraints matter. Orbit and zoom are different operations; asking for both often creates an unstable hybrid.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh63rqexzojra90c56vbg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh63rqexzojra90c56vbg.png" alt="Current Voor 3D Camera Transform interface" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the current direct generator
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://voor.ai/3d-camera-transform/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camera_move_prompt_contract&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;3D Camera Transform workspace&lt;/a&gt;. Verify Seedance 1.5 Pro, upload an authorized still, leave the optional last frame empty for the first test, keep five seconds and 16:9, and choose visibility intentionally. The signed-in form calculates credits from the selected duration and required inputs; read that live estimate before pressing Generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate the output deterministically
&lt;/h2&gt;

&lt;p&gt;Use a five-item acceptance test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;continuousShot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;horizonStable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;targetCentered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;geometryPreserved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;moveStopsAtRequestedAngle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reject camera pass&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frame-scrub the clip at the start, midpoint, and end. Track wheel count, window shapes, logos, and horizon angle. A smooth clip can still fail if the vehicle mutates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Orbit becomes zoom:&lt;/strong&gt; state constant distance and no zoom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subject rotates instead of camera:&lt;/strong&gt; say the subject remains stationary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizon rolls:&lt;/strong&gt; explicitly require a stable horizon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background melts:&lt;/strong&gt; lock major planes and keep the angle modest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cut appears:&lt;/strong&gt; require one continuous shot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;A prompt contract improves reviewability; it does not guarantee physical 3D reconstruction. The model estimates unseen surfaces. Larger angles reveal more invented geometry, so a 15-degree test is safer than a full orbit. Model availability, duration, visibility, and credit estimates are live facts and can change.&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://voor.ai/3d-camera-transform/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camera_move_prompt_contract&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;current Voor camera-movement form&lt;/a&gt; only after the contract can be checked frame by frame.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Create AI Videos With Dialogue Using a Three-Beat Script Contract</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Sat, 01 Aug 2026 02:29:12 +0000</pubDate>
      <link>https://dev.to/voorai/how-to-create-ai-videos-with-dialogue-using-a-three-beat-script-contract-5101</link>
      <guid>https://dev.to/voorai/how-to-create-ai-videos-with-dialogue-using-a-three-beat-script-contract-5101</guid>
      <description>&lt;p&gt;An AI dialogue clip is easier to debug when the prompt is a tiny production contract: one speaker, three ordered beats, one quoted line, causal sound, and a stable ending. This tutorial turns that contract into JSON, validates it, and maps it to the current HappyHorse 1.1 form.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Workspace proof: HappyHorse 1.1 dialogue mode, prompt, duration, aspect ratio, visibility, and Generate controls&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Define the data contract
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aspect_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16:9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"speaker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Maya, bakery owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"identity_lock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"face"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green apron"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"silver name pin"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Maya sets a warm loaf on the counter"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"dialogue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fresh at six. Still warm."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"She smiles and holds the loaf steady"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"soft tray contact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quiet bakery room tone"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forbid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"music"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"second speaker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"camera cut"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"on-screen text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract is intentionally small. Five seconds cannot support a product demo, two characters, three camera moves, and a paragraph of speech.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Validate before prompting
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateScene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration_seconds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;duration required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exactly three beats required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeline must start at 0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;beat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`beat &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has invalid range`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;beat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`gap or overlap before beat &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeline must fill duration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spoken&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use exactly one dialogue beat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fail the build if the validator returns any error. That is cheaper than discovering an impossible timeline after generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Compile JSON into a prompt
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HappyHorse 1.1 dialogue scene, 16:9, five seconds, one continuous medium shot.
0–1.5s: Maya, a bakery owner in a green apron with a silver name pin, sets a warm loaf on the counter.
1.5–3.8s: she looks toward camera and says exactly, “Fresh at six. Still warm.”
3.8–5s: she smiles and holds the loaf steady for a clean ending.
Preserve face, apron, pin, loaf, counter, and light direction. Soft tray contact and quiet bakery room tone only. No music, second speaker, camera cut, captions, or extra text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the compiled prompt into the &lt;a href="https://voor.ai/happyhorse-1-1/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=happyhorse_dialogue_contract&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;HappyHorse 1.1 dialogue workspace&lt;/a&gt;. The live page currently selects Text to Video and Dialogue scene, defaults to five seconds and 16:9, exposes Public visibility, and offers Generate. The model supports audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat cost as part of the contract
&lt;/h2&gt;

&lt;p&gt;The current five-second, 720p pricing configuration calculates to 488 credits in the read-only model data. Do not hard-code that number in an app: request or read the live UI estimate before submission. Three welcome credits do not cover this default video setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Review as a state machine
&lt;/h2&gt;

&lt;p&gt;Check the clip in three passes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Silent:&lt;/strong&gt; identity, prop geometry, camera continuity, final hold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio only:&lt;/strong&gt; exact words, no surprise speaker, causal contact sound, steady room tone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combined:&lt;/strong&gt; mouth motion aligns with the quoted line and audio events match visible actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reject when a beat changes order, two people speak, the line is paraphrased, the camera cuts, or the final pose is unstable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dialogue runs long: shorten the line; do not speed the character unnaturally.&lt;/li&gt;
&lt;li&gt;Lip sync drifts: keep one speaker and place the line in one continuous beat.&lt;/li&gt;
&lt;li&gt;Wardrobe changes: repeat the identity lock once, near the end of the prompt.&lt;/li&gt;
&lt;li&gt;Audio invents music: explicitly request room tone and forbid music.&lt;/li&gt;
&lt;li&gt;The model adds captions: add “no on-screen text”; composite approved captions later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Generation is nondeterministic even when the JSON is deterministic. The validator proves timeline structure, not output quality. Public is the current default visibility. No paid generation was submitted for this tutorial, and model availability or pricing may change.&lt;/p&gt;

&lt;p&gt;When your contract validates, &lt;a href="https://voor.ai/happyhorse-1-1/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=happyhorse_dialogue_contract&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;run it in the exact HappyHorse 1.1 generator after checking the live estimate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzf1kvoirkqorvd39g5e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzf1kvoirkqorvd39g5e3.png" alt="HappyHorse 1.1 generator showing Dialogue scene, five-second duration, 16:9 ratio, prompt, public visibility and Generate control" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>How to Use ControlNet With a Reproducible Structure Manifest</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Fri, 31 Jul 2026 09:54:12 +0000</pubDate>
      <link>https://dev.to/voorai/how-to-use-controlnet-with-a-reproducible-structure-manifest-eea</link>
      <guid>https://dev.to/voorai/how-to-use-controlnet-with-a-reproducible-structure-manifest-eea</guid>
      <description>&lt;p&gt;ControlNet works best when the reference image is treated as an explicit structural input, not as vague inspiration. Pick one control signal, record what geometry must survive, generate, and compare the output against a small manifest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the right control signal
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;Canny&lt;/strong&gt; when hard outlines and object placement matter. Use &lt;strong&gt;Depth&lt;/strong&gt; when camera perspective and front-to-back relationships matter more than every edge. Mixing both concepts in one undocumented prompt makes failures hard to diagnose.&lt;/p&gt;

&lt;p&gt;The current Voor ControlNet form has a required Prompt, a Control Image upload, and a visible Generate button. Its own guidance distinguishes geometry-first ControlNet from identity/texture-first image-to-image work. The public form does not expose a numeric credit estimate before sign-in, so verify the live estimate before generating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define a structure manifest
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"controlType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"canny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mustPreserve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"camera_angle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"product_outline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"handle_position"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"ground_contact"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mayChange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"material"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"color_palette"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"lighting"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forbidden"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"extra_handles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"floating_shadow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"cropped_product"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This manifest becomes both the prompt plan and the review plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the prompt from changeable layers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keep the reference camera angle, outer product silhouette, handle position,
and ground contact. Change the material to matte cobalt ceramic, use a warm
studio gradient background, soft key light from upper left, realistic contact
shadow. No extra handles, no floating object, no crop.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;a href="https://voor.ai/control-net/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=controlnet_structure_manifest&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;ControlNet generator&lt;/a&gt;, upload a clean reference, and start with one control type. A source with a single readable subject will produce a more useful edge or depth map than a cluttered scene.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjhaae38hmgts5rw6cnl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjhaae38hmgts5rw6cnl.png" alt="Voor ControlNet workspace showing the prompt field, control image upload, and Generate action" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a deterministic validator
&lt;/h2&gt;

&lt;p&gt;The image review is visual, but the decision rules can still be deterministic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;camera_angle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_outline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handle_position&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ground_contact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateReview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;review&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preserved&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;preserved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;camera_angle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_outline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handle_position&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ground_contact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validateReview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;review&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simple. It prevents “looks good” from replacing the requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review in a fixed order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Compare the camera angle.&lt;/li&gt;
&lt;li&gt;Compare the outer silhouette.&lt;/li&gt;
&lt;li&gt;Check intersections and repeated lines.&lt;/li&gt;
&lt;li&gt;Inspect small geometry: fingers, handles, labels, hinges.&lt;/li&gt;
&lt;li&gt;Check the contact shadow.&lt;/li&gt;
&lt;li&gt;Only then score material and lighting.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Too rigid:&lt;/strong&gt; simplify the reference or reduce conflicting prompt detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too much drift:&lt;/strong&gt; use a cleaner source and name fewer allowed changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canny preserves clutter:&lt;/strong&gt; crop the source or switch to Depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Depth loses product edges:&lt;/strong&gt; return to Canny for the geometry-critical pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model copies source texture:&lt;/strong&gt; state the new material and palette explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical limits
&lt;/h2&gt;

&lt;p&gt;ControlNet can preserve the broad map while breaking local geometry. It does not guarantee identity, readable labels, valid hands, physical safety, or pixel-perfect correspondence. Model behavior and credit estimates can change; record the live settings with each approved output.&lt;/p&gt;

&lt;p&gt;When your manifest is ready, &lt;a href="https://voor.ai/control-net/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=controlnet_structure_manifest&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;run one controlled pass and validate it before changing a second layer&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Generate Multiple Images With AI From a Variation Matrix</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Thu, 30 Jul 2026 02:34:36 +0000</pubDate>
      <link>https://dev.to/voorai/how-to-generate-multiple-images-with-ai-from-a-variation-matrix-1fie</link>
      <guid>https://dev.to/voorai/how-to-generate-multiple-images-with-ai-from-a-variation-matrix-1fie</guid>
      <description>&lt;p&gt;Generating multiple images reliably is a data-contract problem before it is a prompting problem. Define the invariant visual system once, express only allowed differences in a matrix, generate a small pilot, and validate every output against the same rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the contract
&lt;/h2&gt;

&lt;p&gt;For a four-icon onboarding set, keep invariants and variables separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;canvas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4:3"&lt;/span&gt;
  &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#F7F8FC"&lt;/span&gt;
  &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6px&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rounded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;navy"&lt;/span&gt;
  &lt;span class="na"&gt;lighting&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flat,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shadows"&lt;/span&gt;
  &lt;span class="na"&gt;forbidden&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logos"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gradients"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upload&lt;/span&gt;
    &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;entering&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;arrow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tray"&lt;/span&gt;
    &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#6C5CE7"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review&lt;/span&gt;
    &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;magnifier&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;over&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;four-tile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;grid"&lt;/span&gt;
    &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#00B894"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;approve&lt;/span&gt;
    &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mark&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inside&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shield"&lt;/span&gt;
    &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#0984E3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;a href="https://voor.ai/bulk-assets-creator/?utm_source=dev&amp;amp;utm_medium=referral&amp;amp;utm_campaign=bulk_asset_variation_matrix&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;Voor Bulk Assets Creator&lt;/a&gt;, choose GPT Image 2 and the Icon set preset, then put the invariant block first. Append one item row per generation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcu6madcq7687e2rn28y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcu6madcq7687e2rn28y.png" alt="Voor Bulk Assets Creator configured with shared palette, stroke, lighting, and shape constraints" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a deterministic prompt function
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;promptFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`Create one &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Canvas &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; background &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Use &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; accent &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lighting&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Do not include &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep order stable so prompt diffs stay reviewable. Do not inject random style adjectives between rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pilot before the batch
&lt;/h2&gt;

&lt;p&gt;Generate two contrasting items first. The current GPT Image 2 default family estimate observed today was 4 credits, but size/model changes can alter it; confirm the live estimate and public-visibility setting before Generate. If the pilot fails, edit the contract rather than patching each item separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validator
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stroke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lighting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Missing: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^#&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9A-F&lt;/span&gt;&lt;span class="se"&gt;]{6}&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bad accent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visual QA still needs humans. Check canvas, background, stroke weight, object count, forbidden text, and semantic accuracy. Record pass/fail by item ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Style drifts:&lt;/strong&gt; move the shared style block to the beginning and remove synonyms.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Objects multiply:&lt;/strong&gt; say “one centered object” and reject extra props.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Colors mutate:&lt;/strong&gt; provide exact hex values plus color names.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;One item needs a special exception:&lt;/strong&gt; add an explicit &lt;code&gt;exceptions&lt;/code&gt; field; do not silently change the global contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;This is not a transactional batch API: generation may remain variable, and exact pixel identity is not guaranteed. The current model, credit estimate, and availability can change. Public is the current default, so switch it for confidential assets. Generated icons also require license, trademark, and accessibility review.&lt;/p&gt;

&lt;p&gt;Once the matrix passes validation, &lt;a href="https://voor.ai/bulk-assets-creator/?utm_source=dev&amp;amp;utm_medium=referral&amp;amp;utm_campaign=bulk_asset_variation_matrix&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;run the small pilot in the exact bulk workspace&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Reference-First AI Image Editing: How to Change One Thing Without Redesigning Everything</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Wed, 29 Jul 2026 12:49:51 +0000</pubDate>
      <link>https://dev.to/voorai/reference-first-ai-image-editing-how-to-change-one-thing-without-redesigning-everything-3e27</link>
      <guid>https://dev.to/voorai/reference-first-ai-image-editing-how-to-change-one-thing-without-redesigning-everything-3e27</guid>
      <description>&lt;p&gt;AI image editing is most useful when it behaves like a controlled revision rather than a fresh generation. The difficult request is rarely “make a completely different picture.” It is usually “change the label, lighting, material, or background while keeping the subject recognizably the same.”&lt;/p&gt;

&lt;p&gt;That requires a different prompting habit. Instead of describing only the desired result, separate the brief into &lt;strong&gt;what may change&lt;/strong&gt; and &lt;strong&gt;what must remain stable&lt;/strong&gt;. I use this approach in the &lt;a href="https://www-flux3.com" rel="noopener noreferrer"&gt;FLUX 3 AI Image Generator&lt;/a&gt;, where FLUX 3 is labeled as coming soon and the live FLUX.2, Pro, and Max routes handle current generation and reference edits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a usable reference
&lt;/h2&gt;

&lt;p&gt;A reference image is not just inspiration. For an editing workflow, it is the source of truth for identity, geometry, composition, and brand details.&lt;/p&gt;

&lt;p&gt;Before uploading it, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the subject is large enough to inspect;&lt;/li&gt;
&lt;li&gt;important text is readable;&lt;/li&gt;
&lt;li&gt;product edges are not hidden by hands or props;&lt;/li&gt;
&lt;li&gt;lighting reveals the material you need to preserve;&lt;/li&gt;
&lt;li&gt;the crop leaves enough room for the requested change;&lt;/li&gt;
&lt;li&gt;compression artifacts are not mistaken for texture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the reference already contains a major ambiguity, the edit will inherit it. A clean input reduces the number of decisions the model has to invent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write a two-column brief
&lt;/h2&gt;

&lt;p&gt;I draft every revision as two mental columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change
&lt;/h3&gt;

&lt;p&gt;Name the smallest useful modification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replace the background with pale concrete;&lt;/li&gt;
&lt;li&gt;change the bottle cap from black to brushed aluminum;&lt;/li&gt;
&lt;li&gt;update the headline to an exact phrase;&lt;/li&gt;
&lt;li&gt;move the key light from camera left to camera right;&lt;/li&gt;
&lt;li&gt;turn a daytime sky into early blue hour.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preserve
&lt;/h3&gt;

&lt;p&gt;List the properties that define continuity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;subject identity and facial features;&lt;/li&gt;
&lt;li&gt;product silhouette and proportions;&lt;/li&gt;
&lt;li&gt;label layout, logo placement, and typography;&lt;/li&gt;
&lt;li&gt;camera angle, focal length, and crop;&lt;/li&gt;
&lt;li&gt;hand position and object contact;&lt;/li&gt;
&lt;li&gt;color of every element not named in the change;&lt;/li&gt;
&lt;li&gt;background perspective and shadow direction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A compact edit prompt might read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace the glossy black cap with brushed aluminum. Preserve the bottle geometry, label typography, logo position, camera angle, crop, hand pose, liquid color, background, and shadow placement. Do not add new text or props.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The preservation sentence is not filler. It defines the acceptance criteria.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid style words that reopen the whole image
&lt;/h2&gt;

&lt;p&gt;Terms such as “cinematic,” “premium,” and “modern” are broad. They invite the model to reinterpret many visual choices at once. If the goal is a controlled edit, translate style into physical decisions.&lt;/p&gt;

&lt;p&gt;Instead of “make it premium,” write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use a narrow softbox reflection on the left edge, a subtle warm rim light, lower background saturation, and a clean commercial product-photography finish. Preserve the camera angle and product material.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Concrete direction makes it easier to compare the output with the reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle text as exact content
&lt;/h2&gt;

&lt;p&gt;Text edits need three pieces of information:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the exact replacement string;&lt;/li&gt;
&lt;li&gt;its location and hierarchy;&lt;/li&gt;
&lt;li&gt;the surrounding text that must remain unchanged.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace only the top headline with “DESIGNED FOR QUIET WORK”. Keep the existing typeface style, letter spacing, alignment, white color, and all smaller body text unchanged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use quotation marks around required copy. After generation, inspect every character at full resolution. Even when typography looks plausible at thumbnail size, small substitutions can remain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change one variable per iteration
&lt;/h2&gt;

&lt;p&gt;Suppose the requested final image needs a new headline, warmer light, and a different surface material. Doing all three in one pass makes failure hard to diagnose.&lt;/p&gt;

&lt;p&gt;A more reliable sequence is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass 1: fix the headline while preserving everything else.&lt;/li&gt;
&lt;li&gt;Pass 2: adjust light direction and color temperature.&lt;/li&gt;
&lt;li&gt;Pass 3: change the surface material.&lt;/li&gt;
&lt;li&gt;Pass 4: run a higher-detail final revision only after composition is approved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save the prompt and output for each pass. If a later edit damages the label or silhouette, return to the last stable image rather than trying to repair several accumulated errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the model for the stage
&lt;/h2&gt;

&lt;p&gt;A fast model is useful for checking whether the instruction is understood. A production-oriented model is more appropriate after the edit logic is stable. A higher-detail route should be the final step, not the first experiment.&lt;/p&gt;

&lt;p&gt;This keeps iteration affordable and reduces the temptation to accept a costly but structurally wrong image just because it looks polished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review at three scales
&lt;/h2&gt;

&lt;p&gt;Every result should be checked in three views.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thumbnail
&lt;/h3&gt;

&lt;p&gt;Does the composition still communicate the same idea? Is the requested change immediately visible?&lt;/p&gt;

&lt;h3&gt;
  
  
  Normal view
&lt;/h3&gt;

&lt;p&gt;Are lighting, materials, edges, and spatial relationships coherent? Did any unintended object appear?&lt;/p&gt;

&lt;h3&gt;
  
  
  Full resolution
&lt;/h3&gt;

&lt;p&gt;Inspect text, logos, hands, jewelry, product seams, reflections, repeated patterns, and fine facial details. These are the places where continuity errors often hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reusable editing template
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; [one specific revision]. &lt;strong&gt;Preserve:&lt;/strong&gt; [identity, geometry, composition, typography, palette, and untouched elements]. &lt;strong&gt;Physical direction:&lt;/strong&gt; [light, material, lens, or layout details]. &lt;strong&gt;Exact text:&lt;/strong&gt; [quoted copy and placement, if any]. &lt;strong&gt;Do not:&lt;/strong&gt; [new objects, extra text, changed crop, or other common failure].&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reference-first editing is intentionally conservative. The goal is not to ask the model for a more impressive image every time. The goal is to make one approved change while protecting the decisions that are already correct.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shot-First AI Video Prompting: A Repeatable Workflow for Product Demo Clips</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Wed, 29 Jul 2026 12:47:48 +0000</pubDate>
      <link>https://dev.to/voorai/shot-first-ai-video-prompting-a-repeatable-workflow-for-product-demo-clips-48hg</link>
      <guid>https://dev.to/voorai/shot-first-ai-video-prompting-a-repeatable-workflow-for-product-demo-clips-48hg</guid>
      <description>&lt;p&gt;Short AI video looks easy until a prompt has to survive several iterations. The usual failure mode is not a weak model. It is an overloaded brief: three actions, two camera moves, a lighting transition, a product reveal, dialogue, and sound design are all packed into one short clip.&lt;/p&gt;

&lt;p&gt;A more reliable approach is to treat every generation as a &lt;strong&gt;single shot with one visible event&lt;/strong&gt;. This article describes the workflow I use when planning short product demos and social clips. I test the brief in the &lt;a href="https://flux3-video.online" rel="noopener noreferrer"&gt;FLUX 3 Video Generator&lt;/a&gt;, where FLUX 3 Video is clearly marked as coming soon and live alternatives such as Seedance, HappyHorse, and Kling can be used today.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Define the job of the shot
&lt;/h2&gt;

&lt;p&gt;Before writing visual details, finish this sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At the end of this clip, the viewer should understand that ______.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a product demo, the answer might be “the dashboard turns a text brief into three visual options” or “the mobile app identifies the plant and explains how to care for it.” If the sentence contains “and” more than once, the shot is probably doing too much.&lt;/p&gt;

&lt;p&gt;This constraint helps in two ways. It keeps the visible action readable, and it gives you a clear test for the output. A beautiful result that does not communicate the intended change is still a failed demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Write the event in chronological order
&lt;/h2&gt;

&lt;p&gt;Models respond better when the prompt follows the timeline the viewer will see. I use this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Establish the subject and starting state.&lt;/li&gt;
&lt;li&gt;Describe one action.&lt;/li&gt;
&lt;li&gt;Describe the camera response.&lt;/li&gt;
&lt;li&gt;Describe the lighting or environmental change.&lt;/li&gt;
&lt;li&gt;Add sound or dialogue cues.&lt;/li&gt;
&lt;li&gt;State the ending frame.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A matte black field recorder rests on dark volcanic glass. A hand enters from frame right and presses the illuminated record button. The camera makes a slow 10-centimeter push-in while the cyan status light brightens. A soft mechanical click and low room tone are audible. End on a stable close-up with the display readable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice that the prompt does not jump between the beginning and the ending. Every instruction has a place on the timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Separate subject motion from camera motion
&lt;/h2&gt;

&lt;p&gt;“Dynamic” is not a camera instruction. Neither is “cinematic.” If motion matters, name it precisely.&lt;/p&gt;

&lt;p&gt;Useful camera phrases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;locked tripod frame;&lt;/li&gt;
&lt;li&gt;slow push-in;&lt;/li&gt;
&lt;li&gt;lateral slider move from left to right;&lt;/li&gt;
&lt;li&gt;handheld follow with restrained micro-movement;&lt;/li&gt;
&lt;li&gt;overhead tilt down;&lt;/li&gt;
&lt;li&gt;static macro close-up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then describe the subject independently: turns, lifts, opens, rotates, steps forward, or changes expression. When both motions are stated separately, it is easier to diagnose which part failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Use a reference image as a contract
&lt;/h2&gt;

&lt;p&gt;Image-to-video works best when the source image already contains the composition you want to preserve. The reference should answer the expensive questions before generation: product angle, wardrobe, layout, palette, and background.&lt;/p&gt;

&lt;p&gt;The motion prompt should then focus on what changes. I usually include one explicit preservation sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Preserve the product geometry, label typography, surface material, and background composition.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is especially useful for brand assets. Asking the model to redesign the product and animate it in the same pass creates unnecessary variation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Direct audio on the same timeline
&lt;/h2&gt;

&lt;p&gt;When a live model supports native audio, treat sound as part of the shot rather than a note at the end. Attach each cue to a visible action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the latch clicks &lt;strong&gt;as&lt;/strong&gt; the case opens;&lt;/li&gt;
&lt;li&gt;footsteps become louder &lt;strong&gt;while&lt;/strong&gt; the subject approaches;&lt;/li&gt;
&lt;li&gt;dialogue begins &lt;strong&gt;after&lt;/strong&gt; the camera settles;&lt;/li&gt;
&lt;li&gt;room ambience falls away &lt;strong&gt;when&lt;/strong&gt; the screen turns on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For dialogue, keep the line short enough for the duration. A five-second clip cannot comfortably hold a long sentence plus a product action. If the wording is essential, consider a separate voiceover pass instead of forcing synchronized speech.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Draft cheaply, then change one variable
&lt;/h2&gt;

&lt;p&gt;The first generation is a measurement, not a final deliverable. Use a lower-cost model or draft setting to test timing and composition. After each result, identify the largest visible error and change one variable only.&lt;/p&gt;

&lt;p&gt;A simple iteration log might look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draft 1: action is readable, camera moves too fast.&lt;/li&gt;
&lt;li&gt;Draft 2: camera fixed, button press happens too early.&lt;/li&gt;
&lt;li&gt;Draft 3: timing fixed, reflection hides the display.&lt;/li&gt;
&lt;li&gt;Draft 4: lighting adjusted, approved for final render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Changing several instructions at once makes it impossible to know which edit improved the result. Controlled iteration also gives reusable language for future shots.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Use an acceptance checklist
&lt;/h2&gt;

&lt;p&gt;Before approving a clip, review it at normal speed and frame by frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the intended event understandable without explanation?&lt;/li&gt;
&lt;li&gt;Does the subject remain consistent?&lt;/li&gt;
&lt;li&gt;Is product text or geometry distorted?&lt;/li&gt;
&lt;li&gt;Does camera motion match the instruction?&lt;/li&gt;
&lt;li&gt;Are hands, edges, and reflections stable?&lt;/li&gt;
&lt;li&gt;Do sound cues align with visible events?&lt;/li&gt;
&lt;li&gt;Does the final frame provide a clean editing point?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to the first question is no, do not polish the shot. Rewrite the event. If the event works but one detail fails, revise only that detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  A compact reusable template
&lt;/h2&gt;

&lt;p&gt;Here is the full structure in one block:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Starting frame:&lt;/strong&gt; [subject, environment, composition]. &lt;strong&gt;Action:&lt;/strong&gt; [one visible event]. &lt;strong&gt;Camera:&lt;/strong&gt; [one precise movement or locked frame]. &lt;strong&gt;Light:&lt;/strong&gt; [specific change or stable condition]. &lt;strong&gt;Audio:&lt;/strong&gt; [ambience, effect, or short dialogue tied to the action]. &lt;strong&gt;Preserve:&lt;/strong&gt; [identity, geometry, typography, palette]. &lt;strong&gt;Ending frame:&lt;/strong&gt; [stable final composition].&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This process is deliberately less exciting than writing one giant “cinematic” prompt. That is the point. A good production workflow makes results easier to evaluate, revise, and repeat.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Edit Text in an Image With AI Without Redesigning It</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Wed, 29 Jul 2026 03:09:20 +0000</pubDate>
      <link>https://dev.to/voorai/how-to-edit-text-in-an-image-with-ai-without-redesigning-it-383e</link>
      <guid>https://dev.to/voorai/how-to-edit-text-in-an-image-with-ai-without-redesigning-it-383e</guid>
      <description>&lt;p&gt;The safe way to edit text in a flat image is to define a &lt;strong&gt;small patch contract&lt;/strong&gt;: exact old string, exact new string, one target region, and an explicit list of pixels that must remain semantically unchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiz43ai37b6d2tn0a3b52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiz43ai37b6d2tn0a3b52.png" alt="A scene-text diff showing exact old and new strings plus protected layout, light, and copy." width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Input contract
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;owned_or_authorized&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;poster&lt;/span&gt;
&lt;span class="na"&gt;patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;large_top_headline&lt;/span&gt;
  &lt;span class="na"&gt;old_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;LATE"&lt;/span&gt;
  &lt;span class="na"&gt;new_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DAILY"&lt;/span&gt;
&lt;span class="na"&gt;preserve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;illustration&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;people&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;border&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;background&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;crop&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;all_other_text&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;print_texture&lt;/span&gt;
&lt;span class="na"&gt;acceptance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;exact_new_string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;old_string_absent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;unrelated_copy_unchanged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a source large enough to read at 100% zoom. If the text is tiny, curved around an object, heavily occluded, or repeated several times, split the task into separate patches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current workspace contract
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://voor.ai/ai-text-editor-in-image/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=scene_text_patch_contract&amp;amp;utm_content=workspace_link" rel="noopener noreferrer"&gt;exact scene-text editor&lt;/a&gt;. On July 29, 2026 it showed GPT Image 2, a Poster headline preset, a prefilled exact-replacement prompt, required image URLs, optional mask input, Image Size Auto, Public visibility, and a 6-credit estimate. Required uploads lead through sign-in. The number can change after you supply the source, mask, size, or other settings, so use the final live estimate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40uvido6sui8g01laot2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40uvido6sui8g01laot2.png" alt="Current Voor AI text-edit form with replacement preset, prefilled prompt, image and mask inputs, size, visibility, credits, and Generate." width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: quote both strings
&lt;/h2&gt;

&lt;p&gt;Do not write “change the headline.” Quote the source and replacement literally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit only the large top headline. Replace the exact visible text "OPEN LATE" with "OPEN DAILY".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quotes turn an aesthetic request into a testable assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: protect the rest of the image
&lt;/h2&gt;

&lt;p&gt;Add the rendering contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Match the existing type style, weight, color, alignment, spacing, print texture, and perspective. Preserve the illustration, people, border, background, crop, and every other word. Do not redesign the poster or add text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the editor exposes a mask after upload, paint only the target line plus a small context margin. A mask that covers half the poster silently expands the model's permission to redraw it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: run a regression check
&lt;/h2&gt;

&lt;p&gt;OCR can catch string failures, but it cannot prove visual fidelity. Combine machine and manual checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateTextPatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ocrBefore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ocrAfter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newText&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ocrBefore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ocrAfter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sourceFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldText&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;replacementFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newText&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;oldTextRemoved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldText&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare at 100%:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;baseline and alignment;&lt;/li&gt;
&lt;li&gt;letter spacing and capitalization;&lt;/li&gt;
&lt;li&gt;perspective and surface texture;&lt;/li&gt;
&lt;li&gt;faces, products, logos, borders, and every unrelated word.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The whole poster changes.&lt;/strong&gt; Tighten the mask and repeat “edit only.” Remove stylistic adjectives unrelated to the replacement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The spelling is almost right.&lt;/strong&gt; Shorten the replacement, keep exact quotes, and generate one patch at a time. For compliance-critical copy, rebuild the line in a normal design tool instead of trusting a raster render.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other text mutates.&lt;/strong&gt; Name every protected region and run a visual diff. If several lines changed, reject the output even when the target headline is correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The type looks pasted on.&lt;/strong&gt; Ask for matching perspective, local contrast, grain, shadow, and print texture—not merely the same font.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The result is a raster image, not recovered editable type. AI cannot reliably identify an exact font, preserve brand artwork, or guarantee legal accuracy. Never change invoices, IDs, certificates, evidence, safety labels, or someone else's copyrighted design to mislead. Model availability, credits, size options, and Public visibility may change.&lt;/p&gt;

&lt;p&gt;When the patch contract is narrow and reviewable, &lt;a href="https://voor.ai/ai-text-editor-in-image/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=scene_text_patch_contract&amp;amp;utm_content=final_cta" rel="noopener noreferrer"&gt;replace the exact string and run the regression checklist&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>imageprocessing</category>
    </item>
    <item>
      <title>A Practical Guide to Iterating on AI Images Without Losing the Original Intent</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Tue, 28 Jul 2026 11:06:56 +0000</pubDate>
      <link>https://dev.to/voorai/a-practical-guide-to-iterating-on-ai-images-without-losing-the-original-intent-cfi</link>
      <guid>https://dev.to/voorai/a-practical-guide-to-iterating-on-ai-images-without-losing-the-original-intent-cfi</guid>
      <description>&lt;p&gt;AI image work usually gets harder after the first generation. The initial prompt may produce a useful direction, but the second and third iterations can quietly change the subject, framing, or visual hierarchy.&lt;/p&gt;

&lt;p&gt;A reliable workflow treats every revision as a controlled experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Keep a short visual brief
&lt;/h2&gt;

&lt;p&gt;Before editing, write down the parts that must not change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the main subject and its pose&lt;/li&gt;
&lt;li&gt;the camera angle and crop&lt;/li&gt;
&lt;li&gt;the dominant light direction&lt;/li&gt;
&lt;li&gt;the intended use, such as a product card or a social cover&lt;/li&gt;
&lt;li&gt;any text that must remain readable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This brief becomes the reference for every later prompt. It also makes it easier to compare two versions without relying on memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Separate the change from the constraint
&lt;/h2&gt;

&lt;p&gt;A useful edit prompt has two parts. First describe the single change you want, such as replacing the background or adjusting the color palette. Then state what should remain untouched.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace the background with a quiet studio gradient. Keep the product silhouette, camera angle, label placement, and shadow direction unchanged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is more predictable than adding several unrelated changes in one request. If the result misses the brief, you know which variable to revise.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use references for consistency
&lt;/h2&gt;

&lt;p&gt;When an image needs to stay close to an existing design, provide the reference image and describe the relationship between the reference and the new output. References are especially helpful for packaging, character appearance, recurring props, and brand colors.&lt;/p&gt;

&lt;p&gt;For a practical multimodal workflow, the &lt;a href="https://www-flux3.com" rel="noopener noreferrer"&gt;FLUX 3 AI image and video generator&lt;/a&gt; is one option for moving from a rough visual brief to an editable result. The important part is not the tool name; it is keeping the input, revision, and review steps explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Review at the final delivery size
&lt;/h2&gt;

&lt;p&gt;A draft can look convincing at full size and fail in the place where it will actually be used. Check the image at its target size:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confirm that small text is legible&lt;/li&gt;
&lt;li&gt;check the crop on mobile&lt;/li&gt;
&lt;li&gt;inspect edges around products and people&lt;/li&gt;
&lt;li&gt;compare the contrast against the surrounding page&lt;/li&gt;
&lt;li&gt;export a clean version instead of repeatedly compressing the same file&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Keep a simple iteration log
&lt;/h2&gt;

&lt;p&gt;For repeatable work, record the prompt change, the reference used, and the reason a version was accepted or rejected. This prevents teams from circling back to an earlier mistake and makes successful prompt patterns easier to reuse.&lt;/p&gt;

&lt;p&gt;The goal is not to generate the largest number of variations. It is to make each iteration answer a clear visual question while preserving the original intent.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Practical Shot-Planning Workflow for AI Video With Dialogue and Sound Direction</title>
      <dc:creator>Voor AI</dc:creator>
      <pubDate>Tue, 28 Jul 2026 09:37:48 +0000</pubDate>
      <link>https://dev.to/voorai/a-practical-shot-planning-workflow-for-ai-video-with-dialogue-and-sound-direction-21dd</link>
      <guid>https://dev.to/voorai/a-practical-shot-planning-workflow-for-ai-video-with-dialogue-and-sound-direction-21dd</guid>
      <description>&lt;p&gt;AI video tools are getting better at producing attractive clips, but a polished result still depends more on planning than on a single clever prompt. When a short demo needs dialogue, ambience, and a visual beat to land at the same time, it helps to treat the generation brief like a small production document.&lt;/p&gt;

&lt;p&gt;This workflow is useful for product demos, social clips, explainers, and storyboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with the viewer's job
&lt;/h2&gt;

&lt;p&gt;Before writing a prompt, write one sentence describing what the viewer should understand or feel after the shot. For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The viewer should see the product turn a rough idea into a finished result, and understand that the workflow is quick enough to try.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This keeps the shot from becoming a collection of unrelated visual effects. If the viewer's job is unclear, the prompt usually becomes overloaded with adjectives.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Write a shot contract
&lt;/h2&gt;

&lt;p&gt;A shot contract is a compact checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;subject and action&lt;/li&gt;
&lt;li&gt;camera distance and movement&lt;/li&gt;
&lt;li&gt;setting and lighting&lt;/li&gt;
&lt;li&gt;aspect ratio and approximate duration&lt;/li&gt;
&lt;li&gt;dialogue or on-screen text&lt;/li&gt;
&lt;li&gt;ambience and sound direction&lt;/li&gt;
&lt;li&gt;the transition into the next shot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contract does not need to be poetic. It needs to be testable. “A calm close-up of the interface while the cursor completes the final step” is easier to evaluate than “make it cinematic and inspiring.”&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Separate visual direction from audio direction
&lt;/h2&gt;

&lt;p&gt;Visual instructions and audio instructions solve different problems. Put them in separate paragraphs or labeled sections so the model can resolve conflicts more reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual direction&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;medium close-up&lt;/li&gt;
&lt;li&gt;slow push-in&lt;/li&gt;
&lt;li&gt;neutral desk lighting&lt;/li&gt;
&lt;li&gt;keep the main interface area readable&lt;/li&gt;
&lt;li&gt;leave negative space for a caption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Audio direction&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a short, clear voice line&lt;/li&gt;
&lt;li&gt;light room tone&lt;/li&gt;
&lt;li&gt;a restrained confirmation sound at the end&lt;/li&gt;
&lt;li&gt;no music competing with the spoken instruction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation also makes revisions faster. If the visuals work but the voice feels rushed, you can adjust the audio direction without rewriting the entire shot.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Use references and keyframes deliberately
&lt;/h2&gt;

&lt;p&gt;A reference image should answer a specific question: composition, character appearance, color, or interface state. If it is meant to control composition, say so. If it is only a mood reference, say that instead.&lt;/p&gt;

&lt;p&gt;Keyframes are most useful when a transition matters. Define the starting state and the ending state in plain language, then describe the motion between them. Avoid asking a single short clip to introduce a character, explain a product, change locations, and deliver a punchline all at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make a cheap first pass
&lt;/h2&gt;

&lt;p&gt;The first generation is a test of the contract, not the final export. Check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is the subject doing the intended action?&lt;/li&gt;
&lt;li&gt;Is the camera movement understandable?&lt;/li&gt;
&lt;li&gt;Is the dialogue short enough to fit?&lt;/li&gt;
&lt;li&gt;Does the ambience support the scene?&lt;/li&gt;
&lt;li&gt;Does the final frame leave a clean handoff to the next shot?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If two or more answers are “no,” revise the brief before adding more visual detail. A shorter, clearer prompt usually beats a longer prompt full of rescue instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Review sync as a separate pass
&lt;/h2&gt;

&lt;p&gt;Watch the clip once without sound, then listen once without looking at the screen. Finally, watch and listen together. This catches different classes of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visual action starts before the spoken line&lt;/li&gt;
&lt;li&gt;a sound effect arrives after the action it describes&lt;/li&gt;
&lt;li&gt;ambience changes abruptly between shots&lt;/li&gt;
&lt;li&gt;captions remain on screen after the sentence ends&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A browser-based &lt;a href="https://flux3-video.online" rel="noopener noreferrer"&gt;FLUX 3 video workflow&lt;/a&gt; can be useful here because the visual brief, references, keyframes, and sound direction can be kept together while iterating.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Keep a reusable handoff template
&lt;/h2&gt;

&lt;p&gt;For recurring work, save a small template with these fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;goal&lt;/li&gt;
&lt;li&gt;audience&lt;/li&gt;
&lt;li&gt;shot length&lt;/li&gt;
&lt;li&gt;visual action&lt;/li&gt;
&lt;li&gt;camera&lt;/li&gt;
&lt;li&gt;dialogue&lt;/li&gt;
&lt;li&gt;ambience&lt;/li&gt;
&lt;li&gt;sound effect&lt;/li&gt;
&lt;li&gt;negative constraints&lt;/li&gt;
&lt;li&gt;acceptance checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The template turns each new clip into an editing task instead of a blank page. It also makes collaboration easier because another person can see exactly which part of the brief needs revision.&lt;/p&gt;

&lt;p&gt;The best AI video results are rarely the result of adding more adjectives. They come from making the intended action, timing, and sound easy to verify. Plan the shot, test the contract, and revise the smallest failing part.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
