Skip to content

Commit 42654b2

Browse files
sjuddglide-copybara-robot
authored andcommittedJan 14, 2022
Add support for animated webp detection to Glide's ImageHeaderParser.
PiperOrigin-RevId: 421923663
1 parent afc37e8 commit 42654b2

File tree

6 files changed

+281
-24
lines changed

6 files changed

+281
-24
lines changed
 

‎checkstyle.xml

-5
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,6 @@
7171
<module name="StaticVariableName" />
7272
<module name="TypeName" />
7373

74-
<!-- Allow common trailing comments used to describe suppressions -->
75-
<module name="TrailingComment">
76-
<property name="legalComment" value="^Public API.?$|^NOPMD.*$" />
77-
</module>
78-
7974
<!-- Checks for imports. -->
8075
<!-- See http://checkstyle.sourceforge.net/config_imports.html -->
8176
<module name="AvoidStarImport"/>

‎library/src/main/java/com/bumptech/glide/load/ImageHeaderParser.java

+13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ enum ImageType {
3030
WEBP_A(true),
3131
/** WebP type without alpha. */
3232
WEBP(false),
33+
/** All animated webps. */
34+
ANIMATED_WEBP(true),
3335
/** Avif type (may contain alpha). */
3436
AVIF(true),
3537
/** Unrecognized type. */
@@ -44,6 +46,17 @@ enum ImageType {
4446
public boolean hasAlpha() {
4547
return hasAlpha;
4648
}
49+
50+
public boolean isWebp() {
51+
switch (this) {
52+
case WEBP:
53+
case WEBP_A:
54+
case ANIMATED_WEBP:
55+
return true;
56+
default:
57+
return false;
58+
}
59+
}
4760
}
4861

4962
@NonNull

‎library/src/main/java/com/bumptech/glide/load/resource/bitmap/DefaultImageHeaderParser.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.bumptech.glide.load.resource.bitmap;
22

3+
import static com.bumptech.glide.load.ImageHeaderParser.ImageType.ANIMATED_WEBP;
34
import static com.bumptech.glide.load.ImageHeaderParser.ImageType.AVIF;
45
import static com.bumptech.glide.load.ImageHeaderParser.ImageType.GIF;
56
import static com.bumptech.glide.load.ImageHeaderParser.ImageType.JPEG;
@@ -53,6 +54,7 @@ public final class DefaultImageHeaderParser implements ImageHeaderParser {
5354
private static final int VP8_HEADER_TYPE_EXTENDED = 0x00000058;
5455
// 'L'
5556
private static final int VP8_HEADER_TYPE_LOSSLESS = 0x0000004C;
57+
private static final int WEBP_EXTENDED_ANIMATION_FLAG = 1 << 1;
5658
private static final int WEBP_EXTENDED_ALPHA_FLAG = 1 << 4;
5759
private static final int WEBP_LOSSLESS_ALPHA_FLAG = 1 << 3;
5860
// Avif-related
@@ -146,7 +148,13 @@ private ImageType getType(Reader reader) throws IOException {
146148
// Skip some more length bytes and check for transparency/alpha flag.
147149
reader.skip(4);
148150
short flags = reader.getUInt8();
149-
return (flags & WEBP_EXTENDED_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
151+
if ((flags & WEBP_EXTENDED_ANIMATION_FLAG) != 0) {
152+
return ANIMATED_WEBP;
153+
} else if ((flags & WEBP_EXTENDED_ALPHA_FLAG) != 0) {
154+
return ImageType.WEBP_A;
155+
} else {
156+
return ImageType.WEBP;
157+
}
150158
}
151159
if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) {
152160
// See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt

‎library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ private static void calculateScaling(
575575
} else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {
576576
powerOfTwoWidth = (int) Math.floor(orientedSourceWidth / (float) powerOfTwoSampleSize);
577577
powerOfTwoHeight = (int) Math.floor(orientedSourceHeight / (float) powerOfTwoSampleSize);
578-
} else if (imageType == ImageType.WEBP || imageType == ImageType.WEBP_A) {
578+
} else if (imageType.isWebp()) {
579579
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
580580
powerOfTwoWidth = Math.round(orientedSourceWidth / (float) powerOfTwoSampleSize);
581581
powerOfTwoHeight = Math.round(orientedSourceHeight / (float) powerOfTwoSampleSize);

‎library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/DefaultImageHeaderParserTest.java

+258-17
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public void run(
176176
}
177177

178178
@Test
179-
public void testCanParseWebpWithAlpha() throws IOException {
179+
public void testCanParseLosslessWebpWithAlpha() throws IOException {
180180
byte[] data =
181181
new byte[] {
182182
0x52,
@@ -194,12 +194,12 @@ public void testCanParseWebpWithAlpha() throws IOException {
194194
0x56,
195195
0x50,
196196
0x38,
197-
0x4c,
197+
0x4c, // Lossless
198198
0x30,
199199
0x50,
200200
0x00,
201201
0x00,
202-
0x2f,
202+
0x2f, // Flags
203203
(byte) 0xef,
204204
(byte) 0x80,
205205
0x15,
@@ -231,15 +231,15 @@ public void run(
231231
}
232232

233233
@Test
234-
public void testCanParseWebpWithoutAlpha() throws IOException {
234+
public void testCanParseLosslessWebpWithoutAlpha() throws IOException {
235235
byte[] data =
236236
new byte[] {
237237
0x52,
238238
0x49,
239239
0x46,
240240
0x46,
241-
0x72,
242-
0x1c,
241+
0x3c,
242+
0x50,
243243
0x00,
244244
0x00,
245245
0x57,
@@ -249,23 +249,133 @@ public void testCanParseWebpWithoutAlpha() throws IOException {
249249
0x56,
250250
0x50,
251251
0x38,
252-
0x20,
253-
0x66,
254-
0x1c,
252+
0x4c, // Lossless
253+
0x30,
254+
0x50,
255255
0x00,
256256
0x00,
257+
0x00, // Flags
258+
(byte) 0xef,
259+
(byte) 0x80,
260+
0x15,
261+
0x10,
262+
(byte) 0x8d,
257263
0x30,
264+
0x68,
265+
0x1b,
266+
(byte) 0xc9,
267+
(byte) 0x91,
268+
(byte) 0xb2
269+
};
270+
runTest(
271+
data,
272+
new ParserTestCase() {
273+
@Override
274+
public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)
275+
throws IOException {
276+
assertEquals(ImageType.WEBP, parser.getType(is));
277+
}
278+
279+
@Override
280+
public void run(
281+
DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)
282+
throws IOException {
283+
assertEquals(ImageType.WEBP, parser.getType(byteBuffer));
284+
}
285+
});
286+
}
287+
288+
@Test
289+
public void testCanParseExtendedWebpWithAlpha() throws IOException {
290+
byte[] data =
291+
new byte[] {
292+
0x52,
293+
0x49,
294+
0x46,
295+
0x46,
258296
0x3c,
259-
0x01,
260-
(byte) 0x9d,
261-
0x01,
262-
0x2a,
297+
0x50,
298+
0x00,
299+
0x00,
300+
0x57,
301+
0x45,
302+
0x42,
303+
0x50,
304+
0x56,
305+
0x50,
306+
0x38,
307+
0x58, // Extended
308+
0x30,
309+
0x50,
310+
0x00,
311+
0x00,
312+
0x10, // flags
313+
(byte) 0xef,
314+
(byte) 0x80,
315+
0x15,
316+
0x10,
317+
(byte) 0x8d,
318+
0x30,
319+
0x68,
320+
0x1b,
321+
(byte) 0xc9,
322+
(byte) 0x91,
323+
(byte) 0xb2
324+
};
325+
runTest(
326+
data,
327+
new ParserTestCase() {
328+
@Override
329+
public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)
330+
throws IOException {
331+
assertEquals(ImageType.WEBP_A, parser.getType(is));
332+
}
333+
334+
@Override
335+
public void run(
336+
DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)
337+
throws IOException {
338+
assertEquals(ImageType.WEBP_A, parser.getType(byteBuffer));
339+
}
340+
});
341+
}
342+
343+
@Test
344+
public void testCanParseExtendedWebpWithoutAlpha() throws IOException {
345+
byte[] data =
346+
new byte[] {
263347
0x52,
264-
0x02,
265-
(byte) 0x94,
266-
0x03,
348+
0x49,
349+
0x46,
350+
0x46,
351+
0x3c,
352+
0x50,
353+
0x00,
267354
0x00,
268-
(byte) 0xc7
355+
0x57,
356+
0x45,
357+
0x42,
358+
0x50,
359+
0x56,
360+
0x50,
361+
0x38,
362+
0x58, // Extended
363+
0x30,
364+
0x50,
365+
0x00,
366+
0x00,
367+
0x00, // flags
368+
(byte) 0xef,
369+
(byte) 0x80,
370+
0x15,
371+
0x10,
372+
(byte) 0x8d,
373+
0x30,
374+
0x68,
375+
0x1b,
376+
(byte) 0xc9,
377+
(byte) 0x91,
378+
(byte) 0xb2
269379
};
270380
runTest(
271381
data,
@@ -285,6 +395,137 @@ public void run(
285395
});
286396
}
287397

398+
@Test
399+
public void testCanParseExtendedWebpWithoutAlphaAndWithAnimation() throws IOException {
400+
byte[] data =
401+
new byte[] {
402+
0x52,
403+
0x49,
404+
0x46,
405+
0x46,
406+
0x3c,
407+
0x50,
408+
0x00,
409+
0x00,
410+
0x57,
411+
0x45,
412+
0x42,
413+
0x50,
414+
0x56,
415+
0x50,
416+
0x38,
417+
0x58, // Extended
418+
0x30,
419+
0x50,
420+
0x00,
421+
0x00,
422+
0x02, // Flags
423+
(byte) 0xef,
424+
(byte) 0x80,
425+
0x15,
426+
0x10,
427+
(byte) 0x8d,
428+
0x30,
429+
0x68,
430+
0x1b,
431+
(byte) 0xc9,
432+
(byte) 0x91,
433+
(byte) 0xb2
434+
};
435+
runTest(
436+
data,
437+
new ParserTestCase() {
438+
@Override
439+
public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)
440+
throws IOException {
441+
assertEquals(ImageType.ANIMATED_WEBP, parser.getType(is));
442+
}
443+
444+
@Override
445+
public void run(
446+
DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)
447+
throws IOException {
448+
assertEquals(ImageType.ANIMATED_WEBP, parser.getType(byteBuffer));
449+
}
450+
});
451+
}
452+
453+
@Test
454+
public void testCanParseExtendedWebpWithAlphaAndAnimation() throws IOException {
455+
byte[] data =
456+
new byte[] {
457+
0x52,
458+
0x49,
459+
0x46,
460+
0x46,
461+
0x3c,
462+
0x50,
463+
0x00,
464+
0x00,
465+
0x57,
466+
0x45,
467+
0x42,
468+
0x50,
469+
0x56,
470+
0x50,
471+
0x38,
472+
0x58, // Extended
473+
0x30,
474+
0x50,
475+
0x00,
476+
0x00,
477+
(byte) 0x12, // Flags
478+
(byte) 0xef,
479+
(byte) 0x80,
480+
0x15,
481+
0x10,
482+
(byte) 0x8d,
483+
0x30,
484+
0x68,
485+
0x1b,
486+
(byte) 0xc9,
487+
(byte) 0x91,
488+
(byte) 0xb2
489+
};
490+
runTest(
491+
data,
492+
new ParserTestCase() {
493+
@Override
494+
public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)
495+
throws IOException {
496+
assertEquals(ImageType.ANIMATED_WEBP, parser.getType(is));
497+
}
498+
499+
@Override
500+
public void run(
501+
DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)
502+
throws IOException {
503+
assertEquals(ImageType.ANIMATED_WEBP, parser.getType(byteBuffer));
504+
}
505+
});
506+
}
507+
508+
@Test
509+
public void testCanParseRealAnimatedWebpFile() throws IOException {
510+
byte[] data = Util.readBytes(TestResourceUtil.openResource(getClass(), "animated_webp.webp"));
511+
runTest(
512+
data,
513+
new ParserTestCase() {
514+
@Override
515+
public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)
516+
throws IOException {
517+
assertThat(parser.getType(is)).isEqualTo(ImageType.ANIMATED_WEBP);
518+
}
519+
520+
@Override
521+
public void run(
522+
DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)
523+
throws IOException {
524+
assertThat(parser.getType(byteBuffer)).isEqualTo(ImageType.ANIMATED_WEBP);
525+
}
526+
});
527+
}
528+
288529
@Test
289530
public void testCanParseAvifMajorBrand() throws IOException {
290531
byte[] data =
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.