Skip to content

Commit e3c948f

Browse files
authored
Add support for Qfactor (#308)
* First pass at adding Qfactor * Fixes * Added chroma format * Added tests * Added 4:2:0 test * Fix error codes * Change *.hpp to *.h * Assign type to param_qcd::comp_type * Change set_qfactor() signature
1 parent 8a87841 commit e3c948f

6 files changed

Lines changed: 583 additions & 5 deletions

File tree

src/apps/ojph_compress/ojph_compress.cpp

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ static
361361
bool get_arguments(int argc, char *argv[], char *&input_filename,
362362
char *&output_filename, char *&progression_order,
363363
char *&profile_string, ojph::ui32 &num_decompositions,
364-
float &quantization_step, bool &reversible,
364+
float &quantization_step, int &qfactor, bool &reversible,
365365
int &employ_color_transform,
366366
const int max_num_precincts, int &num_precincts,
367367
ojph::size *precinct_size, ojph::size& block_size,
@@ -383,6 +383,7 @@ bool get_arguments(int argc, char *argv[], char *&input_filename,
383383
interpreter.reinterpret("-profile", profile_string);
384384
interpreter.reinterpret("-num_decomps", num_decompositions);
385385
interpreter.reinterpret("-qstep", quantization_step);
386+
interpreter.reinterpret("-qfactor", qfactor);
386387
interpreter.reinterpret("-reversible", reversible);
387388
interpreter.reinterpret_to_bool("-colour_trans", employ_color_transform);
388389
interpreter.reinterpret("-num_comps", num_comps);
@@ -498,6 +499,7 @@ int main(int argc, char * argv[]) {
498499
char *com_string = NULL;
499500
ojph::ui32 num_decompositions = 5;
500501
float quantization_step = -1.0f;
502+
int qfactor = -1;
501503
bool reversible = false;
502504
int employ_color_transform = -1;
503505

@@ -542,6 +544,10 @@ int main(int argc, char * argv[]) {
542544
" compression; quantization steps size for all subbands are\n"
543545
" derived from this value. {The default value for 8bit\n"
544546
" images is 0.0039}\n"
547+
" -qfactor (1...100) compression quality factor; 1 is worst\n"
548+
" quality and 100 is best quality. Only valid for\n"
549+
" images with 1 or 3 components. Cannot be used\n"
550+
" together with -qstep.\n"
545551
" -reversible <true | false> If this is 'false', an irreversible or\n"
546552
" lossy compression is employed, using the 9/7 wavelet\n"
547553
" transform; if 'true', a reversible compression is\n"
@@ -622,7 +628,8 @@ int main(int argc, char * argv[]) {
622628
}
623629
if (!get_arguments(argc, argv, input_filename, output_filename,
624630
prog_order, profile_string, num_decompositions,
625-
quantization_step, reversible, employ_color_transform,
631+
quantization_step, qfactor, reversible,
632+
employ_color_transform,
626633
max_precinct_sizes, num_precincts, precinct_size,
627634
block_size, dims, image_offset, tile_size, tile_offset,
628635
max_num_comps, num_components,
@@ -634,6 +641,13 @@ int main(int argc, char * argv[]) {
634641
return -1;
635642
}
636643

644+
if (qfactor != -1 && quantization_step != -1.0f)
645+
OJPH_ERROR(0x010000A1,
646+
"-qfactor and -qstep cannot be used together\n");
647+
if (qfactor != -1 && (qfactor < 1 || qfactor > 100))
648+
OJPH_ERROR(0x010000A2,
649+
"-qfactor must be between 1 and 100\n");
650+
637651
clock_t begin = clock();
638652

639653
try
@@ -686,6 +700,9 @@ int main(int argc, char * argv[]) {
686700
cod.set_reversible(reversible);
687701
if (!reversible && quantization_step != -1.0f)
688702
codestream.access_qcd().set_irrev_quant(quantization_step);
703+
if (!reversible && qfactor != -1)
704+
codestream.access_qcd().set_qfactor(0,
705+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
689706
if (profile_string[0] != '\0')
690707
codestream.set_profile(profile_string);
691708
codestream.set_tilepart_divisions(tileparts_at_resolutions,
@@ -742,6 +759,12 @@ int main(int argc, char * argv[]) {
742759
cod.set_reversible(reversible);
743760
if (!reversible && quantization_step != -1.0f)
744761
codestream.access_qcd().set_irrev_quant(quantization_step);
762+
if (!reversible && qfactor != -1) {
763+
ojph::param_qcd qcd = codestream.access_qcd();
764+
qcd.set_qfactor(0, ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
765+
qcd.set_qfactor(1, ojph::param_qcd::OJPH_COMP_CB, (ojph::ui8)qfactor);
766+
qcd.set_qfactor(2, ojph::param_qcd::OJPH_COMP_CR, (ojph::ui8)qfactor);
767+
}
745768
codestream.set_planar(false);
746769
if (profile_string[0] != '\0')
747770
codestream.set_profile(profile_string);
@@ -820,7 +843,20 @@ int main(int argc, char * argv[]) {
820843
cod.set_color_transform(employ_color_transform == 1);
821844
}
822845
cod.set_reversible(reversible);
823-
if (!reversible) {
846+
if (!reversible && qfactor != -1) {
847+
ojph::param_qcd qcd = codestream.access_qcd();
848+
if (num_comps == 1) {
849+
qcd.set_qfactor(0,
850+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
851+
} else {
852+
qcd.set_qfactor(0,
853+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
854+
qcd.set_qfactor(1,
855+
ojph::param_qcd::OJPH_COMP_CB, (ojph::ui8)qfactor);
856+
qcd.set_qfactor(2,
857+
ojph::param_qcd::OJPH_COMP_CR, (ojph::ui8)qfactor);
858+
}
859+
} else if (!reversible) {
824860
const float min_step = 1.0f / 16384.0f;
825861
if (quantization_step == -1.0f)
826862
quantization_step = min_step;
@@ -898,6 +934,24 @@ int main(int argc, char * argv[]) {
898934
cod.set_reversible(reversible);
899935
if (!reversible && quantization_step != -1)
900936
codestream.access_qcd().set_irrev_quant(quantization_step);
937+
if (!reversible && qfactor != -1) {
938+
if (num_comps != 1 && num_comps != 3)
939+
OJPH_ERROR(0x010000A3,
940+
"-qfactor is only supported for images with 1 or 3 "
941+
"components\n");
942+
ojph::param_qcd qcd = codestream.access_qcd();
943+
if (num_comps == 1) {
944+
qcd.set_qfactor(0,
945+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
946+
} else {
947+
qcd.set_qfactor(0,
948+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
949+
qcd.set_qfactor(1,
950+
ojph::param_qcd::OJPH_COMP_CB, (ojph::ui8)qfactor);
951+
qcd.set_qfactor(2,
952+
ojph::param_qcd::OJPH_COMP_CR, (ojph::ui8)qfactor);
953+
}
954+
}
901955
codestream.set_planar(false);
902956
if (profile_string[0] != '\0')
903957
codestream.set_profile(profile_string);
@@ -983,6 +1037,24 @@ int main(int argc, char * argv[]) {
9831037
cod.set_reversible(reversible);
9841038
if (!reversible && quantization_step != -1.0f)
9851039
codestream.access_qcd().set_irrev_quant(quantization_step);
1040+
if (!reversible && qfactor != -1) {
1041+
if (num_components != 1 && num_components != 3)
1042+
OJPH_ERROR(0x010000A4,
1043+
"-qfactor is only supported for images with 1 or 3 "
1044+
"components\n");
1045+
ojph::param_qcd qcd = codestream.access_qcd();
1046+
if (num_components == 1) {
1047+
qcd.set_qfactor(0,
1048+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
1049+
} else {
1050+
qcd.set_qfactor(0,
1051+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
1052+
qcd.set_qfactor(1,
1053+
ojph::param_qcd::OJPH_COMP_CB, (ojph::ui8)qfactor);
1054+
qcd.set_qfactor(2,
1055+
ojph::param_qcd::OJPH_COMP_CR, (ojph::ui8)qfactor);
1056+
}
1057+
}
9861058
codestream.set_planar(true);
9871059
if (profile_string[0] != '\0')
9881060
codestream.set_profile(profile_string);
@@ -1035,6 +1107,9 @@ int main(int argc, char * argv[]) {
10351107
cod.set_reversible(reversible);
10361108
if (!reversible && quantization_step != -1.0f)
10371109
codestream.access_qcd().set_irrev_quant(quantization_step);
1110+
if (!reversible && qfactor != -1)
1111+
codestream.access_qcd().set_qfactor(0,
1112+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
10381113
codestream.set_planar(true);
10391114
if (profile_string[0] != '\0')
10401115
codestream.set_profile(profile_string);
@@ -1075,6 +1150,24 @@ int main(int argc, char * argv[]) {
10751150
cod.set_reversible(reversible);
10761151
if (!reversible && quantization_step != -1)
10771152
codestream.access_qcd().set_irrev_quant(quantization_step);
1153+
if (!reversible && qfactor != -1) {
1154+
if (num_comps != 1 && num_comps != 3)
1155+
OJPH_ERROR(0x010000A5,
1156+
"-qfactor is only supported for images with 1 or 3 "
1157+
"components\n");
1158+
ojph::param_qcd qcd = codestream.access_qcd();
1159+
if (num_comps == 1) {
1160+
qcd.set_qfactor(0,
1161+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
1162+
} else {
1163+
qcd.set_qfactor(0,
1164+
ojph::param_qcd::OJPH_COMP_Y, (ojph::ui8)qfactor);
1165+
qcd.set_qfactor(1,
1166+
ojph::param_qcd::OJPH_COMP_CB, (ojph::ui8)qfactor);
1167+
qcd.set_qfactor(2,
1168+
ojph::param_qcd::OJPH_COMP_CR, (ojph::ui8)qfactor);
1169+
}
1170+
}
10781171
codestream.set_planar(false);
10791172
if (profile_string[0] != '\0')
10801173
codestream.set_profile(profile_string);

src/core/codestream/ojph_params.cpp

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "ojph_file.h"
4444
#include "ojph_params.h"
4545

46+
#include "ojph_visual_weighting.h"
4647
#include "ojph_params_local.h"
4748
#include "ojph_message.h"
4849

@@ -423,6 +424,11 @@ namespace ojph {
423424
state->set_delta(comp_idx, delta);
424425
}
425426

427+
//////////////////////////////////////////////////////////////////////////
428+
void param_qcd::set_qfactor(ui32 comp_idx, comp_type ctype, ui8 qfactor) {
429+
state->set_qfactor(comp_idx, ctype, qfactor);
430+
}
431+
426432
////////////////////////////////////////////////////////////////////////////
427433
//
428434
//
@@ -1164,14 +1170,16 @@ namespace ojph {
11641170

11651171
// initialize every QCC, creating one for every component that (a) cannot
11661172
// use QCD and (b) does not already have a QCC
1173+
// NOTE: Qfactor always creates a QCC and QCD cannot be reused
11671174
for (ui32 c = 0; c < num_comps; ++c)
11681175
{
11691176
param_qcd *qcc = this->get_qcc(c);
11701177
const param_cod *coc = cod.get_coc(c);
11711178

1179+
// check if a QCC exists for the component
11721180
if (qcc == this)
11731181
{
1174-
// no QCC specified for the component, one is created only if QCD cannot be used
1182+
// if none exists, do not create one if QCD can be reused
11751183
if (!this->is_qcc_needed(c, *coc, siz))
11761184
continue;
11771185

@@ -1196,6 +1204,7 @@ namespace ojph {
11961204
this->is_signed = siz.is_signed(comp_num);
11971205
this->is_color_trans = cod.is_employing_color_transform();
11981206
this->wavelet_kern = cod.get_wavelet_kern();
1207+
this->sampling = siz.get_downsampling(comp_num);
11991208
this->num_subbands = 1 + 3 * this->num_decomps;
12001209

12011210
if (this->wavelet_kern == param_cod::DWT_REV53)
@@ -1236,6 +1245,22 @@ namespace ojph {
12361245
p->set_delta(delta);
12371246
}
12381247

1248+
//////////////////////////////////////////////////////////////////////////
1249+
void param_qcd::set_qfactor(ui32 comp_idx, ojph::param_qcd::comp_type ctype, ui8 qfactor) {
1250+
if (this->top_qcd != NULL)
1251+
OJPH_ERROR(0x00040401, "This method is not implemented for QCC.");
1252+
1253+
if (qfactor < 1 || qfactor > 100)
1254+
OJPH_ERROR(0x00040403, "Qfactor must be between 1 and 100, but was set to %i.", qfactor);
1255+
1256+
param_qcd *p = get_qcc(comp_idx);
1257+
if (p == this)
1258+
p = add_qcc_object(comp_idx);
1259+
1260+
p->qfactor = qfactor;
1261+
p->ctype = ctype;
1262+
}
1263+
12391264
//////////////////////////////////////////////////////////////////////////
12401265
void param_qcd::set_rev_quant(ui32 num_decomps, ui32 bit_depth,
12411266
bool is_employing_color_transform)
@@ -1289,6 +1314,40 @@ namespace ojph {
12891314
int guard_bits = 1;
12901315
Sqcd = (ui8)((guard_bits<<5)|0x2);//one guard bit, scalar quantization
12911316

1317+
// the following are used only when Qfactor is set
1318+
float qfactor_power = 0;
1319+
float delta_ref = 0;
1320+
float G_c = 1;
1321+
const open_htj2k::visual_weighting_params vp;
1322+
std::vector<double> W_b;
1323+
if (this->qfactor != QFACTOR_UNSET) {
1324+
const open_htj2k::q_scaling qs = open_htj2k::q_to_delta(this->qfactor, (ui8) this->bit_depth);
1325+
qfactor_power = (float) qs.qfactor_power;
1326+
const open_htj2k::color_transform ct = open_htj2k::resolve_color_transform(vp, this->is_color_trans);
1327+
int comp_index = (int)this->ctype;
1328+
delta_ref = (float) (qs.delta_Q * open_htj2k::color_gain(ct, 0));
1329+
G_c = (float) open_htj2k::color_gain(ct, comp_index);
1330+
1331+
// chroma_format 0 = 4:4:4, 1 = 4:2:0, 2 = 4:2:2
1332+
int sampling = 0;
1333+
if (this->sampling.x == 1 && this->sampling.y == 1)
1334+
{ sampling = 0; }
1335+
else if (this->sampling.x == 2 && this->sampling.y == 2)
1336+
{ sampling = 1; }
1337+
else if (this->sampling.x == 2 && this->sampling.y == 1)
1338+
{ sampling = 2; }
1339+
else
1340+
{
1341+
OJPH_ERROR(0x00050161, "Qfactor can only be used on components with 4:4:4, 4:2:2 or 4:2:0 sampling");
1342+
}
1343+
1344+
if (this->ctype == ojph::param_qcd::OJPH_COMP_Y)
1345+
W_b = open_htj2k::luma_visual_weights((ui8) num_decomps, vp);
1346+
else
1347+
W_b = open_htj2k::chroma_visual_weights((ui8) num_decomps, vp, comp_index, sampling, ct);
1348+
1349+
}
1350+
12921351
// LL, HL, LH, HH, HL, LH, HH...
12931352
for (ui32 s = 0; s < (1 + num_decomps * 3); s++)
12941353
{
@@ -1310,7 +1369,16 @@ namespace ojph {
13101369
{ w_g = gain_l * gain_h; }
13111370
}
13121371

1313-
float delta_b = base_delta / w_g;
1372+
float delta_b;
1373+
if (this->qfactor == QFACTOR_UNSET)
1374+
{
1375+
delta_b = base_delta / w_g;
1376+
}
1377+
else
1378+
{
1379+
float w_b = (s == 0 || s > W_b.size()) ? (float) 1.0 : (float) pow(W_b[W_b.size() - s], qfactor_power);
1380+
delta_b = delta_ref / (w_g * w_b * G_c);
1381+
}
13141382

13151383
int exp = 0, mantissa;
13161384
while (delta_b < 1.0f)

src/core/codestream/ojph_params_local.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,11 @@ namespace ojph {
691691
QCC_TILE = 4 // not implemented
692692
};
693693

694+
////////////////////////////////////////
695+
enum qfactor_const : ui8 {
696+
QFACTOR_UNSET = 0
697+
};
698+
694699
public:
695700
param_qcd(param_qcd* top_qcd = NULL, ui16 comp_idx = OJPH_QCD_DEFAULT)
696701
{ avail = NULL; init(top_qcd, comp_idx); }
@@ -711,6 +716,7 @@ namespace ojph {
711716
bool is_qcc_needed(ui32 comp_num, const param_cod &cod, const param_siz &siz);
712717
void set_delta(float delta) { base_delta = delta; }
713718
void set_delta(ui32 comp_idx, float delta);
719+
void set_qfactor(ui32 comp_idx, ojph::param_qcd::comp_type ctype, ui8 qfactor);
714720
ui32 get_num_guard_bits() const;
715721
ui32 get_MAGB() const;
716722
ui32 get_Kmax(const param_dfs* dfs, ui32 num_decompositions,
@@ -740,6 +746,9 @@ namespace ojph {
740746
memset(&SPqcd, 0, sizeof(SPqcd));
741747
num_subbands = 0;
742748
base_delta = -1.0f;
749+
qfactor = QFACTOR_UNSET;
750+
ctype = ojph::param_qcd::OJPH_COMP_Y;
751+
sampling = ojph::point(1, 1);
743752
enabled = true;
744753
next = NULL;
745754
this->top_qcd = top_qcd;
@@ -788,11 +797,14 @@ namespace ojph {
788797
// variables used to generate the quantization step sizes
789798
float base_delta; // base quantization step size -- all other
790799
// step sizes are derived from it.
800+
ui8 qfactor;
801+
ojph::param_qcd::comp_type ctype;
791802
bool is_color_trans;
792803
ui32 num_decomps;
793804
ui32 bit_depth;
794805
bool is_signed;
795806
ui32 wavelet_kern;
807+
ojph::point sampling;
796808

797809
private: // QCC only variables
798810
ui16 comp_idx;

0 commit comments

Comments
 (0)