<?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: yoya</title>
    <description>The latest articles on DEV Community by yoya (@yoya).</description>
    <link>https://dev.to/yoya</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F44368%2F4ce440a6-0c0e-424a-8596-227a9030ada1.png</url>
      <title>DEV Community: yoya</title>
      <link>https://dev.to/yoya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yoya"/>
    <language>en</language>
    <item>
      <title>Canvas getImageData RGB values are quantized with premultiplied-alpha</title>
      <dc:creator>yoya</dc:creator>
      <pubDate>Wed, 21 Apr 2021 14:01:03 +0000</pubDate>
      <link>https://dev.to/yoya/canvas-getimagedata-premultiplied-alpha-150b</link>
      <guid>https://dev.to/yoya/canvas-getimagedata-premultiplied-alpha-150b</guid>
      <description>&lt;p&gt;When RGBA data including transparent colors is obtained from Canvas using getImageData, the RGB values may differ from the imagine values. The behavior described in this entry is not a bug, but conforms to the specification.&lt;/p&gt;

&lt;h1&gt;
  
  
  sample image
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% magick -size 256x256 xc:white -alpha on \
         -channel RGB -fx "i/w"           \
         -channel A   -fx "j/h"   RGBA.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RGBA.png&lt;/th&gt;
&lt;th&gt;RGB.png&lt;/th&gt;
&lt;th&gt;A.png&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2Fwt6xbmur8i8csijylbuoqv588h3j" alt="RGBA.png"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2Ftxcw22utqsw09m64f1wq393umvqi" alt="RGB"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F0b99245wdpdhxwyadew7ay1molku" alt="A"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  problem
&lt;/h1&gt;

&lt;p&gt;If we use drawImage to draw an image file from this image file onto the Canvas and getImageData, we get the following data.&lt;br&gt;
Since RGBA does not change in appearance, the RGB separated from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% magick RGBA-canvas.png -alpha off RGB-canvas.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RGBA-canvas.png&lt;/th&gt;
&lt;th&gt;RGB-canvas.png&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2Fbjzbangosvlo8u5ndfn7wq6evcy5" alt="RGBA-canvas.png"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2Fke67cg7kj6agcrjeb3cxbfechcco" alt="RGB-canvas.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the upper part of the image, where the A value is small, has collapsed RGB values.&lt;br&gt;
To make it easier to see, let's zoom in vertically at x8.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RGB-canvas-x8.png&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fzenn-user-upload%2F61n2iz3lsi8zw5afdjrop4iztjp2" alt="RGB-canvas-x8.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;A:0 RGB: all 0&lt;/li&gt;
&lt;li&gt;A:1 RGB: (div 2) 0 or 255&lt;/li&gt;
&lt;li&gt;A:2 RGB: (div 3) 0, 128(0x80), 255(0xFF)&lt;/li&gt;
&lt;li&gt;A:3 RGB: (div 4) 0, 85(0x55), 170(0xAA), 255(0xFF)&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smaller A is, the more coarse the RGB values will be. This phenomenon of jumping values is also called quantization.&lt;/p&gt;

&lt;h1&gt;
  
  
  why
&lt;/h1&gt;

&lt;p&gt;This is because Canvas keeps the ImageData RGBA as premultiplied-alpha.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPUs prefer premultiplication

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/" rel="noopener noreferrer"&gt;http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;To explain premultiplied-alpha briefly, it multiplies alpha by A, so if you multiply RGB by A in advance, you can skip the multiplication. It is also widely used for speeding up the compositing process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RGBA(straight alpha): 255, 128, 64, 128&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the RGBA values above, multiply RGB by 128(/255) to get premultiplied.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RGBA(premultiplied alpha): 128, 64, 32, 128&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since getImageData restores the RGB values based on the RGB values that have been divided by A, A:0 means that all RGB values are 0, and A:1 means that only two RGB values, 0 and 255, can be restored.&lt;/p&gt;

&lt;h1&gt;
  
  
  work around
&lt;/h1&gt;

&lt;p&gt;There is no workaround if you use getImageData to retrieve RGBA that has been put into Canvas.&lt;br&gt;
If you want to get the transparency of an image file accurately, you can use a decoder such as PNG.js, or if you import PNG32 using a WebGL2 texture without using the 2d context of Canvas, you can get ImageData with RGBA intact.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How can I stop the alpha-premultiplication with canvas imageData?

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/23497925/how-can-i-stop-the-alpha-premultiplication-with-canvas-imagedata" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/23497925/how-can-i-stop-the-alpha-premultiplication-with-canvas-imagedata&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  notes
&lt;/h1&gt;

&lt;p&gt;There seems to be a request to change the specification because the values are degraded and inconvenient. However, I think it's probably not possible to do so, because it would mean either throwing away the advantages of premultiplied and making it slower, or keeping two RGBAs in memory, one for straight and one for premultiplied.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ImageData alpha premultiplication #5365

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/whatwg/html/issues/5365" rel="noopener noreferrer"&gt;https://github.com/whatwg/html/issues/5365&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  reference
&lt;/h1&gt;

&lt;p&gt;japanese edition&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/yoya/articles/d874880e1820a5" rel="noopener noreferrer"&gt;https://zenn.dev/yoya/articles/d874880e1820a5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>canvas</category>
      <category>getimagedata</category>
      <category>rgba</category>
      <category>premultipliedalpha</category>
    </item>
  </channel>
</rss>
