{"id":1419,"date":"2021-12-31T20:19:10","date_gmt":"2021-12-31T20:19:10","guid":{"rendered":"http:\/\/retroramblings.net\/?p=1419"},"modified":"2025-09-30T10:26:00","modified_gmt":"2025-09-30T10:26:00","slug":"dithering-revisited","status":"publish","type":"post","link":"https:\/\/retroramblings.net\/?p=1419","title":{"rendered":"Dithering Revisited"},"content":{"rendered":"\n<p><strong>Achieving 24-bit colour on a 15-bit device &#8211; 2021-12-31<\/strong><\/p>\n\n\n\n<p>[I found this unfinished post in a dusty corner of my drafts folder, and decided that tonight was the night to finish it!]<\/p>\n\n\n\n<p>While I&#8217;m sitting through the all-too-familiar wait while Quartus builds a core, I wanted to write a few words about dithering and how I approached the problem of doing 24-bit colour video output on a platform which has only 15 bits of colour resolution on its VGA port.<\/p>\n\n\n\n<p>The video DAC on the Turbo Chameleon 64 has just 5 bits per colour gun which means we can output 32 different levels each of red, green and blue for a total range of 32,768 colours.  This is fine for the ECS Minimig core, since the original Amiga has only 4 bits per gun, for a total of 4,096 colours &#8211; but the AGA chipset doubles this colour depth to 8 bits per gun, full 24-bit output &#8211; so some compromises will be needed.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>If we don&#8217;t do any dithering, and simply take each eight-bit value [a b c d e f g h] and truncate it to five bits, then we&#8217;re effectively rounding the value down to [a b c d e 0 0 0].  The idea of dithering is to selectively add a dither value to the lower bits [f g h] before the truncation.  If the result of this addition spills into the upper five bits, then we&#8217;ve rounded the value up instead of down.  By rounding some pixels up and some pixels down we can create the illusion of extra levels of precision in the output.<\/p>\n\n\n\n<p>[It&#8217;s no accident that there are strong parallels between this and the audio noise-shaping I&#8217;ve been exploring in earlier posts.  Once again, we&#8217;re seeking to modulate a signal before applying a threshold function, in order to make the noise less objectionable.  In this case we&#8217;re adding a predetermined high-frequency signal to lift the quantization noise beyond the frequency band occupied by the interesting parts of the signal, and relying on the eye &#8211; and it&#8217;s inability to see individual pixels &#8211; as a low-pass filter.]<\/p>\n\n\n\n<p>In the past I&#8217;ve used two methods together to add two extra bits of colour precision &#8211; namely, rounding up alternate pixels by adding a &#8216;1&#8217; to dither bit [f], and rounding up alternate complete scanlines by adding to dither bit [g].  In both cases I would switch which pixel or scanline was dithered on a frame-by-frame basis, to hide the dithering in a faint shimmer.<\/p>\n\n\n\n<p>I realise now that my approach is basically just using a 2&#215;2 ordered dithering matrix, but inverting it on a frame-by-frame basis.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"395\" height=\"683\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/12\/OrderedMatrix.png\" alt=\"\" class=\"wp-image-1827\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/12\/OrderedMatrix.png 395w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/12\/OrderedMatrix-173x300.png 173w\" sizes=\"auto, (max-width: 395px) 100vw, 395px\" \/><\/figure><\/div>\n\n\n\n<p>Having gained that insight, it occurred to me that I could hide some of the intensity change and shimmer from dithering by using different matrices for the red, green and blue channels.  I found that the best results came from dithering red and blue using one matrix, and using its inverse for green &#8211; then inverting the whole matrix every frame.<\/p>\n\n\n\n<p>The following illustration shows an exaggerated impression of how the dithering looks on even and odd frames when it&#8217;s applied to all three channels together, and how it looks when it&#8217;s split between green and red\/blue.  The latter arrangement significantly reduces the change in contrast from frame to frame, and thus the amount of perceived flicker.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"237\" height=\"241\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/12\/OrderedSplitColour.png\" alt=\"\" class=\"wp-image-1828\"\/><\/figure><\/div>\n\n\n\n<p>Since the 2&#215;2 matrix gives me two bits of dither, but we actually need three bits for the Turbo Chameleon 64, I applied a simple Linear Feedback Shift Register to the lowest bit, to provide some random dithering as well.  On the MiST board this isn&#8217;t necessary since we have a six-bit DAC available for each channel.  On the de10-lite, which I use for development, I only have four bits per gun, so I add two extra bits of dither with the LFSR.  This does produce visible noise in areas of solid colour, but it&#8217;s actually not that objectionable &#8211; almost calling back to the little bit of noise you&#8217;d see when using a TV with an RF modulator!<br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Achieving 24-bit colour on a 15-bit device &#8211; 2021-12-31 [I found this unfinished post in a dusty corner of my drafts folder, and decided that tonight was the night to finish it!] While I&#8217;m sitting through the all-too-familiar wait while &hellip; <a href=\"https:\/\/retroramblings.net\/?p=1419\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,8],"tags":[],"class_list":["post-1419","post","type-post","status-publish","format-standard","hentry","category-fpga","category-hardware"],"_links":{"self":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1419","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1419"}],"version-history":[{"count":2,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1419\/revisions"}],"predecessor-version":[{"id":1829,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1419\/revisions\/1829"}],"wp:attachment":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1419"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1419"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1419"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}