OpenJPH
Open-source implementation of JPEG2000 Part-15
Loading...
Searching...
No Matches
test_tlm.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) Zwaar Contrast
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_tlm.cpp
32// Author: Zwaar Contrast
33// Date: 08 September 2026
34//***************************************************************************/
35
36#include <cstdio>
37#include <cstring>
38#include <vector>
39
40#include "ojph_mem.h"
41#include "ojph_file.h"
42#include "ojph_codestream.h"
43#include "ojph_params.h"
44#include "gtest/gtest.h"
45
47// TLM marker segment tests.
48//
49// A TLM segment can index at most (65535 - 4) / 6 = 10921 tile-parts, because
50// Ltlm is 16 bits. Beyond that the entries continue in further segments with
51// increasing Ztlm, which is 8 bits and so allows 256 of them. These tests
52// check that the entries are split only when they have to be, that the split
53// is well formed, and that the result still decodes.
54
55namespace {
56
57 const ojph::ui32 MAX_PAIRS_PER_SEG = (65535 - 4) / 6;
58
59 struct tlm_seg { ojph::ui8 Ztlm; ojph::ui8 Stlm; ojph::ui32 count; };
60 struct tlm_entry { ojph::ui32 Ttlm; ojph::ui32 Ptlm; };
61 struct tile_part { size_t offset; ojph::ui32 Isot; ojph::ui32 length; };
62
64 // Collect the TLM segments and their entries, and walk the SOT markers by
65 // Psot, so what the index claims can be checked against what is really
66 // there.
67 void scan(const char* filename, std::vector<tlm_seg>& segs,
68 std::vector<tlm_entry>& entries,
69 std::vector<tile_part>& parts, size_t& header_end)
70 {
71 FILE* f = fopen(filename, "rb");
72 ASSERT_NE(f, (FILE*)NULL);
73 fseek(f, 0, SEEK_END);
74 long len = ftell(f);
75 fseek(f, 0, SEEK_SET);
76 std::vector<ojph::ui8> b((size_t)len);
77 size_t got = fread(b.data(), 1, (size_t)len, f);
78 fclose(f);
79 ASSERT_EQ(got, (size_t)len);
80
81 const ojph::ui8* p = b.data();
82 size_t pos = 2; // skip SOC
83 header_end = 0;
84 while (pos + 4 <= (size_t)len)
85 {
86 ojph::ui16 marker = (ojph::ui16)((p[pos] << 8) | p[pos + 1]);
87 if (marker == 0xFF90) { header_end = pos; break; } // SOT
88 ojph::ui16 seg_len = (ojph::ui16)((p[pos + 2] << 8) | p[pos + 3]);
89 if (marker == 0xFF55) // TLM
90 {
91 tlm_seg s;
92 s.Ztlm = p[pos + 4];
93 s.Stlm = p[pos + 5];
94 ojph::ui32 st = (s.Stlm >> 4) & 3;
95 ojph::ui32 sp = (s.Stlm >> 6) & 1;
96 ojph::ui32 entry_size = st + (sp ? 4u : 2u);
97 s.count = (ojph::ui32)(seg_len - 4) / entry_size;
98 segs.push_back(s);
99
100 size_t q = pos + 6; // past marker, Ltlm, Ztlm, Stlm
101 for (ojph::ui32 i = 0; i < s.count; ++i)
102 {
103 tlm_entry e;
104 e.Ttlm = 0;
105 for (ojph::ui32 k = 0; k < st; ++k)
106 e.Ttlm = (e.Ttlm << 8) | p[q + k];
107 q += st;
108 e.Ptlm = 0;
109 for (ojph::ui32 k = 0; k < (sp ? 4u : 2u); ++k)
110 e.Ptlm = (e.Ptlm << 8) | p[q + k];
111 q += sp ? 4 : 2;
112 entries.push_back(e);
113 }
114 }
115 pos += (size_t)seg_len + 2;
116 }
117
118 pos = header_end;
119 while (pos + 12 <= (size_t)len &&
120 ((p[pos] << 8) | p[pos + 1]) == 0xFF90)
121 {
122 ojph::ui32 Isot = (ojph::ui32)((p[pos + 4] << 8) | p[pos + 5]);
123 ojph::ui32 Psot = ((ojph::ui32)p[pos + 6] << 24)
124 | ((ojph::ui32)p[pos + 7] << 16)
125 | ((ojph::ui32)p[pos + 8] << 8)
126 | (ojph::ui32)p[pos + 9];
127 if (Psot == 0)
128 { // the last tile-part may run to EOC
129 size_t end = (size_t)len;
130 if (end >= 2 && ((p[end - 2] << 8) | p[end - 1]) == 0xFFD9)
131 end -= 2;
132 tile_part t = { pos, Isot, (ojph::ui32)(end - pos) };
133 parts.push_back(t);
134 break;
135 }
136 tile_part t = { pos, Isot, Psot };
137 parts.push_back(t);
138 pos += Psot;
139 }
140 }
141
143 // The entries must describe the tile-parts that are really in the file: the
144 // same count, in the same order, with lengths that add up to each
145 // tile-part's offset. Counting entries alone would not catch a wrong Ptlm.
146 void expect_entries_describe_tileparts(const std::vector<tlm_entry>& entries,
147 const std::vector<tile_part>& parts,
148 size_t header_end)
149 {
150 ASSERT_EQ(entries.size(), parts.size());
151 size_t running = header_end;
152 for (size_t i = 0; i < entries.size(); ++i)
153 {
154 ASSERT_EQ(entries[i].Ttlm, parts[i].Isot) << "entry " << i;
155 ASSERT_EQ(entries[i].Ptlm, parts[i].length) << "entry " << i;
156 ASSERT_EQ(running, parts[i].offset) << "entry " << i;
157 running += entries[i].Ptlm;
158 }
159 }
160
162 // Encode a single-component image tiled at tile_size, with one tile-part
163 // per resolution, and a TLM marker.
164 void encode(const char* filename, ojph::ui32 width, ojph::ui32 height,
165 ojph::ui32 tile_size, ojph::ui32 num_decomps)
166 {
167 ojph::codestream codestream;
168
169 ojph::param_siz siz = codestream.access_siz();
170 siz.set_image_extent(ojph::point(width, height));
171 siz.set_num_components(1);
172 siz.set_component(0, ojph::point(1, 1), 8, false);
173 siz.set_image_offset(ojph::point(0, 0));
174 siz.set_tile_size(ojph::size(tile_size, tile_size));
175 siz.set_tile_offset(ojph::point(0, 0));
176
177 ojph::param_cod cod = codestream.access_cod();
178 cod.set_num_decomposition(num_decomps);
179 cod.set_block_dims(64, 64);
180 cod.set_color_transform(false);
181 cod.set_reversible(true);
182 cod.set_progression_order("RPCL");
183
184 codestream.set_tilepart_divisions(true, false);
185 codestream.request_tlm_marker(true);
186 codestream.set_planar(false);
187
188 ojph::j2c_outfile j2c_file;
189 j2c_file.open(filename);
190 codestream.write_headers(&j2c_file);
191
192 ojph::ui32 next_comp;
193 ojph::line_buf* cur_line = codestream.exchange(NULL, next_comp);
194 for (ojph::ui32 y = 0; y < height; ++y)
195 {
196 for (ojph::ui32 x = 0; x < width; ++x)
197 cur_line->i32[x] = (ojph::si32)((x + y) & 0xFF);
198 cur_line = codestream.exchange(cur_line, next_comp);
199 }
200 codestream.flush();
201 codestream.close();
202 }
203
205 void decode(const char* filename)
206 {
207 ojph::codestream codestream;
208 ojph::j2c_infile j2c_file;
209 j2c_file.open(filename);
210 codestream.read_headers(&j2c_file);
211 codestream.create();
212 ojph::ui32 comp_num;
213 ojph::param_siz siz = codestream.access_siz();
214 ojph::ui32 height = (ojph::ui32)siz.get_image_extent().y;
215 for (ojph::ui32 y = 0; y < height; ++y)
216 codestream.pull(comp_num);
217 codestream.close();
218 }
219}
220
222// Below the per-segment limit nothing is split: one TLM segment, Ztlm 0.
223TEST(TestTLM, SingleSegmentBelowLimit) {
224 const char* filename = "tlm_single_segment.j2c";
225 // 32x32 tiles over 512x512 is 256 tiles, 2 tile-parts each = 512
226 encode(filename, 512, 512, 32, 1);
227
228 std::vector<tlm_seg> segs;
229 std::vector<tlm_entry> entries;
230 std::vector<tile_part> parts;
231 size_t header_end = 0;
232 ASSERT_NO_FATAL_FAILURE(scan(filename, segs, entries, parts, header_end));
233
234 EXPECT_EQ(parts.size(), 512u);
235 ASSERT_EQ(segs.size(), 1u);
236 EXPECT_EQ(segs[0].Ztlm, 0);
237 EXPECT_EQ(segs[0].count, (ojph::ui32)parts.size());
238 ASSERT_NO_FATAL_FAILURE(
239 expect_entries_describe_tileparts(entries, parts, header_end));
240
241 decode(filename);
242 remove(filename);
243}
244
246// Past the per-segment limit the entries continue in further segments. Every
247// segment but the last is full, Ztlm increases from zero, and the entries
248// still describe the tile-parts that are really in the codestream.
249TEST(TestTLM, MultipleSegmentsAboveLimit) {
250 const char* filename = "tlm_multiple_segments.j2c";
251 // 8x8 tiles over 512x512 is 4096 tiles, 3 tile-parts each = 12288, which
252 // needs two segments
253 encode(filename, 512, 512, 8, 2);
254
255 std::vector<tlm_seg> segs;
256 std::vector<tlm_entry> entries;
257 std::vector<tile_part> parts;
258 size_t header_end = 0;
259 ASSERT_NO_FATAL_FAILURE(scan(filename, segs, entries, parts, header_end));
260
261 EXPECT_EQ(parts.size(), 12288u);
262 ASSERT_EQ(segs.size(), 2u);
263
264 ojph::ui32 total = 0;
265 for (size_t i = 0; i < segs.size(); ++i)
266 {
267 EXPECT_EQ(segs[i].Ztlm, (ojph::ui8)i);
268 EXPECT_EQ(segs[i].Stlm, 0x60);
269 if (i + 1 < segs.size())
270 EXPECT_EQ(segs[i].count, MAX_PAIRS_PER_SEG);
271 total += segs[i].count;
272 }
273 EXPECT_EQ(total, (ojph::ui32)parts.size());
274 ASSERT_NO_FATAL_FAILURE(
275 expect_entries_describe_tileparts(entries, parts, header_end));
276
277 decode(filename);
278 remove(filename);
279}
280
282// The boundary itself. A codestream with exactly MAX_PAIRS_PER_SEG tile-parts
283// is what unpatched OpenJPH accepts, and it must still be written as one
284// segment.
285TEST(TestTLM, ExactlyOneFullSegment) {
286 const char* filename = "tlm_exactly_full.j2c";
287 // 8x8 tiles over 536x1304 is 67x163 = 10921 tiles, one tile-part each
288 encode(filename, 536, 1304, 8, 0);
289
290 std::vector<tlm_seg> segs;
291 std::vector<tlm_entry> entries;
292 std::vector<tile_part> parts;
293 size_t header_end = 0;
294 ASSERT_NO_FATAL_FAILURE(scan(filename, segs, entries, parts, header_end));
295
296 EXPECT_EQ(parts.size(), MAX_PAIRS_PER_SEG);
297 ASSERT_EQ(segs.size(), 1u);
298 EXPECT_EQ(segs[0].Ztlm, 0);
299 EXPECT_EQ(segs[0].count, MAX_PAIRS_PER_SEG);
300 ASSERT_NO_FATAL_FAILURE(
301 expect_entries_describe_tileparts(entries, parts, header_end));
302
303 decode(filename);
304 remove(filename);
305}
306
308// One tile-part past the boundary is the first codestream that needs a second
309// segment, and that segment carries exactly one entry.
310TEST(TestTLM, OneEntryPastFullSegment) {
311 const char* filename = "tlm_one_past_full.j2c";
312 // 8x8 tiles over 688x1016 is 86x127 = 10922 tiles, one tile-part each
313 encode(filename, 688, 1016, 8, 0);
314
315 std::vector<tlm_seg> segs;
316 std::vector<tlm_entry> entries;
317 std::vector<tile_part> parts;
318 size_t header_end = 0;
319 ASSERT_NO_FATAL_FAILURE(scan(filename, segs, entries, parts, header_end));
320
321 EXPECT_EQ(parts.size(), MAX_PAIRS_PER_SEG + 1);
322 ASSERT_EQ(segs.size(), 2u);
323 EXPECT_EQ(segs[0].Ztlm, 0);
324 EXPECT_EQ(segs[0].count, MAX_PAIRS_PER_SEG);
325 EXPECT_EQ(segs[1].Ztlm, 1);
326 EXPECT_EQ(segs[1].count, 1u);
327 ASSERT_NO_FATAL_FAILURE(
328 expect_entries_describe_tileparts(entries, parts, header_end));
329
330 decode(filename);
331 remove(filename);
332}
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.
void close()
Call this function to close the underlying file; works for both encoding and decoding codestreams.
void set_planar(bool planar)
Sets the sequence of pushing or pull rows from the machinery.
line_buf * exchange(line_buf *line, ui32 &next_component)
This call is used to send image data rows to the library. We expect to send one row from a single com...
void request_tlm_marker(bool needed)
Request the addition of the optional TLM marker segment. This request should occur before writing cod...
void read_headers(infile_base *file)
This call reads the headers of a codestream. It is for a reading (or decoding) codestream,...
void set_tilepart_divisions(bool at_resolutions, bool at_components)
Sets the locations where a tile is partitioned into tile parts.
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...
void create()
This call is for a decoding (or reading) codestream. Call this function after calling restrict_input_...
void flush()
This is the last call to a writing (encoding) codestream. This will write encoded bitstream data to t...
line_buf * pull(ui32 &comp_num)
This call is to pull one row from the codestream, being decoded. The returned line_buf object holds o...
void open(const char *filename)
void open(const char *filename)
Definition ojph_file.cpp:62
void set_num_decomposition(ui32 num_decompositions)
void set_progression_order(const char *name)
void set_block_dims(ui32 width, ui32 height)
void set_color_transform(bool color_transform)
void set_reversible(bool reversible)
void set_tile_size(size s)
point get_image_extent() const
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
int32_t si32
Definition ojph_defs.h:55
uint32_t ui32
Definition ojph_defs.h:54
uint8_t ui8
Definition ojph_defs.h:50
TEST(TestExecutables, OpenJPHCompressNoArguments)