<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Morphio Image Engineering]]></title><description><![CDATA[Morphio Image Engineering]]></description><link>https://morphio-images.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 02 Nov 2026 18:45:55 GMT</lastBuildDate><atom:link href="https://morphio-images.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Treat a saved canvas transform as a snapshot]]></title><description><![CDATA[A matrix returned by ctx.getTransform() is a snapshot. Later changes to the context do not update that object, and changing the object does not update the…]]></description><link>https://morphio-images.hashnode.dev/treat-a-saved-canvas-transform-as-a-snapshot</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/treat-a-saved-canvas-transform-as-a-snapshot</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Mon, 02 Nov 2026 00:59:23 GMT</pubDate><content:encoded><![CDATA[<p>A matrix returned by <code>ctx.getTransform()</code> is a snapshot. Later changes to the context do not update that object, and changing the object does not update the context.</p>
<p>Suppose a preview stores its transform before a temporary translation. Keep the saved matrix if you intend to apply that earlier transform again. This snippet assumes <code>ctx</code> is a valid 2D drawing context with the identity transform at the start:</p>
<pre><code class="language-js">const saved = ctx.getTransform();
ctx.translate(40, 0);
console.log(saved.e); // 0
console.log(ctx.getTransform().e); // 40
ctx.setTransform(saved);
</code></pre>
<p>The last line applies the stored matrix. The stored object affects the context only when you apply it. If you edit a matrix value, call <code>setTransform()</code> when you want the context to use it.</p>
<p>For a photo preview with several drawing stages, label the stored matrix by the stage it belongs to. A transform snapshot covers the matrix, not the entire drawing state. Make the image and annotation stages explicit about which transform they use.</p>
<p>Reference: <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getTransform">MDN getTransform</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Set image smoothing before drawing a scaled canvas image]]></title><description><![CDATA[Set ctx.imageSmoothingEnabled before you call drawImage(). To compare a different setting, draw the image again; the assignment does not change pixels…]]></description><link>https://morphio-images.hashnode.dev/set-image-smoothing-before-drawing-a-scaled-canvas-image</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/set-image-smoothing-before-drawing-a-scaled-canvas-image</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Sun, 01 Nov 2026 02:52:28 GMT</pubDate><content:encoded><![CDATA[<p>Set <code>ctx.imageSmoothingEnabled</code> before you call <code>drawImage()</code>. To compare a different setting, draw the image again; the assignment does not change pixels already drawn.</p>
<p>The default is <code>true</code>. For a pixel-art preview, you can set it to <code>false</code> before drawing an enlarged source. Compare the settings with the same source and destination dimensions.</p>
<p>For a temporary draw, call <code>ctx.save()</code>, assign the smoothing setting, draw the decoded image, and then call <code>ctx.restore()</code>. This keeps the changed setting from being carried into subsequent work that uses the restored state. Make sure restoration also happens if surrounding application code throws.</p>
<p>Inspect the canvas at the size used by the actual preview. A separately scaled page screenshot is another rendering step and should have its viewing conditions recorded. Keep the chosen setting beside the export record if the result is used for delivery.</p>
<p>I work on <a href="https://morphio.online/">Morphio</a> and write about browser image workflows. This note concerns Canvas drawing state, without claiming how any particular product implements resizing. Reference: <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled">MDN imageSmoothingEnabled</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Give each image upload attempt its own AbortController]]></title><description><![CDATA[Make image-upload cancellation belong to one attempt. A new attempt needs a fresh controller and a cancel control that still points to it. Keep the…]]></description><link>https://morphio-images.hashnode.dev/give-each-image-upload-attempt-its-own-abortcontroller</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/give-each-image-upload-attempt-its-own-abortcontroller</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Sat, 31 Oct 2026 10:35:05 GMT</pubDate><content:encoded><![CDATA[<p>Make image-upload cancellation belong to one attempt. A new attempt needs a fresh controller and a cancel control that still points to it.</p>
<p>Keep the controller with the job identifier. When the user cancels, call that job's <code>controller.abort()</code>. Do not use an older job's cancel control to stop a newer upload that now occupies the same result panel.</p>
<p>With no custom reason, aborting uses an <code>AbortError</code> reason. A canceled attempt should have its own interface state rather than being labeled as an accepted image. Start a retry with a new controller instead of reusing an already-aborted signal.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort">MDN abort() reference</a> describes cancellation of fetch and response consumption. Treat the server's saved state as a separate application question: a browser cancel action is not a deletion request to the upload service.</p>
<p>I work on Morphio, our image project at <a href="https://morphio.online/">https://morphio.online/</a>. These notes describe image preparation and review; they do not require a particular editing tool.</p>
]]></content:encoded></item><item><title><![CDATA[Wait for the upload response before saying an image was accepted]]></title><description><![CDATA[Wait for the server response before displaying image accepted. Upload progress describes the request body transfer, so keep its completion separate from…]]></description><link>https://morphio-images.hashnode.dev/wait-for-the-upload-response-before-saying-an-image-was-accepted</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/wait-for-the-upload-response-before-saying-an-image-was-accepted</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Fri, 30 Oct 2026 03:16:49 GMT</pubDate><content:encoded><![CDATA[<p>Wait for the server response before displaying image accepted. Upload progress describes the request body transfer, so keep its completion separate from the application result.</p>
<p>Use progress information to describe transferred bytes. When upload.load fires, move the interface to a waiting-for-response state if the request has not finished. Check the request's HTTP status and the response expected by the application before presenting an accepted result.</p>
<p>Report request failures and application rejections explicitly. A server can reject an image after receiving its bytes, so a progress bar reaching its end is insufficient evidence of acceptance.</p>
<p>Test that distinction with a sample the server deliberately rejects. Record the upload event and the final response separately, then check that the interface reports the rejection.</p>
<p>Source: <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/upload">MDN XMLHttpRequest.upload</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Close an ImageBitmap when its drawing work is finished]]></title><description><![CDATA[Close an ImageBitmap after the work that needs it has finished. Tie that cleanup to the bitmap's owner and account for drawing failures as well as success.]]></description><link>https://morphio-images.hashnode.dev/close-an-imagebitmap-when-its-drawing-work-is-finished</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/close-an-imagebitmap-when-its-drawing-work-is-finished</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Thu, 29 Oct 2026 02:32:02 GMT</pubDate><content:encoded><![CDATA[<p>Close an ImageBitmap after the work that needs it has finished. Tie that cleanup to the bitmap's owner and account for drawing failures as well as success.</p>
<p>For a bitmap used only for one canvas draw, a try/finally around the draw can make cleanup explicit. Create the bitmap before entering that block, draw it inside try, and call bitmap.close() in finally. Handle a failure to create the bitmap separately because no bitmap exists to close in that case.</p>
<p>If another operation still needs the same bitmap, the first draw is not its final use. Assign cleanup to one owner and close the bitmap after its required uses finish. Do not hand a closed bitmap to the next job.</p>
<p>Test the failure path as well as the successful draw. The cleanup path should run when drawing throws after a bitmap has been created.</p>
<p>Source: <a href="https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close">MDN ImageBitmap.close()</a>.</p>
<p>I work on <a href="https://morphio.online/">Morphio</a>, and this note is part of the image-workflow guidance I maintain.</p>
]]></content:encoded></item><item><title><![CDATA[Handle image decode rejection before replacing the preview]]></title><description><![CDATA[Put the preview replacement after a successful await of image.decode(). Keep a catch branch for decoding failure so the page does not report success for an…]]></description><link>https://morphio-images.hashnode.dev/handle-image-decode-rejection-before-replacing-the-preview</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/handle-image-decode-rejection-before-replacing-the-preview</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Wed, 28 Oct 2026 02:25:41 GMT</pubDate><content:encoded><![CDATA[<p>Put the preview replacement after a successful await of image.decode(). Keep a catch branch for decoding failure so the page does not report success for an unusable image.</p>
<p>A small sequence is: create an Image, assign its src, then await image.decode() inside try/catch. Only replace the displayed image after that await succeeds. Show a failure state from the catch branch instead of leaving the interface with a success message.</p>
<p>Keep this check separate from ownership of the preview. If the user chooses another file while decoding is pending, confirm that the completed job still owns the result before changing the page. Decode success alone does not establish that this is the latest selection.</p>
<p>Test a deliberately invalid sample as well as a valid image. Preserve the earlier preview until the new one is ready when that behavior suits the interface. Document whether failure leaves the earlier picture visible.</p>
<p>Source: <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode">MDN HTMLImageElement.decode()</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Check the encoded type before naming a canvas export]]></title><description><![CDATA[Choose a canvas export's filename after checking the produced format. toBlob() exports PNG when the requested type is unsupported; naming only from the…]]></description><link>https://morphio-images.hashnode.dev/check-the-encoded-type-before-naming-a-canvas-export</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/check-the-encoded-type-before-naming-a-canvas-export</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Tue, 27 Oct 2026 04:17:38 GMT</pubDate><content:encoded><![CDATA[<p>Choose a canvas export's filename after checking the produced format. toBlob() exports PNG when the requested type is unsupported; naming only from the request can give that output the wrong extension.</p>
<p>Handle a null callback result before naming the output. Once a Blob exists, compare its reported type with the formats your application accepts. If the application requires a particular format, show a clear failure when the result differs instead of presenting the requested extension as confirmation.</p>
<p>Keep the requested type and the reported type separate in diagnostic notes. This makes a fallback visible when investigating a rejected upload. Do not treat a renamed extension as a conversion of the image data.</p>
<p>Open a generated sample in the destination that will receive the files before running a whole batch. An accepted type still needs to meet the destination's other requirements, including any dimensions or file size limits stated by that destination.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob">Reference: MDN toBlob()</a></p>
<p>Published by <a href="https://morphio.online/">Morphio</a> as part of our practical image workflow notes.</p>
]]></content:encoded></item><item><title><![CDATA[Ignore an older image job when a newer one owns the result panel]]></title><description><![CDATA[Check which image job owns the result panel before updating it. If the panel follows the latest started job, an earlier job must not overwrite it when it…]]></description><link>https://morphio-images.hashnode.dev/ignore-an-older-image-job-when-a-newer-one-owns-the-result-panel</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/ignore-an-older-image-job-when-a-newer-one-owns-the-result-panel</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Mon, 26 Oct 2026 00:51:39 GMT</pubDate><content:encoded><![CDATA[<p>Check which image job owns the result panel before updating it. If the panel follows the latest started job, an earlier job must not overwrite it when it finishes later.</p>
<p>Capture a sequence value at job start and compare it with the active value when asynchronous work finishes. An older job should leave the panel alone once a newer job owns it.</p>
<p>Apply the same ownership check to errors and progress messages. This only controls which job can update the interface; it does not cancel earlier work or release its resources. Define those cleanup steps separately.</p>
]]></content:encoded></item><item><title><![CDATA[Snapshot the file selection before starting a batch]]></title><description><![CDATA[Accept a file selection into its own array before starting image-batch work. The input exposes a FileList; the array gives later work a list belonging to…]]></description><link>https://morphio-images.hashnode.dev/snapshot-the-file-selection-before-starting-a-batch</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/snapshot-the-file-selection-before-starting-a-batch</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Sun, 25 Oct 2026 12:25:17 GMT</pubDate><content:encoded><![CDATA[<p>Accept a file selection into its own array before starting image-batch work. The input exposes a FileList; the array gives later work a list belonging to the accepted batch.</p>
<pre><code class="language-js">function takeSelection(input) {
  const files = Array.from(input.files ?? []);
  if (files.length === 0) return [];
  return files;
}
</code></pre>
<p>This copies references into an array. It does not duplicate the bytes, validate the images or preserve files in permanent storage.</p>
<p>Pass that array to the job you start. Decide separately whether a later selection replaces the active job or creates another one. Reading the current input again at each processing step can make that policy unclear.</p>
<p>Keep output records paired with the accepted list. If the user selects more files while work is running, label them as another selection rather than silently mixing them into the earlier batch.</p>
<p>I work on <a href="https://morphio.online/">Morphio</a>, an image conversion project. This is a proposed way to define batch ownership. It does not describe the project's current implementation.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList">Reference: MDN FileList</a></p>
]]></content:encoded></item><item><title><![CDATA[Check the encoded Blob against the upload budget]]></title><description><![CDATA[Wait for image encoding to finish, then compare the returned Blob with the upload budget. The selected quality value does not establish the output's exact…]]></description><link>https://morphio-images.hashnode.dev/check-the-encoded-blob-against-the-upload-budget</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/check-the-encoded-blob-against-the-upload-budget</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Sat, 24 Oct 2026 05:12:31 GMT</pubDate><content:encoded><![CDATA[<p>Wait for image encoding to finish, then compare the returned Blob with the upload budget. The selected quality value does not establish the output's exact byte size.</p>
<p><code>Blob.size</code> gives the number of bytes. Derive the budget from the destination's documented limit and make the unit conversion explicit.</p>
<pre><code class="language-js">function checkBudget(blob, maxBytes) {
  if (blob === null) throw new Error("No encoded image was returned");
  if (!Number.isSafeInteger(maxBytes) || maxBytes &lt; 1) {
    throw new RangeError("The byte budget must be a positive integer");
  }
  if (blob.size &gt; maxBytes) {
    throw new RangeError("Encoded image exceeds the byte budget");
  }
  return blob;
}
</code></pre>
<p>The input is an actual Blob, or null if the encoder reports failure that way. A passing result returns the same Blob. This helper checks size; it does not perform compression.</p>
<p>For example, 500000 as the budget means 500000 bytes. That is an illustrative number, not a stated limit for an upload service. Check what the destination means by the units it displays before converting its limit.</p>
<p>If the output is too large, report that and let the user revise the settings. Keep the source file so another export can start from it. Measure the replacement Blob too, since the previous result's size does not establish the new one's size.</p>
<p>A size check does not cover the destination's other file requirements. An image within the byte limit can still be rejected for another reason.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob/size">Reference: MDN Blob.size</a></p>
]]></content:encoded></item><item><title><![CDATA[Catch canvas export errors at the point where the export is awaited]]></title><description><![CDATA[Return a Promise from a canvas export helper, then catch failures where the caller awaits it. Update the interface only after that await succeeds. toBlob()…]]></description><link>https://morphio-images.hashnode.dev/catch-canvas-export-errors-at-the-point-where-the-export-is-awaited</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/catch-canvas-export-errors-at-the-point-where-the-export-is-awaited</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Fri, 23 Oct 2026 03:41:36 GMT</pubDate><content:encoded><![CDATA[<p>Return a Promise from a canvas export helper, then catch failures where the caller awaits it. Update the interface only after that await succeeds.</p>
<p><code>toBlob()</code> supplies its result through a callback. This wrapper turns a missing result into a rejection:</p>
<pre><code class="language-js">function exportPng(canvas) {
  return new Promise((resolve, reject) =&gt; {
    canvas.toBlob(blob =&gt; {
      if (blob) resolve(blob);
      else reject(new Error("Canvas encoding returned no image"));
    }, "image/png");
  });
}

