<?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: Winson Yang</title>
    <description>The latest articles on DEV Community by Winson Yang (@winsonaibuilder).</description>
    <link>https://dev.to/winsonaibuilder</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%2F3836843%2Fe8384375-3f41-42e1-afe8-d6c8019de752.jpeg</url>
      <title>DEV Community: Winson Yang</title>
      <link>https://dev.to/winsonaibuilder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/winsonaibuilder"/>
    <language>en</language>
    <item>
      <title>Build a local crop-planning overlay with the Canvas API</title>
      <dc:creator>Winson Yang</dc:creator>
      <pubDate>Sun, 01 Nov 2026 08:15:50 +0000</pubDate>
      <link>https://dev.to/winsonaibuilder/build-a-local-crop-planning-overlay-with-the-canvas-api-30bn</link>
      <guid>https://dev.to/winsonaibuilder/build-a-local-crop-planning-overlay-with-the-canvas-api-30bn</guid>
      <description>&lt;p&gt;A thumbnail can have enough pixels and still cut off the thing that makes it recognizable. Before exporting several versions, it helps to make the crop itself visible.&lt;/p&gt;

&lt;p&gt;This small, dependency-free HTML page displays a local still image, dims the area a crop will discard, and lets you move the retained rectangle. It does not upload the file or save an edited copy. Save the code as &lt;code&gt;crop-overlay.html&lt;/code&gt; and open it in a modern browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check
&lt;/h2&gt;

&lt;p&gt;Start with the intended final aspect ratio. Move the horizontal and vertical sliders until the important detail stays inside the blue box. A centered crop is only a starting point: a face near an edge, a long garment or an asymmetric icon may need a different position.&lt;/p&gt;

&lt;p&gt;For an example, a 1600 × 900 image cropped to 1:1 retains 900 × 900 pixels. The horizontal slider moves that square across the 700 pixels of spare width. The vertical slider does nothing in this case because no height is spare. This is expected, not a broken control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete page
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Crop planning overlay&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;16px&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;800px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;24px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;auto&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="nx"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;16px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;range&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;240px&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Preview what a crop removes&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This page uses your local image. The blue box is the retained area.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Image &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/png,image/jpeg,image/webp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Target ratio &lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ratio"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1:1&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0.8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;4:5&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1.7777777778"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;16:9&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&amp;lt;/select&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Horizontal position &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"x"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"range"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;step=&lt;/span&gt;&lt;span class="s"&gt;"0.01"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Vertical position &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"y"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"range"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;step=&lt;/span&gt;&lt;span class="s"&gt;"0.01"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Choose a still image.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"canvas"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"720"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"480"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&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;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cropRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ratio&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;y&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;cw&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cw&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;ratio&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;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;x&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&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;draw&lt;/span&gt;&lt;span class="p"&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;bitmap&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&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;1&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;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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;1&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;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;s&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;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cropRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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;ratio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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;y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&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;sx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rgba(0,0,0,.55)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strokeStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#00bfff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineWidth&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;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strokeRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&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;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Retained: &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;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&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="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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; px. `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`Source: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&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;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; px. This is a preview, not an export.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&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;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;onchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;files&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;bitmap&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRect&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&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="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&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="nx"&gt;height&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;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&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;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Choose a still image.&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="p"&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;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&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="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&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;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Choose a file under 12 MiB.&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="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;try&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&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;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&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;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This image could not be decoded.&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="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;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ratio&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;x&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;y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;oninput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this geometry works
&lt;/h2&gt;

&lt;p&gt;The largest crop at a chosen ratio fits within both source dimensions. Its width is the smaller of the source width and &lt;code&gt;sourceHeight * ratio&lt;/code&gt;; its height is that width divided by the ratio. Each position slider selects a fraction of the unused space. The preview scales to fit 720 × 600 pixels without enlarging a small source image. The dimensions in the status line refer to the source crop, not the on-screen preview.&lt;/p&gt;

&lt;p&gt;The second &lt;code&gt;drawImage&lt;/code&gt; call redraws only the selected source rectangle over a dimmed full image. MDN documents the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage" rel="noopener noreferrer"&gt;source and destination rectangle form of drawImage&lt;/a&gt;. The request counter prevents an older asynchronous image decode from replacing a newer selection; discarded bitmaps are closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four useful review cases
&lt;/h2&gt;

