OpenJPH
Open-source implementation of JPEG2000 Part-15
Loading...
Searching...
No Matches
test_quant_weights.cpp
Go to the documentation of this file.
1//***************************************************************************/
2// This software is released under the 2-Clause BSD license, included
3// below.
4//
5// Copyright (c) Pierre-Anthony Lemieux
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are
9// met:
10//
11// 1. Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13//
14// 2. Redistributions in binary form must reproduce the above copyright
15// notice, this list of conditions and the following disclaimer in the
16// documentation and/or other materials provided with the distribution.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29//***************************************************************************/
30// This file is part of the OpenJPH software implementation.
31// File: test_quant_weights.cpp
32// Author: Pierre-Anthony Lemieux
33// Date: 02 October 2026
34//***************************************************************************/
35
36#include <cmath>
37#include <vector>
38
39#include "ojph_mem.h"
40#include "ojph_file.h"
41#include "ojph_codestream.h"
42#include "ojph_params.h"
43#include "gtest/gtest.h"
44
46// Tests for param_qcd::set_irrev_quant(comp_idx, delta, weights, num_weights).
47//
48// The headers are generated in memory, and the QCD and QCC marker segments
49// are parsed to check that the step sizes they carry are the ones expected
50// from delta_b = delta_r / (sqrt(G_b) * sqrt(w_b)), with the expected values
51// computed here independently of the library.
52
53namespace {
54
55 // squared norms of the 9/7 synthesis filters (Table 1 of the Qfactor
56 // guideline, WG1N101553), for decomposition levels 1 to 3; the high-pass
57 // filter is scaled by 2, as in the library, hence the factor of 4 below
58 const double NORM2_L[3] = { 1.965908, 4.122410, 8.416737 };
59 const double NORM2_H[3] = { 0.520218, 0.967216, 2.079257 };
60
61 // square roots of the 9/7 synthesis energy gains, for up to 3 levels
62 const float GAIN_L[4] = { 1.0f, (float)std::sqrt(NORM2_L[0]),
63 (float)std::sqrt(NORM2_L[1]), (float)std::sqrt(NORM2_L[2]) };
64 const float GAIN_H[3] = { (float)std::sqrt(4.0 * NORM2_H[0]),
65 (float)std::sqrt(4.0 * NORM2_H[1]), (float)std::sqrt(4.0 * NORM2_H[2]) };
66
67 // square roots of the nominal visual weights for 4:4:4 YCbCr content (Table
68 // 2 of the Qfactor guideline), for 3 decomposition levels, in the order
69 // {LH1, HL1, HH1, LH2, HL2, HH2, LH3, HL3, HH3, LL3}
70 const float CB_SQRT_W[10] = { 0.0863f, 0.0863f, 0.0263f,
71 0.2564f, 0.2564f, 0.1362f,
72 0.4691f, 0.4691f, 0.3346f, 1.0f };
73 const float CR_SQRT_W[10] = { 0.1835f, 0.1835f, 0.0773f,
74 0.4130f, 0.4130f, 0.2598f,
75 0.6464f, 0.6464f, 0.5040f, 1.0f };
76
77 // the square roots of the visual weights, as expected by set_irrev_quant
78 const std::vector<float> CB_W(CB_SQRT_W, CB_SQRT_W + 10);
79 const std::vector<float> CR_W(CR_SQRT_W, CR_SQRT_W + 10);
80
81 // decoded step sizes of a QCD/QCC, in codestream order: LL, then for each
82 // level from the coarsest to the finest HL, LH, HH
83 struct quant { bool found; ojph::ui8 style; std::vector<float> delta; };
84
85 float decode_delta(ojph::ui16 v)
86 {
87 int exp = v >> 11, mant = v & 0x7FF;
88 return std::ldexp(1.0f + (float)mant / 2048.0f, -exp);
89 }
90
92 // Writes the headers of a num_comps-component image, 9/7 wavelet, no color
93 // transform. The callback configures the QCD/QCC.
94 template<class F>
95 void make_headers(ojph::mem_outfile& out, ojph::ui32 num_comps,
96 ojph::ui32 num_decomps, F configure,
97 bool reversible = false)
98 {
100 ojph::param_siz siz = cs.access_siz();
101 siz.set_image_extent(ojph::point(64, 64));
102 siz.set_num_components(num_comps);
103 for (ojph::ui32 c = 0; c < num_comps; ++c)
104 siz.set_component(c, ojph::point(1, 1), 8, false);
105 siz.set_image_offset(ojph::point(0, 0));
106 siz.set_tile_size(ojph::size(0, 0));
107 siz.set_tile_offset(ojph::point(0, 0));
108
109 ojph::param_cod cod = cs.access_cod();
110 cod.set_num_decomposition(num_decomps);
111 cod.set_color_transform(false);
112 cod.set_reversible(reversible);
113
114 configure(cs.access_qcd());
115
116 out.open();
117 cs.write_headers(&out);
118 }
119
121 // Finds the QCD (comp < 0) or the QCC of a component in the main header.
122 quant find_quant(const ojph::mem_outfile& out, int comp,
123 ojph::ui32 num_decomps)
124 {
125 quant q = { false, 0, std::vector<float>() };
126 const ojph::ui8* p = out.get_data();
127 size_t len = (size_t)out.get_used_size();
128 size_t pos = 2; // skip SOC
129 while (pos + 4 <= len)
130 {
131 ojph::ui16 marker = (ojph::ui16)((p[pos] << 8) | p[pos + 1]);
132 if (marker == 0xFF90) break; // SOT
133 ojph::ui16 seg_len = (ojph::ui16)((p[pos + 2] << 8) | p[pos + 3]);
134 size_t q0 = pos + 4;
135 bool match = false;
136 if (comp < 0 && marker == 0xFF5C)
137 match = true;
138 else if (comp >= 0 && marker == 0xFF5D && p[q0++] == comp) // 1-byte Cqcc
139 match = true;
140 if (match)
141 {
142 q.found = true;
143 q.style = p[q0++];
144 EXPECT_EQ(q.style & 0x1F, 2); // scalar expounded
145 for (ojph::ui32 i = 0; i < 1 + 3 * num_decomps; ++i, q0 += 2)
146 q.delta.push_back(
147 decode_delta((ojph::ui16)((p[q0] << 8) | p[q0 + 1])));
148 return q;
149 }
150 pos += (size_t)seg_len + 2;
151 }
152 return q;
153 }
154
156 // Expected step sizes in codestream order, given weights in the API order
157 // {LH1, HL1, HH1, ..., LHN, HLN, HHN, LLN}
158 std::vector<float> expected(float delta, const std::vector<float>& w,
159 ojph::ui32 n)
160 {
161 std::vector<float> e;
162 e.push_back(delta / (GAIN_L[n] * GAIN_L[n] * w[3 * n]));
163 for (ojph::ui32 d = n; d > 0; --d)
164 {
165 float gl = GAIN_L[d], gh = GAIN_H[d - 1];
166 e.push_back(delta / (gh * gl * w[(d - 1) * 3 + 1])); // HL
167 e.push_back(delta / (gl * gh * w[(d - 1) * 3 + 0])); // LH
168 e.push_back(delta / (gh * gh * w[(d - 1) * 3 + 2])); // HH
169 }
170 return e;
171 }
172
173 void expect_close(const std::vector<float>& got,
174 const std::vector<float>& want)
175 {
176 ASSERT_EQ(got.size(), want.size());
177 for (size_t i = 0; i < got.size(); ++i)
178 // the step size is quantized to an 11-bit mantissa; the gain values
179 // here are 5 digits
180 EXPECT_NEAR(got[i] / want[i], 1.0f, 1.5e-3f) << "subband " << i;
181 }
182
183}
184
186// Weights on one component of two end up in that component's QCC, with every
187// subband's step size equal to delta / (sqrt(G_b) * sqrt(w_b)). The weights are all
188// different, so a subband mapped to the wrong weight is detected. The other
189// component keeps using the QCD, with unit weights.
190TEST(QuantWeights, ValuesInQcc)
191{
192 const ojph::ui32 n = 3;
193 // all distinct, so a swapped subband is detected
194 std::vector<float> w = CB_W;
196 make_headers(out, 2, n, [&](ojph::param_qcd qcd) {
197 qcd.set_irrev_quant(0.01f);
198 qcd.set_irrev_quant(1, 0.02f, w.size(), w.data());
199 });
200
201 quant q1 = find_quant(out, 1, n);
202 ASSERT_TRUE(q1.found);
203 expect_close(q1.delta, expected(0.02f, w, n));
204
205 // the other component keeps using the QCD, without weights
206 EXPECT_FALSE(find_quant(out, 0, n).found);
207 quant q0 = find_quant(out, -1, n);
208 ASSERT_TRUE(q0.found);
209 expect_close(q0.delta, expected(0.01f, std::vector<float>(10, 1.0f), n));
210}
211
213// Weights set for the whole image end up in the QCD and apply to every
214// component that has no QCC.
215TEST(QuantWeights, ValuesInQcdForAllComponents)
216{
217 const ojph::ui32 n = 3;
218 std::vector<float> w = CB_W;
220 make_headers(out, 2, n, [&](ojph::param_qcd qcd) {
221 qcd.set_irrev_quant(0.01f, w.size(), w.data());
222 });
223
224 quant q = find_quant(out, -1, n);
225 ASSERT_TRUE(q.found);
226 expect_close(q.delta, expected(0.01f, w, n));
227 EXPECT_FALSE(find_quant(out, 0, n).found);
228 EXPECT_FALSE(find_quant(out, 1, n).found);
229}
230
232// Weights that are all 1 give exactly the same step sizes as not providing
233// weights.
234TEST(QuantWeights, UnitWeightsMatchNoWeights)
235{
236 const ojph::ui32 n = 3;
237 std::vector<float> w(10, 1.0f);
239 make_headers(a, 2, n, [&](ojph::param_qcd qcd) {
240 qcd.set_irrev_quant(0.01f);
241 qcd.set_irrev_quant(1, 0.02f, w.size(), w.data());
242 });
243 make_headers(b, 2, n, [&](ojph::param_qcd qcd) {
244 qcd.set_irrev_quant(0.01f);
245 qcd.set_irrev_quant(1, 0.02f);
246 });
247 quant qa = find_quant(a, 1, n), qb = find_quant(b, 1, n);
248 ASSERT_TRUE(qa.found && qb.found);
249 EXPECT_EQ(qa.delta, qb.delta);
250}
251
253// The weights are mapped correctly for a different number of decomposition
254// levels (1 level, 4 weights), including the single-component case.
255TEST(QuantWeights, FewerDecompositionLevels)
256{
257 const ojph::ui32 n = 1;
258 const float sqrt_w[4] = { 0.0863f, 0.0863f, 0.0263f, 1.0f }; // Cb, 1 level
259 std::vector<float> w(sqrt_w, sqrt_w + 4);
261 make_headers(out, 1, n, [&](ojph::param_qcd qcd) {
262 qcd.set_irrev_quant(0, 0.05f, w.size(), w.data());
263 });
264 quant q = find_quant(out, 0, n);
265 ASSERT_TRUE(q.found);
266 expect_close(q.delta, expected(0.05f, w, n));
267}
268
270// A number of weights that is not 1 + 3 * decomposition levels is an error.
271TEST(QuantWeights, WrongNumberOfWeights)
272{
273 std::vector<float> w(9, 1.0f); // 10 are needed for 3 levels
275 EXPECT_THROW(make_headers(out, 1, 3, [&](ojph::param_qcd qcd) {
276 qcd.set_irrev_quant(0, 0.01f, w.size(), w.data());
277 }), std::runtime_error);
278}
279
281// A weight that is not positive is an error.
282TEST(QuantWeights, InvalidWeights)
283{
284 std::vector<float> w(10, 1.0f);
285 w[4] = 0.0f;
287 EXPECT_THROW(make_headers(out, 1, 3, [&](ojph::param_qcd qcd) {
288 qcd.set_irrev_quant(0, 0.01f, w.size(), w.data());
289 }), std::runtime_error);
290}
291
293// Weights cannot be used with the reversible transform, where they would
294// have no effect.
295TEST(QuantWeights, ReversibleRejected)
296{
297 std::vector<float> w(10, 1.0f);
299 EXPECT_THROW(make_headers(out, 1, 3, [&](ojph::param_qcd qcd) {
300 qcd.set_irrev_quant(0, 0.01f, w.size(), w.data());
301 }, true), std::runtime_error);
302}
303
305// Mixing set_irrev_quant and set_qfactor.
306//
307// Three 4:4:4 components are used, as Qfactor needs them. The expected values
308// of the components using Qfactor are taken from a header where Qfactor alone
309// is set for the whole image, so these tests check how the settings combine,
310// not how Qfactor itself is computed.
311
312namespace {
313
314 typedef ojph::param_qcd::comp_type ctype_t;
315 const ojph::ui32 NQ = 3; // decomposition levels
316 const float QF = 50.0f; // Qfactor
317 const std::vector<float> UNIT(1 + 3 * NQ, 1.0f);
318
319 // headers where Qfactor is the only thing set
320 void qfactor_only(ojph::mem_outfile& out)
321 {
322 make_headers(out, 3, NQ, [&](ojph::param_qcd qcd) {
323 qcd.set_qfactor(QF);
324 });
325 }
326
327 template<class F>
328 void mixed(ojph::mem_outfile& out, F configure)
329 { make_headers(out, 3, NQ, configure); }
330
331 // the component's QCC must be identical to the one of the Qfactor-only
332 // header
333 void expect_like_qfactor(const ojph::mem_outfile& out,
334 const ojph::mem_outfile& ref, int comp)
335 {
336 quant a = find_quant(out, comp, NQ), b = find_quant(ref, comp, NQ);
337 ASSERT_TRUE(a.found && b.found) << "component " << comp;
338 EXPECT_EQ(a.delta, b.delta) << "component " << comp;
339 }
340
341 void expect_not_like_qfactor(const ojph::mem_outfile& out,
342 const ojph::mem_outfile& ref, int comp)
343 {
344 quant a = find_quant(out, comp, NQ), b = find_quant(ref, comp, NQ);
345 ASSERT_TRUE(a.found && b.found) << "component " << comp;
346 EXPECT_NE(a.delta, b.delta) << "component " << comp;
347 }
348
349}
350
352// Qfactor set for the whole image, with a plain delta for one component: that
353// component uses the delta; the others still use Qfactor.
354TEST(QuantMix, GlobalQfactorWithDeltaOnOneComponent)
355{
356 ojph::mem_outfile ref, out;
357 qfactor_only(ref);
358 mixed(out, [&](ojph::param_qcd qcd) {
359 qcd.set_qfactor(QF);
360 qcd.set_irrev_quant(1, 0.02f);
361 });
362 expect_like_qfactor(out, ref, 0);
363 expect_like_qfactor(out, ref, 2);
364 expect_not_like_qfactor(out, ref, 1);
365 quant q = find_quant(out, 1, NQ);
366 expect_close(q.delta, expected(0.02f, UNIT, NQ));
367}
368
370// Qfactor set for the whole image, with delta and weights for one component:
371// that component uses them; the others still use Qfactor.
372TEST(QuantMix, GlobalQfactorWithWeightsOnOneComponent)
373{
374 std::vector<float> w = CB_W;
375 ojph::mem_outfile ref, out;
376 qfactor_only(ref);
377 mixed(out, [&](ojph::param_qcd qcd) {
378 qcd.set_qfactor(QF);
379 qcd.set_irrev_quant(2, 0.02f, w.size(), w.data());
380 });
381 expect_like_qfactor(out, ref, 0);
382 expect_like_qfactor(out, ref, 1);
383 expect_not_like_qfactor(out, ref, 2);
384 quant q = find_quant(out, 2, NQ);
385 expect_close(q.delta, expected(0.02f, w, NQ));
386}
387
389// Qfactor for a single component, with a global delta: the component uses
390// Qfactor, and the others use the global delta through the QCD.
391TEST(QuantMix, QfactorOnOneComponentDeltaElsewhere)
392{
393 ojph::mem_outfile ref, out;
394 qfactor_only(ref);
395 mixed(out, [&](ojph::param_qcd qcd) {
396 qcd.set_irrev_quant(0.01f);
397 qcd.set_qfactor(1, ctype_t::OJPH_COMP_CB, QF);
398 });
399 expect_like_qfactor(out, ref, 1);
400 // the others use the QCD
401 EXPECT_FALSE(find_quant(out, 0, NQ).found);
402 EXPECT_FALSE(find_quant(out, 2, NQ).found);
403 quant q = find_quant(out, -1, NQ);
404 ASSERT_TRUE(q.found);
405 expect_close(q.delta, expected(0.01f, UNIT, NQ));
406}
407
409// Qfactor for one component, weights for another, and a global delta for the
410// third: each gets its own setting.
411TEST(QuantMix, QfactorOnOneComponentWeightsOnAnother)
412{
413 std::vector<float> w = CB_W;
414 ojph::mem_outfile ref, out;
415 qfactor_only(ref);
416 mixed(out, [&](ojph::param_qcd qcd) {
417 qcd.set_irrev_quant(0.01f);
418 qcd.set_qfactor(0, ctype_t::OJPH_COMP_Y, QF);
419 qcd.set_irrev_quant(2, 0.03f, w.size(), w.data());
420 });
421 expect_like_qfactor(out, ref, 0);
422 EXPECT_FALSE(find_quant(out, 1, NQ).found);
423 quant q = find_quant(out, 2, NQ);
424 ASSERT_TRUE(q.found);
425 expect_close(q.delta, expected(0.03f, w, NQ));
426}
427
429// When both are set for the same component, Qfactor wins, whichever was set
430// first (a warning is issued about the ignored step size)
431TEST(QuantMix, QfactorWinsOverDeltaOnSameComponent)
432{
433 ojph::mem_outfile ref, a, b, c;
434 qfactor_only(ref);
435 mixed(a, [&](ojph::param_qcd qcd) {
436 qcd.set_irrev_quant(0, 0.02f);
437 qcd.set_qfactor(0, ctype_t::OJPH_COMP_Y, QF);
438 qcd.set_qfactor(1, ctype_t::OJPH_COMP_CB, QF);
439 qcd.set_qfactor(2, ctype_t::OJPH_COMP_CR, QF);
440 });
441 mixed(b, [&](ojph::param_qcd qcd) {
442 qcd.set_qfactor(0, ctype_t::OJPH_COMP_Y, QF);
443 qcd.set_irrev_quant(0, 0.02f);
444 qcd.set_qfactor(1, ctype_t::OJPH_COMP_CB, QF);
445 qcd.set_qfactor(2, ctype_t::OJPH_COMP_CR, QF);
446 });
447 mixed(c, [&](ojph::param_qcd qcd) { // global settings
448 qcd.set_irrev_quant(0.02f);
449 qcd.set_qfactor(QF);
450 });
451 for (int comp = 0; comp < 3; ++comp)
452 {
453 expect_like_qfactor(a, ref, comp);
454 expect_like_qfactor(b, ref, comp);
455 expect_like_qfactor(c, ref, comp);
456 }
457}
458
460// Qfactor and weights for the same component are incompatible, whichever is
461// set first. The error is raised when the headers are written.
462TEST(QuantMix, QfactorAndWeightsOnSameComponentRejected)
463{
464 std::vector<float> w(1 + 3 * NQ, 1.0f);
466 EXPECT_THROW(mixed(a, [&](ojph::param_qcd qcd) {
467 qcd.set_qfactor(1, ctype_t::OJPH_COMP_CB, QF);
468 qcd.set_irrev_quant(1, 0.02f, w.size(), w.data());
469 }), std::runtime_error);
470 EXPECT_THROW(mixed(b, [&](ojph::param_qcd qcd) {
471 qcd.set_irrev_quant(1, 0.02f, w.size(), w.data());
472 qcd.set_qfactor(1, ctype_t::OJPH_COMP_CB, QF);
473 }), std::runtime_error);
474}
475
477// Mixing set_irrev_quant(delta) and set_irrev_quant(comp_idx, delta, weights,
478// len).
479
480namespace {
481 const std::vector<float> W1 = CB_W;
482 const std::vector<float> W2 = CR_W;
483}
484
486// the QCD delta applies to components without their own settings, and the
487// weights to the component that has them, whatever the call order
488TEST(QuantDeltaWeights, GlobalDeltaWithWeightsOnOneComponent)
489{
491 mixed(a, [&](ojph::param_qcd qcd) {
492 qcd.set_irrev_quant(0.01f);
493 qcd.set_irrev_quant(1, 0.02f, W1.size(), W1.data());
494 });
495 mixed(b, [&](ojph::param_qcd qcd) {
496 qcd.set_irrev_quant(1, 0.02f, W1.size(), W1.data());
497 qcd.set_irrev_quant(0.01f);
498 });
499 for (ojph::mem_outfile* out : { &a, &b })
500 {
501 EXPECT_FALSE(find_quant(*out, 0, NQ).found);
502 EXPECT_FALSE(find_quant(*out, 2, NQ).found);
503 quant q = find_quant(*out, -1, NQ);
504 ASSERT_TRUE(q.found);
505 expect_close(q.delta, expected(0.01f, UNIT, NQ));
506 q = find_quant(*out, 1, NQ);
507 ASSERT_TRUE(q.found);
508 expect_close(q.delta, expected(0.02f, W1, NQ));
509 }
510 // the order does not matter
511 EXPECT_EQ(find_quant(a, 1, NQ).delta, find_quant(b, 1, NQ).delta);
512 EXPECT_EQ(find_quant(a, -1, NQ).delta, find_quant(b, -1, NQ).delta);
513}
514
516// the component's delta is its own; the QCD delta does not leak into it
517TEST(QuantDeltaWeights, ComponentDeltaIndependentOfGlobalDelta)
518{
520 mixed(a, [&](ojph::param_qcd qcd) {
521 qcd.set_irrev_quant(0.01f);
522 qcd.set_irrev_quant(1, 0.02f, W1.size(), W1.data());
523 });
524 mixed(b, [&](ojph::param_qcd qcd) {
525 qcd.set_irrev_quant(0.07f);
526 qcd.set_irrev_quant(1, 0.02f, W1.size(), W1.data());
527 });
528 EXPECT_EQ(find_quant(a, 1, NQ).delta, find_quant(b, 1, NQ).delta);
529 EXPECT_NE(find_quant(a, -1, NQ).delta, find_quant(b, -1, NQ).delta);
530}
531
533// without a QCD delta, the default one is used for the other components,
534// while the weighted component is unaffected
535TEST(QuantDeltaWeights, DefaultGlobalDeltaWithWeights)
536{
538 mixed(out, [&](ojph::param_qcd qcd) {
539 qcd.set_irrev_quant(2, 0.02f, W1.size(), W1.data());
540 });
541 quant q = find_quant(out, 2, NQ);
542 ASSERT_TRUE(q.found);
543 expect_close(q.delta, expected(0.02f, W1, NQ));
544 EXPECT_FALSE(find_quant(out, 0, NQ).found);
545 EXPECT_FALSE(find_quant(out, 1, NQ).found);
546 // 8 bits: default delta is 1 / 2^8
547 quant g = find_quant(out, -1, NQ);
548 ASSERT_TRUE(g.found);
549 expect_close(g.delta, expected(1.0f / 256.0f, UNIT, NQ));
550}
551
553// Two components with different weights and deltas, plus a global delta for
554// the third: each gets its own step sizes, and they do not interfere.
555TEST(QuantDeltaWeights, DifferentWeightsOnDifferentComponents)
556{
558 mixed(out, [&](ojph::param_qcd qcd) {
559 qcd.set_irrev_quant(0.01f);
560 qcd.set_irrev_quant(0, 0.02f, W1.size(), W1.data());
561 qcd.set_irrev_quant(2, 0.03f, W2.size(), W2.data());
562 });
563 quant q0 = find_quant(out, 0, NQ), q2 = find_quant(out, 2, NQ);
564 ASSERT_TRUE(q0.found && q2.found);
565 expect_close(q0.delta, expected(0.02f, W1, NQ));
566 expect_close(q2.delta, expected(0.03f, W2, NQ));
567 EXPECT_FALSE(find_quant(out, 1, NQ).found);
568 expect_close(find_quant(out, -1, NQ).delta, expected(0.01f, UNIT, NQ));
569}
570
572// setting the weights again for a component replaces the previous ones
573TEST(QuantDeltaWeights, WeightsReplacedOnSecondCall)
574{
576 mixed(out, [&](ojph::param_qcd qcd) {
577 qcd.set_irrev_quant(1, 0.02f, W1.size(), W1.data());
578 qcd.set_irrev_quant(1, 0.04f, W2.size(), W2.data());
579 });
580 expect_close(find_quant(out, 1, NQ).delta, expected(0.04f, W2, NQ));
581}
582
584// setting just a delta for a component that has weights keeps the weights
585TEST(QuantDeltaWeights, ComponentDeltaAfterWeights)
586{
588 mixed(out, [&](ojph::param_qcd qcd) {
589 qcd.set_irrev_quant(1, 0.02f, W1.size(), W1.data());
590 qcd.set_irrev_quant(1, 0.04f);
591 });
592 expect_close(find_quant(out, 1, NQ).delta, expected(0.04f, W1, NQ));
593}
The object represent a codestream.
param_siz access_siz()
Returns the underlying SIZ marker segment object.
param_cod access_cod()
Returns the underlying COD marker segment object.
param_qcd access_qcd()
Returns the underlying QCD marker segment object.
void write_headers(outfile_base *file, const comment_exchange *comments=NULL, ui32 num_comments=0)
Writes codestream headers when the codestream is used for writing. This function should be called aft...
mem_outfile stores encoded j2k codestreams in memory
Definition ojph_file.h:127
size_t get_used_size() const
Call this function to get the used size of the memory file.
Definition ojph_file.h:225
void open(size_t initial_size=65536, bool clear_mem=false)
Call this function to open a memory file.
const ui8 * get_data()
Call this function to access memory file data.
Definition ojph_file.h:201
void set_num_decomposition(ui32 num_decompositions)
void set_color_transform(bool color_transform)
void set_reversible(bool reversible)
Quantization parameters object.
void set_qfactor(float qfactor)
Sets Qfactor.
void set_irrev_quant(float delta)
Set the irreversible quantization base delta.
void set_tile_size(size s)
void set_component(ui32 comp_num, const point &downsampling, ui32 bit_depth, bool is_signed)
void set_num_components(ui32 num_comps)
void set_tile_offset(point offset)
void set_image_offset(point offset)
void set_image_extent(point extent)
uint16_t ui16
Definition ojph_defs.h:52
uint32_t ui32
Definition ojph_defs.h:54
uint8_t ui8
Definition ojph_defs.h:50
TEST(TestExecutables, OpenJPHCompressNoArguments)