<?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: Gemy Brooks</title>
    <description>The latest articles on DEV Community by Gemy Brooks (@_3330b5103afff1).</description>
    <link>https://dev.to/_3330b5103afff1</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%2F4170987%2Fb60b4932-4ffa-4f3e-88ed-3f8d7f3faa2b.png</url>
      <title>DEV Community: Gemy Brooks</title>
      <link>https://dev.to/_3330b5103afff1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_3330b5103afff1"/>
    <language>en</language>
    <item>
      <title>Building a browser GIF editor: the frame memory limit matters more than file size</title>
      <dc:creator>Gemy Brooks</dc:creator>
      <pubDate>Tue, 03 Nov 2026 14:26:53 +0000</pubDate>
      <link>https://dev.to/_3330b5103afff1/building-a-browser-gif-editor-the-frame-memory-limit-matters-more-than-file-size-45k9</link>
      <guid>https://dev.to/_3330b5103afff1/building-a-browser-gif-editor-the-frame-memory-limit-matters-more-than-file-size-45k9</guid>
      <description>&lt;p&gt;I built &lt;a href="https://visualformat.tools/" rel="noopener noreferrer"&gt;VisualFormat&lt;/a&gt;, a free set of browser GIF tools. The files are processed locally, with no account required. One practical constraint shaped the implementation: a small GIF file can still be expensive to decode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with decoded pixels
&lt;/h2&gt;

&lt;p&gt;A 1,000 × 1,000 animation with 100 frames represents 100 million frame pixels. At four bytes per RGBA pixel, keeping a full decoded copy of every frame would already take roughly 400 MB, before working canvases and encoding buffers. The compressed download size does not describe that cost.&lt;/p&gt;

&lt;p&gt;VisualFormat scans GIF blocks before allocating decoded frames. It checks dimensions, frame count and the total canvas pixels across the animation. The current GIF input limits are 20 MB, 500 frames, 4,096 pixels per dimension and 40 million total frame pixels. These are guardrails, not a guarantee that every device can process every accepted file.&lt;/p&gt;

&lt;h2&gt;
  
  
  A frame is not always a complete picture
&lt;/h2&gt;

&lt;p&gt;Some frames update only a rectangle of the canvas. Transparency, local palettes and disposal instructions affect the image that should actually appear. A crop or frame extractor needs the composited result, rather than treating each stored rectangle as a standalone image.&lt;/p&gt;

&lt;p&gt;Useful test cases include a partial update over a transparent canvas, restore-to-previous disposal, changing frame delays, local palettes and animations that do not loop. For this project, output samples were compared with independently decoded Pillow results, including disposal and timing cases. A preview that looks right on the first frame is only the beginning of validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move work away from the interface
&lt;/h2&gt;

&lt;p&gt;The GIF processing paths use Web Workers and report progress back to the page. Output buffers are transferred back when processing finishes. This keeps heavy processing away from the main interface, but it does not remove memory limits: a worker still uses memory on the same device. Errors need a useful recovery path, such as trying a shorter animation or smaller dimensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be clear about the tradeoffs
&lt;/h2&gt;

&lt;p&gt;Reducing dimensions and frame count can make a GIF much smaller. Reducing colors can also affect gradients and fine detail, so checking the exported animation remains important. The background remover uses solid-color matching; it does not provide semantic subject segmentation for complex scenes.&lt;/p&gt;

&lt;p&gt;You can try the &lt;a href="https://visualformat.tools/" rel="noopener noreferrer"&gt;GIF cropper&lt;/a&gt; or the &lt;a href="https://visualformat.tools/gif-frame-extractor" rel="noopener noreferrer"&gt;frame extractor&lt;/a&gt;. I would be interested in examples where disposal, transparency or browser memory makes a GIF unexpectedly difficult to edit.&lt;/p&gt;

