/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 | 10.7k | { |
34 | 10.7k | uint16_t channel_count = range.read16(); |
35 | | |
36 | 10.7k | if (limits->max_components && channel_count > limits->max_components) { |
37 | 0 | std::stringstream sstr; |
38 | 0 | sstr << "cdef box wants to define " << channel_count << " JPEG-2000 channels, but the security limit is set to " |
39 | 0 | << limits->max_components << " components"; |
40 | 0 | return {heif_error_Invalid_input, |
41 | 0 | heif_suberror_Security_limit_exceeded, |
42 | 0 | sstr.str()}; |
43 | 0 | } |
44 | | |
45 | 10.7k | if (channel_count > range.get_remaining_bytes() / 6) { |
46 | 3 | std::stringstream sstr; |
47 | 3 | sstr << "cdef box wants to define " << channel_count << " JPEG-2000 channels, but file only contains " |
48 | 3 | << range.get_remaining_bytes() / 6 << " components"; |
49 | 3 | return {heif_error_Invalid_input, |
50 | 3 | heif_suberror_End_of_data, |
51 | 3 | sstr.str()}; |
52 | 3 | } |
53 | | |
54 | 10.7k | m_channels.resize(channel_count); |
55 | | |
56 | 19.3k | for (uint16_t i = 0; i < channel_count && !range.error() && !range.eof(); i++) { |
57 | 8.54k | Channel channel; |
58 | 8.54k | channel.channel_index = range.read16(); |
59 | 8.54k | channel.channel_type = range.read16(); |
60 | 8.54k | channel.channel_association = range.read16(); |
61 | 8.54k | m_channels[i] = channel; |
62 | 8.54k | } |
63 | | |
64 | 10.7k | return range.get_error(); |
65 | 10.7k | } |
66 | | |
67 | | std::string Box_cdef::dump(Indent& indent) const |
68 | 8.79k | { |
69 | 8.79k | std::ostringstream sstr; |
70 | 8.79k | sstr << Box::dump(indent); |
71 | | |
72 | 8.79k | for (const auto& channel : m_channels) { |
73 | 7.00k | sstr << indent << "channel_index: " << channel.channel_index |
74 | 7.00k | << ", channel_type: " << channel.channel_type |
75 | 7.00k | << ", channel_association: " << channel.channel_association << "\n"; |
76 | 7.00k | } |
77 | | |
78 | 8.79k | return sstr.str(); |
79 | 8.79k | } |
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 | 3.65k | { |
137 | 7.02M | while (!range.eof() && !range.error()) { |
138 | 7.02M | Component component; |
139 | 7.02M | component.component_index = range.read16(); |
140 | 7.02M | component.mapping_type = range.read8(); |
141 | 7.02M | component.palette_colour = range.read8(); |
142 | 7.02M | m_components.push_back(component); |
143 | 7.02M | } |
144 | | |
145 | 3.65k | return range.get_error(); |
146 | 3.65k | } |
147 | | |
148 | | |
149 | | std::string Box_cmap::dump(Indent& indent) const |
150 | 2.88k | { |
151 | 2.88k | std::ostringstream sstr; |
152 | 2.88k | sstr << Box::dump(indent); |
153 | | |
154 | 6.41M | for (const auto& component : m_components) { |
155 | 6.41M | sstr << indent << "component_index: " << component.component_index |
156 | 6.41M | << ", mapping_type: " << (int) (component.mapping_type) |
157 | 6.41M | << ", palette_colour: " << (int) (component.palette_colour) << "\n"; |
158 | 6.41M | } |
159 | | |
160 | 2.88k | return sstr.str(); |
161 | 2.88k | } |
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 | 2.10k | { |
182 | 2.10k | uint16_t num_entries = range.read16(); |
183 | 2.10k | uint8_t num_palette_columns = range.read8(); |
184 | 7.92k | for (uint8_t i = 0; i < num_palette_columns; i++) { |
185 | 5.82k | uint8_t bit_depth = range.read8(); |
186 | 5.82k | if (bit_depth & 0x80) { |
187 | 1 | return Error(heif_error_Unsupported_feature, |
188 | 1 | heif_suberror_Unsupported_data_version, |
189 | 1 | "pclr with signed data is not supported"); |
190 | 1 | } |
191 | 5.82k | if (bit_depth > 16) { |
192 | 1 | return Error(heif_error_Unsupported_feature, |
193 | 1 | heif_suberror_Unsupported_data_version, |
194 | 1 | "pclr more than 16 bits per channel is not supported"); |
195 | 1 | } |
196 | 5.82k | m_bitDepths.push_back(bit_depth); |
197 | 5.82k | } |
198 | 22.5M | for (uint16_t j = 0; j < num_entries; j++) { |
199 | 22.5M | PaletteEntry entry; |
200 | 22.5M | for (unsigned long int i = 0; i < entry.columns.size(); i++) { |
201 | 0 | if (m_bitDepths[i] <= 8) { |
202 | 0 | entry.columns.push_back(range.read8()); |
203 | 0 | } |
204 | 0 | else { |
205 | 0 | entry.columns.push_back(range.read16()); |
206 | 0 | } |
207 | 0 | } |
208 | 22.5M | m_entries.push_back(entry); |
209 | 22.5M | } |
210 | | |
211 | 2.10k | return range.get_error(); |
212 | 2.10k | } |
213 | | |
214 | | |
215 | | std::string Box_pclr::dump(Indent& indent) const |
216 | 1.50k | { |
217 | 1.50k | std::ostringstream sstr; |
218 | 1.50k | sstr << Box::dump(indent); |
219 | | |
220 | 1.50k | sstr << indent << "NE: " << m_entries.size(); |
221 | 1.50k | sstr << ", NPC: " << (int) get_num_columns(); |
222 | 1.50k | sstr << ", B: "; |
223 | 3.66k | for (uint8_t b : m_bitDepths) { |
224 | 3.66k | sstr << (int) b << ", "; |
225 | 3.66k | } |
226 | | // TODO: maybe dump entries too? |
227 | 1.50k | sstr << "\n"; |
228 | | |
229 | 1.50k | return sstr.str(); |
230 | 1.50k | } |
231 | | |
232 | | |
233 | | Error Box_pclr::write(StreamWriter& writer) const |
234 | 0 | { |
235 | 0 | if (get_num_columns() == 0) { |
236 | | // skip |
237 | 0 | return Error::Ok; |
238 | 0 | } |
239 | | |
240 | 0 | size_t box_start = reserve_box_header_space(writer); |
241 | |
|
242 | 0 | writer.write16(get_num_entries()); |
243 | 0 | writer.write8(get_num_columns()); |
244 | 0 | for (uint8_t b : m_bitDepths) { |
245 | 0 | writer.write8(b); |
246 | 0 | } |
247 | 0 | for (PaletteEntry entry : m_entries) { |
248 | 0 | for (unsigned long int i = 0; i < entry.columns.size(); i++) { |
249 | 0 | if (m_bitDepths[i] <= 8) { |
250 | 0 | writer.write8((uint8_t) (entry.columns[i])); |
251 | 0 | } |
252 | 0 | else { |
253 | 0 | writer.write16(entry.columns[i]); |
254 | 0 | } |
255 | 0 | } |
256 | 0 | } |
257 | |
|
258 | 0 | prepend_header(writer, box_start); |
259 | |
|
260 | 0 | return Error::Ok; |
261 | 0 | } |
262 | | |
263 | | void Box_pclr::set_columns(uint8_t num_columns, uint8_t bit_depth) |
264 | 0 | { |
265 | 0 | m_bitDepths.clear(); |
266 | 0 | m_entries.clear(); |
267 | 0 | for (int i = 0; i < num_columns; i++) { |
268 | 0 | m_bitDepths.push_back(bit_depth); |
269 | 0 | } |
270 | 0 | } |
271 | | |
272 | | Error Box_j2kL::parse(BitstreamRange& range, const heif_security_limits* limits) |
273 | 580 | { |
274 | 580 | uint16_t layer_count = range.read16(); |
275 | | |
276 | 580 | if (layer_count > range.get_remaining_bytes() / (2+1+2)) { |
277 | 1 | std::stringstream sstr; |
278 | 1 | sstr << "j2kL box wants to define " << layer_count << "JPEG-2000 layers, but the box only contains " |
279 | 1 | << range.get_remaining_bytes() / (2 + 1 + 2) << " layers entries"; |
280 | 1 | return {heif_error_Invalid_input, |
281 | 1 | heif_suberror_End_of_data, |
282 | 1 | sstr.str()}; |
283 | 1 | } |
284 | | |
285 | 579 | m_layers.resize(layer_count); |
286 | | |
287 | 852k | for (int i = 0; i < layer_count && !range.error() && !range.eof(); i++) { |
288 | 851k | Layer layer; |
289 | 851k | layer.layer_id = range.read16(); |
290 | 851k | layer.discard_levels = range.read8(); |
291 | 851k | layer.decode_layers = range.read16(); |
292 | 851k | m_layers[i] = layer; |
293 | 851k | } |
294 | | |
295 | 579 | if (range.get_error()) { |
296 | 3 | m_layers.clear(); |
297 | 3 | } |
298 | | |
299 | 579 | return range.get_error(); |
300 | 580 | } |
301 | | |
302 | | std::string Box_j2kL::dump(Indent& indent) const |
303 | 576 | { |
304 | 576 | std::ostringstream sstr; |
305 | 576 | sstr << Box::dump(indent); |
306 | | |
307 | 851k | for (const auto& layer : m_layers) { |
308 | 851k | sstr << indent << "layer_id: " << layer.layer_id |
309 | 851k | << ", discard_levels: " << (int) (layer.discard_levels) |
310 | 851k | << ", decode_layers: " << layer.decode_layers << "\n"; |
311 | 851k | } |
312 | | |
313 | 576 | return sstr.str(); |
314 | 576 | } |
315 | | |
316 | | |
317 | | Error Box_j2kL::write(StreamWriter& writer) const |
318 | 0 | { |
319 | 0 | size_t box_start = reserve_box_header_space(writer); |
320 | |
|
321 | 0 | writer.write16((uint16_t) m_layers.size()); |
322 | 0 | for (const auto& layer : m_layers) { |
323 | 0 | writer.write16(layer.layer_id); |
324 | 0 | writer.write8(layer.discard_levels); |
325 | 0 | writer.write16(layer.decode_layers); |
326 | 0 | } |
327 | |
|
328 | 0 | prepend_header(writer, box_start); |
329 | |
|
330 | 0 | return Error::Ok; |
331 | 0 | } |
332 | | |
333 | | |
334 | | Error Box_j2kH::parse(BitstreamRange& range, const heif_security_limits* limits) |
335 | 3.77k | { |
336 | 3.77k | return read_children(range, READ_CHILDREN_ALL, limits); |
337 | 3.77k | } |
338 | | |
339 | | std::string Box_j2kH::dump(Indent& indent) const |
340 | 3.39k | { |
341 | 3.39k | std::ostringstream sstr; |
342 | 3.39k | sstr << Box::dump(indent); |
343 | | |
344 | 3.39k | sstr << dump_children(indent); |
345 | | |
346 | 3.39k | return sstr.str(); |
347 | 3.39k | } |
348 | | |
349 | | |
350 | | Error JPEG2000MainHeader::parseHeader(const std::vector<uint8_t>& compressedImageData) |
351 | 0 | { |
352 | | // TODO: it is very inefficient to store the whole image data when we only need the header |
353 | |
|
354 | 0 | headerData = compressedImageData; |
355 | 0 | return doParse(); |
356 | 0 | } |
357 | | |
358 | | Error JPEG2000MainHeader::doParse() |
359 | 0 | { |
360 | 0 | cursor = 0; |
361 | 0 | Error err = parse_SOC_segment(); |
362 | 0 | if (err) { |
363 | 0 | return err; |
364 | 0 | } |
365 | 0 | err = parse_SIZ_segment(); |
366 | 0 | if (err) { |
367 | 0 | return err; |
368 | 0 | } |
369 | 0 | if (cursor < headerData.size() - MARKER_LEN) { |
370 | 0 | uint16_t marker = read16(); |
371 | 0 | if (marker == JPEG2000_CAP_MARKER) { |
372 | 0 | return parse_CAP_segment_body(); |
373 | 0 | } |
374 | 0 | return Error::Ok; |
375 | 0 | } |
376 | | // we should have at least COD and QCD, so this is probably broken. |
377 | 0 | return Error(heif_error_Invalid_input, |
378 | 0 | heif_suberror_Invalid_J2K_codestream, |
379 | 0 | std::string("Missing required header marker(s)")); |
380 | 0 | } |
381 | | |
382 | | Error JPEG2000MainHeader::parse_SOC_segment() |
383 | 0 | { |
384 | 0 | const size_t REQUIRED_BYTES = MARKER_LEN; |
385 | 0 | if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { |
386 | 0 | return Error(heif_error_Invalid_input, |
387 | 0 | heif_suberror_Invalid_J2K_codestream); |
388 | 0 | } |
389 | 0 | uint16_t marker = read16(); |
390 | 0 | if (marker == JPEG2000_SOC_MARKER) { |
391 | 0 | return Error::Ok; |
392 | 0 | } |
393 | 0 | return Error(heif_error_Invalid_input, |
394 | 0 | heif_suberror_Invalid_J2K_codestream, |
395 | 0 | std::string("Missing required SOC Marker")); |
396 | 0 | } |
397 | | |
398 | | Error JPEG2000MainHeader::parse_SIZ_segment() |
399 | 0 | { |
400 | 0 | size_t REQUIRED_BYTES = MARKER_LEN + 38 + 3 * 1; |
401 | 0 | if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { |
402 | 0 | return Error(heif_error_Invalid_input, |
403 | 0 | heif_suberror_Invalid_J2K_codestream); |
404 | 0 | } |
405 | | |
406 | 0 | uint16_t marker = read16(); |
407 | 0 | if (marker != JPEG2000_SIZ_MARKER) { |
408 | 0 | return Error(heif_error_Invalid_input, |
409 | 0 | heif_suberror_Invalid_J2K_codestream, |
410 | 0 | std::string("Missing required SIZ Marker")); |
411 | 0 | } |
412 | 0 | uint16_t lsiz = read16(); |
413 | 0 | if ((lsiz < 41) || (lsiz > 49190)) { |
414 | 0 | return Error(heif_error_Invalid_input, |
415 | 0 | heif_suberror_Invalid_J2K_codestream, |
416 | 0 | std::string("Out of range Lsiz value")); |
417 | 0 | } |
418 | 0 | siz.decoder_capabilities = read16(); |
419 | 0 | siz.reference_grid_width = read32(); |
420 | 0 | siz.reference_grid_height = read32(); |
421 | 0 | siz.image_horizontal_offset = read32(); |
422 | 0 | siz.image_vertical_offset = read32(); |
423 | 0 | siz.tile_width = read32(); |
424 | 0 | siz.tile_height = read32(); |
425 | 0 | siz.tile_offset_x = read32(); |
426 | 0 | siz.tile_offset_y = read32(); |
427 | 0 | uint16_t csiz = read16(); |
428 | 0 | if ((csiz < 1) || (csiz > 16384)) { |
429 | 0 | return Error(heif_error_Invalid_input, |
430 | 0 | heif_suberror_Invalid_J2K_codestream, |
431 | 0 | std::string("Out of range Csiz value")); |
432 | 0 | } |
433 | 0 | if (cursor > headerData.size() - (3 * csiz)) { |
434 | 0 | return Error(heif_error_Invalid_input, |
435 | 0 | heif_suberror_Invalid_J2K_codestream); |
436 | 0 | } |
437 | | // TODO: consider checking for Lsiz consistent with Csiz |
438 | 0 | for (uint16_t c = 0; c < csiz; c++) { |
439 | 0 | JPEG2000_SIZ_segment::component comp; |
440 | 0 | uint8_t ssiz = read8(); |
441 | 0 | comp.is_signed = (ssiz & 0x80); |
442 | 0 | comp.precision = uint8_t((ssiz & 0x7F) + 1); |
443 | 0 | comp.h_separation = read8(); |
444 | 0 | comp.v_separation = read8(); |
445 | 0 | siz.components.push_back(comp); |
446 | 0 | } |
447 | 0 | return Error::Ok; |
448 | 0 | } |
449 | | |
450 | | Error JPEG2000MainHeader::parse_CAP_segment_body() |
451 | 0 | { |
452 | 0 | if (cursor > headerData.size() - 8) { |
453 | 0 | return Error(heif_error_Invalid_input, |
454 | 0 | heif_suberror_Invalid_J2K_codestream); |
455 | 0 | } |
456 | 0 | uint16_t lcap = read16(); |
457 | 0 | if ((lcap < 8) || (lcap > 70)) { |
458 | 0 | return Error(heif_error_Invalid_input, |
459 | 0 | heif_suberror_Invalid_J2K_codestream, |
460 | 0 | std::string("Out of range Lcap value")); |
461 | 0 | } |
462 | 0 | uint32_t pcap = read32(); |
463 | 0 | for (uint8_t i = 2; i <= 32; i++) { |
464 | 0 | if (pcap & (1 << (32 - i))) { |
465 | 0 | switch (i) { |
466 | 0 | case JPEG2000_Extension_Capability_HT::IDENT: |
467 | 0 | parse_Ccap15(); |
468 | 0 | break; |
469 | 0 | default: |
470 | 0 | std::cout << "unhandled extended capabilities value: " << (int)i << std::endl; |
471 | 0 | read16(); |
472 | 0 | } |
473 | 0 | } |
474 | 0 | } |
475 | 0 | return Error::Ok; |
476 | 0 | } |
477 | | |
478 | | void JPEG2000MainHeader::parse_Ccap15() |
479 | 0 | { |
480 | 0 | uint16_t val = read16(); |
481 | 0 | JPEG2000_Extension_Capability_HT ccap; |
482 | | // We could parse more here, but we don't need that yet. |
483 | 0 | ccap.setValue(val); |
484 | 0 | cap.push_back(ccap); |
485 | 0 | } |
486 | | |
487 | | heif_chroma JPEG2000MainHeader::get_chroma_format() const |
488 | 0 | { |
489 | | // Y-plane must be full resolution |
490 | 0 | if (siz.components[0].h_separation != 1 || siz.components[0].v_separation != 1) { |
491 | 0 | return heif_chroma_undefined; |
492 | 0 | } |
493 | | |
494 | 0 | if (siz.components.size() == 1) { |
495 | 0 | return heif_chroma_monochrome; |
496 | 0 | } |
497 | 0 | else if (siz.components.size() == 3) { |
498 | | // TODO: we should map channels through `cdef` ? |
499 | | |
500 | | // both chroma components must have the same sampling |
501 | 0 | if (siz.components[1].h_separation != siz.components[2].h_separation || |
502 | 0 | siz.components[1].v_separation != siz.components[2].v_separation) { |
503 | 0 | return heif_chroma_undefined; |
504 | 0 | } |
505 | | |
506 | 0 | if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==2) { |
507 | 0 | return heif_chroma_420; |
508 | 0 | } |
509 | 0 | if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==1) { |
510 | 0 | return heif_chroma_422; |
511 | 0 | } |
512 | 0 | if (siz.components[1].h_separation == 1 && siz.components[1].v_separation==1) { |
513 | 0 | return heif_chroma_444; |
514 | 0 | } |
515 | 0 | } |
516 | | |
517 | 0 | return heif_chroma_undefined; |
518 | 0 | } |