sjpeg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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@