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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/sjpeg/bit_writer.cc +122 -0
- data/ext/sjpeg/bit_writer.h +169 -0
- data/ext/sjpeg/colors_rgb.cc +691 -0
- data/ext/sjpeg/dichotomy.cc +290 -0
- data/ext/sjpeg/enc.cc +2132 -0
- data/ext/sjpeg/extconf.rb +3 -0
- data/ext/sjpeg/fdct.cc +627 -0
- data/ext/sjpeg/headers.cc +218 -0
- data/ext/sjpeg/jpeg_tools.cc +274 -0
- data/ext/sjpeg/libsjpeg.pc.in +11 -0
- data/ext/sjpeg/score_7.cc +6220 -0
- data/ext/sjpeg/sjpeg.h +353 -0
- data/ext/sjpeg/sjpegi.h +427 -0
- data/ext/sjpeg/yuv_convert.cc +698 -0
- data/lib/sjpeg/version.rb +3 -0
- data/lib/sjpeg.rb +35 -0
- data/sjpeg.gemspec +36 -0
- metadata +143 -0
@@ -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@
|