&lt;p&gt;I build the following products, so these are examples from my own tools, not independent endorsements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For an &lt;a href="https://emoteresize.com/" rel="noopener noreferrer"&gt;Emote Resizer&lt;/a&gt; source, check whether the expression or silhouette survives a square crop before making tiny exports. This preview does not simulate tiny-size legibility or GIF animation.&lt;/li&gt;
&lt;li&gt;For a &lt;a href="https://pawmotif.com/" rel="noopener noreferrer"&gt;PawMotif&lt;/a&gt; portrait, keep ears, whiskers and distinctive markings away from the edge. Decide on a frame ratio before ordering a separate print; the product itself supplies digital artwork.&lt;/li&gt;
&lt;li&gt;For a &lt;a href="https://tryonswap.com/" rel="noopener noreferrer"&gt;TryOnSwap&lt;/a&gt; outfit concept, preserve the hem and relevant accessories when making a vertical crop. Keeping them in frame does not validate AI garment accuracy or real-world fit.&lt;/li&gt;
&lt;li&gt;Before assembling a &lt;a href="https://freeimagemerger.com/" rel="noopener noreferrer"&gt;Free Image Merger&lt;/a&gt; comparison, review each source at the same target ratio. A consistent crop makes scale and framing differences easier to notice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limits and a small check plan
&lt;/h2&gt;

&lt;p&gt;This is a planning overlay. It does not export, strip metadata, validate platform upload rules, manage color profiles or promise print quality. Only still PNG, JPEG and WebP are offered. The 12 MiB file limit reduces accidental large inputs but does not bound decoded memory: a highly compressed image can still be enormous. Close the page if a device struggles with a large file.&lt;/p&gt;

&lt;p&gt;Try a landscape source with a square crop, a portrait source with a wide crop, and a square source at 1:1. At the two ends of either slider, the crop should stay within the image. For a ratio already matching the image, both sliders should leave the full image unchanged. Finally, select two files quickly and check that the last selection is the one shown.&lt;/p&gt;

&lt;p&gt;The crop geometry was checked separately for landscape, portrait and matching-ratio cases. Browser decoding, color appearance and performance remain device-dependent. The code and this article were prepared with AI assistance and should be reviewed before adapting them into a production upload flow.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>A data-minimization worksheet for creative gifts and images</title>
      <dc:creator>Winson Yang</dc:creator>
      <pubDate>Wed, 28 Oct 2026 14:47:18 +0000</pubDate>
      <link>https://dev.to/winsonaibuilder/a-data-minimization-worksheet-for-creative-gifts-and-images-3k52</link>
      <guid>https://dev.to/winsonaibuilder/a-data-minimization-worksheet-for-creative-gifts-and-images-3k52</guid>
      <description>&lt;p&gt;A birthday song, a pet portrait, an emote, or an outfit mockup can start with a surprisingly sensitive set of inputs: faces, names, family stories, addresses, and draft files. Before choosing a tool, decide what the project actually needs. This worksheet helps you make that decision without assuming that every tool has the same privacy model.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Make a three-column inventory
&lt;/h2&gt;

&lt;p&gt;Write down each input before uploading or sharing it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Needed for the output?&lt;/th&gt;
&lt;th&gt;Less revealing alternative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Person's full name&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;First name, nickname, or no name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A face photo&lt;/td&gt;
&lt;td&gt;Only for an image or video based on that person&lt;/td&gt;
&lt;td&gt;A consented image with the background cropped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A family story&lt;/td&gt;
&lt;td&gt;A specific detail may help&lt;/td&gt;
&lt;td&gt;A general memory without a date, address, or other people's names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pet photo&lt;/td&gt;
&lt;td&gt;Only if a pet's appearance matters&lt;/td&gt;
&lt;td&gt;A picture without a visible address tag or location clues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finished file&lt;/td&gt;
&lt;td&gt;Needed to deliver&lt;/td&gt;
&lt;td&gt;A copy with a neutral filename&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep only what is needed. Cropping a background can remove incidental information, but it is not a guarantee of anonymity: a face, clothing, reflection, or unique setting may still identify someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ask the people represented
&lt;/h2&gt;

