loading images without compression
Show older comments
Hello there,
I've made a for loop for filling a struct with png images, this because i need to recall the same images a view times in my application. If i recall my images from the struct i get the images but there seems to be a jpg compression on the png file? This is how i load the struct:
imageDir = 'Edwin\files\'; % Map met de afbeeldingen
imageExt = '.png'; % Extensie van de afbeeldingen
% Lijst van afbeeldingsnamen zonder extensie
imageNames = {'edammer', 'edammer_selected', 'emmentaler', 'emmentaler_selected', 'beemster', 'beemster_selected', ...
'gorgonzola', 'gorgonzola_selected', 'goudse', 'goudse_selected', 'gruyere', 'gruyere_selected', 'manchego', ...
'manchego_selected', 'noordwoudse', 'noordwoudse_selected', 'roquefort', 'roquefort_selected', ...
'kaasfondue3','kaasfondue4','kaasfondue5','kaasfondue6','kaasfondue7','kaasfondue8','kaasfondue9', ...
'edammer_vlag', 'edammer_vlag_selected', 'gorgonzola_vlag', 'gorgonzola_vlag_selected', 'manchego_vlag', ...
'manchego_vlag_selected', 'roquefort_vlag', 'roquefort_vlag_selected', 'haarlem_vlag', 'haarlem_vlag_selected', ...
'basel_vlag', 'basel_vlag_selected', 'gruyere_vlag', 'gruyere_vlag_selected', 'goudse_vlag', 'goudse_vlag_selected', ...
'emmentaler_vlag', 'emmentaler_vlag_selected', 'kaasfondue33','kaasfondue34','kaasfondue35','kaasfondue36','erik0','erik1', ...
'erik2','erik3','erik4','erik5','erik6','erik7'};
% Loop door de lijst van afbeeldingsnamen en laad de afbeeldingen
for i = 1:length(imageNames)
imageName = imageNames{i};
imagePath = [imageDir, imageName, imageExt];
app.imageStruct.(imageName) = imread(imagePath,"png", 'BackgroundColor', [0.94 0.94 0.94]);
end
imageStructfile = app.imageStruct;
save("Edwin\files\imageStruct.mat",'imageStructfile');
And when i load an image from the struct it looks like this:
app.image_Erik.ImageSource = app.imageStruct.erik0;
Can anyone tell me if i am doing something wrong.
If i load the image with app.image_Erik.ImageSource = imread('files/erik0.png'); then i don't see any compression...
Best regards,
Thijs
2 Comments
Answers (1)
Walter Roberson
on 12 Nov 2023
app.imageStruct.(imageName) = imread(imagePath,"png", 'BackgroundColor', [0.94 0.94 0.94]);
The fact that you are specifying the backgroundcolor tells us that your png images are not rgb images. They are probably indexed images -- and you are failing to read and apply the colormap.
30 Comments
Walter Roberson
on 12 Nov 2023
imageDir = 'Edwin\files\'; % Map met de afbeeldingen
imageExt = '.png'; % Extensie van de afbeeldingen
% Lijst van afbeeldingsnamen zonder extensie
imageNames = {'edammer', 'edammer_selected', 'emmentaler', 'emmentaler_selected', 'beemster', 'beemster_selected', ...
'gorgonzola', 'gorgonzola_selected', 'goudse', 'goudse_selected', 'gruyere', 'gruyere_selected', 'manchego', ...
'manchego_selected', 'noordwoudse', 'noordwoudse_selected', 'roquefort', 'roquefort_selected', ...
'kaasfondue3','kaasfondue4','kaasfondue5','kaasfondue6','kaasfondue7','kaasfondue8','kaasfondue9', ...
'edammer_vlag', 'edammer_vlag_selected', 'gorgonzola_vlag', 'gorgonzola_vlag_selected', 'manchego_vlag', ...
'manchego_vlag_selected', 'roquefort_vlag', 'roquefort_vlag_selected', 'haarlem_vlag', 'haarlem_vlag_selected', ...
'basel_vlag', 'basel_vlag_selected', 'gruyere_vlag', 'gruyere_vlag_selected', 'goudse_vlag', 'goudse_vlag_selected', ...
'emmentaler_vlag', 'emmentaler_vlag_selected', 'kaasfondue33','kaasfondue34','kaasfondue35','kaasfondue36','erik0','erik1', ...
'erik2','erik3','erik4','erik5','erik6','erik7'};
num_images = length(imageNames);
num_with_alpha = 0;
% Loop door de lijst van afbeeldingsnamen en laad de afbeeldingen
for i = 1:num_images
imageName = imageNames{i};
imagePath = [imageDir, imageName, imageExt];
[thisimg, thismap, thisalpha] = imread(imagePath,"png", 'BackgroundColor', [0.94 0.94 0.94]);
if isempty(thismap)
rgbimg = thisimg;
else
rgbimg = ind2rgb(thisimg, thismap);
end
if ~isempty(thisalpha); num_with_alpha = num_with_alpha + 1; end
app.imageStruct.(imageName) = rgbimg;
end
if num_with_alpha ~= 0
fprintf('Note: ignored alpha properties on %d of %d images\n', num_with_alpha, num_images );
end
imageStructfile = app.imageStruct;
save("Edwin\files\imageStruct.mat",'imageStructfile');
Thijs Boeree
on 13 Nov 2023
Thijs Boeree
on 13 Nov 2023
Moved: Stephen23
on 14 Nov 2023
Thijs Boeree
on 13 Nov 2023
Moved: Stephen23
on 14 Nov 2023
That's correct. If imread() is called with a single output argument, and it's asked to load an RGBA PNG file, it will not return the original RGB content. It will reduce the RGBA image to RGB by composing it with a solid-color matting. The default color will be black, though the color can be specified explicitly as you're doing here.
% collapse the image by composing it with a colored matting
inpict = imread('image.png','backgroundcolor',[1 0.8 0.3]);
imshow(inpict,'border','tight')
The question is whether that's intended. I see in some places you're calling imread() with one output, and some places, you're using three. In the case you're using three output arguments, you're discarding alpha and using the RGB content alone. That's a huge gamble, because you're assuming that the hidden RGB content is meant to be seen. There's no telling what's in the transparent regions. It's often garbage.
% preserve the original RGB content, but ignore alpha
[inpict,~,alpha] = imread('image.png');
imshow(inpict,'border','tight')
Consider another example. This is a green ellipse with transparency. What's hidden in the transparent regions?

