/src/exiv2/src/jp2image.cpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | // included header files |
4 | | #include "jp2image.hpp" |
5 | | |
6 | | #include "config.h" |
7 | | |
8 | | #include "basicio.hpp" |
9 | | #include "enforce.hpp" |
10 | | #include "error.hpp" |
11 | | #include "futils.hpp" |
12 | | #include "image.hpp" |
13 | | #include "image_int.hpp" |
14 | | #include "jp2image_int.hpp" |
15 | | #include "safe_op.hpp" |
16 | | #include "tiffimage.hpp" |
17 | | #include "types.hpp" |
18 | | |
19 | | #include <algorithm> |
20 | | #include <array> |
21 | | #include <cstring> |
22 | | #include <iostream> |
23 | | |
24 | | #ifdef EXIV2_DEBUG_MESSAGES |
25 | | #include <fstream> |
26 | | #endif |
27 | | |
28 | | // JPEG-2000 box types |
29 | | enum kJp2BoxType : uint32_t { |
30 | | Signature = 0x6a502020, // signature box, required, |
31 | | FileTypeBox = 0x66747970, // File type box, required |
32 | | Header = 0x6a703268, // Jp2 Header Box, required, Superbox |
33 | | ImageHeader = 0x69686472, // Image Header Box ('ihdr'), required, |
34 | | ColorSpec = 0x636f6c72, // Color Specification box ('colr'), required |
35 | | Uuid = 0x75756964, // 'uuid' |
36 | | Close = 0x6a703263, // 'jp2c' |
37 | | }; |
38 | | |
39 | | namespace Exiv2 { |
40 | | namespace { |
41 | | // JPEG-2000 UUIDs for embedded metadata |
42 | | // |
43 | | // See http://www.jpeg.org/public/wg1n2600.doc for information about embedding IPTC-NAA data in JPEG-2000 files |
44 | | // See http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf for information about embedding XMP data in JPEG-2000 |
45 | | // files |
46 | | |
47 | | constexpr auto kJp2UuidExif = std::array<byte, 16>{ |
48 | | 0x4A, 0x70, 0x67, 0x54, 0x69, 0x66, 0x66, 0x45, 0x78, 0x69, 0x66, 0x2D, 0x3E, 0x4A, 0x50, 0x32, |
49 | | }; |
50 | | constexpr auto kJp2UuidIptc = std::array<byte, 16>{ |
51 | | 0x33, 0xc7, 0xa4, 0xd2, 0xb8, 0x1d, 0x47, 0x23, 0xa0, 0xba, 0xf1, 0xa3, 0xe0, 0x97, 0xad, 0x38, |
52 | | }; |
53 | | constexpr auto kJp2UuidXmp = std::array<byte, 16>{ |
54 | | 0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8, 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac, |
55 | | }; |
56 | | |
57 | | // See section B.1.1 (JPEG 2000 Signature box) of JPEG-2000 specification |
58 | | constexpr std::array<byte, 12> Jp2Signature{ |
59 | | 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, |
60 | | }; |
61 | | |
62 | | constexpr std::array<byte, 249> Jp2Blank{ |
63 | | 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, 0x00, 0x00, 0x00, 0x14, 0x66, 0x74, |
64 | | 0x79, 0x70, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x2d, |
65 | | 0x6a, 0x70, 0x32, 0x68, 0x00, 0x00, 0x00, 0x16, 0x69, 0x68, 0x64, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, |
66 | | 0x00, 0x01, 0x00, 0x01, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x63, 0x6f, 0x6c, 0x72, 0x01, 0x00, |
67 | | 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x70, 0x32, 0x63, 0xff, 0x4f, 0xff, 0x51, 0x00, |
68 | | 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
69 | | 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
70 | | 0x01, 0x07, 0x01, 0x01, 0xff, 0x64, 0x00, 0x23, 0x00, 0x01, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3a, |
71 | | 0x20, 0x4a, 0x61, 0x73, 0x50, 0x65, 0x72, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, |
72 | | 0x39, 0x30, 0x30, 0x2e, 0x31, 0xff, 0x52, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x04, 0x04, 0x00, |
73 | | 0x01, 0xff, 0x5c, 0x00, 0x13, 0x40, 0x40, 0x48, 0x48, 0x50, 0x48, 0x48, 0x50, 0x48, 0x48, 0x50, 0x48, 0x48, |
74 | | 0x50, 0x48, 0x48, 0x50, 0xff, 0x90, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x01, 0xff, 0x5d, |
75 | | 0x00, 0x14, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
76 | | 0x00, 0x00, 0xff, 0x93, 0xcf, 0xb4, 0x04, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xff, 0xd9, |
77 | | }; |
78 | | |
79 | | const size_t boxHSize = sizeof(Internal::Jp2BoxHeader); |
80 | | |
81 | 0 | void lf(std::ostream& out, bool& bLF) { |
82 | 0 | if (bLF) { |
83 | 0 | out << '\n'; |
84 | 0 | out.flush(); |
85 | 0 | bLF = false; |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | 843 | void boxes_check(size_t b, size_t m) { |
90 | 843 | if (b > m) { |
91 | | #ifdef EXIV2_DEBUG_MESSAGES |
92 | | std::cout << "Exiv2::Jp2Image::readMetadata box maximum exceeded" << '\n'; |
93 | | #endif |
94 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
95 | 0 | } |
96 | 843 | } |
97 | | |
98 | | } // namespace |
99 | | |
100 | | Jp2Image::Jp2Image(BasicIo::UniquePtr io, const ImageCtorParams& params) : |
101 | 268 | Image(ImageType::jp2, mdExif | mdIptc | mdXmp, std::move(io), params) { |
102 | 268 | if (params.create() && io_->open() == 0) { |
103 | | #ifdef EXIV2_DEBUG_MESSAGES |
104 | | std::cerr << "Exiv2::Jp2Image:: Creating JPEG2000 image to memory" << '\n'; |
105 | | #endif |
106 | 0 | IoCloser closer(*io_); |
107 | 0 | if (io_->write(Jp2Blank.data(), Jp2Blank.size()) != Jp2Blank.size()) { |
108 | | #ifdef EXIV2_DEBUG_MESSAGES |
109 | | std::cerr << "Exiv2::Jp2Image:: Failed to create JPEG2000 image on memory" << '\n'; |
110 | | #endif |
111 | 0 | } |
112 | 0 | } |
113 | 268 | } |
114 | | |
115 | | // Obtains the ascii version from the box.type |
116 | 0 | std::string Jp2Image::toAscii(uint32_t n) { |
117 | 0 | std::string result(sizeof(uint32_t), '\0'); |
118 | 0 | for (size_t i = 0; i < result.size(); ++i) { |
119 | 0 | auto c = static_cast<unsigned char>(n >> (8 * (3 - i))); |
120 | 0 | result[i] = static_cast<char>(c); |
121 | 0 | } |
122 | 0 | return result; |
123 | 0 | } |
124 | | |
125 | 0 | std::string Jp2Image::mimeType() const { |
126 | 0 | if (brand_ == Internal::brandJph) |
127 | 0 | return "image/jph"; |
128 | 0 | return "image/jp2"; |
129 | 0 | } |
130 | | |
131 | 0 | void Jp2Image::setComment(const std::string&) { |
132 | 0 | throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "JP2")); |
133 | 0 | } |
134 | | |
135 | 262 | void Jp2Image::readMetadata() { |
136 | | #ifdef EXIV2_DEBUG_MESSAGES |
137 | | std::cerr << "Exiv2::Jp2Image::readMetadata: Reading JPEG-2000 file " << io_->path() << '\n'; |
138 | | #endif |
139 | 262 | if (io_->open() != 0) { |
140 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); |
141 | 0 | } |
142 | 262 | IoCloser closer(*io_); |
143 | 262 | if (!isJp2Type(*io_, false)) { |
144 | 0 | throw Error(ErrorCode::kerNotAnImage, "JPEG-2000"); |
145 | 0 | } |
146 | | |
147 | 262 | Internal::Jp2BoxHeader box = {0, 0}; |
148 | 262 | Internal::Jp2BoxHeader subBox = {0, 0}; |
149 | 262 | Internal::Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0}; |
150 | 262 | Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; |
151 | 262 | size_t boxesCount = 0; |
152 | 262 | const size_t boxem = 1000; // boxes max |
153 | 262 | uint32_t lastBoxTypeRead = 0; |
154 | 262 | bool boxSignatureFound = false; |
155 | 262 | bool boxFileTypeFound = false; |
156 | | |
157 | 852 | while (io_->read(reinterpret_cast<byte*>(&box), boxHSize) == boxHSize) { |
158 | 843 | boxes_check(boxesCount++, boxem); |
159 | 843 | const size_t position = io_->tell(); |
160 | 843 | box.length = getULong(reinterpret_cast<byte*>(&box.length), bigEndian); |
161 | 843 | box.type = getULong(reinterpret_cast<byte*>(&box.type), bigEndian); |
162 | | #ifdef EXIV2_DEBUG_MESSAGES |
163 | | std::cout << "Exiv2::Jp2Image::readMetadata: Position: " << position << " box type: " << toAscii(box.type) |
164 | | << " length: " << box.length << '\n'; |
165 | | #endif |
166 | 843 | Internal::enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata); |
167 | | |
168 | 843 | if (box.length == 0) |
169 | 26 | return; |
170 | | |
171 | 817 | if (box.length == 1) { |
172 | | /// \todo In this case, the real box size is given in XLBox (bytes 8-15) |
173 | 33 | } |
174 | | |
175 | 817 | switch (box.type) { |
176 | 268 | case kJp2BoxType::Signature: { |
177 | 268 | if (boxSignatureFound) // Only one is allowed |
178 | 6 | throw Error(ErrorCode::kerCorruptedMetadata); |
179 | 262 | boxSignatureFound = true; |
180 | 262 | break; |
181 | 268 | } |
182 | 67 | case kJp2BoxType::FileTypeBox: { |
183 | | // This box shall immediately follow the JPEG 2000 Signature box |
184 | 67 | if (boxFileTypeFound || lastBoxTypeRead != kJp2BoxType::Signature) { // Only one is allowed |
185 | 6 | throw Error(ErrorCode::kerCorruptedMetadata); |
186 | 6 | } |
187 | 61 | boxFileTypeFound = true; |
188 | 61 | Internal::enforce(box.length >= boxHSize, ErrorCode::kerCorruptedMetadata); |
189 | 61 | Blob boxData(box.length - boxHSize); |
190 | 61 | io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata); |
191 | 61 | if (!Internal::isValidBoxFileType(boxData)) |
192 | 55 | throw Error(ErrorCode::kerCorruptedMetadata); |
193 | 6 | brand_ = getULong(boxData.data(), bigEndian); |
194 | 6 | break; |
195 | 61 | } |
196 | 0 | case kJp2BoxType::Header: { |
197 | | #ifdef EXIV2_DEBUG_MESSAGES |
198 | | std::cout << "Exiv2::Jp2Image::readMetadata: JP2Header box found\n"; |
199 | | #endif |
200 | 0 | size_t restore = io_->tell(); |
201 | |
|
202 | 0 | while (io_->read(reinterpret_cast<byte*>(&subBox), boxHSize) == boxHSize && subBox.length) { |
203 | 0 | boxes_check(boxesCount++, boxem); |
204 | 0 | subBox.length = getULong(reinterpret_cast<byte*>(&subBox.length), bigEndian); |
205 | 0 | subBox.type = getULong(reinterpret_cast<byte*>(&subBox.type), bigEndian); |
206 | 0 | if (subBox.length > io_->size()) { |
207 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
208 | 0 | } |
209 | | #ifdef EXIV2_DEBUG_MESSAGES |
210 | | std::cout << "Exiv2::Jp2Image::readMetadata: " |
211 | | << "subBox = " << toAscii(subBox.type) << " length = " << subBox.length << '\n'; |
212 | | #endif |
213 | 0 | if (subBox.type == kJp2BoxType::ColorSpec && subBox.length != 15) { |
214 | | #ifdef EXIV2_DEBUG_MESSAGES |
215 | | std::cout << "Exiv2::Jp2Image::readMetadata: " |
216 | | << "Color data found" << '\n'; |
217 | | #endif |
218 | |
|
219 | 0 | const size_t pad = 3; // 3 padding bytes 2 0 0 |
220 | 0 | const size_t data_length = Safe::add(subBox.length, 8u); |
221 | | // data_length makes no sense if it is larger than the rest of the file |
222 | 0 | if (data_length > io_->size() - io_->tell()) { |
223 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
224 | 0 | } |
225 | 0 | DataBuf data(data_length); |
226 | 0 | io_->read(data.data(), data.size()); |
227 | 0 | const size_t iccLength = data.read_uint32(pad, bigEndian); |
228 | | // subtracting pad from data.size() is safe: |
229 | | // data.size() is at least 8 and pad = 3 |
230 | 0 | if (iccLength > data.size() - pad) { |
231 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
232 | 0 | } |
233 | 0 | DataBuf icc(iccLength); |
234 | 0 | std::copy_n(data.begin() + pad, icc.size(), icc.begin()); |
235 | | #ifdef EXIV2_DEBUG_MESSAGES |
236 | | const char* iccPath = "/tmp/libexiv2_jp2.icc"; |
237 | | if (auto f = std::ofstream(iccPath, std::ios::binary)) { |
238 | | f.write(reinterpret_cast<const char*>(icc.c_data()), static_cast<std::streamsize>(icc.size())); |
239 | | f.close(); |
240 | | std::cout << "Exiv2::Jp2Image::readMetadata: wrote iccProfile " << icc.size() << " bytes to " << iccPath |
241 | | << '\n'; |
242 | | } |
243 | | #endif |
244 | 0 | setIccProfile(std::move(icc)); |
245 | 0 | } |
246 | | |
247 | 0 | if (subBox.type == kJp2BoxType::ImageHeader) { |
248 | 0 | io_->read(reinterpret_cast<byte*>(&ihdr), sizeof(ihdr)); |
249 | | #ifdef EXIV2_DEBUG_MESSAGES |
250 | | std::cout << "Exiv2::Jp2Image::readMetadata: Ihdr data found" << '\n'; |
251 | | #endif |
252 | 0 | ihdr.imageHeight = getULong(reinterpret_cast<byte*>(&ihdr.imageHeight), bigEndian); |
253 | 0 | ihdr.imageWidth = getULong(reinterpret_cast<byte*>(&ihdr.imageWidth), bigEndian); |
254 | 0 | ihdr.componentCount = getShort(reinterpret_cast<byte*>(&ihdr.componentCount), bigEndian); |
255 | 0 | Internal::enforce(ihdr.c == 7, ErrorCode::kerCorruptedMetadata); |
256 | |
|
257 | 0 | pixelWidth_ = ihdr.imageWidth; |
258 | 0 | pixelHeight_ = ihdr.imageHeight; |
259 | 0 | } |
260 | |
|
261 | 0 | io_->seek(restore, BasicIo::beg); |
262 | 0 | if (io_->seek(subBox.length, BasicIo::cur) != 0) { |
263 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
264 | 0 | } |
265 | 0 | restore = io_->tell(); |
266 | 0 | } |
267 | 0 | break; |
268 | 0 | } |
269 | | |
270 | 71 | case kJp2BoxType::Uuid: { |
271 | | #ifdef EXIV2_DEBUG_MESSAGES |
272 | | std::cout << "Exiv2::Jp2Image::readMetadata: UUID box found" << '\n'; |
273 | | #endif |
274 | | |
275 | 71 | if (io_->read(reinterpret_cast<byte*>(&uuid), sizeof(uuid)) == sizeof(uuid)) { |
276 | 71 | DataBuf rawData; |
277 | 71 | size_t bufRead; |
278 | 71 | bool bIsExif = uuid.uuid == kJp2UuidExif; |
279 | 71 | bool bIsIPTC = uuid.uuid == kJp2UuidIptc; |
280 | 71 | bool bIsXMP = uuid.uuid == kJp2UuidXmp; |
281 | | |
282 | 71 | if (bIsExif) { |
283 | | #ifdef EXIV2_DEBUG_MESSAGES |
284 | | std::cout << "Exiv2::Jp2Image::readMetadata: Exif data found" << '\n'; |
285 | | #endif |
286 | 0 | Internal::enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata); |
287 | 0 | rawData.alloc(box.length - (boxHSize + sizeof(uuid))); |
288 | 0 | bufRead = io_->read(rawData.data(), rawData.size()); |
289 | 0 | if (io_->error()) |
290 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
291 | 0 | if (bufRead != rawData.size()) |
292 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
293 | | |
294 | 0 | if (rawData.size() > 8) // "II*\0long" |
295 | 0 | { |
296 | | // Find the position of Exif header in bytes array. |
297 | 0 | const char a = rawData.read_uint8(0); |
298 | 0 | const char b = rawData.read_uint8(1); |
299 | 0 | const size_t notfound = std::numeric_limits<size_t>::max(); |
300 | 0 | size_t pos = (a == b && (a == 'I' || a == 'M')) ? 0 : notfound; |
301 | | |
302 | | // #1242 Forgive having Exif\0\0 in rawData.pData_ |
303 | 0 | std::array<byte, 6> exifHeader{0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; |
304 | 0 | for (size_t i = 0; pos == notfound && i < (rawData.size() - exifHeader.size()); i++) { |
305 | 0 | if (rawData.cmpBytes(i, exifHeader.data(), exifHeader.size()) == 0) { |
306 | 0 | pos = i + sizeof(exifHeader); |
307 | 0 | #ifndef SUPPRESS_WARNINGS |
308 | 0 | EXV_WARNING << "Reading non-standard UUID-EXIF_bad box in " << io_->path() << '\n'; |
309 | 0 | #endif |
310 | 0 | } |
311 | 0 | } |
312 | | |
313 | | // If found it, store only these data at from this place. |
314 | 0 | if (pos != notfound) { |
315 | | #ifdef EXIV2_DEBUG_MESSAGES |
316 | | std::cout << "Exiv2::Jp2Image::readMetadata: Exif header found at position " << pos << '\n'; |
317 | | #endif |
318 | 0 | ByteOrder bo = |
319 | 0 | TiffParser::decode(exifData(), iptcData(), xmpData(), rawData.c_data(pos), rawData.size() - pos); |
320 | 0 | setByteOrder(bo); |
321 | 0 | } |
322 | 0 | } else { |
323 | 0 | #ifndef SUPPRESS_WARNINGS |
324 | 0 | EXV_WARNING << "Failed to decode Exif metadata." << '\n'; |
325 | 0 | #endif |
326 | 0 | exifData_.clear(); |
327 | 0 | } |
328 | 0 | } |
329 | | |
330 | 71 | if (bIsIPTC) { |
331 | | #ifdef EXIV2_DEBUG_MESSAGES |
332 | | std::cout << "Exiv2::Jp2Image::readMetadata: Iptc data found" << '\n'; |
333 | | #endif |
334 | 16 | Internal::enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata); |
335 | 16 | rawData.alloc(box.length - (boxHSize + sizeof(uuid))); |
336 | 16 | bufRead = io_->read(rawData.data(), rawData.size()); |
337 | 16 | if (io_->error()) |
338 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
339 | 16 | if (bufRead != rawData.size()) |
340 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
341 | | |
342 | 16 | if (IptcParser::decode(iptcData_, rawData.c_data(), rawData.size())) { |
343 | 6 | #ifndef SUPPRESS_WARNINGS |
344 | 6 | EXV_WARNING << "Failed to decode IPTC metadata." << '\n'; |
345 | 6 | #endif |
346 | 6 | iptcData_.clear(); |
347 | 6 | } |
348 | 16 | } |
349 | | |
350 | 71 | if (bIsXMP) { |
351 | | #ifdef EXIV2_DEBUG_MESSAGES |
352 | | std::cout << "Exiv2::Jp2Image::readMetadata: Xmp data found" << '\n'; |
353 | | #endif |
354 | 39 | Internal::enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata); |
355 | 39 | rawData.alloc(box.length - (boxHSize + sizeof(uuid))); |
356 | 39 | bufRead = io_->read(rawData.data(), rawData.size()); |
357 | 39 | if (io_->error()) |
358 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
359 | 39 | if (bufRead != rawData.size()) |
360 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
361 | 39 | xmpPacket_.assign(rawData.c_str(), rawData.size()); |
362 | | |
363 | 39 | if (auto idx = xmpPacket_.find_first_of('<'); idx != std::string::npos && idx > 0) { |
364 | 24 | #ifndef SUPPRESS_WARNINGS |
365 | 24 | EXV_WARNING << "Removing " << static_cast<uint32_t>(idx) |
366 | 24 | << " characters from the beginning of the XMP packet" << '\n'; |
367 | 24 | #endif |
368 | 24 | xmpPacket_ = xmpPacket_.substr(idx); |
369 | 24 | } |
370 | | |
371 | 39 | if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_)) { |
372 | 32 | #ifndef SUPPRESS_WARNINGS |
373 | 32 | EXV_WARNING << "Failed to decode XMP metadata." << '\n'; |
374 | 32 | #endif |
375 | 32 | } |
376 | 39 | } |
377 | 71 | } |
378 | 71 | break; |
379 | 71 | } |
380 | | |
381 | 261 | default: |
382 | 261 | break; |
383 | 817 | } |
384 | 590 | lastBoxTypeRead = box.type; |
385 | | |
386 | | // Move to the next box. |
387 | 590 | io_->seek(static_cast<int64_t>(position - boxHSize + box.length), BasicIo::beg); |
388 | 590 | if (io_->error()) |
389 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
390 | 590 | } |
391 | | |
392 | 262 | } // Jp2Image::readMetadata |
393 | | |
394 | 0 | void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) { |
395 | 0 | if (io_->open() != 0) |
396 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); |
397 | | |
398 | 0 | if (!isJp2Type(*io_, false)) { |
399 | 0 | throw Error(ErrorCode::kerNotAJpeg); |
400 | 0 | } |
401 | | |
402 | | // According to the JP2 standard: The start of the first box shall be the first byte of the file, and the |
403 | | // last byte of the last box shall be the last byte of the file. |
404 | | |
405 | 0 | bool bPrint = option == kpsBasic || option == kpsRecursive; |
406 | 0 | bool bRecursive = option == kpsRecursive; |
407 | 0 | bool bICC = option == kpsIccProfile; |
408 | 0 | bool bXMP = option == kpsXMP; |
409 | 0 | bool bIPTCErase = option == kpsIptcErase; |
410 | 0 | bool boxSignatureFound = false; |
411 | |
|
412 | 0 | if (bPrint) { |
413 | 0 | out << "STRUCTURE OF JPEG2000 FILE: " << io_->path() << '\n'; |
414 | 0 | out << " address | length | box | data" << '\n'; |
415 | 0 | } |
416 | |
|
417 | 0 | if (bPrint || bXMP || bICC || bIPTCErase) { |
418 | 0 | Internal::Jp2BoxHeader box = {1, 1}; |
419 | 0 | Internal::Jp2BoxHeader subBox = {1, 1}; |
420 | 0 | Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; |
421 | 0 | bool bLF = false; |
422 | |
|
423 | 0 | while (box.length && box.type != kJp2BoxType::Close && |
424 | 0 | io_->read(reinterpret_cast<byte*>(&box), boxHSize) == boxHSize) { |
425 | 0 | const size_t position = io_->tell(); |
426 | 0 | box.length = getULong(reinterpret_cast<byte*>(&box.length), bigEndian); |
427 | 0 | box.type = getULong(reinterpret_cast<byte*>(&box.type), bigEndian); |
428 | 0 | Internal::enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata); |
429 | |
|
430 | 0 | if (bPrint) { |
431 | 0 | out << stringFormat("{:8} | {:8} | {} | ", position - boxHSize, box.length, toAscii(box.type)); |
432 | 0 | bLF = true; |
433 | 0 | if (box.type == kJp2BoxType::Close) |
434 | 0 | lf(out, bLF); |
435 | 0 | } |
436 | 0 | if (box.type == kJp2BoxType::Close) |
437 | 0 | break; |
438 | | |
439 | 0 | switch (box.type) { |
440 | 0 | case kJp2BoxType::Signature: { |
441 | 0 | if (boxSignatureFound) // Only one is allowed |
442 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
443 | 0 | boxSignatureFound = true; |
444 | 0 | break; |
445 | 0 | } |
446 | 0 | case kJp2BoxType::FileTypeBox: { |
447 | | // This box shall immediately follow the JPEG 2000 Signature box |
448 | | /// \todo All files shall contain one and only one File Type box. |
449 | 0 | Internal::enforce(box.length >= boxHSize, ErrorCode::kerCorruptedMetadata); |
450 | 0 | Blob boxData(box.length - boxHSize); |
451 | 0 | io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata); |
452 | 0 | if (!Internal::isValidBoxFileType(boxData)) |
453 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
454 | 0 | break; |
455 | 0 | } |
456 | 0 | case kJp2BoxType::Header: { |
457 | 0 | lf(out, bLF); |
458 | | /// \todo All files shall contain one and only one Header box. |
459 | |
|
460 | 0 | while (io_->read(reinterpret_cast<byte*>(&subBox), boxHSize) == boxHSize && |
461 | 0 | io_->tell() < position + box.length) // don't read beyond the box! |
462 | 0 | { |
463 | 0 | const size_t address = io_->tell() - boxHSize; |
464 | 0 | subBox.length = getULong(reinterpret_cast<byte*>(&subBox.length), bigEndian); |
465 | 0 | subBox.type = getULong(reinterpret_cast<byte*>(&subBox.type), bigEndian); |
466 | |
|
467 | 0 | if (subBox.length < boxHSize || subBox.length > io_->size() - io_->tell()) { |
468 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
469 | 0 | } |
470 | | |
471 | 0 | DataBuf data(subBox.length - boxHSize); |
472 | 0 | io_->read(data.data(), data.size()); |
473 | 0 | if (bPrint) { |
474 | 0 | out << stringFormat("{:8} | {:8} | sub:{} | ", address, subBox.length, toAscii(subBox.type)) |
475 | 0 | << Internal::binaryToString(makeSlice(data, 0, std::min<size_t>(30, data.size()))); |
476 | 0 | bLF = true; |
477 | 0 | } |
478 | |
|
479 | 0 | if (subBox.type == kJp2BoxType::ImageHeader) { |
480 | 0 | Internal::enforce(subBox.length == 22, ErrorCode::kerCorruptedMetadata); |
481 | | // height (4), width (4), componentsCount (2), bpc (1) |
482 | 0 | auto compressionType = data.read_uint8(11); |
483 | 0 | auto unkC = data.read_uint8(12); |
484 | 0 | auto ipr = data.read_uint8(13); |
485 | 0 | if (compressionType != 7 || unkC > 1 || ipr > 1) { |
486 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
487 | 0 | } |
488 | 0 | } else if (subBox.type == kJp2BoxType::ColorSpec) { |
489 | 0 | const size_t pad = 3; // don't know why there are 3 padding bytes |
490 | | |
491 | | // Bounds-check for the `getULong()` below, which reads 4 bytes, starting at `pad`. |
492 | 0 | Internal::enforce(data.size() >= pad + 4, ErrorCode::kerCorruptedMetadata); |
493 | | |
494 | | /// \todo A conforming JP2 reader shall ignore all Colour Specification boxes after the first. |
495 | 0 | auto METH = data.read_uint8(0); |
496 | | // auto PREC = data.read_uint8(1); |
497 | | // auto APPROX = data.read_uint8(2); |
498 | 0 | if (METH == 1) { // Enumerated Colourspace |
499 | 0 | auto enumCS = data.read_uint32(3, bigEndian); |
500 | 0 | if (enumCS != 16 && enumCS != 17) { |
501 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
502 | 0 | } |
503 | 0 | } else { // Restricted ICC Profile |
504 | | // see the ICC Profile Format Specification, version ICC.1:1998-09 |
505 | 0 | const size_t iccLength = data.read_uint32(pad, bigEndian); |
506 | 0 | if (bPrint) { |
507 | 0 | out << " | iccLength:" << iccLength; |
508 | 0 | } |
509 | 0 | Internal::enforce(iccLength <= data.size() - pad, ErrorCode::kerCorruptedMetadata); |
510 | 0 | if (bICC) { |
511 | 0 | out.write(data.c_str(pad), iccLength); |
512 | 0 | } |
513 | 0 | } |
514 | 0 | } |
515 | 0 | lf(out, bLF); |
516 | 0 | } |
517 | 0 | } break; |
518 | | |
519 | 0 | case kJp2BoxType::Uuid: { |
520 | 0 | if (io_->read(reinterpret_cast<byte*>(&uuid), sizeof(uuid)) == sizeof(uuid)) { |
521 | 0 | bool bIsExif = uuid.uuid == kJp2UuidExif; |
522 | 0 | bool bIsIPTC = uuid.uuid == kJp2UuidIptc; |
523 | 0 | bool bIsXMP = uuid.uuid == kJp2UuidXmp; |
524 | |
|
525 | 0 | bool bUnknown = !(bIsExif || bIsIPTC || bIsXMP); |
526 | |
|
527 | 0 | if (bPrint) { |
528 | 0 | if (bIsExif) |
529 | 0 | out << "Exif: "; |
530 | 0 | if (bIsIPTC) |
531 | 0 | out << "IPTC: "; |
532 | 0 | if (bIsXMP) |
533 | 0 | out << "XMP : "; |
534 | 0 | if (bUnknown) |
535 | 0 | out << "????: "; |
536 | 0 | } |
537 | |
|
538 | 0 | DataBuf rawData; |
539 | 0 | Internal::enforce(box.length >= sizeof(uuid) + boxHSize, ErrorCode::kerCorruptedMetadata); |
540 | 0 | rawData.alloc(box.length - sizeof(uuid) - boxHSize); |
541 | 0 | const size_t bufRead = io_->read(rawData.data(), rawData.size()); |
542 | 0 | if (io_->error()) |
543 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
544 | 0 | if (bufRead != rawData.size()) |
545 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
546 | | |
547 | 0 | if (bPrint) { |
548 | 0 | out << Internal::binaryToString(makeSlice(rawData, 0, std::min<size_t>(40, rawData.size()))); |
549 | 0 | out.flush(); |
550 | 0 | } |
551 | 0 | lf(out, bLF); |
552 | |
|
553 | 0 | if (bIsExif && bRecursive && rawData.size() > 8) { // "II*\0long" |
554 | 0 | const char a = rawData.read_uint8(0); |
555 | 0 | const char b = rawData.read_uint8(1); |
556 | 0 | if (a == b && (a == 'I' || a == 'M')) { |
557 | 0 | MemIo p(rawData.c_data(), rawData.size()); |
558 | 0 | printTiffStructure(p, out, option, depth + 1); |
559 | 0 | } |
560 | 0 | } |
561 | |
|
562 | 0 | if (bIsIPTC && bRecursive) { |
563 | 0 | IptcData::printStructure(out, makeSlice(rawData, 0, rawData.size()), depth); |
564 | 0 | } |
565 | |
|
566 | 0 | if (bIsXMP && bXMP) { |
567 | 0 | out.write(rawData.c_str(), rawData.size()); |
568 | 0 | } |
569 | 0 | } |
570 | 0 | } break; |
571 | | |
572 | 0 | default: |
573 | 0 | break; |
574 | 0 | } |
575 | | |
576 | | // Move to the next box. |
577 | 0 | io_->seek(static_cast<int64_t>(position - boxHSize + box.length), BasicIo::beg); |
578 | 0 | if (io_->error()) |
579 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
580 | 0 | if (bPrint) |
581 | 0 | lf(out, bLF); |
582 | 0 | } |
583 | 0 | } |
584 | 0 | } |
585 | | |
586 | 0 | void Jp2Image::writeMetadata() { |
587 | 0 | if (io_->open() != 0) { |
588 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); |
589 | 0 | } |
590 | 0 | IoCloser closer(*io_); |
591 | 0 | MemIo tempIo; |
592 | |
|
593 | 0 | doWriteMetadata(tempIo); // may throw |
594 | 0 | io_->close(); |
595 | 0 | io_->transfer(tempIo); // may throw |
596 | |
|
597 | 0 | } // Jp2Image::writeMetadata |
598 | | |
599 | 0 | void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { |
600 | 0 | DataBuf output(boxBuf.size() + iccProfile_.size() + 100); // allocate sufficient space |
601 | 0 | size_t outlen = boxHSize; // now many bytes have we written to output? |
602 | 0 | size_t inlen = boxHSize; // how many bytes have we read from boxBuf? |
603 | 0 | Internal::enforce(boxHSize <= output.size(), ErrorCode::kerCorruptedMetadata); |
604 | 0 | uint32_t length = getULong(boxBuf.c_data(0), bigEndian); |
605 | 0 | Internal::enforce(length <= output.size(), ErrorCode::kerCorruptedMetadata); |
606 | 0 | uint32_t count = boxHSize; |
607 | 0 | bool bWroteColor = false; |
608 | |
|
609 | 0 | while (count < length && !bWroteColor) { |
610 | 0 | Internal::enforce(boxHSize <= length - count, ErrorCode::kerCorruptedMetadata); |
611 | 0 | Internal::Jp2BoxHeader subBox; |
612 | 0 | std::memcpy(&subBox, boxBuf.c_data(count), boxHSize); |
613 | 0 | Internal::Jp2BoxHeader newBox = subBox; |
614 | |
|
615 | 0 | if (count < length) { |
616 | 0 | subBox.length = getULong(boxBuf.c_data(count), bigEndian); |
617 | 0 | subBox.type = getULong(boxBuf.c_data(count + 4), bigEndian); |
618 | | #ifdef EXIV2_DEBUG_MESSAGES |
619 | | std::cout << "Jp2Image::encodeJp2Header subbox: " << toAscii(subBox.type) << " length = " << subBox.length |
620 | | << '\n'; |
621 | | #endif |
622 | 0 | Internal::enforce(subBox.length > 0, ErrorCode::kerCorruptedMetadata); |
623 | 0 | Internal::enforce(subBox.length <= length - count, ErrorCode::kerCorruptedMetadata); |
624 | 0 | count += subBox.length; |
625 | 0 | newBox.type = subBox.type; |
626 | 0 | } else { |
627 | 0 | subBox.length = 0; |
628 | 0 | newBox.type = kJp2BoxType::ColorSpec; |
629 | 0 | count = length; |
630 | 0 | } |
631 | |
|
632 | 0 | size_t newlen = subBox.length; |
633 | 0 | if (newBox.type == kJp2BoxType::ColorSpec) { |
634 | 0 | bWroteColor = true; |
635 | 0 | if (!iccProfileDefined()) { |
636 | 0 | const char* pad = "\x01\x00\x00\x00\x00\x00\x10\x00\x00\x05\x1cuuid"; |
637 | 0 | uint32_t psize = 15; |
638 | 0 | Internal::enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata); |
639 | 0 | ul2Data(reinterpret_cast<byte*>(&newBox.length), psize, bigEndian); |
640 | 0 | ul2Data(reinterpret_cast<byte*>(&newBox.type), newBox.type, bigEndian); |
641 | 0 | std::memcpy(output.data(outlen), &newBox, sizeof(newBox)); |
642 | 0 | std::memcpy(output.data(outlen) + sizeof(newBox), pad, psize); |
643 | 0 | } else { |
644 | 0 | const char* pad = "\x02\x00\x00"; |
645 | 0 | uint32_t psize = 3; |
646 | 0 | newlen = sizeof(newBox) + psize + iccProfile_.size(); |
647 | 0 | Internal::enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata); |
648 | 0 | ul2Data(reinterpret_cast<byte*>(&newBox.length), static_cast<uint32_t>(newlen), bigEndian); |
649 | 0 | ul2Data(reinterpret_cast<byte*>(&newBox.type), newBox.type, bigEndian); |
650 | 0 | std::memcpy(output.data(outlen), &newBox, sizeof(newBox)); |
651 | 0 | std::memcpy(output.data(outlen) + sizeof(newBox), pad, psize); |
652 | 0 | std::copy(iccProfile_.begin(), iccProfile_.end(), output.begin() + outlen + sizeof(newBox) + psize); |
653 | 0 | } |
654 | 0 | } else { |
655 | 0 | Internal::enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata); |
656 | 0 | std::copy_n(boxBuf.begin() + inlen, subBox.length, output.begin() + outlen); |
657 | 0 | } |
658 | |
|
659 | 0 | outlen += newlen; |
660 | 0 | inlen += subBox.length; |
661 | 0 | } |
662 | | |
663 | | // allocate the correct number of bytes, copy the data and update the box header |
664 | 0 | outBuf.alloc(outlen); |
665 | 0 | std::copy_n(output.begin(), outlen, outBuf.begin()); |
666 | 0 | ul2Data(outBuf.data(0), static_cast<uint32_t>(outlen), bigEndian); |
667 | 0 | ul2Data(outBuf.data(4), kJp2BoxType::Header, bigEndian); |
668 | 0 | } |
669 | | |
670 | 0 | void Jp2Image::doWriteMetadata(BasicIo& outIo) { |
671 | 0 | if (!io_->isopen()) |
672 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
673 | 0 | if (!outIo.isopen()) |
674 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
675 | | |
676 | | #ifdef EXIV2_DEBUG_MESSAGES |
677 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: Writing JPEG-2000 file " << io_->path() << '\n'; |
678 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: tmp file created " << outIo.path() << '\n'; |
679 | | #endif |
680 | | |
681 | | // Ensure that this is the correct image type |
682 | 0 | if (!isJp2Type(*io_, true)) { |
683 | 0 | throw Error(ErrorCode::kerNoImageInInputData); |
684 | 0 | } |
685 | | |
686 | | // Write JPEG2000 Signature (This is the 1st box) |
687 | 0 | if (outIo.write(Jp2Signature.data(), Jp2Signature.size()) != 12) |
688 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
689 | | |
690 | | #ifdef EXIV2_DEBUG_MESSAGES |
691 | | std::cout << "Jp2Image::doWriteMetadata: JPEG 2000 Signature box written" << '\n'; |
692 | | #endif |
693 | | |
694 | 0 | Internal::Jp2BoxHeader box = {0, 0}; |
695 | |
|
696 | 0 | std::array<byte, 4> boxDataSize; |
697 | 0 | std::array<byte, 4> boxUUIDtype; |
698 | 0 | DataBuf bheaderBuf(8); |
699 | |
|
700 | 0 | while (io_->tell() < io_->size()) { |
701 | | #ifdef EXIV2_DEBUG_MESSAGES |
702 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: Position: " << io_->tell() << " / " << io_->size() << '\n'; |
703 | | #endif |
704 | | |
705 | | // Read chunk header. |
706 | 0 | io_->readOrThrow(bheaderBuf.data(), bheaderBuf.size(), ErrorCode::kerInputDataReadFailed); |
707 | | |
708 | | // Decode box header. |
709 | 0 | box.length = bheaderBuf.read_uint32(0, bigEndian); |
710 | 0 | box.type = bheaderBuf.read_uint32(4, bigEndian); |
711 | |
|
712 | | #ifdef EXIV2_DEBUG_MESSAGES |
713 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: box type: " << toAscii(box.type) << " length: " << box.length |
714 | | << '\n'; |
715 | | #endif |
716 | |
|
717 | 0 | if (box.length == 0) { |
718 | | #ifdef EXIV2_DEBUG_MESSAGES |
719 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: Null Box size has been found. " |
720 | | "This is the last box of file." |
721 | | << '\n'; |
722 | | #endif |
723 | 0 | box.length = static_cast<uint32_t>(io_->size() - io_->tell() + 8); |
724 | 0 | } |
725 | 0 | if (box.length < 8) { |
726 | | // box is broken, so there is nothing we can do here |
727 | 0 | throw Error(ErrorCode::kerCorruptedMetadata); |
728 | 0 | } |
729 | | |
730 | | // Prevent a malicious file from causing a large memory allocation. |
731 | 0 | Internal::enforce(box.length - 8 <= io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata); |
732 | | |
733 | | // Read whole box : Box header + Box data (not fixed size - can be null). |
734 | 0 | DataBuf boxBuf(box.length); // Box header (8 bytes) + box data. |
735 | 0 | std::copy(bheaderBuf.begin(), bheaderBuf.end(), boxBuf.begin()); // Copy header. |
736 | 0 | io_->readOrThrow(boxBuf.data(8), box.length - 8, ErrorCode::kerInputDataReadFailed); // Extract box data. |
737 | |
|
738 | 0 | switch (box.type) { |
739 | 0 | case kJp2BoxType::Header: { |
740 | 0 | DataBuf newBuf; |
741 | 0 | encodeJp2Header(boxBuf, newBuf); |
742 | | #ifdef EXIV2_DEBUG_MESSAGES |
743 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write JP2Header box (length: " << box.length << ")\n"; |
744 | | #endif |
745 | 0 | if (outIo.write(newBuf.data(), newBuf.size()) != newBuf.size()) |
746 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
747 | | |
748 | | // Write all updated metadata here, just after JP2Header. |
749 | | |
750 | 0 | if (!exifData_.empty()) { |
751 | | // Update Exif data to a new UUID box |
752 | |
|
753 | 0 | Blob blob; |
754 | 0 | ExifParser::encode(blob, littleEndian, exifData_); |
755 | 0 | if (!blob.empty()) { |
756 | 0 | DataBuf rawExif(blob.size()); |
757 | 0 | std::copy(blob.begin(), blob.end(), rawExif.begin()); |
758 | |
|
759 | 0 | DataBuf boxData(8 + 16 + rawExif.size()); |
760 | 0 | ul2Data(boxDataSize.data(), static_cast<uint32_t>(boxData.size()), bigEndian); |
761 | 0 | ul2Data(boxUUIDtype.data(), kJp2BoxType::Uuid, bigEndian); |
762 | 0 | std::copy(boxDataSize.begin(), boxDataSize.end(), boxData.begin()); |
763 | 0 | std::copy(boxUUIDtype.begin(), boxUUIDtype.end(), boxData.begin() + 4); |
764 | 0 | std::copy(kJp2UuidExif.begin(), kJp2UuidExif.end(), boxData.begin() + 8); |
765 | 0 | std::copy(rawExif.begin(), rawExif.end(), boxData.begin() + 8 + 16); |
766 | |
|
767 | | #ifdef EXIV2_DEBUG_MESSAGES |
768 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with Exif metadata (length: " << boxData.size() |
769 | | << '\n'; |
770 | | #endif |
771 | 0 | if (outIo.write(boxData.c_data(), boxData.size()) != boxData.size()) |
772 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
773 | 0 | } |
774 | 0 | } |
775 | | |
776 | 0 | if (!iptcData_.empty()) { |
777 | | // Update Iptc data to a new UUID box |
778 | |
|
779 | 0 | DataBuf rawIptc = IptcParser::encode(iptcData_); |
780 | 0 | if (!rawIptc.empty()) { |
781 | 0 | DataBuf boxData(8 + 16 + rawIptc.size()); |
782 | 0 | ul2Data(boxDataSize.data(), static_cast<uint32_t>(boxData.size()), bigEndian); |
783 | 0 | ul2Data(boxUUIDtype.data(), kJp2BoxType::Uuid, bigEndian); |
784 | 0 | std::copy(boxDataSize.begin(), boxDataSize.end(), boxData.begin()); |
785 | 0 | std::copy(boxUUIDtype.begin(), boxUUIDtype.end(), boxData.begin() + 4); |
786 | 0 | std::copy(kJp2UuidIptc.begin(), kJp2UuidIptc.end(), boxData.begin() + 8); |
787 | 0 | std::copy(rawIptc.begin(), rawIptc.end(), boxData.begin() + 8 + 16); |
788 | |
|
789 | | #ifdef EXIV2_DEBUG_MESSAGES |
790 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with Iptc metadata (length: " << boxData.size() |
791 | | << '\n'; |
792 | | #endif |
793 | 0 | if (outIo.write(boxData.c_data(), boxData.size()) != boxData.size()) |
794 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
795 | 0 | } |
796 | 0 | } |
797 | | |
798 | 0 | if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket_, xmpData_) > 1) { |
799 | 0 | #ifndef SUPPRESS_WARNINGS |
800 | 0 | EXV_ERROR << "Failed to encode XMP metadata." << '\n'; |
801 | 0 | #endif |
802 | 0 | } |
803 | 0 | if (!xmpPacket_.empty()) { |
804 | | // Update Xmp data to a new UUID box |
805 | |
|
806 | 0 | DataBuf xmp(reinterpret_cast<const byte*>(xmpPacket_.data()), xmpPacket_.size()); |
807 | 0 | DataBuf boxData(8 + 16 + xmp.size()); |
808 | 0 | ul2Data(boxDataSize.data(), static_cast<uint32_t>(boxData.size()), bigEndian); |
809 | 0 | ul2Data(boxUUIDtype.data(), kJp2BoxType::Uuid, bigEndian); |
810 | 0 | std::copy(boxDataSize.begin(), boxDataSize.end(), boxData.begin()); |
811 | 0 | std::copy(boxUUIDtype.begin(), boxUUIDtype.end(), boxData.begin() + 4); |
812 | 0 | std::copy(kJp2UuidXmp.begin(), kJp2UuidXmp.end(), boxData.begin() + 8); |
813 | 0 | std::copy(xmp.begin(), xmp.end(), boxData.begin() + 8 + 16); |
814 | |
|
815 | | #ifdef EXIV2_DEBUG_MESSAGES |
816 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with XMP metadata (length: " << boxData.size() |
817 | | << ")" << '\n'; |
818 | | #endif |
819 | 0 | if (outIo.write(boxData.c_data(), boxData.size()) != boxData.size()) |
820 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
821 | 0 | } |
822 | | |
823 | 0 | break; |
824 | 0 | } |
825 | | |
826 | 0 | case kJp2BoxType::Uuid: { |
827 | 0 | Internal::enforce(boxBuf.size() >= 24, ErrorCode::kerCorruptedMetadata); |
828 | 0 | if (boxBuf.cmpBytes(8, kJp2UuidExif.data(), 16) == 0) { |
829 | | #ifdef EXIV2_DEBUG_MESSAGES |
830 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Exif Uuid box" << '\n'; |
831 | | #endif |
832 | 0 | } else if (boxBuf.cmpBytes(8, kJp2UuidIptc.data(), 16) == 0) { |
833 | | #ifdef EXIV2_DEBUG_MESSAGES |
834 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Iptc Uuid box" << '\n'; |
835 | | #endif |
836 | 0 | } else if (boxBuf.cmpBytes(8, kJp2UuidXmp.data(), 16) == 0) { |
837 | | #ifdef EXIV2_DEBUG_MESSAGES |
838 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Xmp Uuid box" << '\n'; |
839 | | #endif |
840 | 0 | } else { |
841 | | #ifdef EXIV2_DEBUG_MESSAGES |
842 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: write Uuid box (length: " << box.length << ")" << '\n'; |
843 | | #endif |
844 | 0 | if (outIo.write(boxBuf.c_data(), boxBuf.size()) != boxBuf.size()) |
845 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
846 | 0 | } |
847 | 0 | break; |
848 | 0 | } |
849 | | |
850 | 0 | default: { |
851 | | #ifdef EXIV2_DEBUG_MESSAGES |
852 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: write box (length: " << box.length << ")" << '\n'; |
853 | | #endif |
854 | 0 | if (outIo.write(boxBuf.c_data(), boxBuf.size()) != boxBuf.size()) |
855 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
856 | | |
857 | 0 | break; |
858 | 0 | } |
859 | 0 | } |
860 | 0 | } |
861 | |
|
862 | | #ifdef EXIV2_DEBUG_MESSAGES |
863 | | std::cout << "Exiv2::Jp2Image::doWriteMetadata: EOF" << '\n'; |
864 | | #endif |
865 | |
|
866 | 0 | } // Jp2Image::doWriteMetadata |
867 | | |
868 | | // ************************************************************************* |
869 | | // free functions |
870 | 268 | Image::UniquePtr newJp2Instance(BasicIo::UniquePtr io, const ImageCtorParams& params) { |
871 | 268 | auto image = std::make_unique<Jp2Image>(std::move(io), params); |
872 | 268 | if (!image->good()) { |
873 | 6 | return nullptr; |
874 | 6 | } |
875 | 262 | return image; |
876 | 268 | } |
877 | | |
878 | 9.71k | bool isJp2Type(BasicIo& iIo, bool advance) { |
879 | 9.71k | std::array<byte, Jp2Signature.size()> buf; |
880 | 9.71k | const size_t bytesRead = iIo.read(buf.data(), Jp2Signature.size()); |
881 | 9.71k | if (iIo.error() || iIo.eof() || bytesRead != Jp2Signature.size()) { |
882 | 252 | return false; |
883 | 252 | } |
884 | 9.46k | bool matched = buf == Jp2Signature; |
885 | 9.46k | if (!advance || !matched) { |
886 | 9.46k | iIo.seek(-static_cast<int64_t>(Jp2Signature.size()), BasicIo::cur); // Return to original position |
887 | 9.46k | } |
888 | 9.46k | return matched; |
889 | 9.71k | } |
890 | | } // namespace Exiv2 |