&lt;p&gt;Get permission from the person whose image or story you use, and ask separately before publishing or sharing outside the intended recipient. If a photo includes someone else, crop them out or obtain their permission too. For children or anyone unable to consent independently, a parent or guardian should make the decision. Avoid using a person's image to imply an endorsement they did not give.&lt;/p&gt;

&lt;p&gt;A practical note to send before creating: “I'd like to use this photo to make a private gift. Are you comfortable with that, and may I share the finished result with anyone else?” A yes to a private gift is not a yes to a public post.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check where processing happens
&lt;/h2&gt;

&lt;p&gt;Before entering personal material, look for a product's current privacy policy and retention settings. Check whether processing is local or remote, whether uploads are stored, whether deletion is available, whether material can be used for training, and whether the output can be made public. If any answer is unclear and the material is sensitive, use a less revealing input or choose a different workflow.&lt;/p&gt;

&lt;p&gt;Two of my browser-based utilities explicitly say the image operation runs on the device: &lt;a href="https://emoteresize.com/" rel="noopener noreferrer"&gt;Emote Resizer&lt;/a&gt; resizes emotes, and &lt;a href="https://freeimagemerger.com/" rel="noopener noreferrer"&gt;Free Image Merger&lt;/a&gt; arranges images into a composite. Local processing of images does not mean the page makes no network requests at all; check the site's own current disclosures.&lt;/p&gt;

&lt;p&gt;For a personalized &lt;a href="https://songifted.com/" rel="noopener noreferrer"&gt;Songifted&lt;/a&gt; song or &lt;a href="https://makeitrap.com/" rel="noopener noreferrer"&gt;MakeItRap&lt;/a&gt; video, write the brief with a nickname and a non-sensitive memory first. For a &lt;a href="https://pawmotif.com/" rel="noopener noreferrer"&gt;PawMotif&lt;/a&gt; pet portrait or &lt;a href="https://tryonswap.com/" rel="noopener noreferrer"&gt;TryOnSwap&lt;/a&gt; outfit concept, check the image and sharing permissions first. For these products, review their own current policies and settings before sending any personal information; do not assume they process locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keep the delivery small
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check the export on your own device first: names, faces, captions, and backgrounds.&lt;/li&gt;
&lt;li&gt;Give the file a neutral name such as &lt;code&gt;gift-final.mp4&lt;/code&gt; rather than a full name and date of birth.&lt;/li&gt;
&lt;li&gt;Choose a recipient-specific delivery method and review whether its link is public, expires, or can be forwarded.&lt;/li&gt;
&lt;li&gt;Ask the recipient before using the result as a testimonial or public example.&lt;/li&gt;
&lt;li&gt;Delete unnecessary source copies from your own devices and services when you no longer need them. Check each provider's actual deletion controls and retention terms; deleting your local copy does not delete a provider's copy.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Copyable decision card
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project:
People or pets represented:
Who agreed to the intended use?
Inputs strictly needed:
Inputs removed or generalized:
Where the tool says processing happens:
Provider retention/deletion terms checked on:
Who may receive the output?
Public sharing separately approved? yes / no
Source copies to remove and where:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I build and operate the six products linked above. This is a practical decision aid, not a claim that every service has identical privacy or retention practices. This article was prepared with AI assistance; verify each service's current terms before use.&lt;/p&gt;

</description>
      <category>privacy</category>
    </item>
    <item>
      <title>Build a local file manifest for creative project handoffs in Python</title>
      <dc:creator>Winson Yang</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:09:37 +0000</pubDate>
      <link>https://dev.to/winsonaibuilder/build-a-local-file-manifest-for-creative-project-handoffs-in-python-2o60</link>
      <guid>https://dev.to/winsonaibuilder/build-a-local-file-manifest-for-creative-project-handoffs-in-python-2o60</guid>
      <description>&lt;p&gt;A creative project can produce several small files that look almost identical: a source image, exports for different chat placements, a comparison grid, an outfit concept and a song draft. Before handing them off, it helps to know exactly which files you are sending and whether a file changed after review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I build Emote Resizer, Free Image Merger, TryOnSwap and Songifted, which are mentioned as examples below. This article was drafted with AI assistance and is my own educational resource, not an independent review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a manifest can and cannot do
&lt;/h2&gt;

