File Coverage

lib/Image/Info/WEBP.pm
Criterion Covered Total %
statement 60 62 96.7
branch 21 38 55.2
condition n/a
subroutine 5 5 100.0
pod 0 2 0.0
total 86 107 80.3


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # Copyright (C) 2019 Preisvergleich Internet Services AG. All rights reserved.
5             # This package is free software; you can redistribute it and/or
6             # modify it under the same terms as Perl itself.
7             #
8              
9             # File magic is
10             # R I F F
11             # length (4 bytes)
12             # WEPB
13              
14             =begin register
15              
16             MAGIC: /^RIFF.{4}WEBP/s
17              
18             VP8 (lossy), VP8L (lossless) and VP8X (extended) files are supported.
19             Sets the key C to true if the file is an animation. Otherwise
20             sets the key C to either C or C.
21              
22             =end register
23              
24             =cut
25              
26             package Image::Info::WEBP;
27              
28 2     2   13 use strict;
  2         4  
  2         72  
29 2     2   11 use warnings;
  2         4  
  2         66  
30              
31 2     2   10 use vars qw($VERSION);
  2         2  
  2         1731  
32             $VERSION = '0.01';
33              
34             sub my_read
35             {
36 20     20 0 35 my($source, $len) = @_;
37 20         22 my $buf;
38 20         157 my $n = read($source, $buf, $len);
39 20 50       77 die "read failed: $!" unless defined $n;
40 20 50       35 die "short read ($len/$n) at pos " . tell($source) unless $n == $len;
41 20         76 $buf;
42             }
43              
44             my @upscale = (1, 5/4, 5/3, 2);
45              
46             sub process_file
47             {
48 10     10 0 26 my($info, $fh) = @_;
49              
50 10         19 my $signature = my_read($fh, 16);
51 10 50       53 die "Bad WEBP signature"
52             unless $signature =~ /\ARIFF....WEBPVP8([ LX])/s;
53              
54 10         27 my $type = $1;
55              
56 10         36 $info->push_info(0, "file_media_type" => "image/webp");
57 10         30 $info->push_info(0, "file_ext" => "webp");
58              
59             # This code is (arguably) 4 bytes out of sync with the description in the
60             # spec, because the spec describes ChunkHeader('ABCD') as an 8-byte quantity
61             # and we've processed the first 4 bytes above, but need to handle the second
62             # 4 (the length) here:
63 10 100       34 if ($type eq 'X') {
    100          
64             # 32 bits of length
65             # 8 bits of flags
66             # 24 bits reserved
67             # 24 bits canvas width
68             # 24 bits canvas height
69             # and then chunks...
70 4         8 my ($length, $flags, $raw_width, $raw_height)
71             = unpack 'VVVv', my_read($fh, 14);
72             # Of the 14 bytes now read, 10 were included in length:
73 4         10 $length -= 10;
74 4 50       10 die sprintf "Bad WEBP VP8X reserved bits 0x%02X", $flags & 0xC1
75             if $flags & 0xC1;
76 4 50       9 die sprintf "Bad WEBP VP8X reserved bits 0x%06X", $flags >> 8
77             if $flags >> 8;
78              
79             # Shuffle the 24 bit values into shape:
80 4         11 $raw_height = ($raw_height << 8) | ($raw_width >> 24);
81 4         6 $raw_width &= 0xFFFFFF;
82             # Strictly this is the canvas width/height, not that of the first frame.
83             # But 1 image, that might be animated. Hence it doesn't quite map to the
84             # "$n images in a file" model that Image::Info::GIF provides.
85              
86 4         14 $info->push_info(0, "width", 1 + $raw_width);
87 4         14 $info->push_info(0, "height", 1 + $raw_height);
88              
89 4 100       12 if ($flags & 0x02) {
90 1         4 $info->push_info(0, "Animation", 1);
91             } else {
92             # Possibly could also handle EXIF chunks here, although it's unclear
93             # how much code that should share with
94             # Image::Info::JPEG::process_app1_exif(), as that seems to have both
95             # JPEG-specific logic, and more generic EXIF logic.
96              
97 3         5 while (1) {
98             # Spec says that length is actual length, without accounting for
99             # padding. Odd sizes are padded to the next even size:
100 3 50       7 ++$length
101             if $length & 1;
102 3 50       33 die "seek failed: $!"
103             unless seek $fh, $length, 1;
104 3         12 my $buf;
105 3         27 my $n = read $fh, $buf, 8;
106 3 50       10 die "read failed: $!" unless defined $n;
107 3 50       8 die "No VP8 or VP8L chunk found in WEPB Extended File Format"
108             if $n == 0;
109 3 50       8 die "short read (8/$n) at pos " . tell $fh
110             unless $n == 8;
111 3         15 (my $chunk, $length) = unpack "a4V", $buf;
112 3 50       9 if ($chunk eq 'VP8 ') {
    0          
113 3         11 $info->push_info(0, "Compression", "VP8");
114 3         9 last;
115             } elsif ($chunk eq 'VP8L') {
116 0         0 $info->push_info(0, "Compression", "Lossless");
117 0         0 last;
118             }
119             }
120             }
121             } elsif ($type eq 'L') {
122             # There doesn't seem to be a better name for this:
123 3         9 $info->push_info(0, "Compression", "Lossless");
124             # Discard the 4 bytes of length; grab the next 5.
125 3         5 my ($sig, $size_and_flags) = unpack "x4CV", my_read($fh, 9);
126 3 50       9 die sprintf "Bad WEBP Lossless signature 0x%02X", $sig
127             unless $sig == 0x2f;
128 3         5 my $version = $size_and_flags >> 30;
129 3 50       6 die "Bad WEBP Lossless version $sig"
130             unless $version == 0;
131 3         10 $info->push_info(0, "width", 1 + $size_and_flags & 0x3FFF);
132 3         8 $info->push_info(0, "height", 1 + ($size_and_flags >> 14) & 0x3FFF);
133             } else {
134 3         8 $info->push_info(0, "Compression", "VP8");
135             # The fun format for a key frame is
136             # 32 bits of length
137             # 24 bits of frame tag
138             # 3 signature bytes
139             # 2+14 bits of width
140             # 2+14 bits of height
141             # We don't have a pack format for 3 bytes, but the bits we need can be
142             # got by approximating it as 2, 4, 2, 2:
143 3         10 my ($type, $start, $raw_horiz, $raw_vert)
144             = unpack "x4vVvv", my_read($fh, 14);
145 3 50       12 die "Bad WEBP VP8 type 1 (ie interframe)"
146             if $type & 1;
147 3         6 $start >>= 8;
148 3 50       9 die sprintf "Bad WEBP VP8 key frame start signature 0x%06X", $start
149             unless $start == 0x2a019d;
150              
151             # The top two bits of the raw width and height values are used as to
152             # flag a ratio to upscale.
153             # However, testing against dwebp and webpmux and then re-checking the
154             # documentation, it seems that these are really intended as information
155             # for the video hardware to render the image, because they don't change
156             # the size of bitmap returned from the decoder library. So return them
157             # as extra information, but don't recalculate the width and height.
158 3         10 $info->push_info(0, "width", ($raw_horiz & 0x3FFF));
159 3         11 $info->push_info(0, "height", ($raw_vert & 0x3FFF));
160 3         10 $info->push_info(0, "Width_Upscale", $upscale[$raw_horiz >> 14]);
161 3         10 $info->push_info(0, "Height_Upscale", $upscale[$raw_vert >> 14]);
162              
163             }
164             }