&lt;p&gt;Disclosure: I am the maker of VisualFormat. This article was prepared with AI assistance and checked against the project implementation.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Why Cropping an Animated GIF Is Harder Than Cropping a Regular Image</title>
      <dc:creator>Gemy Brooks</dc:creator>
      <pubDate>Sun, 01 Nov 2026 16:06:08 +0000</pubDate>
      <link>https://dev.to/_3330b5103afff1/why-cropping-an-animated-gif-is-harder-than-cropping-a-regular-image-1mkj</link>
      <guid>https://dev.to/_3330b5103afff1/why-cropping-an-animated-gif-is-harder-than-cropping-a-regular-image-1mkj</guid>
      <description>&lt;h1&gt;
  
  
  Why Cropping an Animated GIF Is Harder Than Cropping a Regular Image
&lt;/h1&gt;

&lt;p&gt;Cropping a PNG or JPEG is straightforward: load the image, select a rectangular area, crop it, and export the result.&lt;/p&gt;

&lt;p&gt;Animated GIFs are a different story.&lt;/p&gt;

&lt;p&gt;If you've ever tried to build an online GIF cropper, you may have discovered that simply cropping each frame independently doesn't always produce the animation you expect.&lt;/p&gt;

&lt;p&gt;Frames can disappear, transparency can break, timing can change, and unexpected visual artifacts can appear.&lt;/p&gt;

&lt;p&gt;Let's look at why this happens and what a reliable GIF cropping workflow should look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A GIF Isn't Just a Collection of Full Images
&lt;/h2&gt;

&lt;p&gt;One common misconception is that an animated GIF is simply a sequence of complete images.&lt;/p&gt;

&lt;p&gt;In reality, GIF uses a frame-based structure that can be more complicated.&lt;/p&gt;

&lt;p&gt;An animated GIF contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A logical screen describing the animation canvas.&lt;/li&gt;
&lt;li&gt;Image descriptors defining individual frame regions.&lt;/li&gt;
&lt;li&gt;Frame image data.&lt;/li&gt;
&lt;li&gt;Graphic Control Extensions containing timing and disposal information.&lt;/li&gt;
&lt;li&gt;Optional looping information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An important detail is that individual frames don't always contain the entire canvas.&lt;/p&gt;

&lt;p&gt;Some GIFs use partial frame updates. Instead of storing a complete image for every frame, they only store the pixels that changed.&lt;/p&gt;

&lt;p&gt;This can make GIF files smaller, but it also makes image processing more complicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Disposal Method Can Change Everything
&lt;/h2&gt;

&lt;p&gt;GIF frames have disposal methods that determine what happens to the canvas after a frame is displayed.&lt;/p&gt;

&lt;p&gt;The commonly encountered methods include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do Not Dispose:&lt;/strong&gt; Leave the previous frame in place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore to Background:&lt;/strong&gt; Restore the frame area to the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore to Previous:&lt;/strong&gt; Restore the previous canvas state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These behaviors matter when reconstructing an animation.&lt;/p&gt;

&lt;p&gt;Imagine a GIF where a character moves from left to right.&lt;/p&gt;

&lt;p&gt;Frame 1 contains the character on the left.&lt;/p&gt;

&lt;p&gt;Frame 2 only contains the character's new position.&lt;/p&gt;

&lt;p&gt;Frame 3 contains another small update.&lt;/p&gt;

&lt;p&gt;If you crop the encoded frame rectangles directly, you may lose information needed to reconstruct the actual animation.&lt;/p&gt;

&lt;p&gt;The safer approach is to reconstruct the complete displayed canvas before cropping.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A More Reliable GIF Cropping Workflow
&lt;/h2&gt;

&lt;p&gt;A robust implementation can follow this pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original GIF
     |
     v
Decode GIF structure
     |
     v
Reconstruct complete frames
     |
     v
Apply crop coordinates
     |
     v
Preserve frame timing
     |
     v
Re-encode GIF
     |
     v
Validate output animation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break it down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Decode the GIF
&lt;/h3&gt;

