diff options
Diffstat (limited to 'image/jpeg/quality.go')
-rw-r--r-- | image/jpeg/quality.go | 158 |
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 @@ | |||
1 | package jpeg | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "io" | ||
6 | ) | ||
7 | |||
8 | var 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 | |||
31 | func 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 | ||
42 | func 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 | } | ||