aboutsummaryrefslogtreecommitdiff
path: root/image/jpeg/quality.go
diff options
context:
space:
mode:
Diffstat (limited to 'image/jpeg/quality.go')
-rw-r--r--image/jpeg/quality.go158
1 files changed, 158 insertions, 0 deletions
diff --git a/image/jpeg/quality.go b/image/jpeg/quality.go
new file mode 100644
index 0000000..674f0b8
--- /dev/null
+++ b/image/jpeg/quality.go
@@ -0,0 +1,158 @@
1package jpeg
2
3import (
4 "fmt"
5 "io"
6)
7
8var defaultTables = [][]int{
9 { // Luminance
10 16, 11, 12, 14, 12, 10, 16, 14,
11 13, 14, 18, 17, 16, 19, 24, 40,
12 26, 24, 22, 22, 24, 49, 35, 37,
13 29, 40, 58, 51, 61, 60, 57, 51,
14 56, 55, 64, 72, 92, 78, 64, 68,
15 87, 69, 55, 56, 80, 109, 81, 87,
16 95, 98, 103, 104, 103, 62, 77, 113,
17 121, 112, 100, 120, 92, 101, 103, 99,
18 },
19 { // Chrominance
20 17, 18, 18, 24, 21, 24, 47, 26,
21 26, 47, 99, 66, 56, 66, 99, 99,
22 99, 99, 99, 99, 99, 99, 99, 99,
23 99, 99, 99, 99, 99, 99, 99, 99,
24 99, 99, 99, 99, 99, 99, 99, 99,
25 99, 99, 99, 99, 99, 99, 99, 99,
26 99, 99, 99, 99, 99, 99, 99, 99,
27 99, 99, 99, 99, 99, 99, 99, 99,
28 },
29}
30
31func read2Bytes(r io.Reader) ([]byte, error) {
32 b := make([]byte, 2)
33 if _, err := r.Read(b); err != nil {
34 return nil, err
35 }
36 return b, nil
37}
38
39// ReadJPEGQuality returns the JPEG quality estimate.
40//
41// Cleaned up version of github.com/liut/jpegquality
42func ReadJPEGQuality(r io.ReadSeeker) (int, error) {
43 sign, err := read2Bytes(r)
44 if err != nil {
45 return 0, err
46 }
47
48 if sign[0] != 0xff && sign[1] != 0xd8 {
49 return 0, fmt.Errorf("invalid jpeg header")
50 }
51
52 for {
53 mark, err := read2Bytes(r)
54 if err != nil {
55 return 0, err
56 }
57
58 if mark[0] != 0xff || mark[1] == 0xff || mark[1] == 0x00 {
59 // Would also be valid to just keep re-reading to find the marker
60 return 0, fmt.Errorf("invalid marker")
61 }
62
63 marker := int(mark[0])<<8 + int(mark[1])
64 if marker == 0 {
65 return 0, fmt.Errorf("invalid jpeg header")
66 }
67
68 sign, err := read2Bytes(r)
69 if err != nil {
70 return 0, err
71 }
72
73 length := int(sign[0])<<8 + int(sign[1]) - 2
74 if length < 0 {
75 return 0, fmt.Errorf("short segment read")
76 }
77
78 if (marker & 0xff) != 0xdb { // not a quantization table
79 if _, err := r.Seek(int64(length), 1); err != nil {
80 return 0, err
81 }
82 continue
83 }
84
85 if length%65 != 0 {
86 return 0, fmt.Errorf("wrong size for quantization table")
87 }
88
89 tabuf := make([]byte, length)
90 n, err := r.Read(tabuf)
91 if err != nil {
92 return 0, err
93 }
94 tabuf = tabuf[0:n]
95
96 allones := 1
97
98 var reftable []int
99 var cumsf, cumsf2 float64
100
101 for a := 0; a < n; {
102 tableindex := int(tabuf[a] & 0x0f)
103 a++
104
105 if tableindex < 2 {
106 reftable = defaultTables[tableindex]
107 }
108
109 // Read in the table, compute statistics relative to reference table
110 if a+64 > n {
111 return 0, fmt.Errorf("DQT segment too short")
112 }
113
114 for coefindex := 0; coefindex < 64 && a < n; coefindex++ {
115 var val int
116
117 if tableindex>>4 != 0 {
118 temp := int(tabuf[a])
119 a++
120 temp *= 256
121 val = int(tabuf[a]) + temp
122 a++
123 } else {
124 val = int(tabuf[a])
125 a++
126 }
127
128 // scaling factor in percent
129 x := 100.0 * float64(val) / float64(reftable[coefindex])
130 cumsf += x
131 cumsf2 += x * x
132
133 // separate check for all-ones table (Q 100)
134 if val != 1 {
135 allones = 0
136 }
137 }
138
139 if 0 != len(reftable) { // terse output includes quality
140 var qual float64
141 cumsf /= 64.0 // mean scale factor
142 cumsf2 /= 64.0
143
144 if allones == 1 { // special case for all-ones table
145 qual = 100.0
146 } else if cumsf <= 100.0 {
147 qual = (200.0 - cumsf) / 2.0
148 } else {
149 qual = 5000.0 / cumsf
150 }
151
152 if tableindex == 0 {
153 return (int)(qual + 0.5), nil
154 }
155 }
156 }
157 }
158}