&lt;p&gt;Read the GIF structure and extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas dimensions&lt;/li&gt;
&lt;li&gt;Frame data&lt;/li&gt;
&lt;li&gt;Frame offsets&lt;/li&gt;
&lt;li&gt;Frame delays&lt;/li&gt;
&lt;li&gt;Disposal methods&lt;/li&gt;
&lt;li&gt;Transparency information&lt;/li&gt;
&lt;li&gt;Loop settings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Reconstruct Each Frame
&lt;/h3&gt;

&lt;p&gt;Before cropping, reconstruct each frame as it should appear on the complete animation canvas.&lt;/p&gt;

&lt;p&gt;This step is particularly important for GIFs using partial frame updates.&lt;/p&gt;

&lt;p&gt;A decoder that returns raw frame rectangles requires additional compositing logic.&lt;/p&gt;

&lt;p&gt;A decoder that already returns fully composited frames can simplify this stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Apply the Same Crop Region to Every Frame
&lt;/h3&gt;

&lt;p&gt;Suppose the original GIF has dimensions of 800 × 600 pixels.&lt;/p&gt;

&lt;p&gt;The user selects this crop region:&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;crop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the same coordinates to every reconstructed frame.&lt;/p&gt;

&lt;p&gt;The output dimensions should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;400 × 300 pixels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important detail: if the GIF preview is scaled to fit the browser window, the selection coordinates must be converted back to the original GIF coordinate system.&lt;/p&gt;

&lt;p&gt;Otherwise, the exported result may not match the visible selection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Preserve Animation Timing
&lt;/h3&gt;

&lt;p&gt;Cropping should not unintentionally change how the animation plays.&lt;/p&gt;

&lt;p&gt;Keep track of each frame's original delay.&lt;/p&gt;

&lt;p&gt;For example:&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;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frameCanvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;originalDelay&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The encoder should receive the appropriate delay for each corresponding frame.&lt;/p&gt;

&lt;p&gt;Also check whether the original animation uses looping information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Re-encode and Validate
&lt;/h3&gt;

&lt;p&gt;After cropping, encode the reconstructed frames into a new GIF.&lt;/p&gt;

&lt;p&gt;Don't assume that a successful encoding operation means the output is correct.&lt;/p&gt;

&lt;p&gt;Decode the exported file again and verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frame count&lt;/li&gt;
&lt;li&gt;Frame order&lt;/li&gt;
&lt;li&gt;Frame delays&lt;/li&gt;
&lt;li&gt;Total animation duration&lt;/li&gt;
&lt;li&gt;Transparency&lt;/li&gt;
&lt;li&gt;Loop behavior&lt;/li&gt;
&lt;li&gt;Canvas dimensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This final validation step can catch problems that are difficult to notice in a single preview.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Transparency Is Another Common Source of Bugs
&lt;/h2&gt;

&lt;p&gt;GIF transparency is not the same as the alpha channel in PNG.&lt;/p&gt;

&lt;p&gt;GIF transparency is based on a transparent color index in the frame's color table.&lt;/p&gt;

&lt;p&gt;That means GIF transparency is essentially binary: a palette entry is transparent or it isn't.&lt;/p&gt;

&lt;p&gt;When rebuilding frames, careless palette conversion can introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unexpected background colors&lt;/li&gt;
&lt;li&gt;Transparent pixels becoming opaque&lt;/li&gt;
&lt;li&gt;Visible edges around objects&lt;/li&gt;
&lt;li&gt;Flickering between frames&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good implementation should preserve transparency information during decoding and handle palette generation carefully during encoding.&lt;/p&gt;

&lt;p&gt;If exact visual preservation is important, compare the exported animation against the original frame by frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Browser-Based Processing Is Worth Considering
&lt;/h2&gt;

&lt;p&gt;For small and medium-sized GIF files, browser-based processing can be a practical architecture.&lt;/p&gt;

