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