&lt;p&gt;A manifest lists a file's relative path, byte length and SHA-256 digest. Comparing two manifests helps detect changes in file content; it does not prove the content is correct, licensed, safe, accessible or approved. A hash also does not hide a sensitive file name. Store and share the manifest only with people who may see the file names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example folder structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;creative-project/
  sources/
    original.png
  exports/
    emote-small.png
    image-comparison.png
    outfit-concept.png
  music/
    approved-lyrics.txt
    preview-audio.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use only files that belong to the actual project; the names above are examples. Emote exports might come from &lt;a href="https://emoteresize.com/" rel="noopener noreferrer"&gt;Emote Resizer&lt;/a&gt; and a comparison image from &lt;a href="https://freeimagemerger.com/" rel="noopener noreferrer"&gt;Free Image Merger&lt;/a&gt;. A fashion concept can come from &lt;a href="https://tryonswap.com/" rel="noopener noreferrer"&gt;TryOnSwap&lt;/a&gt;, and a lyrics-and-audio gift from &lt;a href="https://songifted.com/" rel="noopener noreferrer"&gt;Songifted&lt;/a&gt;. These tools have different functions, preview terms and prices; a manifest does not change those terms. Keep private or unapproved images out of the folder you plan to share.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate an inventory locally
&lt;/h2&gt;

&lt;p&gt;Save the following as &lt;code&gt;manifest.py&lt;/code&gt;. It uses only the Python standard library and reads files without changing them. It skips symbolic links, reads in chunks, and prints sorted relative paths, byte sizes and hashes as JSON. Do not modify files while it runs; like any simple scan, it is not an atomic snapshot.&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="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Create a read-only SHA-256 inventory of regular files in a project folder.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&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;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&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;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_symlink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;path&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;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;stream&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;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_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;sha256&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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;is_dir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Usage: python3 manifest.py PROJECT_FOLDER&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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;resolve&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it from a terminal, replacing the paths with your own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 manifest.py /path/to/creative-project &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /path/outside-project/manifest.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the output &lt;strong&gt;outside&lt;/strong&gt; the scanned folder. Shell redirection creates the output file before the script starts; placing it inside the folder can cause the script to include its own incomplete output. If a file cannot be read, the program stops with an error rather than silently omitting it. Correct the permission or remove the file from the handoff folder and run again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review and compare
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the files in the folder and check names, spelling, dimensions, consent, visual details and playback. Keep an approved reference copy.&lt;/li&gt;
&lt;li&gt;Generate a manifest after the review. If you later receive or copy the files, generate another manifest from the received folder and compare the &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;bytes&lt;/code&gt; and &lt;code&gt;sha256&lt;/code&gt; values. Different hashes indicate different bytes; the same hash is a useful integrity check, not a substitute for reviewing content.&lt;/li&gt;
&lt;li&gt;Keep the source and export separate. Avoid committing sensitive images, lyrics, manifests or private names to a public repository.&lt;/li&gt;
&lt;li&gt;Check the target service's current specifications and purchase conditions before promising a format or a free download. This sample script does not upload anything or alter product sites.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Making a stream emote easier to recognize</title>
      <dc:creator>Winson Yang</dc:creator>
      <pubDate>Wed, 28 Oct 2026 10:46:37 +0000</pubDate>
      <link>https://dev.to/winsonaibuilder/making-a-stream-emote-easier-to-recognize-2m7n</link>
      <guid>https://dev.to/winsonaibuilder/making-a-stream-emote-easier-to-recognize-2m7n</guid>
      <description>&lt;p&gt;A tiny emote has to communicate quickly. A few accessibility checks can make a set easier to understand for more people, even when the platform displays it at a small size or the viewer cannot distinguish every color.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I build &lt;a href="https://emoteresize.com/" rel="noopener noreferrer"&gt;Emote Resizer&lt;/a&gt;, mentioned below. This is my own practical guide, not an independent product review. This article was prepared with AI assistance and reviewed for accuracy before publication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give every emote a distinct job
&lt;/h2&gt;

&lt;p&gt;List the reaction behind each design before drawing: celebrate, thank, surprise, concern and so on. If two designs both mean “happy,” decide whether viewers need both. Ask a reviewer who has not seen the names to describe each image in a few words. Confusion between two designs is useful feedback, not a failed test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use shape as well as color
&lt;/h2&gt;