&lt;p&gt;A typical setup might use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas for rendering and cropping.&lt;/li&gt;
&lt;li&gt;A GIF decoder for reading frame data.&lt;/li&gt;
&lt;li&gt;A GIF encoder for generating the output.&lt;/li&gt;
&lt;li&gt;Web Workers for moving CPU-intensive work away from the main thread.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser UI
    |
    v
File Upload
    |
    v
Web Worker
    |
    +-- GIF Decoder
    |
    +-- Frame Compositor
    |
    +-- Crop Processor
    |
    +-- GIF Encoder
    |
    v
Output Blob
    |
    v
Download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are trade-offs.&lt;/p&gt;

&lt;p&gt;Large animations can consume considerable memory because reconstructing every frame may require multiple full-resolution image buffers.&lt;/p&gt;

&lt;p&gt;For this reason, a good tool should consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximum file size&lt;/li&gt;
&lt;li&gt;Maximum frame count&lt;/li&gt;
&lt;li&gt;Processing cancellation&lt;/li&gt;
&lt;li&gt;Memory cleanup&lt;/li&gt;
&lt;li&gt;Clear error messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is also important to release object URLs and temporary resources when processing finishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. My GIF Cropping Test Checklist
&lt;/h2&gt;

&lt;p&gt;When developing or evaluating a GIF cropper, I would test these cases before considering the implementation reliable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test case&lt;/th&gt;
&lt;th&gt;What to verify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple animated GIF&lt;/td&gt;
&lt;td&gt;All frames remain visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial-frame GIF&lt;/td&gt;
&lt;td&gt;No missing image regions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transparent GIF&lt;/td&gt;
&lt;td&gt;Transparency remains intact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable frame delays&lt;/td&gt;
&lt;td&gt;Animation speed remains consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Looping GIF&lt;/td&gt;
&lt;td&gt;Loop behavior is preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large GIF&lt;/td&gt;
&lt;td&gt;Processing doesn't freeze the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different crop ratios&lt;/td&gt;
&lt;td&gt;Output coordinates are accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A tool that works with one simple GIF isn't necessarily ready for real-world use.&lt;/p&gt;

&lt;p&gt;The difficult cases are usually the ones that reveal implementation problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building an animated GIF cropper is not simply an image manipulation task.&lt;/p&gt;

&lt;p&gt;It requires understanding frame composition, disposal methods, timing, transparency, and encoding.&lt;/p&gt;

&lt;p&gt;The most important principle is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reconstruct the animation correctly before modifying it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the animation is represented as complete displayed frames, applying the same crop region becomes much more predictable.&lt;/p&gt;

&lt;p&gt;I'm also exploring browser-based visual media utilities through &lt;a href="https://visualformat.tools/" rel="noopener noreferrer"&gt;VisualFormat&lt;/a&gt;, where GIF processing is one of the areas I'm interested in building practical tools for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This article was prepared with AI assistance and should be reviewed by the author for technical accuracy before publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Fixing Closed Eyes with AI: Why the Edit Boundary Matters</title>
      <dc:creator>Gemy Brooks</dc:creator>
      <pubDate>Sat, 24 Oct 2026 10:37:29 +0000</pubDate>
      <link>https://dev.to/_3330b5103afff1/fixing-closed-eyes-with-ai-why-the-edit-boundary-matters-25p4</link>
      <guid>https://dev.to/_3330b5103afff1/fixing-closed-eyes-with-ai-why-the-edit-boundary-matters-25p4</guid>
      <description>&lt;p&gt;A group photo can be almost perfect: good light, everyone in the frame, a moment worth keeping.&lt;/p&gt;

&lt;p&gt;Then you notice that one person blinked.&lt;/p&gt;

&lt;p&gt;For a developer, this looks like a small image-editing task. But the real requirement is more demanding:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open the eyes while preserving everything the user already likes about the photo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm building MomentRescue, a photo-repair project. This post looks at one engineering question behind its Open Eyes workflow: how do you constrain a generative edit when the requested change is tiny?&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Define what is allowed to change
&lt;/h2&gt;

