nonsensical imread behavior with multiframe gifs in R2019b
17 views (last 30 days)
Show older comments
I have been working primarily in R2009b and R2015b. When some users brought up some issues they were having with my tools in R2019a/b, I borrowed access to another computer with R2019b to straighten out those issues. Along the way I found out that I could no longer read muiltiframe gifs correctly in R2019b. I normally use my own gifread/gifwrite tools for this, but for the sake of certainty, I'm going to do most of these examples the long way using imread, imwrite, and imfinfo.
I found that certain simple images could be read correctly, but most images with unique LCTs couldn't. I will start with a simple gif which does work:
%% try an existing gif using mostly native tools
originalfile='d6.gif';
rewritefile1='d6rw2019-ind.gif';
rewritefile2='d6rw2019-rgb.gif';
% read the original image using imread directly
[A, ~]=imread(originalfile,'Frames','all');
s=size(A);
nframes=size(A,4);
Amap=zeros([256 3 1 nframes],'double');
infostruct=imfinfo(originalfile);
for f=1:nframes
thismap=infostruct(1,f).ColorTable;
Amap(1:size(thismap,1),:,:,f)=thismap; % LCT might not be full-length
end
% show frame 10 for inspection of indexed copy (look at SE corner with datatip tool)
% the background is solid and uniform, no artifacts. all pixels are index 7, mapped to [0.247 0.251 0.243]
imshow(A(:,:,:,10),Amap(:,:,:,10));
% make an RGB copy
Argb=zeros([s(1:2) 3 nframes]);
for f=1:nframes
Argb(:,:,:,f)=ind2rgb(A(:,:,:,f),Amap(:,:,:,f));
end
% show first 12 frames for simple web display
imshow2(imtile(addborder(Argb,2,[127 255 60]/255),[3 4]),'invert','tools')
Clearly, this image is read correctly. If the image is written directly using the indexed data and original CTs, the image can still be read back correctly:
%% continue
% rewrite the file directly using imwrite and original indexed image and maps
nframes=size(A,4);
for f=1:nframes
if f==1
imwrite(A(:,:,:,f),Amap(:,:,:,f),rewritefile1)
else
imwrite(A(:,:,:,f),Amap(:,:,:,f),rewritefile1,'writemode','append')
end
end
% read the second image using imread directly
[B, ~]=imread(rewritefile1,'Frames','all');
s=size(B);
nframes=size(B,4);
Bmap=zeros([256 3 1 nframes],'double');
infostruct=imfinfo(rewritefile1);
for f=1:nframes
thismap=infostruct(1,f).ColorTable;
Bmap(1:size(thismap,1),:,:,f)=thismap; % LCT might not be full-length
end
% show frame 10 for inspection of indexed copy (look at SE corner with datatip tool)
% the background is solid and uniform, no artifacts. all pixels are still index 7
imshow(B(:,:,:,10),Bmap(:,:,:,10));
% map is read exactly as written
imrange(Amap-Bmap) % returns global max and min of [0 0]
% make an RGB copy for display
Brgb=zeros([s(1:2) 3 12]);
for f=1:12
Brgb(:,:,:,f)=ind2rgb(B(:,:,:,f),Bmap(:,:,:,f));
end
% show first 12 frames for simple web display
imshow2(imtile(addborder(Brgb,2,[60 127 255]/255),[3 4]),'tools')
However, if the image is converted to RGB and back to indexed and written, the image can no longer be read correctly.
%% continue
% rewrite the file directly using imwrite, but use the RGB image instead
nframes=size(A,4);
Cmapwritten=zeros([256 3 1 nframes],'double');
for f=1:nframes
[thisframe thismap]=rgb2ind(Argb(:,:,:,f),256);
Cmapwritten(1:size(thismap,1),:,:,f)=thismap;
if f==10; writtensample=thisframe(130:140,130:140,:), end
if f==1
imwrite(thisframe,thismap,rewritefile2)
else
imwrite(thisframe,thismap,rewritefile2,'writemode','append')
end
end
% read the second image using imread directly
[C, ~]=imread(rewritefile2,'Frames','all');
s=size(C);
nframes=size(C,4);
Cmap=zeros([256 3 1 nframes],'double');
infostruct=imfinfo(rewritefile2);
for f=1:nframes
thismap=infostruct(1,f).ColorTable;
Cmap(1:size(thismap,1),:,:,f)=thismap; % LCT might not be full-length
end
% show frame 10 for inspection of indexed copy (look at SE corner with datatip tool)
% the background has worm artifacts, dominant index is 35 (not what was written)
imshow(C(:,:,:,10),Cmap(:,:,:,10));
readsample=C(130:140,130:140,:,10)
% map is read exactly as written
imrange(Cmapwritten-Cmap)
% make an RGB copy for display
Crgb=zeros([s(1:2) 3 12]);
for f=1:12
Crgb(:,:,:,f)=ind2rgb(C(:,:,:,f),Cmap(:,:,:,f));
end
% show first 12 frames for simple web display
imshow2(imtile(addborder(Crgb,2,[255 60 127]/255),[3 4]),'tools')
Inspecting the BG ROI of frame 10 reveals that the indices of the read data don't correspond to what was written.
writtensample =
11×11 uint8 matrix
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32
readsample =
11×11 uint8 matrix
35 35 35 35 35 35 35 35 35 35 35
35 35 35 35 35 35 35 35 35 35 35
35 35 22 35 35 2 22 35 35 35 35
22 2 22 35 22 22 22 2 22 22 35
35 22 22 2 22 35 22 22 35 35 2
35 35 35 22 22 2 22 35 22 35 22
35 35 35 35 35 22 22 2 22 35 22
35 35 35 35 35 35 35 22 22 2 22
35 35 35 35 35 35 35 35 35 22 22
35 35 35 35 35 35 35 35 35 35 35
35 35 35 35 35 35 35 35 35 35 35
So imread isn't reading the image data block correctly, but the object content is still there Iit's still shaped like a d6). At first I thought this might be some LZW decoding bug, but then I accidentally found this baffling case:
%% try to "heal" the bad indexed image by using the wrong map
clc;
Crgbmagic=zeros([s(1:2) 3 12]);
for f=1:12
Crgbmagic(:,:,:,f)=ind2rgb(C(:,:,:,f),Cmap(:,:,:,1));
end
imshow2(imtile(addborder(Crgbmagic,2,[255 60 127]/255),[3 4]),'tools')
So if I use the wrong colormap, it almost looks right. The worm artifacts are still there, but they're very subtle. I have no idea how this is possible if the indexed image itself is being read incorrectly
So I guess I can sum this up into three core questions:
- Can this behavior be replicated by anyone else?
- Why can't imread read the image data for multiframe images correctly?
- Why do the incorrect images almost magically work with the wrong color table?
I've searched for bug reports and haven't found anything regarding this. I apologize for making this post so ridiculously long. If anyone tries to run the code blocks, just comment out the lines with the custom tools; either that or feel free to get my "Image Manipulation Toolbox" from the FEX.
EDIT:
As soon as I had posted that and went to rest, I had by a sinking suspicion. The color tables are correct, The image data is incorrect, but loosely matches when using the first color table. The incorrect image data has worm artifacts. Why would there be worm artifacts? Error diffusion dithering in flat image regions. That's why. Surely they aren't remapping all the frames to match CT1?
After a bit of digging, I found this (line 263 readgif.m)
% See geck:g1678142: Currently imread returns only the first local
% color table (if present).
% Using the first local color table to read all the frames later
% shows distorted frames of the image.
%
% In order to fix this issue, rescale the frames (starting from second
% frame) such that it works correctly when rendered using the first
% local color table.
So instead of supporting the output of multiple color tables, imread deliberately remaps it to fit the first color table instead. I guess I can scrub those prior three questions and ask some others.
- How is this not a bug? Even disregarding the needlessly destructive adulteration of the image data, I'm sure anyone can think of a scenario where presuming that all LCTs are similar would fall flat on its face. This turns imread's multiframe gif handling from "clumsy" to "broken". Should I just report this?
- What versions does this imapct? I only have tried R2015b and R2019b. Like I said, I haven't found any mention of this elsewhere. The number in readgif.m hasn't turned up any results, though maybe i'm looking in the wrong place. Has this been fixed already in newer versions?
- Is there any practical way to correctly read multiframe gifs anymore? I can set write permissions on readgif.m and comment out lines 304, 337, 366, 396 to fix it, but this is not something that any student in a lab can do. My own gifread tool has a workaround mode for an old bug (#813126), which also serves as a workaround for this one, but that requires external tools to split the file -- something which won't work for most people either.
3 Comments
Stephen23
on 20 Nov 2020
"Should I just report this?"
Yes. As you say, requiring destructive data changes is not a feature, it is a bug.
Accepted Answer
DGM
on 26 Nov 2020
Edited: DGM
on 26 Nov 2020
2 Comments
Stephen23
on 21 Apr 2023
Edited: Stephen23
on 21 Apr 2023
"I assert that silently altering image data needlessly is inappropriate for a technical environment like Matlab."
Agreed.
Very impressive analysis, by the way. I would give this one hundred votes, if I could.
Just out of curiousity: what happens if the input idx is specified?:
Does IMREAD then use the idx-th frame and LCT, or does it still perform that ugly rigmarole involving the 1st LCT?
More Answers (0)
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!