&lt;p&gt;Do not make color the only difference between two expressions. Change the mouth shape, eyebrows, pose or a simple symbol so the reaction remains recognizable in grayscale. This does not guarantee every viewer will distinguish the icons, but it gives them another cue when colors look similar or the screen is dim.&lt;/p&gt;

&lt;p&gt;Try this quick review:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Put the designs on both a light and a dark background.&lt;/li&gt;
&lt;li&gt;Look at them at the approximate size the destination displays.&lt;/li&gt;
&lt;li&gt;Check them in grayscale or with reduced saturation.&lt;/li&gt;
&lt;li&gt;Ask someone unfamiliar with the set what each one conveys.&lt;/li&gt;
&lt;li&gt;Revise whichever pair is consistently confused, then test again.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Name and explain the set
&lt;/h2&gt;

&lt;p&gt;Use short, recognizable names that describe the expression rather than an inside joke only the creator understands. Where the platform supports an accessible label or description, check it before publishing. If it does not, explain the meaning of custom emotes in a channel guide or a pinned introduction, and provide an equivalent in words when an emote carries important information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the real export
&lt;/h2&gt;

&lt;p&gt;Small previews can reveal problems the large artwork hides. Make a separate copy for the destination, verify its current dimensions and file rules, then open the exported image independently. &lt;a href="https://emoteresize.com/" rel="noopener noreferrer"&gt;Emote Resizer&lt;/a&gt; can create smaller versions in a browser; inspect the output before uploading, especially the facial expression, edges and any transparent areas.&lt;/p&gt;

&lt;p&gt;Keep the editable originals and record which version was uploaded. If a viewer points out that two reactions are hard to distinguish, you can revise the source rather than trying to repair a tiny export.&lt;/p&gt;

</description>
      <category>a11y</category>
    </item>
    <item>
      <title>A practical browser checklist for testing small transparent images</title>
      <dc:creator>Winson Yang</dc:creator>
      <pubDate>Wed, 28 Oct 2026 09:40:40 +0000</pubDate>
      <link>https://dev.to/winsonaibuilder/a-practical-browser-checklist-for-testing-small-transparent-images-ic7</link>
      <guid>https://dev.to/winsonaibuilder/a-practical-browser-checklist-for-testing-small-transparent-images-ic7</guid>
      <description>&lt;p&gt;Small icons and emotes often look fine in a design file but lose detail on a busy page. A useful test is to review the exported asset at its final display size, on both light and dark surfaces, before sharing it. This checklist works with any image editor and a modern browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Keep a source and an export
&lt;/h2&gt;

&lt;p&gt;Keep the original artwork separate from the version you resize. Export a PNG when you need transparency, and use a clear filename such as &lt;code&gt;wave-32.png&lt;/code&gt;. Check the target platform's current size and file rules before exporting; different products have different limits. Avoid repeatedly resizing the same already-small image, because each resampling pass can remove detail.&lt;/p&gt;

&lt;p&gt;If you need a quick set of emote sizes, &lt;a href="https://emoteresize.com/" rel="noopener noreferrer"&gt;Emote Resizer&lt;/a&gt; is one option. I build it, so this is a product mention rather than an independent endorsement. Check the downloaded files against the destination's current requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Test on contrasting backgrounds
&lt;/h2&gt;

&lt;p&gt;Put each asset on white, near-black and a mid-tone background. Look for pale halos at the edges, accidental opaque rectangles, and important details that disappear at small sizes. A quick local HTML file can help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"samples"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"wave-32.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Waving hand"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"wave-32.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Waving hand"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"wave-32.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Waving hand"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.samples&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.samples&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.samples&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.light&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="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.dark&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="nx"&gt;#171717&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.mid&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="nx"&gt;#888&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the filename and description with your own. View the page at normal zoom and on the devices you expect people to use. A larger browser zoom is helpful for finding edge artifacts, but it cannot substitute for checking the real display size.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check dimensions and meaning
&lt;/h2&gt;

&lt;p&gt;Open the image in a browser or image viewer and check its actual pixel dimensions, not just the CSS display dimensions. If the image conveys information, give it a useful alternative description. If it is purely decorative next to equivalent text, an empty &lt;code&gt;alt&lt;/code&gt; can prevent repeated announcements. For an emote picker, make sure the control itself has an understandable name and can be reached with a keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Prepare a review sheet
&lt;/h2&gt;