&lt;p&gt;“Make this photo better” is an open-ended request.&lt;/p&gt;

&lt;p&gt;“Repair the selected person's closed eyes” has a much narrower boundary.&lt;/p&gt;

&lt;p&gt;That distinction should appear in the system design, not just in the prompt. A useful contract is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One selected person per operation.&lt;/li&gt;
&lt;li&gt;A repair region around the eyes.&lt;/li&gt;
&lt;li&gt;Original image pixels outside that region.&lt;/li&gt;
&lt;li&gt;A separate output file, with the original kept intact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model may generate more than you intend to use. Your application still decides which generated pixels reach the final image.&lt;/p&gt;

&lt;p&gt;That is the difference between asking for restraint and enforcing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Separate generation from composition
&lt;/h2&gt;

&lt;p&gt;A practical conceptual pipeline looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original photo
  -&amp;gt; detect faces and confirm the selected person
  -&amp;gt; prepare a face crop and optional reference
  -&amp;gt; generate a candidate repair
  -&amp;gt; align the candidate to the original coordinates
  -&amp;gt; blend it through a bounded eye-area mask
  -&amp;gt; compare, review, and export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a design outline, not a complete implementation or a claim that every photo-repair tool works this way.&lt;/p&gt;

&lt;p&gt;The important separation is between &lt;strong&gt;producing a candidate&lt;/strong&gt; and &lt;strong&gt;accepting pixels into the final photo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For an opaque RGB image, the basic blend is:&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="c1"&gt;// Illustrative RGB compositing, not production image-processing code.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;blendPixel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maskAlpha&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maskAlpha&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;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;channel&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="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;candidate&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="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;a&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;Outside the permitted region, maskAlpha must be zero. At the boundary, a gradual transition can soften the seam.&lt;/p&gt;

&lt;p&gt;The difficult work sits around this small formula: coordinate mapping, alignment, mask placement, and handling the edges of glasses. A soft mask cannot fix a misaligned eye.&lt;/p&gt;

&lt;p&gt;Also, preserving decoded pixels is different from preserving an original file byte for byte. Exporting can change compression and metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Treat a reference as evidence, not a guarantee
&lt;/h2&gt;

&lt;p&gt;Closed eyelids hide information. There is no reliable “sharpen” operation that reveals the person's actual eyes underneath.&lt;/p&gt;

&lt;p&gt;An open-eye reference of the same person provides visual clues. But a sharp reference with a very different head angle can still be a poor match.&lt;/p&gt;

&lt;p&gt;For a reference picker, useful guidance is concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose the same person with both eyes visible.&lt;/li&gt;
&lt;li&gt;Prefer a similar head angle and lighting.&lt;/li&gt;
&lt;li&gt;Try a neighboring shot from the same session.&lt;/li&gt;
&lt;li&gt;Avoid sunglasses, heavy blur, or a face that occupies very few pixels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the reference contains several people, the interface needs to confirm the matching person there too.&lt;/p&gt;

&lt;p&gt;A reference reduces guesswork. It does not establish that the generated result is accurate.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Inspect an example at two scales
&lt;/h2&gt;

&lt;p&gt;Here is an existing &lt;a href="https://momentrescue.com/" rel="noopener noreferrer"&gt;MomentRescue Open Eyes test&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These images use synthetic people. They are a product demonstration, not a customer photo, endorsement, or benchmark. Results vary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before: the center person has closed eyes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodyv2b5sjkl0lfkxeomy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodyv2b5sjkl0lfkxeomy.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
After: the existing test result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo689pnx54xjzb01z62vp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo689pnx54xjzb01z62vp.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
A full-photo view answers, “Does the picture still feel coherent?”&lt;/p&gt;

&lt;p&gt;A close inspection answers different questions: Are both eyes looking in a compatible direction? Do the eyelids fit the face? Are the glasses frames continuous? Is there a visible blend boundary?&lt;/p&gt;

