<?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: Codnet Software Company</title>
    <description>The latest articles on DEV Community by Codnet Software Company (@codnet_softwarecompany_7).</description>
    <link>https://dev.to/codnet_softwarecompany_7</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%2F4092661%2Fd519997b-0214-4744-967b-31dd840f36c0.png</url>
      <title>DEV Community: Codnet Software Company</title>
      <link>https://dev.to/codnet_softwarecompany_7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codnet_softwarecompany_7"/>
    <language>en</language>
    <item>
      <title>Why scrolling screenshots fall apart on video feeds</title>
      <dc:creator>Codnet Software Company</dc:creator>
      <pubDate>Mon, 24 Aug 2026 16:20:57 +0000</pubDate>
      <link>https://dev.to/codnet_softwarecompany_7/why-scrolling-screenshots-fall-apart-on-video-feeds-ad0</link>
      <guid>https://dev.to/codnet_softwarecompany_7/why-scrolling-screenshots-fall-apart-on-video-feeds-ad0</guid>
      <description>&lt;p&gt;I maintain a small Android app that stitches a scrolling screen into one long image. It worked well until people started pointing it at social feeds, and then users began reporting that captures "overlapped" around video posts.&lt;/p&gt;

&lt;p&gt;Chasing that report taught me it was two separate defects wearing one description, and that my measurement could only see one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement comes first
&lt;/h2&gt;

&lt;p&gt;Before changing anything I made the failure reproducible offline. Debug builds write every delivered frame's grayscale to a journal file, so a real session can be replayed against any candidate algorithm - the only honest way to compare two engines, since you can never reproduce a hand gesture twice.&lt;/p&gt;

&lt;p&gt;Then I needed ground truth that did not come from the engine. Matching consecutive journal frames directly, with no chaining and no accumulated estimate, gives the real scroll between them, and therefore the real page position of every frame. Now every row the engine commits can be checked: canvas rows increase, so the page rows they carry must increase too. A step backwards means content already emitted was emitted again.&lt;/p&gt;

&lt;p&gt;That audit is what let me stop guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defect one: the video captures the measurement
&lt;/h2&gt;

&lt;p&gt;A playing video is not noise. Uncorrelated noise costs the same at every candidate offset, so it cannot move the argmin - I built a noise fixture first and it passed happily while the real bug sat untouched. Real video is structured and its scene moves coherently, which plants a &lt;strong&gt;second minimum&lt;/strong&gt; in the offset landscape at the video's own motion.&lt;/p&gt;

&lt;p&gt;When the video covers more of the textured pixels than the page does, every majority-of-pixels metric prefers that minimum. Rows land at the wrong page address. On a synthetic feed with a coherently panning video the error compounded at exactly the video's pan rate, every single frame.&lt;/p&gt;

&lt;p&gt;The obvious fix is to exclude moving rows from the measurement. That fix has a trap in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inversion
&lt;/h2&gt;

&lt;p&gt;To know which rows are moving you compare the last two frames - but you have to compare them &lt;em&gt;at some offset&lt;/em&gt;, and the only offset available is the last one you accepted. So the test is anchored to the answer it is supposed to be checking.&lt;/p&gt;

&lt;p&gt;The moment that offset is wrong, the test &lt;strong&gt;inverts&lt;/strong&gt;. Under the video's inflated offset the video's rows line up perfectly and are judged still, while the real page's rows do not and are judged to be the thing that moves. The exclusion mask ends up protecting the noise and discarding the evidence.&lt;/p&gt;

&lt;p&gt;It cannot be repaired downstream either. I tried re-registering against the committed image - but that image was written by the same wrong chain, so it agrees with the error and confirms it. Measured: the trimmed cost was 73-89, flat, at every single offset. There was nothing left to appeal to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rigid slice consensus
&lt;/h2&gt;

&lt;p&gt;What worked was removing the anchor.&lt;/p&gt;

&lt;p&gt;Page content is a &lt;strong&gt;rigid body&lt;/strong&gt;. Every horizontal slice of it moves by the same offset and reproduces the previous frame almost exactly there. Whatever refuses to fit that one offset is moving by itself: a video, a GIF, an advert, a live chart.&lt;/p&gt;

&lt;p&gt;So: split the visible band into slices of about 128 px, score each against the offset the frame just produced, and read off which ones fit. If nothing fits at all, refuse the frame. If the winner explains almost nothing but a distant rival exists, score the rival too and let consensus - not average cost - pick between them.&lt;/p&gt;

&lt;p&gt;This needs no previous estimate, which is the entire point. A consensus cannot invert. At worst it finds too little agreement, and that is a refusal, not a wrong answer.&lt;/p&gt;

&lt;p&gt;Two details mattered more than the idea itself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The consensus must never be fed back into verification.&lt;/strong&gt; I tried it. An offset that is allowed to excuse whatever it fails to explain will certify itself, and my fuzz harness immediately started putting solid rows on the wrong page. Verification has to use evidence that is independent of the offset under test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Excluding a slice and remembering it need different thresholds.&lt;/strong&gt; Excluding a slice from one frame is cheap and reversible. Recording a page region as animated steers every later frame and survives freezes, so it needs roughly double the evidence - otherwise a one-off entrance animation brands that address for the rest of the session.&lt;/p&gt;

&lt;p&gt;Results on the synthetic worst case - a coherently panning video over 76% of the viewport - went from 10, 30 and 2 misplaced rows across three seeds to 0, 1 and 11. On a real captured session, re-emitted rows went from 1056 to 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defect two: the one placement checks cannot see
&lt;/h2&gt;

&lt;p&gt;Meanwhile the user kept saying the video area still overlapped, and my audit kept reporting zero misplaced rows. We were both right.&lt;/p&gt;

&lt;p&gt;A media area's rows arrive over many frames. The video moves on between them. So the area ends up assembled from &lt;strong&gt;different instants&lt;/strong&gt; - the scene visibly cuts part-way down - while every row sits at exactly the page address it belongs to. Positional accuracy cannot fix it, because the addresses were never wrong. The moment was.&lt;/p&gt;

&lt;p&gt;You need a second measure entirely: for each animated page range, how many distinct source frames contributed. One is coherent. More is a tear. Adding that number to the audit was the first time the defect became visible as a number instead of as a complaint.&lt;/p&gt;

&lt;p&gt;The fix follows directly: stop stitching those rows. Once a media area is wholly on screen in a single frame, that frame holds a coherent picture of it, so write the whole area from that one frame, with static rows riding along on both sides so the canvas can prove the placement before anything lands.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell my past self
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build ground truth that does not run through the thing you are testing.&lt;/strong&gt; My audit and my engine shared a measurement for a while, so they agreed with each other while both were wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixtures can lie by being too kind.&lt;/strong&gt; The noise-video fixture passed. It felt like evidence. It was the absence of evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When two failures share one symptom, the user is usually reporting the one your instrument cannot see.&lt;/strong&gt; Three rounds of "still broken" against a clean audit should have told me the instrument was incomplete far sooner than it did.&lt;/p&gt;




&lt;p&gt;The app is &lt;a href="https://play.google.com/store/apps/details?id=com.codnet.longshot" rel="noopener noreferrer"&gt;LongShot&lt;/a&gt; - free, no ads, no accounts, and no INTERNET permission at all, which for a tool that reads your screen felt like the only defensible design. Text tools are Latin-script only and you do the scrolling yourself; there is deliberately no accessibility service.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>computervision</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
