sjpeg 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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@
|