&lt;p&gt;A result can pass one view and fail the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Give “finished” and “good” different meanings
&lt;/h2&gt;

&lt;p&gt;An API response tells you that processing finished. It does not tell you whether someone looks like themselves.&lt;/p&gt;

&lt;p&gt;I would evaluate a focused repair with three separate checks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Edit containment&lt;/td&gt;
&lt;td&gt;Did any decoded pixels outside the permitted region change?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local quality&lt;/td&gt;
&lt;td&gt;Do gaze, eyelids, lighting, and glasses look plausible?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User acceptance&lt;/td&gt;
&lt;td&gt;Does the person still look like themselves to the user?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first check can be automated by comparing decoded images outside the mask, before export. The other two still need visual judgment.&lt;/p&gt;

&lt;p&gt;Useful evaluation cases include small faces, side profiles, glasses, mismatched references, and groups with more than one person blinking. A clean demonstration image cannot establish performance across those cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader engineering lesson
&lt;/h2&gt;

&lt;p&gt;For a narrowly scoped AI edit, the output boundary is part of the feature.&lt;/p&gt;

&lt;p&gt;The same question applies to removing a distraction, repairing a document region, or changing one object in a product photo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which parts of the input is the system actually allowed to rewrite?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A clear answer gives you something concrete to implement, inspect, and test.&lt;/p&gt;

&lt;p&gt;How would you evaluate identity preservation in a small face edit, beyond checking whether the result looks plausible?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I am the developer behind MomentRescue. AI assistance was used to draft this article. The compositing snippet is illustrative; it is not the project's production implementation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Workflow reference: &lt;a href="https://momentrescue.com/about" rel="noopener noreferrer"&gt;MomentRescue's description of its selected-face repair pipeline&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How I Built an AI-Powered Real Estate Photo Editing Workflow</title>
      <dc:creator>Gemy Brooks</dc:creator>
      <pubDate>Tue, 13 Oct 2026 15:44:51 +0000</pubDate>
      <link>https://dev.to/_3330b5103afff1/how-i-built-an-ai-powered-real-estate-photo-editing-workflow-8g1</link>
      <guid>https://dev.to/_3330b5103afff1/how-i-built-an-ai-powered-real-estate-photo-editing-workflow-8g1</guid>
      <description>&lt;h1&gt;
  
  
  How I Built an AI-Powered Real Estate Photo Editing Workflow
&lt;/h1&gt;

&lt;p&gt;Real estate photography has an interesting problem: a photo can be technically correct and still look terrible in a property listing.&lt;/p&gt;

&lt;p&gt;A room might be beautifully designed, but the camera captures a dark interior and an almost completely white window. The walls look slightly yellow, vertical lines appear tilted, and shadows hide important details.&lt;/p&gt;

&lt;p&gt;Professional photographers know how to fix these problems. But editing dozens or hundreds of listing photos manually takes time.&lt;/p&gt;

&lt;p&gt;As an independent developer, I became interested in a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Could I build a workflow that automates most of the repetitive editing while still giving users control over the final result?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question led me to work on &lt;a href="https://listingpolish.online/" rel="noopener noreferrer"&gt;ListingPolish&lt;/a&gt;, an AI-powered real estate photo editing tool.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain the engineering decisions behind this kind of workflow, the problems it needs to solve, and why I think quality control matters as much as image enhancement.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Real Estate Photos Have Their Own Challenges
&lt;/h2&gt;

&lt;p&gt;At first glance, real estate photo editing looks like a standard image enhancement problem.&lt;/p&gt;

&lt;p&gt;Increase brightness, adjust contrast, correct colors, and you're done.&lt;/p&gt;

&lt;p&gt;Unfortunately, it's not that simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blown-out windows
&lt;/h3&gt;

&lt;p&gt;This is one of the most common problems in interior photography.&lt;/p&gt;

