<?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: David Miller</title>
    <description>The latest articles on DEV Community by David Miller (@morphio).</description>
    <link>https://dev.to/morphio</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%2F4179400%2F09c81e5a-b04c-4ad5-bc95-3aaba5e95a9f.png</url>
      <title>DEV Community: David Miller</title>
      <link>https://dev.to/morphio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/morphio"/>
    <language>en</language>
    <item>
      <title>What PNG's row filters actually do</title>
      <dc:creator>David Miller</dc:creator>
      <pubDate>Wed, 14 Oct 2026 15:43:06 +0000</pubDate>
      <link>https://dev.to/morphio/what-pngs-row-filters-actually-do-53a8</link>
      <guid>https://dev.to/morphio/what-pngs-row-filters-actually-do-53a8</guid>
      <description>&lt;p&gt;PNG preserves its pixels through a sequence of reversible steps. Before compression, it can transform each row of image bytes so that the compressor has a more predictable pattern to work with. PNG calls that transformation a filter.&lt;/p&gt;

&lt;p&gt;Take one row of an 8-bit grayscale image with byte values 100, 102, and 103. A filter called Sub can represent that row as 100, 2, and 1: the first value, followed by differences from the value immediately to the left. The decoder reverses the process, adding each difference to the value it has already reconstructed.&lt;/p&gt;

&lt;p&gt;For a color image, the operation still works on bytes. Sub compares a byte with the corresponding byte of the previous pixel, such as red with red, rather than simply subtracting the byte directly before it. Another filter, Up, compares with the corresponding byte in the row above. PNG defines other prediction methods as well. Each row starts with a byte that tells the decoder which filter was used there.&lt;/p&gt;

&lt;p&gt;Filtering does not shorten the row on its own. The filtered bytes then go through DEFLATE compression. A flat background or a repeated graphic pattern often gives the compressor useful repetition. A noisy photograph is less predictable and can produce a large PNG, even though no image detail was lost during PNG encoding.&lt;/p&gt;

&lt;p&gt;The encoder can choose a different filter for each row. That is one reason two PNG files can decode to identical pixels yet have different file sizes. If you are exporting a screenshot with fine text, compare the displayed pixels and the file size after export. The filter affects the bytes on disk; it does not change what the PNG decoder reconstructs.&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How to verify a HEIC-to-JPEG conversion before upload</title>
      <dc:creator>David Miller</dc:creator>
      <pubDate>Tue, 13 Oct 2026 02:41:41 +0000</pubDate>
      <link>https://dev.to/morphio/how-to-verify-a-heic-to-jpeg-conversion-before-upload-3b3l</link>
      <guid>https://dev.to/morphio/how-to-verify-a-heic-to-jpeg-conversion-before-upload-3b3l</guid>
      <description>&lt;p&gt;An HEIC converter can return successfully while a browser preview or thumbnail service still refuses the result. The app may even have named the output &lt;code&gt;photo.jpg&lt;/code&gt;. Before upload, inspect the bytes that came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the result in the browser
&lt;/h2&gt;

&lt;p&gt;If your converter returns a &lt;code&gt;Blob&lt;/code&gt;, this function rejects an empty file, checks the first two bytes for a JPEG marker, and asks the browser to decode the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;inspectJpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&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;blob&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;blob&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;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The converter returned an empty file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&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;header&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="o"&gt;!==&lt;/span&gt; &lt;span class="mh"&gt;0xff&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;header&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="o"&gt;!==&lt;/span&gt; &lt;span class="mh"&gt;0xd8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The output does not start with a JPEG marker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&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;blob&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&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="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;image&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The decoded image has no dimensions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&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="nx"&gt;image&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="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;image&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="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;image&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The marker check rejects many wrong-format files, but those two bytes can also be present in a truncated JPEG. Decoding catches more failures. Even a file that passes both checks may have the wrong orientation or colors, or may have lost transparency or metadata during conversion. Check those properties with sample photos.&lt;/p&gt;

&lt;p&gt;The filename and &lt;code&gt;Blob.type&lt;/code&gt; are useful hints, but neither verifies the bytes. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob/type" rel="noopener noreferrer"&gt;MDN explains why &lt;code&gt;Blob.type&lt;/code&gt; cannot be the only validation check&lt;/a&gt;. The file input's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept" rel="noopener noreferrer"&gt;&lt;code&gt;accept&lt;/code&gt; attribute only guides the picker&lt;/a&gt;; it doesn't validate the selected file. Validate uploads again on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch for a canvas fallback
&lt;/h2&gt;

&lt;p&gt;A canvas conversion has one easy-to-miss case. If you call &lt;code&gt;canvas.toBlob(callback, "image/jpeg")&lt;/code&gt; and the browser doesn't support the requested format, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob" rel="noopener noreferrer"&gt;it exports PNG instead&lt;/a&gt;. If the app still gives that blob a .jpg name, the extension disagrees with the file. The check above catches that mismatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a few awkward test photos
&lt;/h2&gt;

&lt;p&gt;Use a portrait photo whose orientation matters and an image where a color shift would be visible. A multi-image HEIC and a damaged file are useful too. Check the decoded dimensions and look at the output. The smoke test won't tell you whether the chosen image, its colors, or its metadata match the original.&lt;/p&gt;

&lt;p&gt;For a manual comparison, you can try &lt;a href="https://morphio.online/heic-to-jpg/" rel="noopener noreferrer"&gt;Morphio's HEIC-to-JPG converter&lt;/a&gt; with the same samples. Morphio is my product; the browser check above works with any converter.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>testing</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
