| 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__ |