How to get the RGB values of each point on a surface when illuminated by 'light' objects?

5 views (last 30 days)
I generated a surface with 200x200 points and illuminated it under some light. Now I want to know how the color of each pixel/point is perceived when the surface is viewed from top. I looked at the 'CData' matrix of the surface but it just returns the z-values of the surface. What I want is a 200x200x3 array with RGB values for the second image below and haven't figured out how to do so. I hope my inquiry makes sense.
% generating 200 by 200 surface
m = 200;
n = 200;
x = single(linspace(-8,8,m));
y = single(linspace(-8,8,n));
[X,Y] = meshgrid(x,y);
%Z = (4-abs(X)) + (4-abs(Y));
Z = X.^3-3.*X.*Y.^2;
s = surf(X,Y,Z);
s.EdgeColor = 'none';
% making light objects
l1 = light;
l1.Position = [160 400 80];
l1.Style = 'local';
l1.Color = [0 0.8 0.8];
l2 = light;
l2.Position = [.5 -1 .4];
l2.Color = [0.8 0.8 0];
% painting the surface red
s.FaceColor = [0.9 0.2 0.2];
% assigning reflectance values
s.FaceLighting = 'gouraud';
s.AmbientStrength = 0.3;
s.DiffuseStrength = 0.6;
s.BackFaceLighting = 'lit';
s.SpecularStrength = 1;
s.SpecularColorReflectance = 1;
s.SpecularExponent = 7;

Accepted Answer

DGM on 9 Jan 2023
Moved: DGM on 9 Jan 2023
I don't have exportgraphics(), and I can't seem to ever get predictably-clean (i.e. unpadded) results from saveas or print, so I'm not bothering with any of those. truesize() doesn't work without an image or texturemapped surface in the axes, so I'm going to ignore that for now as well.
It's hardly elegant, but here's one workaround. This only really works so long as the figure is undocked.
% ... prior plotting stuff
% make sure axes box matches the data extents
xlim([-8 8])
ylim([-8 8])
% make sure the figure is large enough for the sample
set(gcf,'position',[0 0 1 1]);
% set size of axes
set(gca,'position',[100 100 200 200]);
% get screenshot of axes
thisframe = frame2im(getframe(gca));
rawframesize = size(thisframe) % this should match the axes height,width
% instead of trying to reset the figure, i'm just going to close it
close gcf
% show the captured image
Ashraful Haque
Ashraful Haque on 9 Jan 2023
Moved: DGM on 9 Jan 2023
I will play around with your suggestion to see what seems to be the issue but getting an image with larger than desired dimensions is not necessarily a bad thing for my purpose. Also, I don't know how to mark your response as the accepted answer since it's a comment to another answer. If you could post your response an an official answer, that would be great. thanks

Sign in to comment.

More Answers (4)

Walter Roberson
Walter Roberson on 8 Jan 2023
Unfortunately you need to use techniques such as exportgraphics or getframe
... Unless you are lucky enough to find something in the File Exchange.

Sign in to comment.

Sulaymon Eshkabilov
Sulaymon Eshkabilov on 8 Jan 2023
If understood your question correctly, this might be the solution:
s.SpecularStrength = 1;
s.SpecularColorReflectance = 1;
s.SpecularExponent = 7;
%% Create an RGB image
RGBim(:,:,1)=X; RGBim(:,:,2) = Y; RGBim(:,:,3)=Z;
% See pixelvalues
  1 Comment
Ashraful Haque
Ashraful Haque on 8 Jan 2023
Thank tou for responding. What I want is a 200x200x3 array that can reproduce the second image in the question with the function imshow. I edited my question for a bit more clarification.

Sign in to comment.

