@@ -86,32 +86,19 @@ torch::Tensor decode_gif(const torch::Tensor& encoded_data) {
86
86
// This check should already done within DGifSlurp(), just to be safe
87
87
TORCH_CHECK (num_images > 0 , " GIF file should contain at least one image!" );
88
88
89
- // Note:
90
- // The GIF format has this notion of "canvas" and "canvas size", where each
91
- // image could be displayed on the canvas at different offsets, forming a
92
- // mosaic/picture wall like so:
93
- //
94
- // <--- canvas W --->
95
- // ------------------------ ^
96
- // | | | |
97
- // | img1 | img3 | |
98
- // | |------------| canvas H
99
- // |---------- | |
100
- // | img2 | img4 | |
101
- // | | | |
102
- // ------------------------ v
103
- // The GifLib docs indicate that this is mostly vestigial
104
- // (https://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html), and
105
- // modern viewers ignore the canvas size as well as image offsets. Hence,
106
- // we're ignoring that too:
107
- // - We're ignoring the canvas width and height and assume that the shape of
108
- // the canvas and of all images is the shape of the first image.
109
- // - We're enforcing that all images have the same shape.
110
- // - Left and Top offsets of each image are ignored as well and assumed to be
111
- // 0.
112
-
113
- auto out_h = gifFile->SavedImages [0 ].ImageDesc .Height ;
114
- auto out_w = gifFile->SavedImages [0 ].ImageDesc .Width ;
89
+ GifColorType bg = {0 , 0 , 0 };
90
+ if (gifFile->SColorMap ) {
91
+ bg = gifFile->SColorMap ->Colors [gifFile->SBackGroundColor ];
92
+ }
93
+
94
+ // The GIFLIB docs say that the canvas's height and width are potentially
95
+ // ignored by modern viewers, so to be on the safe side we set the output
96
+ // height to max(canvas_heigh, first_image_height). Same for width.
97
+ // https://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
98
+ auto out_h =
99
+ std::max (gifFile->SHeight , gifFile->SavedImages [0 ].ImageDesc .Height );
100
+ auto out_w =
101
+ std::max (gifFile->SWidth , gifFile->SavedImages [0 ].ImageDesc .Width );
115
102
116
103
// We output a channels-last tensor for consistency with other image decoders.
117
104
// Torchvision's resize tends to be is faster on uint8 channels-last tensors.
@@ -121,30 +108,59 @@ torch::Tensor decode_gif(const torch::Tensor& encoded_data) {
121
108
auto out = torch::empty (
122
109
{int64_t (num_images), 3 , int64_t (out_h), int64_t (out_w)}, options);
123
110
auto out_a = out.accessor <uint8_t , 4 >();
124
-
125
111
for (int i = 0 ; i < num_images; i++) {
126
112
const SavedImage& img = gifFile->SavedImages [i];
127
- const GifImageDesc& desc = img.ImageDesc ;
128
- TORCH_CHECK (
129
- desc.Width == out_w && desc.Height == out_h,
130
- " All images in the gif should have the same dimensions." );
131
113
114
+ GraphicsControlBlock gcb;
115
+ DGifSavedExtensionToGCB (gifFile, i, &gcb);
116
+
117
+ const GifImageDesc& desc = img.ImageDesc ;
132
118
const ColorMapObject* cmap =
133
119
desc.ColorMap ? desc.ColorMap : gifFile->SColorMap ;
134
120
TORCH_CHECK (
135
121
cmap != nullptr ,
136
122
" Global and local color maps are missing. This should never happen!" );
137
123
124
+ // When going from one image to another, there is a "disposal method" which
125
+ // specifies how to handle the transition. E.g. DISPOSE_DO_NOT means that
126
+ // the current image should essentially be drawn on top of the previous
127
+ // canvas. The pixels of that previous canvas will appear on the new one if
128
+ // either:
129
+ // - a pixel is transparent in the current image
130
+ // - the current image is smaller than the canvas, hence exposing its pixels
131
+ // The "background" disposal method means that the current canvas should be
132
+ // set to the background color.
133
+ // We only support these 2 modes and default to "background" when the
134
+ // disposal method is unspecified, or when it's set to "DISPOSE_PREVIOUS"
135
+ // which according to GIFLIB is not widely supported.
136
+ // (https://giflib.sourceforge.net/whatsinagif/animation_and_transparency.html).
137
+ if (i > 0 && gcb.DisposalMode == DISPOSE_DO_NOT) {
138
+ out[i] = out[i - 1 ];
139
+ } else {
140
+ // Background. If bg wasn't defined, it will be (0, 0, 0)
141
+ for (int h = 0 ; h < gifFile->SHeight ; h++) {
142
+ for (int w = 0 ; w < gifFile->SWidth ; w++) {
143
+ out_a[i][0 ][h][w] = bg.Red ;
144
+ out_a[i][1 ][h][w] = bg.Green ;
145
+ out_a[i][2 ][h][w] = bg.Blue ;
146
+ }
147
+ }
148
+ }
149
+
138
150
for (int h = 0 ; h < desc.Height ; h++) {
139
151
for (int w = 0 ; w < desc.Width ; w++) {
140
152
auto c = img.RasterBits [h * desc.Width + w];
153
+ if (c == gcb.TransparentColor ) {
154
+ continue ;
155
+ }
141
156
GifColorType rgb = cmap->Colors [c];
142
- out_a[i][0 ][h][w] = rgb.Red ;
143
- out_a[i][1 ][h][w] = rgb.Green ;
144
- out_a[i][2 ][h][w] = rgb.Blue ;
157
+ out_a[i][0 ][h + desc. Top ][w + desc. Left ] = rgb.Red ;
158
+ out_a[i][1 ][h + desc. Top ][w + desc. Left ] = rgb.Green ;
159
+ out_a[i][2 ][h + desc. Top ][w + desc. Left ] = rgb.Blue ;
145
160
}
146
161
}
147
162
}
163
+
148
164
out = out.squeeze (0 ); // remove batch dim if there's only one image
149
165
150
166
DGifCloseFile (gifFile, &error);
0 commit comments