In this case, there aren't any transparent regions left anymore.
% collapse the image by composing it with a colored matting
inpict = imread('tiltellipse.png','backgroundcolor',[1 0.8 0.3]);
imshow(inpict,'border','tight')
In this case, we find that there isn't necessarily any correlation between the apparent object content and the content of the RGB channels alone. The object content exists entirely in alpha. Discarding alpha without composition destroys the image.
% preserve the original RGB content, but ignore alpha
[inpict,~,alpha] = imread('tiltellipse.png');
imshow(inpict,'border','tight')
If you want to preserve the original RGB content without alteration, you must call imread() with all three outputs. If you want to preserve transparency information, you will then have to contend with the fact that nothing in base MATLAB or IPT makes concession for images with transparency. Imshow() can't display them without jumping through hoops, and transporting monolithic RGBA images becomes a problem because of the fact that the few ways they can be used (e.g. by imshow()/imwrite()) requires the alpha to be split into a separate array.
So the question is what you want to do with the image, and whether this is all supposed to be permanent alteration or just something that's done for sake of viewing it. If you're discarding alpha as in the second example, you're essentially destroying the image by throwing away necessary information. Operating on it or saving it would likely be a waste of time. If you want the image composed with a fixed, solid color background, as in the first example, then you can do that via the call to imread(), or you could do it after the fact -- but know that you're still losing transparency information.
Maybe you're just trying to display images without alteration and need to find some way to represent transparent content for temporary visualization purposes?