&lt;p&gt;Place variants next to each other so a reviewer can compare them quickly. Label each variant and keep an untouched copy of the individual export; a contact sheet is for review, not a replacement for the required files. &lt;a href="https://freeimagemerger.com/" rel="noopener noreferrer"&gt;Free Image Merger&lt;/a&gt; can combine images in a browser. I also build this tool. Before using any service, check its current privacy information and avoid uploading sensitive artwork unless you are comfortable with the handling described there.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Final pass
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Compare each export with the source and check for unwanted cropping.&lt;/li&gt;
&lt;li&gt;Test the smallest intended size against light and dark backgrounds.&lt;/li&gt;
&lt;li&gt;Confirm dimensions, transparency, filenames and any platform-specific limits.&lt;/li&gt;
&lt;li&gt;Keep the original files so you can revise without degrading a small export.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is a repeatable visual check, not a guarantee that an image will pass every platform's review.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Five web app workflows: local image tools, AI visuals and creative downloads</title>
      <dc:creator>Winson Yang</dc:creator>
      <pubDate>Wed, 14 Oct 2026 01:37:48 +0000</pubDate>
      <link>https://dev.to/winsonaibuilder/five-web-app-workflows-local-image-tools-ai-visuals-and-creative-downloads-pd</link>
      <guid>https://dev.to/winsonaibuilder/five-web-app-workflows-local-image-tools-ai-visuals-and-creative-downloads-pd</guid>
      <description>&lt;p&gt;I build five small web products: two tools for working with existing images, one for fashion visuals, one for personalized songs and one for digital pet portraits. Their interfaces use familiar words such as &lt;em&gt;upload&lt;/em&gt;, &lt;em&gt;preview&lt;/em&gt; and &lt;em&gt;download&lt;/em&gt;, but those words refer to different workflows.&lt;/p&gt;

&lt;p&gt;This walkthrough explains those differences using the products' current public behavior. It also gives readers a short checklist for evaluating an output before using or purchasing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start by identifying the operation
&lt;/h2&gt;

&lt;p&gt;Before choosing a tool, ask whether you need to transform an existing asset, combine several assets or generate something new. That distinction affects the input, the processing step and the output you should inspect.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Starting point&lt;/th&gt;
&lt;th&gt;Result to inspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Community graphics&lt;/td&gt;
&lt;td&gt;Existing artwork&lt;/td&gt;
&lt;td&gt;Resized emote, badge or sticker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image layout&lt;/td&gt;
&lt;td&gt;Several existing images&lt;/td&gt;
&lt;td&gt;One combined image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fashion visual&lt;/td&gt;
&lt;td&gt;Model photo and garment references&lt;/td&gt;
&lt;td&gt;Generated clothing and scene details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personalized song&lt;/td&gt;
&lt;td&gt;Name, memories and an occasion&lt;/td&gt;
&lt;td&gt;Lyrics, then an audio preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pet portrait&lt;/td&gt;
&lt;td&gt;A pet photo and a style&lt;/td&gt;
&lt;td&gt;A portrait preview, then a digital PNG&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Two browser tools for existing images
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://emoteresize.com/" rel="noopener noreferrer"&gt;Emote Resizer&lt;/a&gt; prepares emotes, badges, emoji and stickers for online communities. It provides platform presets, PNG and GIF workflows, generated file sizes and over-limit warnings. The resizing takes place locally in the browser. The review step is practical: check the dimensions, animation and file size against the receiving platform's requirements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://freeimagemerger.com/" rel="noopener noreferrer"&gt;Free Image Merger&lt;/a&gt; has a different purpose. It combines photos side by side, vertically, in a grid or in a freestyle layout. Processing is local to the browser, with PNG, JPG and WebP export and no signup requirement. Here the review step is the composition: order, layout, spacing and whether the final combined image contains what you intended.&lt;/p&gt;

&lt;p&gt;Both workflows start with assets you already have. They transform or arrange those inputs rather than generate new artwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reference-based fashion workflow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tryonswap.com/" rel="noopener noreferrer"&gt;TryOnSwap&lt;/a&gt; starts with a model photo and garment images. Its studio supports changing one garment, assembling an outfit and creating a fashion scene. Uploading references requires signing in; generation uses credits, with introductory credits after email verification and credit packs without a subscription.&lt;/p&gt;

