/src/libheif/libheif/codecs/jpeg2000_boxes.cc
Line | Count | Source |
1 | | /* |
2 | | * HEIF JPEG 2000 codec. |
3 | | * Copyright (c) 2023 Brad Hards <bradh@frogmouth.net> |
4 | | * |
5 | | * This file is part of libheif. |
6 | | * |
7 | | * libheif is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as |
9 | | * published by the Free Software Foundation, either version 3 of |
10 | | * the License, or (at your option) any later version. |
11 | | * |
12 | | * libheif is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libheif. If not, see <http://www.gnu.org/licenses/>. |
19 | | */ |
20 | | |
21 | | #include "jpeg2000_boxes.h" |
22 | | #include "api_structs.h" |
23 | | #include <cstdint> |
24 | | #include <iostream> |
25 | | #include <cstdio> |
26 | | |
27 | | static const uint16_t JPEG2000_CAP_MARKER = 0xFF50; |
28 | | static const uint16_t JPEG2000_SIZ_MARKER = 0xFF51; |
29 | | static const uint16_t JPEG2000_SOC_MARKER = 0xFF4F; |
30 | | |
31 | | |
32 | | Error Box_cdef::parse(BitstreamRange& range, const heif_security_limits* limits) |
33 | 81 | { |
34 | 81 | uint16_t channel_count = range.read16(); |
35 | | |
36 | 81 | if (limits->max_components && channel_count > limits->max_components) { |
37 | 4 | std::stringstream sstr; |
38 | 4 | sstr << "cdef box wants to define " << channel_count << " JPEG-2000 channels, but the security limit is set to " |
39 | 4 | << limits->max_components << " components"; |
40 | 4 | return {heif_error_Invalid_input, |
41 | 4 | heif_suberror_Security_limit_exceeded, |
42 | 4 | sstr.str()}; |
43 | 4 | } |
44 | | |
45 | 77 | if (channel_count > range.get_remaining_bytes() / 6) { |
46 | 8 | std::stringstream sstr; |
47 | 8 | sstr << "cdef box wants to define " << channel_count << " JPEG-2000 channels, but file only contains " |
48 | 8 | << range.get_remaining_bytes() / 6 << " components"; |
49 | 8 | return {heif_error_Invalid_input, |
50 | 8 | heif_suberror_End_of_data, |
51 | 8 | sstr.str()}; |
52 | 8 | } |
53 | | |
54 | 69 | m_channels.resize(channel_count); |
55 | | |
56 | 1.76k | for (uint16_t i = 0; i < channel_count && !range.error() && !range.eof(); i++) { |
57 | 1.69k | Channel channel; |
58 | 1.69k | channel.channel_index = range.read16(); |
59 | 1.69k | channel.channel_type = range.read16(); |
60 | 1.69k | channel.channel_association = range.read16(); |
61 | 1.69k | m_channels[i] = channel; |
62 | 1.69k | } |
63 | | |
64 | 69 | return range.get_error(); |
65 | 77 | } |
66 | | |
67 | | std::string Box_cdef::dump(Indent& indent) const |
68 | 0 | { |
69 | 0 | std::ostringstream sstr; |
70 | 0 | sstr << Box::dump(indent); |
71 | |
|
72 | 0 | for (const auto& channel : m_channels) { |
73 | 0 | sstr << indent << "channel_index: " << channel.channel_index |
74 | 0 | << ", channel_type: " << channel.channel_type |
75 | 0 | << ", channel_association: " << channel.channel_association << "\n"; |
76 | 0 | } |
77 | |
|
78 | 0 | return sstr.str(); |
79 | 0 | } |
80 | | |
81 | | |
82 | | Error Box_cdef::write(StreamWriter& writer) const |
83 | 0 | { |
84 | 0 | size_t box_start = reserve_box_header_space(writer); |
85 | |
|
86 | 0 | writer.write16((uint16_t) m_channels.size()); |
87 | 0 | for (const auto& channel : m_channels) { |
88 | 0 | writer.write16(channel.channel_index); |
89 | 0 | writer.write16(channel.channel_type); |
90 | 0 | writer.write16(channel.channel_association); |
91 | 0 | } |
92 | |
|
93 | 0 | prepend_header(writer, box_start); |
94 | |
|
95 | 0 | return Error::Ok; |
96 | 0 | } |
97 | | |
98 | | |
99 | | void Box_cdef::set_channels(heif_colorspace colorspace) |
100 | 0 | { |
101 | | // TODO - Check for the presence of a cmap box which specifies channel indices. |
102 | |
|
103 | 0 | const uint16_t TYPE_COLOR = 0; |
104 | 0 | const uint16_t ASOC_GREY = 1; |
105 | 0 | const uint16_t ASOC_RED = 1; |
106 | 0 | const uint16_t ASOC_GREEN = 2; |
107 | 0 | const uint16_t ASOC_BLUE = 3; |
108 | 0 | const uint16_t ASOC_Y = 1; |
109 | 0 | const uint16_t ASOC_Cb = 2; |
110 | 0 | const uint16_t ASOC_Cr = 3; |
111 | |
|
112 | 0 | switch (colorspace) { |
113 | 0 | case heif_colorspace_RGB: |
114 | 0 | m_channels.push_back({0, TYPE_COLOR, ASOC_RED}); |
115 | 0 | m_channels.push_back({1, TYPE_COLOR, ASOC_GREEN}); |
116 | 0 | m_channels.push_back({2, TYPE_COLOR, ASOC_BLUE}); |
117 | 0 | break; |
118 | | |
119 | 0 | case heif_colorspace_YCbCr: |
120 | 0 | m_channels.push_back({0, TYPE_COLOR, ASOC_Y}); |
121 | 0 | m_channels.push_back({1, TYPE_COLOR, ASOC_Cb}); |
122 | 0 | m_channels.push_back({2, TYPE_COLOR, ASOC_Cr}); |
123 | 0 | break; |
124 | | |
125 | 0 | case heif_colorspace_monochrome: |
126 | 0 | m_channels.push_back({0, TYPE_COLOR, ASOC_GREY}); |
127 | 0 | break; |
128 | | |
129 | 0 | default: |
130 | | //TODO - Handle remaining cases. |
131 | 0 | break; |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | | Error Box_cmap::parse(BitstreamRange& range, const heif_security_limits* limits) |
136 | 77 | { |
137 | 619 | while (!range.eof() && !range.error()) { |
138 | 542 | Component component; |
139 | 542 | component.component_index = range.read16(); |
140 | 542 | component.mapping_type = range.read8(); |
141 | 542 | component.palette_colour = range.read8(); |
142 | 542 | m_components.push_back(component); |
143 | 542 | } |
144 | | |
145 | 77 | return range.get_error(); |
146 | 77 | } |
147 | | |
148 | | |
149 | | std::string Box_cmap::dump(Indent& indent) const |
150 | 0 | { |
151 | 0 | std::ostringstream sstr; |
152 | 0 | sstr << Box::dump(indent); |
153 | |
|
154 | 0 | for (const auto& component : m_components) { |
155 | 0 | sstr << indent << "component_index: " << component.component_index |
156 | 0 | << ", mapping_type: " << (int) (component.mapping_type) |
157 | 0 | << ", palette_colour: " << (int) (component.palette_colour) << "\n"; |
158 | 0 | } |
159 | |
|
160 | 0 | return sstr.str(); |
161 | 0 | } |
162 | | |
163 | | |
164 | | Error Box_cmap::write(StreamWriter& writer) const |
165 | 0 | { |
166 | 0 | size_t box_start = reserve_box_header_space(writer); |
167 | |
|
168 | 0 | for (const auto& component : m_components) { |
169 | 0 | writer.write16(component.component_index); |
170 | 0 | writer.write8(component.mapping_type); |
171 | 0 | writer.write8(component.palette_colour); |
172 | 0 | } |
173 | |
|
174 | 0 | prepend_header(writer, box_start); |
175 | |
|
176 | 0 | return Error::Ok; |
177 | 0 | } |
178 | | |
179 | | |
180 | | Error Box_pclr::parse(BitstreamRange& range, const heif_security_limits* limits) |
181 | 93 | { |
182 | 93 | uint16_t num_entries = range.read16(); |
183 | 93 | uint8_t num_palette_columns = range.read8(); |
184 | | |
185 | | // ISO/IEC 15444-1 (Table I.12) requires NPC to be in the range 1 to 255. |
186 | | // A palette without columns carries no entry data, so the per-entry byte |
187 | | // count below would be zero and could not bound num_entries. The entry loop |
188 | | // then allocated up to 65535 empty PaletteEntry vectors from an 11-byte box, |
189 | | // and nested 'j2kH' containers could repeat this hundreds of times without |
190 | | // any of it being charged to the memory limits (GHSA-9c75-9g8r-4728). |
191 | 93 | if (num_palette_columns == 0) { |
192 | 5 | return Error(heif_error_Invalid_input, |
193 | 5 | heif_suberror_Invalid_J2K_codestream, |
194 | 5 | "pclr box declares zero palette columns"); |
195 | 5 | } |
196 | | |
197 | 1.05k | for (uint8_t i = 0; i < num_palette_columns; i++) { |
198 | | // B_i (Table I.13): the high bit marks signed values, the low 7 bits hold |
199 | | // the column precision minus one (0..37 for 1..38 bits). |
200 | 974 | uint8_t b = range.read8(); |
201 | 974 | if (b & 0x80) { |
202 | 6 | return Error(heif_error_Unsupported_feature, |
203 | 6 | heif_suberror_Unsupported_data_version, |
204 | 6 | "pclr with signed data is not supported"); |
205 | 6 | } |
206 | 968 | uint8_t bit_depth = static_cast<uint8_t>((b & 0x7F) + 1); |
207 | 968 | if (bit_depth > 16) { |
208 | 3 | return Error(heif_error_Unsupported_feature, |
209 | 3 | heif_suberror_Unsupported_data_version, |
210 | 3 | "pclr more than 16 bits per channel is not supported"); |
211 | 3 | } |
212 | 965 | m_bitDepths.push_back(bit_depth); |
213 | 965 | } |
214 | | // Number of bytes each palette entry occupies in the box: each C_ji value is |
215 | | // padded to a whole number of bytes (I.5.3.4). Used to bound num_entries by |
216 | | // the data actually present, so a small header cannot force a large |
217 | | // allocation (analogous to the 'cdef'/'j2kL' checks). The precision is at |
218 | | // least 1 bit, so every entry occupies at least one byte and the bound is |
219 | | // never vacuous. num_entries is a uint16_t and bytes_per_entry is at most |
220 | | // 255 * 2, so the product cannot overflow. |
221 | 79 | size_t bytes_per_entry = 0; |
222 | 932 | for (uint8_t bd : m_bitDepths) { |
223 | 932 | bytes_per_entry += (bd + 7) / 8; |
224 | 932 | } |
225 | | |
226 | 79 | if (static_cast<size_t>(num_entries) * bytes_per_entry > range.get_remaining_bytes()) { |
227 | 12 | return Error(heif_error_Invalid_input, |
228 | 12 | heif_suberror_End_of_data, |
229 | 12 | "pclr box declares more entries than the box contains"); |
230 | 12 | } |
231 | | |
232 | | // Each entry is stored as its own vector, so the palette costs noticeably |
233 | | // more memory than the bytes it occupies in the file. Charge that storage |
234 | | // to the memory limits before allocating it. |
235 | 67 | size_t bytes_per_stored_entry = sizeof(PaletteEntry) + num_palette_columns * sizeof(uint16_t); |
236 | 67 | if (auto err = m_memory_handle.alloc(num_entries, bytes_per_stored_entry, |
237 | 67 | limits, "the 'pclr' palette")) { |
238 | 0 | return err; |
239 | 0 | } |
240 | | |
241 | 67 | m_entries.reserve(num_entries); |
242 | | |
243 | 110 | for (uint16_t j = 0; j < num_entries; j++) { |
244 | 43 | PaletteEntry entry; |
245 | 91 | for (size_t i = 0; i < m_bitDepths.size(); i++) { |
246 | 48 | if (m_bitDepths[i] <= 8) { |
247 | 42 | entry.columns.push_back(range.read8()); |
248 | 42 | } |
249 | 6 | else { |
250 | 6 | entry.columns.push_back(range.read16()); |
251 | 6 | } |
252 | 48 | } |
253 | 43 | m_entries.push_back(std::move(entry)); |
254 | 43 | } |
255 | | |
256 | 67 | return range.get_error(); |
257 | 67 | } |
258 | | |
259 | | |
260 | | std::string Box_pclr::dump(Indent& indent) const |
261 | 0 | { |
262 | 0 | std::ostringstream sstr; |
263 | 0 | sstr << Box::dump(indent); |
264 | |
|
265 | 0 | sstr << indent << "NE: " << m_entries.size(); |
266 | 0 | sstr << ", NPC: " << (int) get_num_columns(); |
267 | 0 | sstr << ", B: "; |
268 | 0 | for (uint8_t b : m_bitDepths) { |
269 | 0 | sstr << (int) b << ", "; |
270 | 0 | } |
271 | | // TODO: maybe dump entries too? |
272 | 0 | sstr << "\n"; |
273 | |
|
274 | 0 | return sstr.str(); |
275 | 0 | } |
276 | | |
277 | | |
278 | | Error Box_pclr::write(StreamWriter& writer) const |
279 | 0 | { |
280 | 0 | if (get_num_columns() == 0) { |
281 | | // skip |
282 | 0 | return Error::Ok; |
283 | 0 | } |
284 | | |
285 | 0 | size_t box_start = reserve_box_header_space(writer); |
286 | |
|
287 | 0 | writer.write16(get_num_entries()); |
288 | 0 | writer.write8(get_num_columns()); |
289 | 0 | for (uint8_t bd : m_bitDepths) { |
290 | 0 | writer.write8(static_cast<uint8_t>(bd - 1)); // B_i stores the precision minus one |
291 | 0 | } |
292 | 0 | for (PaletteEntry entry : m_entries) { |
293 | 0 | for (unsigned long int i = 0; i < entry.columns.size(); i++) { |
294 | 0 | if (m_bitDepths[i] <= 8) { |
295 | 0 | writer.write8((uint8_t) (entry.columns[i])); |
296 | 0 | } |
297 | 0 | else { |
298 | 0 | writer.write16(entry.columns[i]); |
299 | 0 | } |
300 | 0 | } |
301 | 0 | } |
302 | |
|
303 | 0 | prepend_header(writer, box_start); |
304 | |
|
305 | 0 | return Error::Ok; |
306 | 0 | } |
307 | | |
308 | | void Box_pclr::set_columns(uint8_t num_columns, uint8_t bit_depth) |
309 | 0 | { |
310 | 0 | m_bitDepths.clear(); |
311 | 0 | m_entries.clear(); |
312 | 0 | for (int i = 0; i < num_columns; i++) { |
313 | 0 | m_bitDepths.push_back(bit_depth); |
314 | 0 | } |
315 | 0 | } |
316 | | |
317 | | Error Box_j2kL::parse(BitstreamRange& range, const heif_security_limits* limits) |
318 | 56 | { |
319 | 56 | uint16_t layer_count = range.read16(); |
320 | | |
321 | 56 | if (layer_count > range.get_remaining_bytes() / (2+1+2)) { |
322 | 7 | std::stringstream sstr; |
323 | 7 | sstr << "j2kL box wants to define " << layer_count << "JPEG-2000 layers, but the box only contains " |
324 | 7 | << range.get_remaining_bytes() / (2 + 1 + 2) << " layers entries"; |
325 | 7 | return {heif_error_Invalid_input, |
326 | 7 | heif_suberror_End_of_data, |
327 | 7 | sstr.str()}; |
328 | 7 | } |
329 | | |
330 | 49 | m_layers.resize(layer_count); |
331 | | |
332 | 165 | for (int i = 0; i < layer_count && !range.error() && !range.eof(); i++) { |
333 | 116 | Layer layer; |
334 | 116 | layer.layer_id = range.read16(); |
335 | 116 | layer.discard_levels = range.read8(); |
336 | 116 | layer.decode_layers = range.read16(); |
337 | 116 | m_layers[i] = layer; |
338 | 116 | } |
339 | | |
340 | 49 | if (range.get_error()) { |
341 | 2 | m_layers.clear(); |
342 | 2 | } |
343 | | |
344 | 49 | return range.get_error(); |
345 | 56 | } |
346 | | |
347 | | std::string Box_j2kL::dump(Indent& indent) const |
348 | 0 | { |
349 | 0 | std::ostringstream sstr; |
350 | 0 | sstr << Box::dump(indent); |
351 | |
|
352 | 0 | for (const auto& layer : m_layers) { |
353 | 0 | sstr << indent << "layer_id: " << layer.layer_id |
354 | 0 | << ", discard_levels: " << (int) (layer.discard_levels) |
355 | 0 | << ", decode_layers: " << layer.decode_layers << "\n"; |
356 | 0 | } |
357 | |
|
358 | 0 | return sstr.str(); |
359 | 0 | } |
360 | | |
361 | | |
362 | | Error Box_j2kL::write(StreamWriter& writer) const |
363 | 0 | { |
364 | 0 | size_t box_start = reserve_box_header_space(writer); |
365 | |
|
366 | 0 | writer.write16((uint16_t) m_layers.size()); |
367 | 0 | for (const auto& layer : m_layers) { |
368 | 0 | writer.write16(layer.layer_id); |
369 | 0 | writer.write8(layer.discard_levels); |
370 | 0 | writer.write16(layer.decode_layers); |
371 | 0 | } |
372 | |
|
373 | 0 | prepend_header(writer, box_start); |
374 | |
|
375 | 0 | return Error::Ok; |
376 | 0 | } |
377 | | |
378 | | |
379 | | Error Box_j2kH::parse(BitstreamRange& range, const heif_security_limits* limits) |
380 | 86 | { |
381 | 86 | return read_children(range, READ_CHILDREN_ALL, limits); |
382 | 86 | } |
383 | | |
384 | | std::string Box_j2kH::dump(Indent& indent) const |
385 | 0 | { |
386 | 0 | std::ostringstream sstr; |
387 | 0 | sstr << Box::dump(indent); |
388 | |
|
389 | 0 | sstr << dump_children(indent); |
390 | |
|
391 | 0 | return sstr.str(); |
392 | 0 | } |
393 | | |
394 | | |
395 | | Error JPEG2000MainHeader::parseHeader(const std::vector<uint8_t>& compressedImageData) |
396 | 0 | { |
397 | | // TODO: it is very inefficient to store the whole image data when we only need the header |
398 | |
|
399 | 0 | headerData = compressedImageData; |
400 | 0 | return doParse(); |
401 | 0 | } |
402 | | |
403 | | Error JPEG2000MainHeader::doParse() |
404 | 0 | { |
405 | 0 | cursor = 0; |
406 | 0 | Error err = parse_SOC_segment(); |
407 | 0 | if (err) { |
408 | 0 | return err; |
409 | 0 | } |
410 | 0 | err = parse_SIZ_segment(); |
411 | 0 | if (err) { |
412 | 0 | return err; |
413 | 0 | } |
414 | 0 | if (cursor < headerData.size() - MARKER_LEN) { |
415 | 0 | uint16_t marker = read16(); |
416 | 0 | if (marker == JPEG2000_CAP_MARKER) { |
417 | 0 | return parse_CAP_segment_body(); |
418 | 0 | } |
419 | 0 | return Error::Ok; |
420 | 0 | } |
421 | | // we should have at least COD and QCD, so this is probably broken. |
422 | 0 | return Error(heif_error_Invalid_input, |
423 | 0 | heif_suberror_Invalid_J2K_codestream, |
424 | 0 | std::string("Missing required header marker(s)")); |
425 | 0 | } |
426 | | |
427 | | Error JPEG2000MainHeader::parse_SOC_segment() |
428 | 0 | { |
429 | 0 | const size_t REQUIRED_BYTES = MARKER_LEN; |
430 | 0 | if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { |
431 | 0 | return Error(heif_error_Invalid_input, |
432 | 0 | heif_suberror_Invalid_J2K_codestream); |
433 | 0 | } |
434 | 0 | uint16_t marker = read16(); |
435 | 0 | if (marker == JPEG2000_SOC_MARKER) { |
436 | 0 | return Error::Ok; |
437 | 0 | } |
438 | 0 | return Error(heif_error_Invalid_input, |
439 | 0 | heif_suberror_Invalid_J2K_codestream, |
440 | 0 | std::string("Missing required SOC Marker")); |
441 | 0 | } |
442 | | |
443 | | Error JPEG2000MainHeader::parse_SIZ_segment() |
444 | 0 | { |
445 | 0 | size_t REQUIRED_BYTES = MARKER_LEN + 38 + 3 * 1; |
446 | 0 | if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { |
447 | 0 | return Error(heif_error_Invalid_input, |
448 | 0 | heif_suberror_Invalid_J2K_codestream); |
449 | 0 | } |
450 | | |
451 | 0 | uint16_t marker = read16(); |
452 | 0 | if (marker != JPEG2000_SIZ_MARKER) { |
453 | 0 | return Error(heif_error_Invalid_input, |
454 | 0 | heif_suberror_Invalid_J2K_codestream, |
455 | 0 | std::string("Missing required SIZ Marker")); |
456 | 0 | } |
457 | 0 | uint16_t lsiz = read16(); |
458 | 0 | if ((lsiz < 41) || (lsiz > 49190)) { |
459 | 0 | return Error(heif_error_Invalid_input, |
460 | 0 | heif_suberror_Invalid_J2K_codestream, |
461 | 0 | std::string("Out of range Lsiz value")); |
462 | 0 | } |
463 | 0 | siz.decoder_capabilities = read16(); |
464 | 0 | siz.reference_grid_width = read32(); |
465 | 0 | siz.reference_grid_height = read32(); |
466 | 0 | siz.image_horizontal_offset = read32(); |
467 | 0 | siz.image_vertical_offset = read32(); |
468 | 0 | siz.tile_width = read32(); |
469 | 0 | siz.tile_height = read32(); |
470 | 0 | siz.tile_offset_x = read32(); |
471 | 0 | siz.tile_offset_y = read32(); |
472 | 0 | uint16_t csiz = read16(); |
473 | 0 | if ((csiz < 1) || (csiz > 16384)) { |
474 | 0 | return Error(heif_error_Invalid_input, |
475 | 0 | heif_suberror_Invalid_J2K_codestream, |
476 | 0 | std::string("Out of range Csiz value")); |
477 | 0 | } |
478 | 0 | if (3 * static_cast<size_t>(csiz) > headerData.size() - cursor) { |
479 | 0 | return Error(heif_error_Invalid_input, |
480 | 0 | heif_suberror_Invalid_J2K_codestream); |
481 | 0 | } |
482 | | // TODO: consider checking for Lsiz consistent with Csiz |
483 | 0 | for (uint16_t c = 0; c < csiz; c++) { |
484 | 0 | JPEG2000_SIZ_segment::component comp; |
485 | 0 | uint8_t ssiz = read8(); |
486 | 0 | comp.is_signed = (ssiz & 0x80); |
487 | 0 | comp.precision = uint8_t((ssiz & 0x7F) + 1); |
488 | 0 | comp.h_separation = read8(); |
489 | 0 | comp.v_separation = read8(); |
490 | 0 | siz.components.push_back(comp); |
491 | 0 | } |
492 | 0 | return Error::Ok; |
493 | 0 | } |
494 | | |
495 | | Error JPEG2000MainHeader::parse_CAP_segment_body() |
496 | 0 | { |
497 | | // Need at least Lcap (2) + Pcap (4) = 6 bytes. |
498 | 0 | if (headerData.size() - cursor < 6) { |
499 | 0 | return Error(heif_error_Invalid_input, |
500 | 0 | heif_suberror_Invalid_J2K_codestream); |
501 | 0 | } |
502 | 0 | uint16_t lcap = read16(); |
503 | 0 | if ((lcap < 8) || (lcap > 70)) { |
504 | 0 | return Error(heif_error_Invalid_input, |
505 | 0 | heif_suberror_Invalid_J2K_codestream, |
506 | 0 | std::string("Out of range Lcap value")); |
507 | 0 | } |
508 | | // Lcap counts the Lcap field itself, so Lcap-2 bytes must follow it. |
509 | 0 | if (headerData.size() - cursor < static_cast<size_t>(lcap) - 2) { |
510 | 0 | return Error(heif_error_Invalid_input, |
511 | 0 | heif_suberror_Invalid_J2K_codestream); |
512 | 0 | } |
513 | 0 | uint32_t pcap = read32(); |
514 | 0 | size_t segment_end = cursor + (static_cast<size_t>(lcap) - 6); |
515 | 0 | for (uint8_t i = 2; i <= 32; i++) { |
516 | 0 | if (pcap & (1u << (32 - i))) { |
517 | 0 | if (segment_end - cursor < 2) { |
518 | 0 | return Error(heif_error_Invalid_input, |
519 | 0 | heif_suberror_Invalid_J2K_codestream, |
520 | 0 | std::string("CAP segment Pcap inconsistent with Lcap")); |
521 | 0 | } |
522 | 0 | switch (i) { |
523 | 0 | case JPEG2000_Extension_Capability_HT::IDENT: |
524 | 0 | parse_Ccap15(); |
525 | 0 | break; |
526 | 0 | default: |
527 | 0 | read16(); |
528 | 0 | } |
529 | 0 | } |
530 | 0 | } |
531 | 0 | return Error::Ok; |
532 | 0 | } |
533 | | |
534 | | void JPEG2000MainHeader::parse_Ccap15() |
535 | 0 | { |
536 | 0 | uint16_t val = read16(); |
537 | 0 | JPEG2000_Extension_Capability_HT ccap; |
538 | | // We could parse more here, but we don't need that yet. |
539 | 0 | ccap.setValue(val); |
540 | 0 | cap.push_back(ccap); |
541 | 0 | } |
542 | | |
543 | | heif_chroma JPEG2000MainHeader::get_chroma_format() const |
544 | 0 | { |
545 | | // Y-plane must be full resolution |
546 | 0 | if (siz.components[0].h_separation != 1 || siz.components[0].v_separation != 1) { |
547 | 0 | return heif_chroma_undefined; |
548 | 0 | } |
549 | | |
550 | 0 | if (siz.components.size() == 1) { |
551 | 0 | return heif_chroma_monochrome; |
552 | 0 | } |
553 | 0 | else if (siz.components.size() == 3) { |
554 | | // TODO: we should map channels through `cdef` ? |
555 | | |
556 | | // both chroma components must have the same sampling |
557 | 0 | if (siz.components[1].h_separation != siz.components[2].h_separation || |
558 | 0 | siz.components[1].v_separation != siz.components[2].v_separation) { |
559 | 0 | return heif_chroma_undefined; |
560 | 0 | } |
561 | | |
562 | 0 | if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==2) { |
563 | 0 | return heif_chroma_420; |
564 | 0 | } |
565 | 0 | if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==1) { |
566 | 0 | return heif_chroma_422; |
567 | 0 | } |
568 | 0 | if (siz.components[1].h_separation == 1 && siz.components[1].v_separation==1) { |
569 | 0 | return heif_chroma_444; |
570 | 0 | } |
571 | 0 | } |
572 | | |
573 | 0 | return heif_chroma_undefined; |
574 | 0 | } |