Thijs Boeree
on 13 Nov 2023
I'm not familiar with appdesigner stuff, since it won't even work properly in my environment. I don't really know how the image displayer behaves. That said, I recalled that uiimage objects behave inconsistently depending on how they're used. See:
To summarize, uiimage() can only handle grayscale or transparency if it reads it directly from a file. If the image comes from the workspace instead, it will not handle either properly, and there is no accessible alphadata property that could be used as one would with image(). If using uiimage() with images from the workspace, any composition must be performed prior to feeding it to uiimage(), since uiimage() only supports RGB, not I/IA/RGBA.
How would composition be handled? That depends what's adequate for your needs. Backing up for a moment, consider what was happening before when the image was loaded with imread():
% let imread() do the matting composition
bgc = [0.94 0.94 0.94]; % approximate figure color
composed = imread('image.png','backgroundcolor',bgc); % RGB
imshow(composed,'border','tight')
In this case, we're basically doing a composition with a solid color matting. We could do the same thing by doing the matting composition outside of imread().
% do the composition manually using base tools
bgc = [0.94 0.94 0.94]; % approximate figure color
[FG,~,FGa] = imread('image.png'); % RGB+A
FGa = im2double(FGa);
composed = FGa.*im2double(FG) + (1-FGa).*permute(bgc,[1 3 2]);
composed = im2uint8(composed); % RGB
imshow(composed,'border','tight')
If we wanted to display the image with its transparency, composited against the figure background (or whatever is rendered beneath it in the uistack, we could do it with image objects like so. This is what happens internally when you call uiimage(filename), but as far as I know, there's no other way to do this with uiimage() due to the inconsistency in its behavior and the lack of accessible AlphaData properties. That said, I'm not super familiar with using uiimage(), so maybe I'm missing something.
% do the composition in-figure using image() objects (not uiimage())
% normally, the default figure background is uint8(240), but not on the forum
[FG,~,FGa] = imread('image.png'); % RGB+A
hi = imshow(FG,'border','tight');
hi.AlphaData = FGa;
For other ideas like the checkerboard matting, the thread I linked above has some examples. See also:
In-figure composition, external solid-color and checkerboard matting; base/IPT or MIMT tools
Checkerboard matting using MIMT joinalpha(), alphasafe(), or imshow2()
Mask generation and checkerboard matting using base/IPT tools only
Some of those answers use MIMT tools, but I also cover base/IPT alternatives.
Thijs Boeree
on 13 Nov 2023
Thijs Boeree
on 14 Nov 2023
Moved: Stephen23
on 14 Nov 2023
DGM
on 14 Nov 2023
It would help to have an example of the file and/or a screenshot of how it appears. Bear in mind that I can't do anything with appdesigner. It's completely broken for me.
If I had to guess, you might be describing aliasing artifacts caused by the nearest-neighbor display interpolation, but that's just a wild guess.
Thijs Boeree
on 14 Nov 2023
DGM
on 14 Nov 2023
Oh I missed that.
I started messing around with building a uifigure() and I think I see what you're talking about -- and it's bad. I'm going to need a bit to dig into this.
Thijs Boeree
on 14 Nov 2023
In $MLROOT/toolbox/matlab/uitools/uicomponents/components/+matlab/+ui/+internal/IconUtils.m
function iconString = getURLFromCData(cdata)
% create ImageComp directory under tempdir
tmpDir = fullfile(tempdir, 'ImageComp');
[~,~,~] = mkdir(tmpDir);
% create tempfile for image writing
tmpFile = fullfile([tempname(tmpDir), '.jpg']);
% convert CData to JPG file
imwrite(cdata, tmpFile);
% get URL for the tmpFile created
iconString = matlab.ui.internal.URLUtils.getURLToUserFile(tmpFile, false);
end
When fed raster image data, it literally writes it to a garbage-tier 70% 4:2:0 downsampled temporary JPG and reads it back. What the hell?
I guess that means if you want to display raster image data with uiimage(), you have to add an extra layer of what should already be completely unnecessary levels of babysitting:
- Make sure any single-channel images are expanded to RGB
- Make sure any logical or signed integer images are converted to uint8 or double
- Write the modified image to a temporary file using some lossless format (e.g. PNG)
- Feed the temp file name to uiimage()
- Deal with cleaning up piles of temporary images as necessary
I guess if you're forced into a workflow using temporary files, that does give you the opportunity to use any alpha content in the temporary PNG -- since uiimage() seems to only support alpha when reading from a file. That's not really much of a silver lining.
I suppose you could alternatively change that part of IconUtils.m to use PNG instead of JPG for the temp file, but that wouldn't give you the opportunity to pass through any available alpha content. There is mention in some of the documentation that RGBA inputs are supported, but I haven't traced through and figured out which cases (if any) actually do, or where any attached alpha winds up. Obviously, if it uses a JPG temp file in certain cases, it can't support alpha, and external observation confirms that. Also consider that modifying IconUtils.m wouldn't fix the problem for anyone else who might use your code.
Unless someone else knows of a good explanation/excuse or a better workaround, I'd recommend filing a bug report. It won't fix the problem in time for your needs (or ever), but I don't know how anyone could consider this to be a feature.
Thijs Boeree
on 14 Nov 2023
It's internal to uiimage() stuff, not imread(). I haven't sussed out everything that it does. If you tell uiimage() to open a filename, it uses a particular routine to read that image, handle its potentially various forms (depth,class,alpha) and render it. If you have raster image data (e.g. from imread()) and you feed that to uiimage(), it converts it to a JPG file and then somehow uses the tempfile that it created. I'm assuming that it probably ends up using the same code as it would normally use when reading an image file in the prior case.
I'm calling it a bug because I don't think it's acceptable to be reading/writing low-quality JPG temp files as part of a graphics rendering routine. It seems so absurd, but it's apparently been this way long enough that I have to ask if I'm the crazy one.
One thing you should know is that whenever you're writing a JPG in MATLAB (e.g. imwrite(), saveas(), etc), it's probably a lower-quality JPG than you expect. Default is either 70% or 75% quality (don't remember). While that could be changed in direct calls to imwrite(), you don't have that opportunity when using saveas(), or internal calls like the one in IconUtils.m. Furthermore, the default is 4:2:0 chroma subsampled, meaning you lose 75% of your color information. That really shows up. As far as I know, that's simply not something that can be configured. While the lossy nature of JPG needs to be weighed in any context, MATLAB makes it more costly than I think most people expect. I think default JPG settings in GIMP are 90% with 4:4:4 downsampling (i.e. none). I imagine Photoshop is similar.
Consider this example of exploiting the direct file reading to handle images in the workspace. I've attached a few test files.
app.UIFigure = uifigure('Visible', 'on');
app.UIFigure.Position = [100 100 940 320];
app.UIFigure.Name = 'MATLAB App';
%fname = 'peppers.png'; % uint8, RGB
fname = 'peppers_rgba.png'; % uint8, RGBA
%fname = 'cmania.png'; % uint8, IA
%fname = 'grid_301.png'; % logical, I
%fname = 'indexedblobs.png'; % uint8, indexed
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% direct file reading
% this handles I/IA/RGB/RGBA/indexed images and multiframe images
% handles a broader range of classes than example #2 does
% but IA/RGBA inputs can only be rendered against the figure background
% there are no other ways to control the matting
app.image1 = uiimage(app.UIFigure);
app.image1.ScaleMethod = 'none';
app.image1.Position = [10 10 300 300];
app.image1.ImageSource = fname;
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% direct raster image data
% need to do extra babysitting because uiimage()
% can't handle raster cdata inputs sensibly
% no I/IA/RGBA/indexed/indexed+transparency or multiframe inputs
% no logical or signed integer inputs
[cdata,CT,adata] = imread(fname); % read file
if ~isempty(CT)
cdata = ind2rgb(cdata,CT);
end
inpict = joinalpha(cdata,adata); % attach any alpha if present
inpict = alphasafe(inpict); % discard alpha by doing checkerboard matting comp
inpict = im2uint8(gray2rgb(inpict)); % make sure image is RGB and a supported class
app.image2 = uiimage(app.UIFigure);
app.image2.ScaleMethod = 'none';
app.image2.Position = [20+300 10 300 300];
app.image2.ImageSource = inpict;
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% raster data -> tempfile -> direct file read
% This creates temp files the same way (and in the same place) that IconUtils.m does.
% Just like IconUtils.m, unused temp files are never cleaned up, even if MATLAB exits.
% Assuming tempdir is on a ramdisk or something, it'll just be wasted memory until you reboot.
% Should be able to handle I/IA/RGB/RGBA/indexed,
% but not indexed+transparency, and no multiframe
[cdata,CT,adata] = imread(fname);
inpict = joinalpha(cdata,adata);
tempfname = createtempfile(inpict,CT,'discardalpha'); % see options
app.image3 = uiimage(app.UIFigure);
app.image3.ScaleMethod = 'none';
app.image3.Position = [30+600 10 300 300];
app.image3.ImageSource = tempfname;
function tempfname = createtempfile(inpict,CT,alphaoption)
% TEMPFILENAME = CREATETEMPFILE(INPICT,[],ALPHAOPTION)
% TEMPFILENAME = CREATETEMPFILE(INPICT,CT,ALPHAOPTION)
%
% INPICT is an I/IA/RGB/RGBA or indexed-color image of any
% standard image class. Multiframe inputs are not supported.
% CT is a Mx3 unit-scale color table associated with
% indexed-color image inputs. When CT is non-empty, INPICT
% must be a single-channel integer-valued index array.
% ALPHAOPTION specifies how IA/RGBA images are handled
% 'keepalpha' will preserve the given alpha and let uiimage()
% handle the compositing with the figure background or
% whatever is beneath it in the uistack.
% 'discardalpha' will flatten the image by composing it
% with a gray checkerboard background.
% create ImageComp directory under tempdir
tmpDir = fullfile(tempdir, 'ImageComp');
[~,~,~] = mkdir(tmpDir);
% create tempfile for image writing
tempfname = fullfile([tempname(tmpDir), '.png']);
% prepare and write temp file
if isempty(CT)
switch lower(alphaoption)
case 'keepalpha'
% keep any attached alpha and let uiimage() handle the compositing
[cdata adata] = splitalpha(inpict);
if isempty(adata)
imwrite(cdata,tempfname);
else
imwrite(cdata,tempfname,'alpha',adata);
end
case 'discardalpha'
% discard alpha by doing checkerboard matting composition
cdata = alphasafe(inpict);
imwrite(cdata,tempfname);
end
else
imwrite(inpict,CT,tempfname);
end
end
The third example avoids the JPG-ification problem, and it works around a bunch of the class/depth/alpha limitations.
For my own convenience, I wrote this using some MIMT tools. I also didn't consider multiframe images and some types of indexed images.
Thijs Boeree
on 16 Nov 2023
Stephen23
on 16 Nov 2023
"My version of matlab doesn't recognizes the joinalpha function. Is it in a toolbox?"
DGM refers to (and their code examples use) their own MIMT toolbox which is available for download here:
Thijs Boeree
on 17 Nov 2023
Yes, the direct file reading method (the first uiimage() example) does seem to load the file nondestructively, and it preserves alpha content. It will simply compose the image over the figure background (i.e. uint8(240)), or whatever is under it in the uistack.
The problem comes if you need to display an image from the workspace (e.g. one that is being worked on). The second uiimage() example demonstrates the problem. Uiimage() just simply does not acceptably handle inputs from the workspace. Not only is it destructive, it creates an inconsistency in the image types that are supported.
The third example allows images in the workspace to be displayed using the direct file reading method. There are some limitations, given that the temp file is a PNG (e.g. can't support multiframe images), but it does allow the opportunity to control how any alpha is handled.
'keepalpha' lets uiimage() compose the transparent image as in the first example.

'discardalpha' gets rid of the transparency by composing with checkerboard matting

So the third example can do it whichever way you want, without ruining the image with JPG crust.

Thijs Boeree
on 17 Nov 2023
Image Analyst
on 17 Nov 2023
Depends on what the format is. If it's some proprietary format, then no, the axes may not understand how to deal with the variable when it tries to display it. If it's a logical binary image, you can use imread but if it's something peculiar, then you may need a custom reader for that type of file.
DGM
on 17 Nov 2023
What is the binary file? All logical images are binary images, but not all binary images are logical-class.
Uiimage cannot support any logical image when fed it from the workspace, but it could support binarized uint8, uint16, etc so long as it has exactly 3 channels.
It's not clear what configurations/formats are supported if the image is read directly from a file.
Thijs Boeree
on 17 Nov 2023
DGM
on 17 Nov 2023
I'm confused. Are you trying to load the image directly from disk, or are you trying to load it from the workspace?
Thijs Boeree
on 19 Nov 2023
DGM
on 21 Nov 2023
That's basically the gist of it. Needing to rely upon direct file reads makes uiimage() significantly less practical in my opinion. I've filed a bug report, but that doesn't really fix things for current needs.
Andrew Horchler
on 29 Jan 2025
Thank you all. I was going crazy until I found this thread. Unfortunately this bug (JPEG compression on uiimage loading from workspace variable) is still an issue in R2024b and the R2025a prerelease. I've also not seen any documentation clearly stating this limitation if MathWorks doesn't consider it a bug.
Categories
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!