&lt;p&gt;The useful review questions are different from a resize operation. Are the garment details consistent? Is the model consistent? Does the scene match the intended presentation? A generated fashion visual is a creative concept, not a measurement of clothing size or fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two creative download workflows
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://songifted.com/" rel="noopener noreferrer"&gt;Songifted&lt;/a&gt; separates a personalized song into stages. You supply the recipient's name, occasion and memories, review the lyrics before singing, then listen to a free 45-second preview. Purchasing the full track is a later decision. Name spelling and personal details are worth checking while the output is still text; the preview then helps evaluate the musical direction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pawmotif.com/" rel="noopener noreferrer"&gt;PawMotif&lt;/a&gt; starts with a pet photo and a selected style. Users inspect a free portrait preview before deciding whether to buy the finished digital PNG. The delivery format matters: it is a digital download. Gallery frames are presentation mockups, so a physical print or frame requires a separate plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small review checklist
&lt;/h2&gt;

&lt;p&gt;Before exporting, publishing or purchasing an output, check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The input:&lt;/strong&gt; do you have the artwork, photo, garment reference or personal details the workflow actually needs?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The operation:&lt;/strong&gt; are you resizing, arranging or generating?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The boundary:&lt;/strong&gt; does processing stay in the browser, or does the workflow require an account and uploaded references?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The preview:&lt;/strong&gt; are you looking at dimensions and file size, image details, lyrics or an audio excerpt?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The delivery:&lt;/strong&gt; is the result an image file, a full song or a digital portrait, and is a purchase required for that result?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A successful export or a visible preview is the start of this review step. The final asset should be inspected for its intended use.&lt;/p&gt;

&lt;p&gt;I am the builder of the products linked here. This is a product walkthrough based on their current website descriptions.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>product</category>
      <category>tools</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My $200/month AI tool audit: 6 subscriptions down to 2</title>
      <dc:creator>Winson Yang</dc:creator>
      <pubDate>Sat, 21 Mar 2026 10:12:33 +0000</pubDate>
      <link>https://dev.to/winsonaibuilder/my-200month-ai-tool-audit-6-subscriptions-down-to-2-p24</link>
      <guid>https://dev.to/winsonaibuilder/my-200month-ai-tool-audit-6-subscriptions-down-to-2-p24</guid>
      <description>&lt;p&gt;I'm a solo founder and I was subscribing to 6 AI tools: Claude, ChatGPT, Gemini, Manus, YouMind, and Spokenly. About $200/month total.&lt;/p&gt;

&lt;p&gt;One day I realized I was spending more time deciding which AI to open than actually doing the work. So I did an audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit
&lt;/h2&gt;

&lt;p&gt;For one week, I tracked every time I opened an AI tool and wrote down what I used it for. The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude: ~80% of everything&lt;/li&gt;
&lt;li&gt;ChatGPT: ~10% (mostly quick brainstorming)&lt;/li&gt;
&lt;li&gt;The other 4: splitting the remaining 10%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was paying for 6 tools but really only needed 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened after I cancelled 4
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Monthly cost: $200 → $40&lt;/li&gt;
&lt;li&gt;Productivity: went UP, not down&lt;/li&gt;
&lt;li&gt;The biggest gain wasn't the money — it was not having to think "which AI should I use for this?" every single time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you only have 2 tools, you actually learn them properly. You build real workflows instead of jumping between 5 different interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  I built a tool for this
&lt;/h2&gt;

&lt;p&gt;I figured other solopreneurs probably have the same problem, so I built a free quiz: &lt;a href="https://ai-stack-finder-app.vercel.app" rel="noopener noreferrer"&gt;AI Stack Finder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5 questions, takes 2 minutes. It tells you which 2-3 AI tools you actually need based on your role and tasks, and how much you could save by cancelling the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech stack&lt;/strong&gt;: Next.js, Supabase, Vercel. The recommendation engine uses a greedy set-cover algorithm to find the minimum set of tools that covers all your tasks.&lt;/p&gt;

&lt;p&gt;Would love to hear: how many AI subscriptions are you running right now? Have you done an audit like this?&lt;/p&gt;

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