<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged images</title>
<link>https://coderlegion.com/tag/images</link>
<description>Powered by Question2Answer</description>
<item>
<title>Design Publishing Queues That Survive Image Provider Outages</title>
<link>https://coderlegion.com/31607/design-publishing-queues-that-survive-image-provider-outages</link>
<description>&lt;p&gt;A publishing queue should not forget an article because a photo API had a bad afternoon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick solution:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;if (providerError.isTransient) {
  return queue.defer({ state: &quot;MEDIA_PENDING&quot;, retryAfter: &quot;1h&quot; });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The retry design must respect &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workflows/reference/limits/&quot;&gt;Cloudflare Workflow limits&lt;/a&gt; and the provider's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;Pexels API documentation&lt;/a&gt;; provider failure does not decide whether the article itself is valid.&lt;/p&gt;
&lt;p&gt;Yet that is exactly what happens when a pipeline treats every missing hero image as &quot;invalid content.&quot;&lt;/p&gt;
&lt;p&gt;The article may be complete. The metadata may be valid. The internal links may pass. The only failure is an external media provider returning no usable result, a rate-limit response, or a temporary server error.&lt;/p&gt;
&lt;p&gt;That is not the same class of failure as a malformed article.&lt;/p&gt;
&lt;p&gt;The queue should preserve the article and defer the &lt;strong&gt;media stage&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;separate-source-validity-from-media-availability&quot;&gt;Separate source validity from media availability&lt;/h3&gt;
&lt;p&gt;A clean pipeline asks two different questions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Is the article publishable?
Is a valid hero asset available right now?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first question is editorial.&lt;/p&gt;
&lt;p&gt;The second depends partly on external systems.&lt;/p&gt;
&lt;p&gt;If you merge them into one boolean, this happens:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;article valid
photo provider unavailable
=&amp;gt; source rejected
=&amp;gt; next topic generated
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now a temporary outage has changed editorial history.&lt;/p&gt;
&lt;p&gt;A better state model is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;VALIDATED
MEDIA_PENDING
MEDIA_SELECTED
READY_TO_PUBLISH
PUBLISHED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The article identity stays the same while the media stage retries.&lt;/p&gt;
&lt;h3 id=&quot;classify-media-failures&quot;&gt;Classify media failures&lt;/h3&gt;
&lt;p&gt;Not every visual failure deserves a retry.&lt;/p&gt;
&lt;h4 id=&quot;retryable&quot;&gt;Retryable&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;provider 429 rate limit;&lt;/li&gt;
&lt;li&gt;temporary 5xx response;&lt;/li&gt;
&lt;li&gt;network timeout;&lt;/li&gt;
&lt;li&gt;provider returns zero acceptable results for one query;&lt;/li&gt;
&lt;li&gt;asset download temporarily fails;&lt;/li&gt;
&lt;li&gt;deployment check is still pending.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;deterministic-repair&quot;&gt;Deterministic repair&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;selected asset already used when uniqueness is required;&lt;/li&gt;
&lt;li&gt;wrong dimensions;&lt;/li&gt;
&lt;li&gt;missing attribution;&lt;/li&gt;
&lt;li&gt;stale manifest hash;&lt;/li&gt;
&lt;li&gt;unsupported media type.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;terminal-for-that-candidate&quot;&gt;Terminal for that candidate&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;license does not allow the intended use;&lt;/li&gt;
&lt;li&gt;source URL cannot be established;&lt;/li&gt;
&lt;li&gt;raster is corrupt;&lt;/li&gt;
&lt;li&gt;asset content is clearly irrelevant;&lt;/li&gt;
&lt;li&gt;provider explicitly rejects the request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A terminal candidate is not necessarily a terminal article. Select another asset.&lt;/p&gt;
&lt;h3 id=&quot;bound-provider-retries&quot;&gt;Bound provider retries&lt;/h3&gt;
&lt;p&gt;External APIs need retry budgets.&lt;/p&gt;
&lt;p&gt;Pexels’ &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;API documentation&lt;/a&gt; currently documents default API limits of 200 requests per hour and 20,000 per month. A publisher that retries without bounds can turn temporary scarcity into a quota problem. Cloudflare’s &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workflows/reference/limits/&quot;&gt;Workflow limits&lt;/a&gt; are a separate runtime ceiling, so keep provider quotas and orchestration limits distinct.&lt;/p&gt;
&lt;p&gt;Use a fixed strategy such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;query 1 -&amp;gt; retry transient failure up to 2 times
query 2 -&amp;gt; try one alternate search phrase
reviewed catalog -&amp;gt; use only if a genuinely relevant asset exists
otherwise -&amp;gt; MEDIA_PENDING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not keep changing the query forever inside one Workflow instance.&lt;/p&gt;
&lt;p&gt;The purpose of retries is to absorb transient noise, not to search the entire internet before the run can finish.&lt;/p&gt;
&lt;h3 id=&quot;keep-the-source-queued&quot;&gt;Keep the source queued&lt;/h3&gt;
&lt;p&gt;When media is unavailable, persist the article in a state that makes the next run resume it.&lt;/p&gt;
&lt;p&gt;Do not delete the source, mark it duplicate, move to a new topic, create another draft with the same intent, or reset the article ID.&lt;/p&gt;
&lt;p&gt;A durable record needs enough identity to resume:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;article_id: art_example
slug: example-article
state: MEDIA_PENDING
revision: 2
visual_attempts: 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whether this lives in Git, Workflow state, or another durable store depends on the architecture. The invariant is the important part: the article survives.&lt;/p&gt;
&lt;h3 id=&quot;reuse-reviewed-assets-carefully&quot;&gt;Reuse reviewed assets carefully&lt;/h3&gt;
&lt;p&gt;A local catalog can reduce provider dependence.&lt;/p&gt;
&lt;p&gt;If the catalog already contains an unused or reusable raster that is genuinely relevant and its license permits reuse, it may be a better fallback than another external request.&lt;/p&gt;
&lt;p&gt;But &quot;available&quot; is not enough.&lt;/p&gt;
&lt;p&gt;Check topic relevance, provider/source URL, license, attribution, byte hash, dimensions, reuse rules, and whether the same visual is already overused.&lt;/p&gt;
&lt;p&gt;Never reuse an unrelated image simply to make the pipeline green.&lt;/p&gt;
&lt;h3 id=&quot;bind-provenance-when-selection-succeeds&quot;&gt;Bind provenance when selection succeeds&lt;/h3&gt;
&lt;p&gt;Once an asset is selected, record the evidence immediately.&lt;/p&gt;
&lt;p&gt;Useful fields include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;provider: Pexels
source_url: https://www.pexels.com/photo/...
license_url: https://www.pexels.com/license/
attribution: Photo by ...
article_id: art_example
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After downloading the final raster, add the MIME type, width, height, byte count, and SHA-256 of the transformed file.&lt;/p&gt;
&lt;p&gt;The first block explains where the image came from. The second explains exactly which bytes the publication uses.&lt;/p&gt;
&lt;p&gt;Do not invent file metadata before the file exists.&lt;/p&gt;
&lt;h3 id=&quot;avoid-duplicate-work-after-uncertain-responses&quot;&gt;Avoid duplicate work after uncertain responses&lt;/h3&gt;
&lt;p&gt;Media providers and storage APIs can fail after doing the work.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;download completes
network layer times out before caller records success
retry starts
same image downloaded again under another asset ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use stable stage identity.&lt;/p&gt;
&lt;p&gt;If an asset selection has already been recorded for article A revision 3, the retry should inspect that record before searching again.&lt;/p&gt;
&lt;p&gt;Likewise, if the final raster already exists and its hash matches the intended manifest, verify it instead of creating another copy.&lt;/p&gt;
&lt;p&gt;This is the same idempotency principle used throughout &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/building-a-reliable-batch-publishing-workflow/&quot;&gt;Building a Reliable Batch Publishing Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;verify-the-actual-raster-not-provider-metadata&quot;&gt;Verify the actual raster, not provider metadata&lt;/h3&gt;
&lt;p&gt;A provider response can describe the original photograph, while your pipeline may crop or resize it.&lt;/p&gt;
&lt;p&gt;The publication should bind to the final transformed bytes, not merely the provider metadata.&lt;/p&gt;
&lt;p&gt;After transformation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;decode the file;&lt;/li&gt;
&lt;li&gt;read actual dimensions;&lt;/li&gt;
&lt;li&gt;compute byte length;&lt;/li&gt;
&lt;li&gt;compute SHA-256;&lt;/li&gt;
&lt;li&gt;update the asset manifest;&lt;/li&gt;
&lt;li&gt;verify the article points to that asset.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The provider page remains provenance. The transformed file becomes publication evidence.&lt;/p&gt;
&lt;h3 id=&quot;keep-provider-outages-out-of-editorial-scoring&quot;&gt;Keep provider outages out of editorial scoring&lt;/h3&gt;
&lt;p&gt;A photo API outage says nothing about whether an article has information gain.&lt;/p&gt;
&lt;p&gt;Do not lower article quality, factual confidence, duplicate status, or rank-potential merely because an image API is unavailable.&lt;/p&gt;
&lt;p&gt;Instead report the failure in the stage that owns it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;editorial: PASS
research: PASS
visual: DEFERRED_PROVIDER
publication: PENDING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes the blocker understandable to both humans and automation.&lt;/p&gt;
&lt;h3 id=&quot;verify-after-deployment&quot;&gt;Verify after deployment&lt;/h3&gt;
&lt;p&gt;A queue is not finished when the image reaches Git.&lt;/p&gt;
&lt;p&gt;After deployment, fetch the public hero URL and verify a successful response, correct content type, decodable bytes, expected dimensions, and that the expected article actually references it.&lt;/p&gt;
&lt;p&gt;If the article is live but the asset is stale, keep the same publication pending until the intended raster is visible.&lt;/p&gt;
&lt;p&gt;That exact-version discipline is part of &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/designing-a-reliable-serverless-ai-publishing-workflow/&quot;&gt;Designing a Reliable Serverless AI Publishing Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;design-for-a-provider-independent-future&quot;&gt;Design for a provider-independent future&lt;/h3&gt;
&lt;p&gt;Do not let one provider's response schema leak across the whole publishing system.&lt;/p&gt;
&lt;p&gt;Normalize a candidate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type ImageCandidate = {
  provider: string;
  sourceUrl: string;
  downloadUrl: string;
  attribution: string;
  licenseUrl: string;
  width?: number;
  height?: number;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the rest of the pipeline can perform relevance checks, dedupe, transforms, manifest binding, and deployment verification without caring where the candidate came from.&lt;/p&gt;
&lt;p&gt;That makes adding a second provider a local adapter change instead of a workflow rewrite.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-recovery-algorithm&quot;&gt;A practical recovery algorithm&lt;/h3&gt;
&lt;p&gt;When an article reaches the visual stage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. inspect existing bound asset
2. if healthy -&amp;gt; continue
3. inspect reviewed catalog for relevant candidate
4. if found -&amp;gt; bind, transform, verify
5. otherwise query provider within request budget
6. reject duplicates or invalid provenance
7. transform final raster
8. validate bytes and dimensions
9. if provider unavailable -&amp;gt; MEDIA_PENDING
10. retry same article later
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The crucial line is number 10.&lt;/p&gt;
&lt;p&gt;When maintaining these pipelines, teams should also review policies for &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/preventing-search-intent-duplicates-in-automated-publishing/&quot;&gt;preventing search intent&lt;/a&gt; duplicates in automated systems to ensure safe retries.&lt;/p&gt;
&lt;h3 id=&quot;verification-and-execution-checklist&quot;&gt;Verification and execution checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Inspect the active article state to confirm &lt;code&gt;MEDIA_PENDING&lt;/code&gt; is recorded instead of deleting the draft.&lt;/li&gt;
&lt;li&gt;Verify that external API calls respect rate-limit thresholds and orchestration timeouts.&lt;/li&gt;
&lt;li&gt;Confirm that transformed asset hashes match the recorded manifest before deployment.&lt;/li&gt;
&lt;li&gt;Check public staging endpoints to ensure the final raster is accessible and correctly linked.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31607/design-publishing-queues-that-survive-image-provider-outages</guid>
<pubDate>Sun, 18 Oct 2026 11:52:14 +0000</pubDate>
</item>
<item>
<title>60 % of our image decode failures were HEIC files named .jpg</title>
<link>https://coderlegion.com/28863/60-of-our-image-decode-failures-were-heic-files-named-jpg</link>
<description>&lt;p&gt;For a few days our analytics showed a class of failure that made no sense: images rejected by the decoder in well under a second. Not slow failures, not out-of-memory on a huge photo — instant refusals. Between 2026-09-06 and 09-10, &lt;strong&gt;60 % of every &lt;code&gt;decode_failed&lt;/code&gt; event came from the same shape of file: an iPhone photo called &lt;code&gt;photo.jpg&lt;/code&gt;, reporting &lt;code&gt;image/jpeg&lt;/code&gt;, whose actual bytes were HEIC.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We had a HEIC path. It was never tried, because the gate that decides whether to load the HEIC decoder was reading the filename.&lt;/p&gt;
&lt;p&gt;Disclosure: this is from &lt;a rel=&quot;nofollow&quot; href=&quot;https://lensup.ai/&quot;&gt;LensUp&lt;/a&gt;, which is ours. The fix is the boring one — read the bytes — but the interesting part is where naive byte-sniffing then went wrong, and the precedence rule we ended up needing.&lt;/p&gt;
&lt;h3 id=&quot;how-a-heic-ends-up-called-jpg&quot;&gt;How a HEIC ends up called .jpg&lt;/h3&gt;
&lt;p&gt;Nobody renames these on purpose. The path is mundane: an iPhone shoots HEIC, you share the photo through a messenger or copy it to a PC, and somewhere along the way the transport relabels it. The extension becomes &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;file.type&lt;/code&gt; becomes &lt;code&gt;image/jpeg&lt;/code&gt;, and the container is still HEIC.&lt;/p&gt;
&lt;p&gt;From the browser's side there is no hint of this. &lt;code&gt;File.type&lt;/code&gt; is not derived from content — it is whatever the OS or the transport asserted. So:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Cheap, synchronous gate so the page can decide whether to lazy-load the HEIC decoder
// module at all. Byte-level truth lives elsewhere — names and MIME lie often enough
// that this is only a candidate check, never a verdict.
export function isHeicCandidate(file) {
  const mime = file?.type?.toLowerCase();
  if (mime === 'image/heic' || mime === 'image/heif') return true;
  const extension = file?.name?.split('.').pop()?.toLowerCase();
  return extension === 'heic' || extension === 'heif';
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That function is fine — as a hint for whether to &lt;em&gt;lazy-load&lt;/em&gt; a wasm decoder you do not want to ship to everyone. It is not fine as the thing that decides which decoder runs. That was the bug: a hint had been promoted to a verdict.&lt;/p&gt;
&lt;h3 id=&quot;sniffing-twelve-to-sixteen-bytes-never-the-file&quot;&gt;Sniffing: twelve to sixteen bytes, never the file&lt;/h3&gt;
&lt;p&gt;The fix reads a small slice. Not the file — a slice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const FTYP_READ_BYTES = 64;
export async function sniffInputSignature(file) {
  let head;
  try {
    head = new Uint8Array(await file.slice(0, FTYP_READ_BYTES).arrayBuffer());
  } catch { return null; }
  if (head.length &amp;lt; 4) return null;
  const ascii = (from, to) =&amp;gt; String.fromCharCode(...head.subarray(from, Math.min(to, head.length)));

  if (head[0] === 0xff &amp;amp;&amp;amp; head[1] === 0xd8 &amp;amp;&amp;amp; head[2] === 0xff) return 'jpeg';
  if (head[0] === 0x89 &amp;amp;&amp;amp; ascii(1, 4) === 'PNG') return 'png';
  if (ascii(0, 4) === 'GIF8') return 'gif';
  if (ascii(0, 4) === 'RIFF' &amp;amp;&amp;amp; head.length &amp;gt;= 12 &amp;amp;&amp;amp; ascii(8, 12) === 'WEBP') return 'webp';
  if (ascii(0, 2) === 'BM') return 'bmp';
  if (ascii(0, 4) === '%PDF') return 'pdf';
  // … TIFF (II*\0 / MM\0*), then the ISO-BMFF family below
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sixteen bytes covers every format the importer accepts, and &lt;code&gt;file.slice()&lt;/code&gt; means the read is one small range request against the blob rather than a load of a 12-megapixel photo.&lt;/p&gt;
&lt;h3 id=&quot;where-just-read-the-bytes-is-not-enough&quot;&gt;Where &quot;just read the bytes&quot; is not enough&lt;/h3&gt;
&lt;p&gt;ISO base media files — HEIC, HEIF, AVIF, MP4 and friends — all start the same way: bytes 4–8 spell &lt;code&gt;ftyp&lt;/code&gt;, bytes 8–12 carry the &lt;strong&gt;major brand&lt;/strong&gt;. So the naive version is: read the major brand, map &lt;code&gt;heic&lt;/code&gt; → HEIC decoder, &lt;code&gt;avif&lt;/code&gt; → native.&lt;/p&gt;
&lt;p&gt;Then review found the hole. Two of those brands are generic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const HEIC_BRANDS = new Set(['heic', 'heix', 'hevc', 'hevx', 'heim', 'heis', 'hevm', 'hevs']);
const AVIF_BRANDS = new Set(['avif', 'avis']);
const GENERIC_HEIF_BRANDS = new Set(['mif1', 'msf1']);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mif1&lt;/code&gt; and &lt;code&gt;msf1&lt;/code&gt; mean &quot;this is a HEIF container&quot; and say &lt;strong&gt;nothing about the codec inside&lt;/strong&gt;. An AVIF file may carry &lt;code&gt;mif1&lt;/code&gt; as its major brand and declare &lt;code&gt;avif&lt;/code&gt; only further down, in the compatible-brands list. Treat &lt;code&gt;mif1&lt;/code&gt; as HEIC and you hand a file the browser decodes natively — losslessly, instantly — to a lossy wasm transcode. You have recompressed somebody's photo for no reason.&lt;/p&gt;
&lt;p&gt;So a generic major brand gets resolved from the compatible brands, and the &lt;code&gt;ftyp&lt;/code&gt; box is walked using its own declared size:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if (head.length &amp;gt;= 12 &amp;amp;&amp;amp; ascii(4, 8) === 'ftyp') {
  const major = ascii(8, 12).trim().toLowerCase();
  if (HEIC_BRANDS.has(major)) return 'heic';
  if (AVIF_BRANDS.has(major)) return 'avif';
  if (GENERIC_HEIF_BRANDS.has(major)) {
    // ftyp box: [size:4]['ftyp'][major:4][minor version:4][compatible brands:4 each …]
    const boxSize = ((head[0] &amp;lt;&amp;lt; 24) | (head[1] &amp;lt;&amp;lt; 16) | (head[2] &amp;lt;&amp;lt; 8) | head[3]) &amp;gt;&amp;gt;&amp;gt; 0;
    const end = Math.min(head.length, boxSize || head.length);
    const compatible = [];
    for (let offset = 16; offset + 4 &amp;lt;= end; offset += 4) {
      compatible.push(ascii(offset, offset + 4).trim().toLowerCase());
    }
    if (compatible.some((b) =&amp;gt; AVIF_BRANDS.has(b))) return 'avif';
    if (compatible.some((b) =&amp;gt; HEIC_BRANDS.has(b))) return 'heic';
    return 'heif';   // a HEIF container that names no codec
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two details in there that are easy to skip. The box size is clamped with &lt;code&gt;Math.min(head.length, …)&lt;/code&gt; so a bogus length cannot walk past the slice we actually read. And when no compatible brand is specific, the answer is the honest &lt;code&gt;'heif'&lt;/code&gt; — &quot;a HEIF container, codec unknown&quot; — rather than a guess.&lt;/p&gt;
&lt;h3 id=&quot;the-precedence-rule-bytes-win-the-label-breaks-ties&quot;&gt;The precedence rule: bytes win, the label breaks ties&lt;/h3&gt;
&lt;p&gt;&quot;Always trust the bytes&quot; is the slogan, and it is &lt;em&gt;almost&lt;/em&gt; right. The generic-HEIF case is where it breaks: a file whose bytes only say &lt;code&gt;heif&lt;/code&gt; could be a HEIC the browser cannot open, or something it can. There is no byte that settles it.&lt;/p&gt;
&lt;p&gt;So the rule that shipped is narrower and, I think, the actually correct one: &lt;strong&gt;bytes win over the label; the label breaks ties only when the bytes say nothing.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export async function classifyInput(file) {
  const signature = await sniffInputSignature(file);
  const labelPdf  = isPdfCandidate(file);
  const labelHeic = isHeicCandidate(file);
  const labelTiff = isTiffCandidate(file);
  const isPdf  = signature === 'pdf'  || (labelPdf  &amp;amp;&amp;amp; signature === null);
  const isHeic = !isPdf &amp;amp;&amp;amp; (signature === 'heic' || (labelHeic &amp;amp;&amp;amp; (signature === null || signature === 'heif')));
  const isTiff = !isPdf &amp;amp;&amp;amp; !isHeic &amp;amp;&amp;amp; (signature === 'tiff' || (labelTiff &amp;amp;&amp;amp; signature === null));
  return { signature, isPdf, isHeic, isTiff, isImage: !isPdf &amp;amp;&amp;amp; !isHeic &amp;amp;&amp;amp; !isTiff,
           mislabelled: labelPdf &amp;amp;&amp;amp; IMAGE_SIGNATURES.has(signature) };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the three interesting rows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scan.pdf&lt;/code&gt; whose bytes are a JPEG is an image&lt;/strong&gt;, not a broken PDF. Before this, those went to the PDF parser and produced &lt;code&gt;pdf_invalid&lt;/code&gt;: 39 of them between 09-06 and 09-11, most from Windows desktops retrying the same file over and over — the user's file was fine, our routing was not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IMG_0001.jpg&lt;/code&gt; whose bytes start with &lt;code&gt;%PDF&lt;/code&gt; is a PDF.&lt;/strong&gt; Same rule, other direction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A generic HEIF goes to the HEIC decoder only if the label agrees.&lt;/strong&gt; &lt;code&gt;signature === 'heif' &amp;amp;&amp;amp; labelHeic&lt;/code&gt; → HEIC. A &lt;code&gt;.jpg&lt;/code&gt; with those bytes stays on the native path, because that is the case where the label is the only evidence that exists.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;one-more-lie-application-octet-stream&quot;&gt;One more lie: application/octet-stream&lt;/h3&gt;
&lt;p&gt;Worth its own line, because it is not a type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// application/octet-stream is what browsers say when they do not know — treat it as
// undeclared so the extension map (and downstream byte sniffing) can decide.
if (declared &amp;amp;&amp;amp; declared !== 'application/octet-stream') return declared;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows file pickers hand over &lt;code&gt;.pdf&lt;/code&gt; files with an empty or &lt;code&gt;application/octet-stream&lt;/code&gt; type often enough that treating it as a real MIME type poisons every branch downstream. It means &quot;no opinion&quot;, and the code should say so.&lt;/p&gt;
&lt;p&gt;The same file also needs a small alias table, because the same format arrives under several names: &lt;code&gt;image/pjpeg&lt;/code&gt;, &lt;code&gt;image/x-ms-bmp&lt;/code&gt;, &lt;code&gt;image/x-png&lt;/code&gt;, &lt;code&gt;image/jpg&lt;/code&gt;. Normalise first, decide second.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-take-from-this&quot;&gt;What I would take from this&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A cheap label check and an expensive byte check are different functions and should be named differently.&lt;/strong&gt; Our bug was one function doing both jobs — a lazy-load hint named like a verdict. &lt;code&gt;isHeicCandidate&lt;/code&gt; and &lt;code&gt;isHeicFile&lt;/code&gt; now sit next to each other with comments saying which is which, and that naming is doing more work than the sniffer is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Read the bytes&quot; needs a tie-break rule, not just a preference.&lt;/strong&gt; Some formats genuinely do not identify their own contents. Write the precedence down explicitly — bytes, then label, then a documented default — rather than letting it emerge from the order of your &lt;code&gt;if&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failures that are too fast are a routing smell.&lt;/strong&gt; A decoder that gives up in 200 ms did not struggle with your file; it was handed the wrong file. That timing was the clue that pointed at the label, and it is the one I will look for first next time.&lt;/p&gt;
&lt;p&gt;If you want to see the byte path do its thing, drop a HEIC — renamed or not — into &lt;a rel=&quot;nofollow&quot; href=&quot;https://lensup.ai/cam-scanner/&quot;&gt;a browser-based document scanner&lt;/a&gt;. It runs in the tab and files are never uploaded.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28863/60-of-our-image-decode-failures-were-heic-files-named-jpg</guid>
<pubDate>Mon, 28 Sep 2026 05:02:05 +0000</pubDate>
</item>
<item>
<title>The transparency bug you cannot see in a thumbnail</title>
<link>https://coderlegion.com/27130/the-transparency-bug-you-cannot-see-in-a-thumbnail</link>
<description>&lt;p&gt;There is a failure mode in image generation that survives every visual check you&lt;br&gt;
are likely to run, ships to production, and only surfaces when a designer drops&lt;br&gt;
the asset onto a coloured background. It is worth knowing about because the&lt;br&gt;
thing that makes it dangerous is not the model — it is the preview.&lt;/p&gt;
&lt;h3 id=&quot;what-actually-goes-wrong&quot;&gt;What actually goes wrong&lt;/h3&gt;
&lt;p&gt;Ask an image model for a subject &quot;on a transparent background&quot; and you can get&lt;br&gt;
back one of two very different files.&lt;/p&gt;
&lt;p&gt;The first is what you wanted: an RGBA image where the pixels around the subject&lt;br&gt;
have an alpha value of zero. Nothing is drawn there. Composite it over red and&lt;br&gt;
you see red.&lt;/p&gt;
&lt;p&gt;The second is an RGB image, fully opaque, in which the model has &lt;em&gt;painted&lt;/em&gt; a&lt;br&gt;
light grey and white checkerboard around the subject. Composite that over red&lt;br&gt;
and you see a chequered rectangle with your subject in the middle.&lt;/p&gt;
&lt;p&gt;The second one happens because the checkerboard is a convention, not a fact.&lt;br&gt;
Photoshop, GIMP, Figma and every image editor written in the last thirty years&lt;br&gt;
draw that grid to represent &lt;em&gt;absence&lt;/em&gt;. So the training data contains an enormous&lt;br&gt;
number of images in which &quot;transparent background&quot; and &quot;a grid of light grey and&lt;br&gt;
white squares&quot; are the same picture. A generative model has no privileged access&lt;br&gt;
to the alpha channel; it predicts pixels. Asked for the look of transparency, it&lt;br&gt;
sometimes produces exactly that — the look.&lt;/p&gt;
&lt;h3 id=&quot;why-every-preview-lies-about-it&quot;&gt;Why every preview lies about it&lt;/h3&gt;
&lt;p&gt;This is the part that catches people, and it is pure interface design.&lt;/p&gt;
&lt;p&gt;Your file browser, your image viewer, your CDN's thumbnailer and the preview&lt;br&gt;
pane in whatever tool generated the image all render &lt;em&gt;genuine&lt;/em&gt; transparency as a&lt;br&gt;
checkerboard. That is the standard way to display an empty alpha channel.&lt;/p&gt;
&lt;p&gt;So both files look identical in the preview:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Real alpha &lt;/th&gt;&lt;th&gt;Painted checkerboard&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Thumbnail in Finder / Explorer &lt;/td&gt;&lt;td&gt;grey-and-white grid &lt;/td&gt;&lt;td&gt;grey-and-white grid&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Image viewer &lt;/td&gt;&lt;td&gt;grey-and-white grid &lt;/td&gt;&lt;td&gt;grey-and-white grid&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dropped onto a red &lt;code&gt;div&lt;/code&gt; &lt;/td&gt;&lt;td&gt;red &lt;/td&gt;&lt;td&gt;grey-and-white grid&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The failure is invisible in exactly the three places you would look, and visible&lt;br&gt;
only in the one place you look last. If your pipeline generates assets in bulk —&lt;br&gt;
product cut-outs, icon sets, sprite sheets — you can ship hundreds of broken&lt;br&gt;
files before anyone notices, and the person who notices will be a customer.&lt;/p&gt;
&lt;h3 id=&quot;checking-it-properly-in-about-ten-lines&quot;&gt;Checking it properly, in about ten lines&lt;/h3&gt;
&lt;p&gt;You do not need a library and you do not need to upload anything. The check runs&lt;br&gt;
on the file the browser already has. Read the pixels, look at the alpha channel,&lt;br&gt;
and ask one question: does any pixel have alpha below 255?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;async function hasRealAlpha(file) {
  const bitmap = await createImageBitmap(file);
  const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
  const ctx = canvas.getContext(&quot;2d&quot;, { willReadFrequently: true });
  ctx.drawImage(bitmap, 0, 0);

  const { data } = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
  for (let i = 3; i &amp;lt; data.length; i += 4) {
    if (data[i] &amp;lt; 255) return true;       // at least one non-opaque pixel
  }
  return false;                            // fully opaque: no alpha channel in use
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two notes on getting this right.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check the corners before you check everything.&lt;/strong&gt; A cut-out almost always has&lt;br&gt;
transparent corners, so sampling the four corner pixels answers the question for&lt;br&gt;
the common case at a fraction of the cost. Fall back to the full scan only when&lt;br&gt;
the corners are opaque — that is either a genuine edge-to-edge image or the bug.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A JPEG cannot pass this test, ever.&lt;/strong&gt; JPEG has no alpha channel. If your&lt;br&gt;
pipeline re-encodes to JPEG anywhere — a thumbnailer, an upload handler, an&lt;br&gt;
overeager optimiser — you will destroy real transparency and produce a file that&lt;br&gt;
is indistinguishable from the painted-checkerboard failure. Check the format&lt;br&gt;
before you blame the model.&lt;/p&gt;
&lt;h3 id=&quot;the-stronger-version-of-the-check&quot;&gt;The stronger version of the check&lt;/h3&gt;
&lt;p&gt;Detecting &lt;em&gt;some&lt;/em&gt; transparency is not the same as detecting &lt;em&gt;correct&lt;/em&gt;&lt;br&gt;
transparency. A file can have a real alpha channel and still be wrong: a halo of&lt;br&gt;
semi-opaque pixels around the subject, left over from a soft matte, shows up as&lt;br&gt;
a grey fringe on dark backgrounds.&lt;/p&gt;
&lt;p&gt;The honest test is the one that mirrors how the asset will be used: composite it&lt;br&gt;
over a solid colour and look. Two colours, ideally — one much darker than the&lt;br&gt;
subject and one much lighter. Fringing that is invisible on white is obvious on&lt;br&gt;
black, and the reverse is true for dark subjects.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// draw the result over a flat colour, then look at it like a human would
ctx.globalCompositeOperation = &quot;destination-over&quot;;
ctx.fillStyle = &quot;#c0392b&quot;;
ctx.fillRect(0, 0, canvas.width, canvas.height);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is three lines and it turns an invisible failure into an obvious one.&lt;/p&gt;
&lt;h3 id=&quot;why-this-is-worth-building-into-the-tool-rather-than-the-test-suite&quot;&gt;Why this is worth building into the tool rather than the test suite&lt;/h3&gt;
&lt;p&gt;A test suite catches this after generation, which means after you have paid for&lt;br&gt;
the image. Running the check at the moment the file appears means the failure is&lt;br&gt;
reported next to the thing that failed, while the prompt that produced it is&lt;br&gt;
still on screen and still editable.&lt;/p&gt;
&lt;p&gt;That is the reasoning behind the&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://gptimage25.top/transparent-background-generator&quot;&gt;transparent background generator&lt;/a&gt;&lt;br&gt;
on the interface I work on: it runs the alpha check in the browser on the file&lt;br&gt;
that is already there, composites the result over a solid colour, and shows you&lt;br&gt;
that before the download button does anything. It costs nothing per image&lt;br&gt;
because nothing is uploaded and nothing is re-encoded — it is the same&lt;br&gt;
&lt;code&gt;getImageData&lt;/code&gt; call as above, run on every result rather than on the ones you&lt;br&gt;
remember to check.&lt;/p&gt;
&lt;p&gt;The broader tool is the&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://gptimage25.top/&quot;&gt;GPT Image 2.5 Image Generator&lt;/a&gt;, an independent&lt;br&gt;
third-party interface — not affiliated with OpenAI — that prints the model id,&lt;br&gt;
quality tier and size under every result. Same principle as the alpha check:&lt;br&gt;
the things you would otherwise have to take on faith are shown to you instead.&lt;/p&gt;
&lt;h3 id=&quot;the-short-version&quot;&gt;The short version&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Transparent background&quot; is a &lt;em&gt;visual&lt;/em&gt; convention in the training data, so a&lt;br&gt;
model can produce the appearance of transparency rather than the thing.&lt;/li&gt;
&lt;li&gt;Every preview surface renders both cases identically, which is why this&lt;br&gt;
reaches production more often than it should.&lt;/li&gt;
&lt;li&gt;The check is one &lt;code&gt;getImageData&lt;/code&gt; pass over the alpha byte; the &lt;em&gt;useful&lt;/em&gt; check&lt;br&gt;
is compositing over two solid colours and looking.&lt;/li&gt;
&lt;li&gt;If any stage of your pipeline emits JPEG, the question is already answered and&lt;br&gt;
the answer is no.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27130/the-transparency-bug-you-cannot-see-in-a-thumbnail</guid>
<pubDate>Mon, 14 Sep 2026 08:01:17 +0000</pubDate>
</item>
<item>
<title>What Developers Get Wrong About Free Images</title>
<link>https://coderlegion.com/24611/what-developers-get-wrong-about-free-images</link>
<description>&lt;p&gt;Most of us treat images as a design problem, paste a URL from a stock site into a template, and move on. That is the part of the stack that quietly creates both legal exposure and slow pages, so here is what actually matters about free images from an engineering point of view.&lt;/p&gt;
&lt;h3 id=&quot;free-is-three-different-licenses&quot;&gt;Free Is Three Different Licenses&lt;/h3&gt;
&lt;p&gt;The word free covers three situations that are not the same thing. Public domain and CC0 let you do anything with no conditions. Attribution licenses let you do almost anything as long as the credit line exists and is correct. Non commercial and share alike licenses carry conditions that are legally binding even though the download cost nothing, and share alike in particular can reach into what you wrap around the image.&lt;/p&gt;
&lt;p&gt;There is a fourth question that has nothing to do with the license file. If a recognizable person, a piece of artwork, or a branded product appears in the frame, you may need a release regardless of what the photographer granted you. The photographer can license their copyright. They cannot license someone else's face.&lt;/p&gt;
&lt;h3 id=&quot;the-metadata-nobody-automates&quot;&gt;The Metadata Nobody Automates&lt;/h3&gt;
&lt;p&gt;The failure mode is not picking the wrong license, it is being unable to prove which license you were given. Stock sites reorganize, accounts get deleted, and terms pages get rewritten, so two years later the evidence is gone and the burden is on you.&lt;/p&gt;
&lt;p&gt;Treat it like any other dependency. When an image lands in the repo, drop a sidecar file next to it holding the source URL, the license name and version, the uploader, the download date, and an archived copy of the terms as they read that day. It is the same instinct as a lockfile: pin what you actually got, not what the page says today.&lt;/p&gt;
&lt;h3 id=&quot;image-prep-belongs-in-the-build&quot;&gt;Image Prep Belongs In The Build&lt;/h3&gt;
&lt;p&gt;The other half of the problem is delivery. Serve AVIF or WebP with a fallback, generate the responsive sizes at build time rather than shipping one 4000px original, set width and height attributes so nothing shifts while loading, lazy load anything below the fold, and write alt text that describes the image instead of repeating the filename. All of that is mechanical, which means it belongs in CI, not in a checklist someone follows by hand on a Friday.&lt;/p&gt;
&lt;h3 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h3&gt;
&lt;p&gt;Images are the one asset class most teams handle with less rigor than a dependency, and both failure modes, a takedown notice and a two second render, arrive long after the decision was made. The libraries worth using, the free AI generators, the license rules and the prep steps are collected in this &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.aiappsapi.com/free-images-collection/&quot;&gt;guide to free images&lt;/a&gt;, which is a good place to point whoever on your team is pasting stock URLs into templates.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24611/what-developers-get-wrong-about-free-images</guid>
<pubDate>Fri, 14 Aug 2026 02:48:44 +0000</pubDate>
</item>
</channel>
</rss>