sjpeg 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,218 @@
1
+ // Copyright 2018 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
+ // header and chunks writing
16
+ //
17
+ // Author: Skal (pascal.massimino@gmail.com)
18
+
19
+ #include <math.h>
20
+ #include <stdio.h>
21
+ #include <stdlib.h>
22
+ #include <stdint.h>
23
+
24
+ #include "sjpegi.h"
25
+
26
+ namespace sjpeg {
27
+
28
+ ////////////////////////////////////////////////////////////////////////////////
29
+ // Headers
30
+ //
31
+ // NOTE(skal): all chunks start with a startcode '0xff??' (0xffd8 e.g),
32
+ // followed by the size of the payload *not counting the startcode*!
33
+ // That's why you often find these 'Reserve(data_size + 2)' below, the '+2'
34
+ // accounting for the 0xff?? startcode size.
35
+
36
+ static const uint8_t kHeaderAPP0[] = {
37
+ 0xff, 0xd8, // SOI
38
+ 0xff, 0xe0, 0x00, 0x10, // APP0
39
+ 0x4a, 0x46, 0x49, 0x46, 0x00, // 'JFIF'
40
+ 0x01, 0x01, // v1.01
41
+ 0x00, 0x00, 0x01, 0x00, 0x01, // aspect ratio = 1:1
42
+ 0x00, 0x00 // thumbnail width/height
43
+ };
44
+
45
+ void Encoder::WriteAPP0() { // SOI + APP0
46
+ ok_ = ok_ && bw_.Reserve(sizeof(kHeaderAPP0));
47
+ if (!ok_) return;
48
+ bw_.PutBytes(kHeaderAPP0, sizeof(kHeaderAPP0));
49
+ }
50
+
51
+ bool Encoder::WriteAPPMarkers(const std::string& data) {
52
+ if (data.size() == 0) return true;
53
+ const size_t data_size = data.size();
54
+ ok_ = ok_ && bw_.Reserve(data_size);
55
+ if (!ok_) return false;
56
+ bw_.PutBytes(reinterpret_cast<const uint8_t*>(data.data()), data.size());
57
+ return true;
58
+ }
59
+
60
+ bool Encoder::WriteEXIF(const std::string& data) {
61
+ if (data.size() == 0) return true;
62
+ const uint8_t kEXIF[] = "Exif\0";
63
+ const size_t kEXIF_len = 6; // includes the \0's
64
+ const size_t data_size = data.size() + kEXIF_len + 2;
65
+ if (data_size > 0xffff) return false;
66
+ ok_ = ok_ && bw_.Reserve(data_size + 2);
67
+ if (!ok_) return false;
68
+ bw_.PutByte(0xff);
69
+ bw_.PutByte(0xe1);
70
+ bw_.PutByte((data_size >> 8) & 0xff);
71
+ bw_.PutByte((data_size >> 0) & 0xff);
72
+ bw_.PutBytes(kEXIF, kEXIF_len);
73
+ bw_.PutBytes(reinterpret_cast<const uint8_t*>(data.data()), data.size());
74
+ return true;
75
+ }
76
+
77
+ bool Encoder::WriteICCP(const std::string& data) {
78
+ if (data.size() == 0) return true;
79
+ size_t data_size = data.size();
80
+ const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data.data());
81
+ const uint8_t kICCP[] = "ICC_PROFILE";
82
+ const size_t kICCP_len = 12; // includes the \0
83
+ const size_t chunk_size_max = 0xffff - kICCP_len - 4;
84
+ size_t max_chunk = (data_size + chunk_size_max - 1) / chunk_size_max;
85
+ if (max_chunk >= 256) return false;
86
+ size_t seq = 1;
87
+ while (data_size > 0) {
88
+ size_t size = data_size;
89
+ if (size > chunk_size_max) size = chunk_size_max;
90
+ ok_ = ok_ && bw_.Reserve(size + kICCP_len + 4 + 2);
91
+ if (!ok_) return false;
92
+ bw_.PutByte(0xff);
93
+ bw_.PutByte(0xe2);
94
+ bw_.PutByte(((size + kICCP_len + 4) >> 8) & 0xff);
95
+ bw_.PutByte(((size + kICCP_len + 4) >> 0) & 0xff);
96
+ bw_.PutBytes(kICCP, kICCP_len);
97
+ bw_.PutByte(seq & 0xff);
98
+ bw_.PutByte(max_chunk & 0xff);
99
+ bw_.PutBytes(ptr, size);
100
+ ptr += size;
101
+ data_size -= size;
102
+ seq += 1;
103
+ }
104
+ return true;
105
+ }
106
+
107
+ bool Encoder::WriteXMP(const std::string& data) {
108
+ if (data.size() == 0) return true;
109
+ const uint8_t kXMP[] = "http://ns.adobe.com/xap/1.0/";
110
+ const size_t kXMP_size = 29;
111
+ const size_t data_size = 2 + data.size() + kXMP_size;
112
+ if (data_size > 0xffff) return false; // error
113
+ ok_ = ok_ && bw_.Reserve(data_size + 2);
114
+ if (!ok_) return false;
115
+ bw_.PutByte(0xff);
116
+ bw_.PutByte(0xe1);
117
+ bw_.PutByte((data_size >> 8) & 0xff);
118
+ bw_.PutByte((data_size >> 0) & 0xff);
119
+ bw_.PutBytes(kXMP, kXMP_size);
120
+ bw_.PutBytes(reinterpret_cast<const uint8_t*>(data.data()), data.size());
121
+ return true;
122
+ }
123
+
124
+ void Encoder::WriteDQT() {
125
+ const size_t data_size = 2 * 65 + 2;
126
+ const uint8_t kDQTHeader[] = { 0xff, 0xdb, 0x00, (uint8_t)data_size };
127
+ ok_ = ok_ && bw_.Reserve(data_size + 2);
128
+ if (!ok_) return;
129
+ bw_.PutBytes(kDQTHeader, sizeof(kDQTHeader));
130
+ for (int n = 0; n <= 1; ++n) {
131
+ bw_.PutByte(n);
132
+ const uint8_t* quant = quants_[n].quant_;
133
+ for (int i = 0; i < 64; ++i) {
134
+ bw_.PutByte(quant[kZigzag[i]]);
135
+ }
136
+ }
137
+ }
138
+
139
+ ////////////////////////////////////////////////////////////////////////////////
140
+
141
+ #define DATA_16b(X) ((uint8_t)((X) >> 8)), ((uint8_t)((X) & 0xff))
142
+
143
+ void Encoder::WriteSOF() { // SOF
144
+ const size_t data_size = 3 * nb_comps_ + 8;
145
+ assert(data_size <= 255);
146
+ const uint8_t kHeader[] = {
147
+ 0xff, 0xc0, DATA_16b(data_size), // SOF0 marker, size
148
+ 0x08, // 8bits/components
149
+ DATA_16b(H_), DATA_16b(W_), // height, width
150
+ (uint8_t)nb_comps_ // number of components
151
+ };
152
+ ok_ = ok_ && bw_.Reserve(data_size + 2);
153
+ if (!ok_) return;
154
+ bw_.PutBytes(kHeader, sizeof(kHeader));
155
+ for (int c = 0; c < nb_comps_; ++c) {
156
+ bw_.PutByte(c + 1);
157
+ bw_.PutByte(block_dims_[c]);
158
+ bw_.PutByte(quant_idx_[c]);
159
+ }
160
+ }
161
+
162
+ void Encoder::WriteDHT() {
163
+ InitCodes(false);
164
+ const int nb_tables = (nb_comps_ == 1 ? 1 : 2);
165
+ for (int c = 0; c < nb_tables; ++c) { // luma, chroma
166
+ for (int type = 0; type <= 1; ++type) { // dc, ac
167
+ const HuffmanTable* const h = Huffman_tables_[type * 2 + c];
168
+ const size_t data_size = 3 + 16 + h->nb_syms_;
169
+ assert(data_size <= 255);
170
+ ok_ = ok_ && bw_.Reserve(data_size + 2);
171
+ if (!ok_) return;
172
+ bw_.PutByte(0xff);
173
+ bw_.PutByte(0xc4);
174
+ bw_.PutByte(0x00 /*data_size >> 8*/);
175
+ bw_.PutByte(data_size);
176
+ bw_.PutByte((type << 4) | c);
177
+ bw_.PutBytes(h->bits_, 16);
178
+ bw_.PutBytes(h->syms_, h->nb_syms_);
179
+ }
180
+ }
181
+ }
182
+
183
+ ////////////////////////////////////////////////////////////////////////////////
184
+
185
+ void Encoder::WriteSOS() { // SOS
186
+ const size_t data_size = 3 + nb_comps_ * 2 + 3;
187
+ assert(data_size <= 255);
188
+ const uint8_t kHeader[] = {
189
+ 0xff, 0xda, DATA_16b(data_size), (uint8_t)nb_comps_
190
+ };
191
+ ok_ = ok_ && bw_.Reserve(data_size + 2);
192
+ if (!ok_) return;
193
+ bw_.PutBytes(kHeader, sizeof(kHeader));
194
+ for (int c = 0; c < nb_comps_; ++c) {
195
+ bw_.PutByte(c + 1);
196
+ bw_.PutByte(quant_idx_[c] * 0x11);
197
+ }
198
+ bw_.PutByte(0x00); // Ss
199
+ bw_.PutByte(0x3f); // Se
200
+ bw_.PutByte(0x00); // Ah/Al
201
+ }
202
+
203
+ ////////////////////////////////////////////////////////////////////////////////
204
+
205
+ void Encoder::WriteEOI() { // EOI
206
+ if (ok_) bw_.Flush();
207
+ ok_ = ok_ && bw_.Reserve(2);
208
+ if (!ok_) return;
209
+ // append EOI
210
+ bw_.PutByte(0xff);
211
+ bw_.PutByte(0xd9);
212
+ }
213
+
214
+ ////////////////////////////////////////////////////////////////////////////////
215
+
216
+ #undef DATA_16b
217
+
218
+ } // namespace sjpeg
@@ -0,0 +1,274 @@
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.
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
+
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
+
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
+
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
+
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
+
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
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.
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).
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.
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
@@ -0,0 +1,11 @@
1
+ prefix=@prefix@
2
+ exec_prefix=@exec_prefix@
3
+ libdir=@libdir@
4
+ includedir=@includedir@
5
+
6
+ Name: libsjpeg
7
+ Description: Library compressing or recompressing pictures in JPEG format.
8
+ Version: @PACKAGE_VERSION@
9
+ Cflags: -I${includedir}
10
+ Libs: -L${libdir} -lsjpeg
11
+ Libs.private: -lm @PTHREAD_CFLAGS@ @PTHREAD_LIBS@