File Coverage

blib/lib/Image/JPEG/EstimateQuality.pm
Criterion Covered Total %
statement 69 76 90.7
branch 23 38 60.5
condition 2 3 66.6
subroutine 10 10 100.0
pod 1 1 100.0
total 105 128 82.0


line stmt bran cond sub pod time code
1             package Image::JPEG::EstimateQuality;
2 3     3   83613 use 5.008005;
  3         11  
  3         125  
3 3     3   17 use strict;
  3         7  
  3         106  
4 3     3   31 use warnings;
  3         5  
  3         113  
5 3     3   15 use Exporter 'import';
  3         5  
  3         104  
6 3     3   17 use Carp;
  3         5  
  3         644  
7              
8             our $VERSION = "0.02";
9              
10             our @EXPORT = qw( jpeg_quality );
11              
12             use constant {
13 3         3792 SECTION_MARKER => "\xFF",
14             SOI => "\xFF\xD8",
15             EOI => "\xFF\xD8",
16             SOS => "\xFF\xDA",
17             DQT => "\xFF\xDB",
18              
19             ERR_NOT_JPEG => "Not a JPEG file",
20             ERR_FILE_READ => "File read error",
21             ERR_FAILED => "Could not determine quality",
22 3     3   21 };
  3         5  
23              
24             sub jpeg_quality {
25 18     18 1 10594 my ($file) = @_;
26              
27 18         25 my ($fh, $r);
28 18 100 66     65 if (! ref $file) {
    100          
    50          
29 15 50       800 open $fh, '<', $file or croak ERR_FILE_READ . qq{($file): $!};
30 15         38 binmode $fh;
31 15         35 $r = _jpeg_quality_for_fh($fh);
32 15         300 close $fh;
33 15         132 return $r;
34             } elsif (ref $file eq 'SCALAR') {
35             # image data in memory
36 1 50   1   43 open $fh, '<', $file or croak ERR_FILE_READ . qq{: $!};
  1         11  
  1         2  
  1         8  
37 1         1554 binmode $fh;
38 1         4 $r = _jpeg_quality_for_fh($fh);
39 1         4 close $fh;
40 1         10 return $r;
41 1         17 } elsif (ref $file eq 'GLOB' || eval { $file->isa('IO::Handle') }) {
42 2         6 binmode $file;
43 2         4 $fh = $file;
44 2         5 $r = _jpeg_quality_for_fh($fh);
45 2         12 return $r;
46             } else {
47 0         0 croak "Unsupported file: $file";
48             }
49             }
50              
51             # TODO: lossless support
52              
53             sub _jpeg_quality_for_fh {
54 18     18   29 my ($fh) = @_;
55 18         18 my ($buf);
56              
57 18 50       266 read $fh, $buf, 2 or croak ERR_FILE_READ . qq{: $!};
58 18 50       54 croak ERR_NOT_JPEG unless $buf eq SOI;
59              
60 18         25 while (1) {
61 36 50       237 read $fh, $buf, 2 or croak ERR_FILE_READ . qq{: $!};
62              
63 36 50       74 if ($buf eq EOI) {
64 0         0 croak ERR_FAILED;
65             }
66 36 50       78 if ($buf eq SOS) {
67 0         0 croak ERR_FAILED;
68             }
69              
70 36         59 my $marker = substr $buf, 0, 1;
71 36 50       73 croak ERR_NOT_JPEG unless $marker eq SECTION_MARKER;
72              
73 36 100       81 if ($buf ne DQT) {
74             # skip to next segment
75 18 50       44 read $fh, $buf, 2 or croak ERR_FILE_READ . qq{: $!};
76 18         57 my $len = unpack 'n', $buf;
77 18 50       152 seek $fh, $len - 2, 1 or croak ERR_FILE_READ . qq{: $!};
78 18         28 next;
79             }
80              
81             # read DQT length
82 18 50       45 read $fh, $buf, 2 or croak ERR_FILE_READ . qq{: $!};
83 18         29 my $len = unpack 'n', $buf;
84 18         25 $len -= 2;
85 18 50       38 croak ERR_FAILED unless $len >= 64+1;
86              
87             # read DQT
88 18 50       46 read $fh, $buf, $len or croak ERR_FILE_READ . qq{: $!};
89              
90 18         39 my $dqt8bit = ((ord substr($buf, 0, 1) & 0xF0) == 0);
91              
92 18         38 return _judge_quality($buf, $dqt8bit);
93             }
94              
95             # NEVER REACH HERE
96             }
97              
98             # Precalculated sums of luminance quantization table for each qualities.
99             # Base table is from Table K.1 in JPEG Standard Annex K
100              
101             my @sums_dqt = (
102             16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12861, 12560,
103             12245, 11867, 11467, 11084, 10718, 10371, 10027, 9702, 9371, 9056,
104             8680, 8345, 8005, 7683, 7376, 7092, 6829, 6586, 6360, 6148,
105             5949, 5771, 5584, 5422, 5265, 5122, 4980, 4852, 4729, 4616,
106             4502, 4396, 4290, 4194, 4097, 4008, 3929, 3845, 3755, 3688,
107             3621, 3541, 3467, 3396, 3323, 3247, 3170, 3096, 3021, 2952,
108             2874, 2804, 2727, 2657, 2583, 2509, 2437, 2362, 2290, 2211,
109             2136, 2068, 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477,
110             1398, 1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
111             667, 592, 518, 441, 369, 292, 221, 151, 86, 64,
112             );
113              
114             sub _judge_quality {
115 18     18   26 my ($buf, $is_8bit) = @_;
116              
117 18         22 my $sum = 0;
118 18 50       30 if ($is_8bit) {
119 18         39 $sum += $_ for map { unpack('C', substr($buf, 1+1*$_, 1)) } (1..64);
  1152         9405  
120             } else {
121 0         0 $sum += $_ for map { unpack('n', substr($buf, 1+2*$_, 2)) } (1..64);
  0         0  
122 0         0 $sum /= 256;
123             }
124              
125 18         66 for my $i (0 .. 99) {
126 945 100       2251 if ($sum < $sums_dqt[99 - $i]) {
127 18         64 return 100 - $i;
128             }
129             }
130              
131 0         0 return 1;
132             }
133              
134             1;
135             __END__