async function prepareExport(canvas, report) {
  try {
    const blob = await exportPng(canvas);
    report({ state: "ready", bytes: blob.size });
    return blob;
  } catch (error) {
    report({ state: "failed", message: String(error) });
    return null;
  }
}
</code></pre>
<p>A synchronous exception thrown inside the Promise executor also rejects that Promise. The surrounding catch can therefore receive a synchronous <code>toBlob()</code> exception as well as the explicit rejection for a null callback result. It does not catch errors from unrelated work started elsewhere.</p>
<p>The example assumes <code>report</code> is a small UI update function that does not throw. Let the caller decide what to do with a null return before enabling download or upload controls.</p>
<p>I work on <a href="https://morphio.online/">Morphio</a>. In image export examples, I keep “requested,” “ready,” and “failed” states separate and update them as the export progresses.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob">Reference: MDN toBlob()</a></p>
]]></content:encoded></item><item><title><![CDATA[Wait for image exports with a loop that actually awaits them]]></title><description><![CDATA[An image export batch can report success too early if its work runs inside an async forEach callback. In an image export screen, that can make a success…]]></description><link>https://morphio-images.hashnode.dev/wait-for-image-exports-with-a-loop-that-actually-awaits-them</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/wait-for-image-exports-with-a-loop-that-actually-awaits-them</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Thu, 22 Oct 2026 02:02:33 GMT</pubDate><content:encoded><![CDATA[<p>An image export batch can report success too early if its work runs inside an async forEach callback. In an image export screen, that can make a success message appear while work is still running.</p>
<p>forEach does not wait for promises returned by its callback. Writing await in front of forEach does not change that, because forEach itself returns undefined.</p>
<p>For deliberately sequential work, use a loop and await each operation. The function below accepts the export operation as a dependency; it does not decode or encode any images itself.</p>
<pre><code class="language-js">async function exportInOrder(selectedFiles, exportFile) {
  const files = Array.from(selectedFiles);
  const results = [];
  for (const file of files) {
    const output = await exportFile(file);
    results.push({ name: file.name, output });
  }
  return results;
}
</code></pre>
<p>Await exportInOrder before showing that the whole batch succeeded. This version stops on the first thrown error or rejected promise. Handle that rejection in the caller, and decide whether a retry should restart the batch or resume from a saved result.</p>
<p>Sequential processing also means the next call begins only after the previous one settles. It does not automatically release memory held by the export function or by the results array. If output objects are large, decide how long the interface should keep them.</p>
<p>A parallel implementation needs a different completion strategy and a deliberate concurrency limit. Choose a loop that matches the order and failure behavior promised by the interface.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach">Reference: MDN</a></p>
]]></content:encoded></item><item><title><![CDATA[Keep successful image results when one file in a batch fails]]></title><description><![CDATA[If one photo in a batch is damaged, keep the successful files available. Report the failure against that photo so the user can retry it without losing the…]]></description><link>https://morphio-images.hashnode.dev/keep-successful-image-results-when-one-file-in-a-batch-fails</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/keep-successful-image-results-when-one-file-in-a-batch-fails</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Wed, 21 Oct 2026 07:09:34 GMT</pubDate><content:encoded><![CDATA[<p>If one photo in a batch is damaged, keep the successful files available. Report the failure against that photo so the user can retry it without losing the other results.</p>
<p>Promise.allSettled() is useful when each task can succeed independently. Its result array follows input order, even when the tasks finish in another order. That lets you pair each outcome with the original file without relying on completion timing.</p>
<p>Copy the input list before starting. Later outcomes must be paired with that same snapshot. If the user changes the selection during processing, a newer list may attach a result to the wrong filename.</p>
<p>The example accepts processFile as a dependency. It does not implement a decoder or converter. Wrapping the call in a promise callback also turns a synchronous throw from that function into a rejected outcome.</p>
<p>Give each failed file a short, useful error message. Keep detailed diagnostics where they belong, since raw decoder exceptions may expose internal details in the interface.</p>
<p>This pattern starts every task in the small batch immediately. It does not limit concurrency or memory use. Large photos need a separate queue or deliberately small batches. Keep successful results available for download while offering a retry only for failed files. A failure count should come from these settled outcomes, not from how many progress callbacks happened to run.</p>
<pre><code class="language-js">async function settleImageBatch(selectedFiles, processFile) {
  const files = Array.from(selectedFiles);
  const results = await Promise.allSettled(
    files.map(file =&gt; Promise.resolve().then(() =&gt; processFile(file)))
  );
  return results.map((result, index) =&gt; ({
    file: files[index],
    ...result
  }));
}
</code></pre>
<p>Reference: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled">MDN Promise.allSettled()</a></p>
<p>I work on <a href="https://morphio.online/">Morphio</a>, a HEIC conversion project. The workflow advice here concerns preparing and checking image files.</p>
]]></content:encoded></item><item><title><![CDATA[Use currentSrc to find out which responsive image the browser selected]]></title><description><![CDATA[An image element can display a different file from the one written in its src attribute. A page with responsive image candidates gives the browser several…]]></description><link>https://morphio-images.hashnode.dev/use-currentsrc-to-find-out-which-responsive-image-the-browser-selected</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/use-currentsrc-to-find-out-which-responsive-image-the-browser-selected</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Tue, 20 Oct 2026 02:40:42 GMT</pubDate><content:encoded><![CDATA[<p>An image element can display a different file from the one written in its src attribute. A page with responsive image candidates gives the browser several choices. Reading src in a debugging session can therefore send you to the fallback file while the visible photograph came from another candidate.</p>
<p>Inspect currentSrc on the image element to see the selected resource's full URL. This helps identify the file behind a soft-looking card or an unexpectedly large download. Read it after the browser has selected a resource.</p>
<h2>Separate the candidate list from the result</h2>
<p>The srcset attribute describes available resources. With width descriptors, sizes describes the expected image slot under different layout conditions. These values help the browser choose a candidate; they do not give you a reliable log of what it actually chose on a particular device.</p>
<p>Consider a hypothetical card displayed in a narrow column on a desktop page. The source markup might list several widths. If sizes describes the card as spanning the full viewport, it gives the browser a misleading estimate of the slot. A large candidate can be reasonable for that estimate even when the rendered card is much narrower.</p>
<p>Record the card's rendered width, viewport width, and device pixel ratio beside currentSrc. Those details let someone else reproduce the selection. The URL alone leaves out the conditions that made a particular candidate useful.</p>
<h2>A selected URL does not prove a successful load</h2>
<p>A resource URL can be selected even when the resource fails to load. currentSrc identifies the choice; it does not certify that the response contained a usable image. Check the rendered result and the browser's network information when investigating a broken picture.</p>
<p>Avoid treating the complete property as a success flag on its own. It can be true for a broken image as well as a finished one. A decoding check or the element's error handling provides information that a selected URL cannot supply. Keep those checks separate so an error report says what actually failed.</p>
<p>If the page uses a picture element, inspect its source elements too. Media conditions and supported types can change the applicable candidate set. The fallback img still provides the element whose currentSrc you inspect, but looking only at its srcset may miss the relevant source declaration.</p>
<h2>Change one condition at a time</h2>
<p>Begin with a known viewport and a fresh load, then change the width and inspect the selection again. A smaller window need not make the browser immediately replace an image already downloaded. Resource selection can reuse what is available.</p>
<p>A useful comparison records the selected URL, the card's displayed width, and whether the picture looks acceptable at that width. Also check the candidate files themselves. Two filenames that suggest different widths may point to incorrectly generated copies, so markup inspection alone cannot validate the asset pipeline.</p>
<p>When the wrong file is selected, correct the candidate descriptors or the slot estimate that led to the choice. When the correct file still looks poor, inspect that export's dimensions and content. currentSrc helps locate the file under examination; it does not tell you which compression setting to use. Save those observations with the layout conditions before changing the image exports.</p>
]]></content:encoded></item><item><title><![CDATA[Why a smaller JPEG can still be the wrong export]]></title><description><![CDATA[Suppose an upload box accepts a photograph but the result looks poor. The export's lower byte count looks encouraging, yet an important label in the…]]></description><link>https://morphio-images.hashnode.dev/why-a-smaller-jpeg-can-still-be-the-wrong-export</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/why-a-smaller-jpeg-can-still-be-the-wrong-export</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Mon, 19 Oct 2026 04:14:05 GMT</pubDate><content:encoded><![CDATA[<p>Suppose an upload box accepts a photograph but the result looks poor. The export's lower byte count looks encouraging, yet an important label in the picture may now be unreadable. File size tells you how many bytes you will send. It does not tell you whether the picture has enough pixels for its destination or whether the compression has damaged an important detail.</p>
<p>Width, quality, and composition can each change during export. Pixel dimensions describe the sample grid. JPEG quality influences how the encoder represents that grid. Cropping decides which part of the scene remains. You can make a file smaller through any of these choices, with different consequences for the finished picture.</p>
<h2>Write down the destination first</h2>
<p>Imagine a photograph that will appear in a 900-pixel-wide article column. A 600-pixel-wide export has fewer horizontal samples than that display width. Enlarging it may reveal softness. A 3000-pixel-wide export contains more samples, but may carry more data than this particular placement needs.</p>
<p>Those figures are a worked example, not a universal export recommendation. A publication may provide a larger source for high-density displays or allow readers to open the original. Use the destination's actual requirements rather than copying a width from another website.</p>
<p>Start by recording what the upload form requires and what the page displays. Keep those two requirements separate. A form may accept a much larger image than its layout needs, and an accepted upload may still be cropped after submission.</p>
<h2>Compare two changes separately</h2>
<p>Take one source copy and make two experimental exports. For the first, keep the pixel dimensions unchanged and lower the JPEG quality setting. For the second, keep a chosen quality setting and reduce the dimensions. Give the files names that identify the change so that the comparison remains readable later.</p>
<p>Open both results at the size readers will see. Then inspect a detail that matters to the photograph. A face, a product label, or fine branches can reveal different problems. Reduced dimensions remove spatial samples. Stronger JPEG compression can add visible artifacts even when the width and height remain unchanged.</p>
<p>Avoid reading the quality slider as a promised percentage reduction in bytes. Encoders and source images differ. A setting of 0.8 does not mean that the result will contain 80 percent of the source file's bytes, or retain a measurable 80 percent of its visual quality.</p>
<h2>Save the reason for choosing this file</h2>
<p>Record the destination dimensions, file bytes, and the visible reason for choosing one export. A short note such as "label readable at article width" gives the decision more context than "smallest file." If neither sample works, adjust the export and inspect again rather than accepting the lower byte count by default.</p>
<p>I work on <a href="https://morphio.online/">Morphio</a>, which focuses on image conversion. The useful handoff from a conversion workflow is a file that fits its intended use, with an original still available if the destination changes. The chosen file needs enough detail for the intended placement while meeting its transfer requirements.</p>
<p>Keep the source outside the experiment folder and avoid repeated JPEG saves of the same working copy. When you need another comparison, return to that source. This makes it easier to tell whether the new settings helped, rather than comparing two files with different histories of editing and compression.</p>
<p>Reference: <a href="https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types">MDN's image format guide</a>.</p>
]]></content:encoded></item><item><title><![CDATA[A visible remote image can still block canvas export]]></title><description><![CDATA[An image loads in a page. Drawing it onto a canvas also appears to work. Then the export throws a SecurityError. A visible picture does not establish that…]]></description><link>https://morphio-images.hashnode.dev/a-visible-remote-image-can-still-block-canvas-export</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/a-visible-remote-image-can-still-block-canvas-export</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Sun, 18 Oct 2026 10:04:35 GMT</pubDate><content:encoded><![CDATA[<p>An image loads in a page. Drawing it onto a canvas also appears to work. Then the export throws a <code>SecurityError</code>. A visible picture does not establish that the application can read its pixels back.</p>
<p>Browsers place separate restrictions on reading a remote image through a canvas. When an image from another origin is drawn without the required CORS permission, the canvas becomes tainted. MDN explains the restrictions in its <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image">cross-origin image guide</a>.</p>
<h2>Check the request and the response</h2>
<p>For a public image that does not need credentials, an application can request it in anonymous CORS mode:</p>
<pre><code class="language-js">const image = new Image();
image.crossOrigin = "anonymous";
image.src = imageUrl;
await image.decode();
</code></pre>
<p>Set <code>crossOrigin</code> before assigning <code>src</code>. The image server must also return an appropriate <code>Access-Control-Allow-Origin</code> header. Changing the JavaScript alone does not grant access to a server that has not permitted it.</p>
<p>If the server allows ordinary embedding but does not allow CORS access, this modified request can fail to load. Record that load failure: the application requested CORS access, and the server did not provide the response needed for it.</p>
<h2>Keep the experiment small</h2>
<p>Start with a new canvas and draw one image. Try the export immediately. Do not include a remote watermark, background texture, or another source yet. Otherwise, a permitted photograph can be blamed for a restriction introduced by a different image drawn later.</p>
<p>Compare that result with a same-origin test asset. If the same export function works for the local asset but fails after drawing the remote image, inspect the remote request in the browser's network panel. Record the final resource URL and response headers. A redirect can make the final origin different from the one suggested by the URL initially supplied to the application.</p>
<p>Do not try to repair the situation by changing a <code>.png</code> suffix to <code>.jpg</code>, lowering quality, or using a different canvas export method. Those changes do not supply the missing permission. A production error message should explain which source could not be exported, rather than reporting that the image format is unsupported.</p>
<h2>Decide which sources the feature supports</h2>
<p>If users supply their own files through a file picker, keep that workflow separate from the feature that imports arbitrary remote URLs. They have different failure cases. A conversion interface that mixes both paths behind one generic error makes troubleshooting unnecessarily difficult.</p>
<p>If the application controls the remote server, configure and test its CORS response for the intended callers. If it does not control that server, offer an authorized file-upload path or state that the particular URL cannot be exported. A server-side import feature needs an explicit design for access and resource limits. Keep that decision separate from the browser example.</p>
<p>For a bug report, a useful record includes the source origin, request mode, returned CORS header, and the operation that failed. The fact that the image was visible belongs in that report too, but visibility alone does not establish permission to export the canvas.</p>
<p>Keep this record with the failing example. The next person investigating the export can compare the request and response directly, without first having to reproduce every step of the larger editor interface.</p>
]]></content:encoded></item><item><title><![CDATA[A file picker filter does not validate an image]]></title><description><![CDATA[The HTML accept attribute helps the file picker guide people towards suitable inputs. A form that requests JPEG and PNG can still receive an unsuitable…]]></description><link>https://morphio-images.hashnode.dev/a-file-picker-filter-does-not-validate-an-image</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/a-file-picker-filter-does-not-validate-an-image</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Sat, 17 Oct 2026 05:15:39 GMT</pubDate><content:encoded><![CDATA[<p>The HTML <code>accept</code> attribute helps the file picker guide people towards suitable inputs. A form that requests JPEG and PNG can still receive an unsuitable file, so the receiving workflow needs its own validation.</p>
<p>Help people choose a suitable file with the picker filter, then check the selected input and explain any failure where it appears. A file that exceeds a stated byte limit needs a different message from a file that cannot be decoded.</p>
<h2>Give the filter a specific purpose</h2>
<p>Match the filter to formats your receiving workflow actually handles. Advertising a broad image category can leave people uncertain about why a photo selected from their library later fails. State the supported types beside the input too, where someone can read them before opening the picker.</p>
<p>A browser preview is useful feedback. It should have its own failure state and should not be described as proof that a server will accept the file. The preview and receiver may use different processing paths. A successful local display does not replace the checks required by the receiving service.</p>
<p>I work on <a href="https://morphio.online/">Morphio</a>, which has HEIC conversion tools. When an upload destination specifically needs JPEG, conversion can prepare a compatible copy of an HEIC photograph. Keep that advice separate from the upload control's validation: a link to a converter cannot establish that every returned file satisfies the receiver's rules.</p>
<h2>Test messages with distinct inputs</h2>
<p>Prepare a small set of files with known purposes. Include an allowed image, an image exceeding the form's stated limit, and an input the preview cannot decode. Check that the interface explains each outcome without discarding a previously valid selection unexpectedly.</p>
<p>Also inspect the path after an upload is rejected by the receiver. Preserve enough context for the user to correct the file. If the receiver reports a size limit, show the unit and whether the limit concerns a single file or the whole request.</p>
<p>Keep server-side validation even when the interface looks restrictive. The receiver must decide what it can process and store after the picker has helped someone choose an input. Document those responsibilities where future changes to the form will be made, so a cleaner picker is not mistaken for permission to remove receiving checks.</p>
<p>Reference: MDN's <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept">accept attribute documentation</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Why object-fit: cover cuts off part of a photo]]></title><description><![CDATA[A portrait can lose the top of someone's head inside a card while the downloaded image remains complete. Check the image element's dimensions and…]]></description><link>https://morphio-images.hashnode.dev/why-object-fit-cover-cuts-off-part-of-a-photo</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/why-object-fit-cover-cuts-off-part-of-a-photo</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Fri, 16 Oct 2026 09:15:18 GMT</pubDate><content:encoded><![CDATA[<p>A portrait can lose the top of someone's head inside a card while the downloaded image remains complete. Check the image element's dimensions and object-fit rule before exporting another copy. The page may be cropping the display of an otherwise valid file.</p>
<p>Consider a 4000 by 3000 photograph placed in a 300 by 300 pixel image element. The source ratio is 4:3 and the element is square. The layout needs a rule for fitting those different ratios.</p>
<h2>Work through the two scale factors</h2>
<p>To fit the full width into the square, the scale factor is 300 divided by 4000, or 0.075. That produces a displayed image 300 pixels wide and 225 pixels tall. Seventy-five pixels of the square's height remain unfilled.</p>
<p>To fill the full height, the factor is 300 divided by 3000, or 0.1. The displayed image becomes 400 pixels wide and 300 pixels tall. One hundred pixels of its width extend beyond the square. With centered positioning, the visible area excludes 50 displayed pixels on each side.</p>
<p>These calculations describe this example's display size. The downloaded file keeps its own dimensions and byte count.</p>
<h2>Choose the behavior the card needs</h2>
<pre><code class="language-css">.photo {
  width: 300px;
  height: 300px;
  object-fit: cover;
  object-position: 50% 50%;
}
</code></pre>
<p>Cover preserves the source aspect ratio while filling the element. Parts that do not fit are clipped. Contain preserves the full image within the element and can leave unused space. Fill can stretch the image to the element's proportions.</p>
<p>For a gallery that must show every edge, contain may be a useful choice. For a card whose image area must always be filled, cover may suit the design. Decide with a sample containing a subject near the border. A landscape with empty sky can conceal a crop that would damage a group portrait.</p>
<p>Object-position controls where the image sits within the element. A different position can improve a particular crop, but it cannot guarantee that every subject remains visible at every container ratio. Test the narrow layout as well as the desktop layout before keeping a position as a default.</p>
<h2>Compare the card with the original</h2>
<p>Open the original file in its own viewer and compare its full frame with the card. If the full frame is present there, changing JPEG quality will not bring the hidden edges back inside the square. Review the layout rule or create an intentional crop for that component.</p>
<p>CSS cropping also does not, by itself, make the downloaded file smaller. A page can hide much of a large image while transferring the entire asset. Choosing an appropriately sized delivery image is another part of the publishing workflow.</p>
<p>For a practical review, prepare one wide image and one tall image with a visible border. Place each in the same card and compare cover with contain. Then move a subject toward an edge and test the responsive layouts. Keep the full source so that a future card design can use a different crop.</p>
<p>References: MDN's <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/object-fit">object-fit</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/object-position">object-position</a> documentation.</p>
]]></content:encoded></item><item><title><![CDATA[A smaller-looking photo can still exceed the upload limit]]></title><description><![CDATA[An upload form measures the file you send it. Making a picture look smaller inside a document may change only its layout, leaving the file too large for…]]></description><link>https://morphio-images.hashnode.dev/a-smaller-looking-photo-can-still-exceed-the-upload-limit</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/a-smaller-looking-photo-can-still-exceed-the-upload-limit</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Fri, 16 Oct 2026 02:12:52 GMT</pubDate><content:encoded><![CDATA[<p>An upload form measures the file you send it. Making a picture look smaller inside a document may change only its layout, leaving the file too large for the form.</p>
<p>After exporting, check its pixel dimensions and its size in bytes. They describe different things. A preview that fits neatly on your screen tells you little about either measurement.</p>
<p>Take a 4000-by-3000 photo and export it at 2000 by 1500. You now have a quarter as many pixels. You cannot infer that the file is a quarter as large, because its format, the picture's content, and the encoder settings also affect the byte count.</p>
<p>JPEG usually uses lossy compression. Reducing its quality setting can make the file smaller while losing detail. PNG compresses without that loss, but a PNG version of a photo may be larger than a JPEG. Check the form's accepted formats before spending time on a different export.</p>
<p>I would write down the destination's limits before changing anything. A form may require a minimum width as well as a maximum file size. Export one copy that meets the dimensions, check its actual byte count, then open it at the size readers will see. If the photo contains text, check that you can still read it.</p>
<p>An iPhone HEIC photo may need conversion to JPEG for the receiving site. I work on <a href="https://morphio.online/heic-to-jpg/">Morphio's HEIC-to-JPG tool</a>. A converted copy still needs the same measurements; changing formats does not guarantee it will fit a particular upload limit.</p>
<p>Keep the source photo while trying settings. If the smaller copy loses too much detail, return to the source for another export instead of repeatedly saving the compressed JPEG. Once the form accepts a copy that looks right, keep its settings with it so you can prepare the next photos the same way.</p>
<p>Reference: <a href="https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types">MDN's image format guide</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Why red text can look soft in a JPEG]]></title><description><![CDATA[Put red text on a blue background, save it as a JPEG, and zoom in. The letter shapes may still be sharp while the color seems to spill across their edges…]]></description><link>https://morphio-images.hashnode.dev/why-red-text-can-look-soft-in-a-jpeg</link><guid isPermaLink="true">https://morphio-images.hashnode.dev/why-red-text-can-look-soft-in-a-jpeg</guid><dc:creator><![CDATA[David Miller]]></dc:creator><pubDate>Wed, 14 Oct 2026 15:46:00 GMT</pubDate><content:encoded><![CDATA[<p>Put red text on a blue background, save it as a JPEG, and zoom in. The letter shapes may still be sharp while the color seems to spill across their edges. A photograph saved with the same settings might look fine.</p>
<p>Many JPEG encoders handle brightness and color detail separately. They can keep the brightness information at full resolution while storing less detail for color. This is called chroma subsampling. With the common 4:2:0 arrangement, a two-by-two group of pixels has separate brightness samples but shares lower-resolution color information. When the file is opened, the decoder estimates the color between those stored samples.</p>
<p>In a photograph, neighboring pixels often have similar colors, so that shortcut may be hard to see. Thin colored text is less forgiving. Its color can jump abruptly at an edge, and reduced color resolution can make that edge look smeared even if the brightness boundary is still clear.</p>
<p>A fuzzy JPEG edge is not proof of chroma subsampling. JPEG quantization can soften edges too. If your encoder exposes a subsampling setting, export one small crop twice with the same quality setting: once at 4:2:0 and once at 4:4:4. View both at 100%. The 4:4:4 copy keeps full chroma resolution, though it may take more space and is still a lossy JPEG.</p>
<p>This matters most when the image has small type, interface elements, or hard color boundaries. For a UI screenshot or diagram, also make a PNG copy and compare the text at the size people will actually see it. A photograph can tolerate a different trade-off from a line of colored text.</p>
]]></content:encoded></item></channel></rss>