&lt;p&gt;The camera exposes for the darker room, leaving the outdoor view overexposed.&lt;/p&gt;

&lt;p&gt;Simply reducing the brightness doesn't necessarily recover missing information.&lt;/p&gt;

&lt;p&gt;For a good result, the editing system needs to handle the difference between interior and exterior exposure intelligently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uneven lighting
&lt;/h3&gt;

&lt;p&gt;A room may have bright areas near windows and dark corners elsewhere.&lt;/p&gt;

&lt;p&gt;A global brightness adjustment can make one area better while making another worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color casts
&lt;/h3&gt;

&lt;p&gt;Indoor lighting can introduce yellow, green, or blue color casts.&lt;/p&gt;

&lt;p&gt;These are particularly noticeable on white walls, ceilings, and neutral furniture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Perspective distortion
&lt;/h3&gt;

&lt;p&gt;Wide-angle lenses are useful for photographing interiors, but they can make vertical lines appear tilted.&lt;/p&gt;

&lt;p&gt;In real estate photography, correcting these lines can make a significant difference.&lt;/p&gt;

&lt;p&gt;These issues convinced me that a generic "make this photo look better" button isn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why I Chose APIs Instead of Training a Model
&lt;/h2&gt;

&lt;p&gt;When building an AI image product, one of the first decisions is whether to develop a custom model or use an existing service.&lt;/p&gt;

&lt;p&gt;For an independent developer, the trade-offs are important.&lt;/p&gt;

&lt;p&gt;Training a specialized model requires datasets, computing resources, evaluation pipelines, and ongoing maintenance.&lt;/p&gt;

&lt;p&gt;Using an established image processing API offers a much faster path to building a usable product.&lt;/p&gt;

&lt;p&gt;It also lets me focus on the parts that directly affect the user experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploading and managing multiple photos&lt;/li&gt;
&lt;li&gt;Coordinating processing requests&lt;/li&gt;
&lt;li&gt;Evaluating output quality&lt;/li&gt;
&lt;li&gt;Handling failed processing attempts&lt;/li&gt;
&lt;li&gt;Presenting before-and-after comparisons&lt;/li&gt;
&lt;li&gt;Managing downloads and processing costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't to build another image generation model.&lt;/p&gt;

&lt;p&gt;It's to build a reliable product around image processing capabilities that already exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Designing the Processing Workflow
&lt;/h2&gt;

&lt;p&gt;The workflow I designed for ListingPolish follows this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User uploads photos
        |
        v
Validate input files
        |
        v
Submit enhancement job
        |
        v
Receive processed preview
        |
        v
Run quality checks
        |
        +---- Acceptable ----&amp;gt; Show result
        |
        +---- Needs review --&amp;gt; Generate feedback
                                  |
                                  v
                            Retry if supported
                                  |
                                  v
                            Review new result
        |
        v
User reviews comparison
        |
        v
Download final image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the workflow architecture I'm working toward, rather than a claim that every quality check or retry path is already fully automated in production.&lt;/p&gt;

&lt;p&gt;The important idea is that image processing shouldn't be treated as a single API call.&lt;/p&gt;

&lt;p&gt;A successful API response only tells us that processing completed.&lt;/p&gt;

&lt;p&gt;It doesn't necessarily tell us whether the output is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why Quality Assurance Is the Interesting Part
&lt;/h2&gt;

&lt;p&gt;Imagine an AI service successfully processes an interior photograph.&lt;/p&gt;

&lt;p&gt;The result is brighter, but the walls now have an unnatural color cast.&lt;/p&gt;

&lt;p&gt;Technically, the processing job succeeded.&lt;/p&gt;

&lt;p&gt;From the user's perspective, it may have failed.&lt;/p&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;I think an automated editing workflow should eventually evaluate several dimensions of output quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exposure:&lt;/strong&gt; Are important areas still too dark or overexposed?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Color consistency:&lt;/strong&gt; Do neutral surfaces look natural?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Geometry:&lt;/strong&gt; Are vertical lines reasonably straight?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image artifacts:&lt;/strong&gt; Did processing introduce visible distortion or unnatural details?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overall consistency:&lt;/strong&gt; Does the edited image still look like the original property?&lt;/p&gt;