Ashraful Haque
Ashraful Haque on 9 Jan 2023
Edited: Ashraful Haque on 9 Jan 2023
Matlab's print and exportgraphics functions allow me to save the top view of the surface plot as an image. And I can get the rgb values from the saved image. But the saved image has a white padding that I can not seem to get rid of. So I ended up using the export_fig function from File Exchange which gets rid of the white padding by default. So I just added the following lines to my original code and got what I wanted. Nonetheless, this is a very roundabout way of solving my issue and I hope someone has a better solution.
% makes the axes invisible
set(gca, 'Visible', 'off')
% extracts the RGB info of the figure
% returns an array with dimensions axbx3
% value of a and b depend on the figure window size on screen
img = export_fig();
% resize the array from axbx3 to 200x200x3
img = imresize(img,[200 200]);

DGM on 9 Jan 2023
In a sense, this is a non-serious answer, but perhaps consider this as tangential commentary.
For what it's worth, this is what I had to do to get it to behave in R2015b. I don't know if this works for anyone else, but maybe it demonstrates a concept.
% plot some data of arbitrary size
p = peaks(200);
s = pcolor(p);
s.EdgeColor = 'none';
% make sure axes box matches the data extents
axis tight
% make sure the figure is large enough for the screenshot
set(gcf,'position',[0 0 1 1]);
% set size of axes by trial and error
axsz0 = [200 200]; % [x y]
bg = permute(get(gcf,'color'),[1 3 2])*255; % background color
actualsize = [0 0]; % initialize
axsz = axsz0; % initialize
while any(actualsize ~= axsz0)
set(gca,'position',[100 100 axsz]);
% try to make behavior more consistent?
% neither option seem to work 100%, but both are better
% get frame
thisframe = frame2im(getframe(gca));
% get mask describing padding
% i'm assuming padding occurs on N and E edges (i can't guarantee that)
% i'm using bsxfun() because i'm testing in R2015b
padimg = abs(bsxfun(@minus,double(thisframe),bg));
padimg = all(padimg<2,3);
% calculate padding amount and base image geometry
% padding color is only inconsistently equal to figure color
% corners exhibit occasional gray spur pixels
% hence the soft check that at least 98% of pixels match
rawframesize = [size(padimg,2) size(padimg,1)]; % [x y]
padding = [sum(mean(padimg,1)>0.98) sum(mean(padimg,2)>0.98)]; % [x y]
actualsize = rawframesize-padding % [x y]
% try to correct for padding or improper sizing
axsz = axsz.*axsz0./actualsize;
% crop out the good portion of the padded image
% again, i'm assuming padding only occurs on N and E edges
outpict = thisframe(1+padding(2):end,1:actualsize(1),:);
On the first pass, this usually returns an oversize image which, when the padding is removed, is the correct size. The added padding is typically 1-4px. Occasionally, the base geometry or padding will be different (by 10-40px), but the routine will correct for it within about 3-5 attempts. As I mentioned, this doesn't happen for me in R2019b (hooray?). In R2009b, getframe() consistently returns an unpadded image exactly 1px oversize (though other concessions need to be made for the script to work that far back). I have no idea what this will do in a different environment.
One might ask, if getframe() returns a correctly-sized base image with some random additional padding, why not call getframe() with an explicit geometry so as to omit said padding? in R2015b, calling getframe() with an explicit geometry will return a predictably-sized frame with an unpredictable amount of padding. In other words, instead of the raw frame (outer) geometry being random, the base image (inner) geometry is random. I decided to manipulate the axes geometry instead; it solves more potential problems that way anyway.
Am I missing some magic bullet that makes figure capture something other than an unpredictable mess? Something that I can expect works the same way on another computer ... or even the same computer? Something that I can actually recommend to anyone without immediately causing them disappointment?
Cropping and resizing the captured frame after the fact might be sufficient, but the fact that anything so routine involves an unpredictable element is bothersome. Maybe that's just a side effect of dealing with all that's involved in rendering across different platforms.
As I use R2009b rarely these days, I've forgotten how much faster it can be at these things.


Find more on Convert Image Type in Help Center and File Exchange

Community Treasure Hunt

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

Start Hunting!

Translated by