1 // Copyright 2017 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 // Misc tools for quickly parsing JPEG data
16 //
17 // Author: Skal (pascal.massimino@gmail.com)
18
19 #include <string.h> // for memset
20 #include <vector>
21
22 #include "sjpegi.h"
23
24 ///////////////////////////////////////////////////////////////////////////////
25 // Dimensions (SOF)
26
27 namespace {
28 // This function will quickly locate the first appareance of an SOF marker in
29 // the passed JPEG buffer. It assumes the streams starts wih an SOI marker,
30 // like any valid JPEG should. Returned value points to the beginning of the
31 // marker and is guarantied to contain a least 8 bytes of valid data.
GetSOFData(const uint8_t * src,int size)32 const uint8_t* GetSOFData(const uint8_t* src, int size) {
33 if (src == NULL) return NULL;
34 const uint8_t* const end = src + size - 8; // 8 bytes of safety, for marker
35 src += 2; // skip M_SOI
36 for (; src < end && *src != 0xff; ++src) { /* search first 0xff marker */ }
37 while (src < end) {
38 const uint32_t marker = static_cast<uint32_t>((src[0] << 8) | src[1]);
39 if (marker == M_SOF0 || marker == M_SOF1) return src;
40 const size_t s = 2 + ((src[2] << 8) | src[3]);
41 src += s;
42 }
43 return NULL; // No SOF marker found
44 }
45 } // anonymous namespace
46
SjpegDimensions(const uint8_t * src0,size_t size,int * width,int * height,int * is_yuv420)47 bool SjpegDimensions(const uint8_t* src0, size_t size,
48 int* width, int* height, int* is_yuv420) {
49 if (width == NULL || height == NULL) return false;
50 const uint8_t* src = GetSOFData(src0, size);
51 const size_t left_over = size - (src - src0);
52 if (src == NULL || left_over < 8 + 3 * 1) return false;
53 if (height != NULL) *height = (src[5] << 8) | src[6];
54 if (width != NULL) *width = (src[7] << 8) | src[8];
55 if (is_yuv420 != NULL) {
56 const size_t nb_comps = src[9];
57 *is_yuv420 = (nb_comps == 3);
58 if (left_over < 11 + 3 * nb_comps) return false;
59 for (int c = 0; *is_yuv420 && c < 3; ++c) {
60 const int expected_dim = (c == 0 ? 0x22 : 0x11);
61 *is_yuv420 &= (src[11 + c * 3] == expected_dim);
62 }
63 }
64 return true;
65 }
66
67 ///////////////////////////////////////////////////////////////////////////////
68 // Quantizer marker (DQT)
69
SjpegFindQuantizer(const uint8_t * src,size_t size,uint8_t quant[2][64])70 int SjpegFindQuantizer(const uint8_t* src, size_t size,
71 uint8_t quant[2][64]) {
72 memset(quant[0], 0, sizeof(quant[0]));
73 memset(quant[1], 0, sizeof(quant[1]));
74 // minimal size for 64 coeffs and the markers (5 bytes)
75 if (src == NULL || size < 69 || src[0] != 0xff || src[1] != 0xd8) {
76 return 0;
77 }
78 const uint8_t* const end = src + size - 8; // 8 bytes of safety, for marker
79 src += 2; // skip over the initial M_SOI
80 for (; src < end && *src != 0xff; ++src) { /* search first 0xff marker */ }
81 int nb_comp = 0;
82 while (src < end) {
83 const uint32_t marker = static_cast<uint32_t>((src[0] << 8) | src[1]);
84 const int chunk_size = 2 + ((src[2] << 8) | src[3]);
85 if (src + chunk_size > end) {
86 break;
87 }
88 if (marker == M_SOS) {
89 // we can stop searching at the first SOS marker encountered, to avoid
90 // parsing the whole data
91 break;
92 } else if (marker == M_DQT) {
93 // Jump over packets of 1 index + 64 coeffs
94 int i = 4;
95 while (i + 1 < chunk_size) {
96 const int Pq = src[i] >> 4;
97 const int Tq = src[i] & 0x0f;
98 if (Pq > 1 || Tq > 3) return 0; // invalid bitstream. See B.4.
99 const int m_size = 64 * Pq + 65;
100 if (i + m_size > chunk_size) return 0;
101 if (Tq < 2) {
102 for (int j = 0; j < 64; ++j) {
103 int v;
104 if (Pq == 0) {
105 v = src[i + 1 + j];
106 } else {
107 // convert 16b->8b by clamping
108 v = ((int)src[i + 1 + 2 * j + 0] << 8)
109 | src[i + 1 + 2 * j + 1];
110 v = (v > 255) ? 255 : v;
111 }
112 quant[Tq][sjpeg::kZigzag[j]] = (v < 1) ? 1u : (uint8_t)v;
113 }
114 } else {
115 // we don't store the pointer, but we record the component
116 }
117 nb_comp |= 1 << Tq;
118 i += m_size;
119 }
120 }
121 src += chunk_size;
122 }
123 return ((nb_comp & 1) != 0) + ((nb_comp & 2) != 0)
124 + ((nb_comp & 4) != 0) + ((nb_comp & 8) != 0);
125 }
126
127 ///////////////////////////////////////////////////////////////////////////////
128
SjpegQuantMatrix(float quality,bool for_chroma,uint8_t matrix[64])129 void SjpegQuantMatrix(float quality, bool for_chroma, uint8_t matrix[64]) {
130 const float q_factor = sjpeg::GetQFactor(quality) / 100.f;
131 const uint8_t* const matrix0 = sjpeg::kDefaultMatrices[for_chroma];
132 for (int i = 0; i < 64; ++i) {
133 const int v = static_cast<int>(matrix0[i] * q_factor + .5f);
134 matrix[i] = (v < 1) ? 1u : (v > 255) ? 255u : v;
135 }
136 }
137
SjpegEstimateQuality(const uint8_t matrix[64],bool for_chroma)138 float SjpegEstimateQuality(const uint8_t matrix[64], bool for_chroma) {
139 // There's a lot of way to speed up this search (dichotomy, Newton, ...)
140 // but also a lot of way to fabricate a twisted input to fool it.
141 // So we're better off trying all the 100 possibilities since it's not
142 // a lot after all.
143 int best_quality = 0;
144 float best_score = 256 * 256 * 64 + 1;
145 for (int quality = 0; quality <= 100; ++quality) {
146 uint8_t m[64];
147 SjpegQuantMatrix(quality, for_chroma, m);
148 float score = 0;
149 for (size_t i = 0; i < 64; ++i) {
150 const float diff = m[i] - matrix[i];
151 score += diff * diff;
152 if (score > best_score) {
153 break;
154 }
155 }
156 if (score < best_score) {
157 best_score = score;
158 best_quality = quality;
159 }
160 }
161 return best_quality;
162 }
163
164 ////////////////////////////////////////////////////////////////////////////////
165 // Bluriness risk evaluation and YUV420 / sharp-YUV420 / YUV444 decision
166
167 static const int kNoiseLevel = 4;
168 static const double kThreshYU420 = 40.0;
169 static const double kThreshSharpYU420 = 70.0;
170
SjpegRiskiness(const uint8_t * rgb,int width,int height,int stride,float * risk)171 SjpegYUVMode SjpegRiskiness(const uint8_t* rgb,
172 int width, int height, int stride, float* risk) {
173 const sjpeg::RGBToIndexRowFunc cvrt_func = sjpeg::GetRowFunc();
174
175 std::vector<uint16_t> row1(width), row2(width);
176 double total_score = 0;
177 double count = 0;
178 const int kRGB3 = sjpeg::kRGBSize * sjpeg::kRGBSize * sjpeg::kRGBSize;
179
180 cvrt_func(rgb, width, &row2[0]); // convert first row ahead
181 for (int j = 1; j < height; ++j) {
182 rgb += stride;
183 std::swap(row1, row2);
184 cvrt_func(rgb, width, &row2[0]); // this is the row below
185 for (int i = 0; i < width - 1; ++i) {
186 const int idx0 = row1[i + 0];
187 const int idx1 = row1[i + 1];
188 const int idx2 = row2[i];
189 const int score = sjpeg::kSharpnessScore[idx0 + kRGB3 * idx1]
190 + sjpeg::kSharpnessScore[idx0 + kRGB3 * idx2]
191 + sjpeg::kSharpnessScore[idx1 + kRGB3 * idx2];
192 if (score > kNoiseLevel) {
193 total_score += score;
194 count += 1.0;
195 }
196 }
197 }
198 if (count > 0) total_score /= count;
199 // number of pixels evaluated
200 const double frac = 100. * count / (width * height);
201 // if less than 1% of pixels were evaluated -> below noise level.
202 if (frac < 1.) total_score = 0.;
203
204 // recommendation (TODO(skal): tune thresholds)
205 total_score = (total_score > 25.) ? 100. : total_score * 100. / 25.;
206 if (risk != NULL) *risk = (float)total_score;
207
208 const SjpegYUVMode recommendation =
209 (total_score < kThreshYU420) ? SJPEG_YUV_420 :
210 (total_score < kThreshSharpYU420) ? SJPEG_YUV_SHARP :
211 SJPEG_YUV_444;
212 return recommendation;
213 }
214
215 namespace sjpeg {
216
217 // (X * 0x0101 >> 16) ~= X / 255
Convert(uint32_t v)218 static uint32_t Convert(uint32_t v) {
219 return (v * (0x0101u * (sjpeg::kRGBSize - 1))) >> 16;
220 }
221
222 // Convert 8b values y/u/v to index entry.
YUVToRiskIdx(int16_t y,int16_t u,int16_t v)223 int YUVToRiskIdx(int16_t y, int16_t u, int16_t v) {
224 const int idx = Convert(y + 128)
225 + Convert(u + 128) * sjpeg::kRGBSize
226 + Convert(v + 128) * sjpeg::kRGBSize * sjpeg::kRGBSize;
227 return idx;
228 }
229
230 // return riskiness score on an 8x8 block. Input is YUV444 block
231 // of DCT coefficients (Y/U/V).
DCTRiskinessScore(const int16_t yuv[3* 8],int16_t scores[8* 8])232 double DCTRiskinessScore(const int16_t yuv[3 * 8], int16_t scores[8 * 8]) {
233 uint16_t idx[64];
234 for (int k = 0; k < 64; ++k) {
235 idx[k] = YUVToRiskIdx(yuv[k + 0 * 64], yuv[k + 1 * 64], yuv[k + 2 * 64]);
236 }
237 const int kRGB3 = sjpeg::kRGBSize * sjpeg::kRGBSize * sjpeg::kRGBSize;
238 double total_score = 0;
239 double count = 0;
240 for (size_t J = 0; J <= 7; ++J) {
241 for (size_t I = 0; I <= 7; ++I) {
242 const int k = I + J * 8;
243 const int idx0 = idx[k + 0];
244 const int idx1 = idx[k + (I < 7 ? 1 : -1)];
245 const int idx2 = idx[k + (J < 7 ? 8 : -8)];
246 int score = sjpeg::kSharpnessScore[idx0 + kRGB3 * idx1]
247 + sjpeg::kSharpnessScore[idx0 + kRGB3 * idx2]
248 + sjpeg::kSharpnessScore[idx1 + kRGB3 * idx2];
249 if (score <= kNoiseLevel) {
250 score = 0;
251 } else {
252 total_score += score;
253 count += 1.0;
254 }
255 scores[I + J * 8] = static_cast<int16_t>(score);
256 }
257 }
258 if (count > 0) total_score /= count;
259 total_score = (total_score > 25.) ? 100. : total_score * 100. / 25.;
260 return total_score;
261 }
262
263 // This function returns the raw per-pixel riskiness scores. The input rgb[]
264 // samples is a 8x8 block, the output is a 8x8 block.
265 // Not an official API, because a little too specific. But still accessible.
BlockRiskinessScore(const uint8_t * rgb,int stride,int16_t scores[8* 8])266 double BlockRiskinessScore(const uint8_t* rgb, int stride,
267 int16_t scores[8 * 8]) {
268 const RGBToYUVBlockFunc get_block = GetBlockFunc(true);
269 int16_t yuv444[3 * 64];
270 get_block(rgb, stride, yuv444);
271 return DCTRiskinessScore(yuv444, scores);
272 }
273
274 } // namespace sjpeg
275