&lt;p&gt;Some checks may be possible using image analysis algorithms. Others may benefit from vision models.&lt;/p&gt;

&lt;p&gt;The difficult part is deciding when the system should accept a result, request another attempt, or ask the user to review it.&lt;/p&gt;

&lt;p&gt;A useful QA system should also avoid unnecessary retries. Reprocessing an image repeatedly can increase cost without improving quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Importance of Before-and-After Previews
&lt;/h2&gt;

&lt;p&gt;One lesson from thinking through this product is that users need to see what changed.&lt;/p&gt;

&lt;p&gt;A simple result image isn't always enough.&lt;/p&gt;

&lt;p&gt;A before-and-after comparison helps photographers identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether window details improved&lt;/li&gt;
&lt;li&gt;Whether colors look natural&lt;/li&gt;
&lt;li&gt;Whether shadows contain enough detail&lt;/li&gt;
&lt;li&gt;Whether the overall image feels realistic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also makes the tool easier to trust.&lt;/p&gt;

&lt;p&gt;For AI image editing, trust is especially important because a visually attractive result isn't always an accurate result.&lt;/p&gt;

&lt;p&gt;Real estate images should represent the property faithfully.&lt;/p&gt;

&lt;p&gt;Enhancement should improve presentation without inventing features or materially changing the property.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Building Around a Pay-for-Download Model
&lt;/h2&gt;

&lt;p&gt;Another interesting design decision is when to charge the user.&lt;/p&gt;

&lt;p&gt;Some image processing providers support previewing results before charging for the final downloadable image.&lt;/p&gt;

&lt;p&gt;That creates an opportunity for a more user-friendly workflow.&lt;/p&gt;

&lt;p&gt;Instead of requiring payment before users know whether the enhancement worked, the product can allow them to inspect the preview first.&lt;/p&gt;

&lt;p&gt;The challenge is balancing preview quality, processing costs, and abuse prevention.&lt;/p&gt;

&lt;p&gt;Even when the final download is the billable action, uploads, processing requests, storage, and retries may still create infrastructure or provider costs.&lt;/p&gt;

&lt;p&gt;A sustainable product needs to account for those costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What I'd Improve Next
&lt;/h2&gt;

&lt;p&gt;There are several areas I'd like to continue exploring:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better automated quality checks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A processing pipeline becomes much more useful when it can identify obvious problems before users notice them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More useful retry feedback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of retrying with the same settings, the system should ideally understand what went wrong.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Issue: Interior is still too dark
Suggestion: Improve shadow detail

Issue: Walls have a warm color cast
Suggestion: Adjust white balance

Issue: Vertical lines appear tilted
Suggestion: Apply perspective correction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These examples illustrate the kind of structured feedback a future review system could produce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More consistent batch processing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Photographers often need an entire property listing to have a consistent visual style.&lt;/p&gt;

&lt;p&gt;Editing each photo independently can produce inconsistent results.&lt;/p&gt;

&lt;p&gt;Solving that problem could make automated editing substantially more valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building an AI photo editing tool isn't just about finding the best image processing model.&lt;/p&gt;

&lt;p&gt;The surrounding workflow matters just as much.&lt;/p&gt;

&lt;p&gt;Uploading, previewing, quality checking, retrying, comparing, and downloading are all part of the experience.&lt;/p&gt;

&lt;p&gt;I'm exploring these ideas through &lt;a href="https://listingpolish.online/" rel="noopener noreferrer"&gt;ListingPolish, an AI real estate photo editing workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For developers building AI-powered image tools, I'm curious:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you evaluate the quality of AI-generated or AI-edited images before showing them to users?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do you rely on automated checks, human review, or a combination of both?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>buildinpublic</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
