Transparency limited by rounding issues

According to the math of alpha compositing and color blending (also on Wikipedia), no matter the transparency (alpha), a sufficiently large number of overlapping dots of a color should look the same as an opaque dot of the same color. Yet, while attempting to visualize the density of point clouds using point transparency, I found that the color never saturated, no matter how dense the point cloud. Instead, the "final" color reached depended on the alpha of the dots, which it should not. By "final", I mean the color for which adding more dots doesn't change the color. To demonstrate this, in the following example, in each pass through the for loop, I'm plotting a very large number, 100,000, of coincident points of the same color and transparency; each pass is for a different alpha (transparency). The number of coincident points plotted is large enough that the apparent color is constant, i.e. plotting more coincident points doesn't change the apparent color.
alphas=[0.001,0.002,0.01,0.1,1]; %transparencies to use, 1=opaque
figure
for cnt1=1:numel(alphas)
%plot 1e5 coincident dots at x=cnt1, y=0, and of transparency alphas(cnt1)
scatter(zeros(1e5,1)+cnt1,zeros(1e5,1),100,'k','markerfacecolor',[0,0.7,0.7],'markeredgecolor',[0,0,0],'markerfacealpha',alphas(cnt1))
hold on
xticklabels{cnt1}=num2str(alphas(cnt1));
end
axis([1,numel(alphas),-1,1])
set(gca,'xtick',1:numel(alphas),'xticklabel',xticklabels,'ytick',[])
xlabel('alpha')
Per the first sentence of this post, all the dots in the figure this makes should appear, as an integer RGP triplet, as [0,179,179], but due to the issue, only the opaque dot achieves this color. The apparent colors are, from left to right:
[255,255,255], [128,255,255], [41,221,221], [4,4,183], [0,179,179]
Note the left-most dot is transparent; the transparencies also vary in an incorrect fashion. Via some research and testing, I've found that the exact problem appears to be due to roundoff error in the calculation of color with transparency. Specifically, working with integer colors from 0 to 255, when the change in apparent color due to adding one more dot is smaller than 0.5, adding another dot doesn't change the apparent color. This is a function of the level of transparency used: the higher the transparency, the larger the error in "final" color. It is also affected by the background color. The higher the transparency, the more severe the problem, as the example figure shows.
I'm using the default hardware OpenGL renderer. If I switch to opengl software, the problem worsens. If I switch to painters, it changes marginally. Behavior is consistent across multiple Windows-based machines.
Is this a fundamental limitation of 2D rendering in OpenGL, an issue with Matlab's implementation, or is there some way around it?
Per the discussion in the comments with J. Alex Lee, we can see that in 3D, this does not happen.
alphas=0.1; %transparency
intervalRepeat=1; %step size for repeats
nRepeat=30; %maximum number of repeats (overlapping dots) to plot
% alphas=0.01; %transparency
% intervalRepeat=10; %step size for repeats
% nRepeat=300; %maximum number of repeats (overlapping dots) to plot
%2d plot
figure
% dockit
for cnt1=1:intervalRepeat:nRepeat
%plot cnt1 coincident dots in 2D at x=cnt1, y=0 and of transparency alphas
scatter(cnt1+zeros(cnt1,1),zeros(cnt1,1),100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',alphas)
hold on
end
%plot a final opaque dot on the right-hand side
scatter3(cnt1+intervalRepeat,0,1,100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',1)
grid on
% title('2D')
%3d plot
% figure
% % dockit
for cnt1=1:intervalRepeat:nRepeat
%plot cnt1 coincident dots in 3D at x=cnt1, y=1, z=1:cnt1 and of transparency alphas
scatter3(cnt1+zeros(cnt1,1),zeros(cnt1,1)+1,1:cnt1+zeros(cnt1,1),100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',alphas)
hold on
end
%plot a final opaque dot on the right-hand side
scatter3(cnt1+intervalRepeat,0+1,1,100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',1)
xlabel('number of overlapping points')
ylim([-1,2])
set(gca,'ytick',[0,1],'yticklabel',{'2D','3D'})
% grid off
% title('3D')
view(2) %make view angle be the same as that of a 2D plot
Although this plots the same thing in 2D and 3D, only the 3D leads to the correct alpha compositing and color blending. If you run the code, you can rotate the view to see the 3D dots.

11 个评论

This isn't what you expect?
No, that behavior is incorrect. As I noted in the question, the behavior can be tracked to integer round off error in the color calculation. Note that I'm plotting 1E5 overlapping dots. I've got a much lengthier code that steps through the details, but I'd hoped this example would be self-explanatory.
Do you expect all the dots in your minimal example to look like the right-most dot?
This page explains alpha blending math that I think is being used. If this calculation is carried out with persistent floating point precision when rendering each subsequent layer, then, yes, rounded to the nearest 0 to 255 integer, they would all have the same RGB value, and so would appear the same. This is also how I found that the actual color values that are produced occur when the incremental step size drops below 0.5, implying a roundoff issue. Should I provide a more in-depth illustration in my original question?
More to the point, the colors displayed do not change when plotting further overlapping dots of the same color+transparency, i.e. they've reached a steady state. This is why I chose 1E5 points--I'll clarify this in the question now.
To the point of the apparent color of overlapping dots with nominally the same RGBA values, I didn't originally appreciate that you were attempting to paint 100,000 dots over each other. But the speed with which the code executes suggests that Matlab isn't doing that...I guess it's "short-cutting" and interpreting that the collection as a single point with the color that you specified. Maybe another way to test is to make a 3D scatter and overlap your data points sequentially in the depth so that there Matlab is forced to draw each point.
This test shows that actually painting over dots with fractional alpha values does inteed change the apparent color within the 2D plot framework:
N = 30;
figure(1); hold on;
alphas=[0.001,0.002,0.01,0.02,0.05,0.1];
for j = 1:length(alphas)
alph = alphas(j)
for i = 1:N
scatter((i:N),j*ones(size(i:N)),100,'k',...
'MarkerFaceColor',[0,0.7,0.7],...
'MarkerEdgeColor',[0,0,0],...
'MarkerFaceAlpha',alph);
end
end
set(gca,'XLim',[0,N+1],'YLim',[0,length(alphas)+1])
The result I see is this:
The second lowest row has alpha=0.002, and within N=30 paint-overs, you see appreciable change in apparent color.
Right, I didn't doubt that. I had the same thought with regard to how quickly it draws, rather that it saturates (i.e. ceases to change color) after a certain number of dots, and that the color it converges to is wrong. Here's a longer code with most of my testing, which hopefully adds clarity.
Note that the 'o-' curve shows the anticipated color with an increasing number of dots, similar to your example. This curve always converges to the opaque equivalent color with a sufficient number of dots. The actual drawn color, however, ceases to change well before that. The s- curve on yyaxis right shows the anticipated change in color with sequential dots. So, the fact that the 'o-' and '--' curves intersect at the same number of dots as when the 's-' curve reaches a color diff of 0.5 supports my roundoff hypothesis.
%July 19, 2020
%
% OVERVIEW
%I noticed that the apparent final color resulting from LOTS of
%semi-transparent overlapping dots is dependent on the background color and
%the dots' alpha (transparency) value. But, it seemed to me that if there
%are enough dots, it should converge to (very nearly) the same color. So,
%I sought out to investigate this.
%
%
% IMPLICATIONS
%Plotting many overlapping high transparency (low alpha) graphics objects
%is problematic because the apparent color seen on the screen will be far
%from the color it intuitively should be.
%
%I (not in this code) also tried setting the axes and figure colors to
%'none' (I didn't see an alpha value for them, so I assume this is
%functionally the same'). I'd hoped this would serve as a fix to the
%problem. It does affect the apparent color of a dense point cloud, just
%not as hoped.
%
%
% RESULTS
%The issue seems to be that the color and alpha blending method is done
%using integer math, with each calculation being done sequentially, such
%that the solution never converges to where it should.
%
%My calculations here assume the blending is done by starting at the lowest
%level and blending from bottom-up. If one were to start with the highest
%object and blend top-down, I don't think the problem would occur, though
%this might result in other issues, I don't know. Note that I've been
%using floating point math, so the results here are almost certainly
%somewhat different from what's actually done in the graphics computations.
%
%
% METHODS
%This page
%https://en.wikipedia.org/wiki/Alpha_compositing
%gives the formula for determining the resulting color and transparency
%from two overlapping objects of given color and transparency. This math
%agrees with what I found here for OpenGL (what Matlab uses):
%https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/
%
%To determine the final apparent color, I took a screenshot of the Matlab
%figure, then opened that in Microsoft Paint and used the Color Picker to
%select the color and then looked at the (R,G,B) value.
%%
clear all
close all
clc
%% Observations
%Tests with dot alpha 0.1
origColor=[0.9,0.9,0.9]; %dot color used in original plot
origAlpha=0.1; %dot transparency used in original plot
cyWhite=255 %white background
cyBlack=0 %black background
cfinalWhite=234 %observed final color white background when dot alpha is 0.1
cfinalBlack=226 %observed final color black background when dot alpha is 0.1
backgroundAlpha=1; %transparency of background
%Tests with dot alpha 0.01
% origColor=[0.9,0.9,0.9]; %dot color used in original plot
% origAlpha=0.01; %dot transparency used in original plot
% cyWhite=255 %white background
% cyBlack=0 %black background
% cfinalWhite=255 %observed final color white background when dot alpha is 0.01
% cfinalBlack=187 %observed final color black background when dot alpha is 0.01
% backgroundAlpha=1; %transparency of background
%% Input run state
%White
cfinal=cfinalWhite
cy=cyWhite
ay=backgroundAlpha
%Black
% cfinal=cfinalBlack
% cy=cyBlack
% ay=backgroundAlpha
cx=mean(origColor).*255 %dot color
ax=origAlpha %dot transparency
nRepeat=20; %max number of overlapping dots to do
% nRepeat=100; %max number of overlapping dots to do
% nRepeat=1000; %max number of overlapping dots to do
opengl info %this displays opengl rendering engine info, should be opengl hardware
% opengl software %I tried switching to software rendering, was
% %originally hardware, but this made the problem
% %worse.
%% Color and transparency calcs
%Functions to calculate resulting color and alpha. Note the color function
%should work for a single (R, G, or B) color or for a (R,G,B) vector.
%Object x is on top of object y.
%Equations taken from
%https://en.wikipedia.org/wiki/Alpha_compositing
fc=@(vcx,vcy,vax,vay)(vcx.*vax+vcy.*vay.*(1-vax))./... %color_combined = (color_x*alpha_x + color_y*alpha_y*(1-alpha_x)) /
(vax+vay.*(1-vax)); % (alpha_x + alpha_y*(1-alpha_x))
fa=@(vax,vay)(vax+vay.*(1-vax)); %alpha_combined = alpha_x + alpha_y*(1-alpha_x)
cc=fc(cx,cy,ax,ay); %combined
ac=fa(ax,ay);
ct=nan(nRepeat,1);
at=nan(nRepeat,1);
ct(1)=cc;
ac(1)=ac;
for cnt1=2:nRepeat
cc=fc(cx,cc,ax,ac);
ac=fa(ax,ac);
ct(cnt1)=cc;
at(cnt1)=ac;
end
cc
ac
%% Plots
dotLevels=round(linspace(1,nRepeat,10)); %which dots to plot
figure
% dockit
plot(1:nRepeat,ct,'o-')
hold on
set(gca,'color',[cy,cy,cy]./255)
plot([1,nRepeat],[cx,cx],'--','linewidth',1)
plot([1,nRepeat],[cfinal,cfinal],'--','linewidth',3)
for cnt1=1:numel(dotLevels)
scatter(repmat(dotLevels(cnt1),dotLevels(cnt1),1),repmat(cfinal.*0.75,dotLevels(cnt1),1),...
'markerfacecolor',origColor,'markeredgecolor','k','markerfacealpha',origAlpha,'sizedata',100)
end
xlabel('number of dots')
ylabel('apparent color')
yyaxis right
plot(2:nRepeat,diff(ct),'s-')
ylabel('diff of colors')
legend({'color progression with more dots';'dot color';'observed final color';'incremental change in color'},'location','best')
Confirmed this is something that doesn't happen with a 3D plot (code below). Though I don't know that this tells us anything more about whether it's an OpenGL-level or Matlab-level issue, and whether it can be mitigated in 2D.
alphas=0.1;
nRepeat=30;
%2d plot
figure
for cnt1=1:nRepeat
scatter(cnt1+zeros(cnt1,1),zeros(cnt1,1),100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',alphas)
hold on
end
scatter3(cnt1+1,0,1,100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',1)
title('2D')
%3d plot
figure
for cnt1=1:nRepeat
scatter3(cnt1+zeros(cnt1,1),zeros(cnt1,1),1:cnt1+zeros(cnt1,1),100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',alphas)
hold on
end
scatter3(cnt1+1,0,1,100,'k',...
'markeredgecolor','none','markerfacecolor',[0,0.7,0.7],'markerfacealpha',1)
title('3D')
grid off
view(2)
I'll take a look at your detailed test at some point, but my point is maybe they're not "converging to the wrong color", but because of the way Matlab chooses to render the collection as a single point, the apparent color is exactly that you would expect at the alpha value plus a white background. Is this a possibility you have considered/verified?
It occurred to me that I can test that very quickly:
alphas=[0.001,0.002,0.01,0.1,1];
figure
hold on
for cnt1=1:numel(alphas)
scatter(zeros(1e5,1)+cnt1,zeros(1e5,1),100,'k','markerfacecolor',[0,0.7,0.7],'markeredgecolor',[0,0,0],'markerfacealpha',alphas(cnt1))
scatter(cnt1,1,100,'k','markerfacecolor',[0,0.7,0.7],'markeredgecolor',[0,0,0],'markerfacealpha',alphas(cnt1))
end
axis([0,numel(alphas)+1,-1,2])
I think this is the test you should have led with. Now the question is clearer.
And I now I can disappointly say that I have no futher insights to help :)
I had, but that's not what it's doing. The only explanation I see for the particular colors produced is the rounding issue. In my (somewhat limited) knowledge of alpha compositing, there's no other reason for the color that is produced. 20 colocated dots with alpha 0.1 shouldn't lead to the same color as 100,000 dots with the same alpha.

请先登录,再进行评论。

 采纳的回答

T A
T A 2020-8-28
编辑:T A 2020-8-28
I contacted Mathworks support and, after a dialog, they posted this question and answer:
In it, they confirm my suspicion that this is an integer rounding issue and that it is related to lower-level graphics controls. As an independent test, I used a drawing program, Inkscape, to draw a sequence of overlapping semi-transparent dots, just as in this test. It showed the same behavior of converging to the wrong color. The observed numeric color values were different, probably because the background color and alpha were different. Here's a screen shot of it:

更多回答(0 个)

类别

帮助中心File Exchange 中查找有关 Graphics Performance 的更多信息

产品

版本

R2020a

标签

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by