/src/exiv2/src/preview.cpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | // included header files |
4 | | #include "preview.hpp" |
5 | | #include "basicio.hpp" |
6 | | #include "config.h" |
7 | | #include "enforce.hpp" |
8 | | #include "futils.hpp" |
9 | | #include "image.hpp" |
10 | | #include "photoshop.hpp" |
11 | | #include "properties.hpp" |
12 | | #include "safe_op.hpp" |
13 | | #include "tags.hpp" |
14 | | #include "tiffimage.hpp" |
15 | | #include "tiffimage_int.hpp" |
16 | | #include "value.hpp" |
17 | | |
18 | | #include <algorithm> |
19 | | #include <array> |
20 | | #include <climits> |
21 | | #include <cstring> |
22 | | |
23 | | namespace { |
24 | | using namespace Exiv2; |
25 | | using Exiv2::byte; |
26 | | |
27 | | /// @brief Decode a Hex string. |
28 | | DataBuf decodeHex(const byte* src, size_t srcSize); |
29 | | |
30 | | /// @brief Decode a Base64 string. |
31 | | DataBuf decodeBase64(const std::string& src); |
32 | | |
33 | | /// @brief Decode an Illustrator thumbnail that follows after %AI7_Thumbnail. |
34 | | DataBuf decodeAi7Thumbnail(const DataBuf& src); |
35 | | |
36 | | /// @brief Create a PNM image from raw RGB data. |
37 | | DataBuf makePnm(size_t width, size_t height, const DataBuf& rgb); |
38 | | |
39 | | /*! |
40 | | Base class for image loaders. Provides virtual methods for reading properties |
41 | | and DataBuf. |
42 | | */ |
43 | | class Loader { |
44 | | public: |
45 | | //! Virtual destructor. |
46 | 0 | virtual ~Loader() = default; |
47 | | |
48 | | Loader(const Loader&) = delete; |
49 | | Loader& operator=(const Loader&) = delete; |
50 | | |
51 | | //! Loader auto pointer |
52 | | using UniquePtr = std::unique_ptr<Loader>; |
53 | | |
54 | | //! Create a Loader subclass for requested id |
55 | | static UniquePtr create(PreviewId id, const Image& image); |
56 | | |
57 | | //! Check if a preview image with given params exists in the image |
58 | 0 | [[nodiscard]] virtual bool valid() const { |
59 | 0 | return valid_; |
60 | 0 | } |
61 | | |
62 | | //! Get properties of a preview image with given params |
63 | | [[nodiscard]] virtual PreviewProperties getProperties() const; |
64 | | |
65 | | //! Get a buffer that contains the preview image |
66 | | [[nodiscard]] virtual DataBuf getData() const = 0; |
67 | | |
68 | | //! Read preview image dimensions when they are not available directly |
69 | 0 | virtual bool readDimensions() { |
70 | 0 | return true; |
71 | 0 | } |
72 | | |
73 | | //! A number of image loaders configured in the loaderList_ table |
74 | | static PreviewId getNumLoaders(); |
75 | | |
76 | | protected: |
77 | | //! Constructor. Sets all image properties to unknown. |
78 | | Loader(PreviewId id, const Image& image); |
79 | | |
80 | | //! Functions that creates a loader from given parameters |
81 | | using CreateFunc = UniquePtr (*)(PreviewId, const Image&, int); |
82 | | |
83 | | //! Structure to list possible loaders |
84 | | struct LoaderList { |
85 | | const char* imageMimeType_; //!< Image type for which the loader is valid, 0 matches all images |
86 | | CreateFunc create_; //!< Function that creates particular loader instance |
87 | | int parIdx_; //!< Parameter that is passed into CreateFunc |
88 | | }; |
89 | | |
90 | | //! Table that lists possible loaders. PreviewId is an index to this table. |
91 | | static const LoaderList loaderList_[]; |
92 | | |
93 | | //! Identifies preview image type |
94 | | PreviewId id_; |
95 | | |
96 | | //! Source image reference |
97 | | const Image& image_; |
98 | | |
99 | | //! Preview image width |
100 | | size_t width_{0}; |
101 | | |
102 | | //! Preview image length |
103 | | size_t height_{0}; |
104 | | |
105 | | //! Preview image size in bytes |
106 | | size_t size_{0}; |
107 | | |
108 | | //! True if the source image contains a preview image of given type |
109 | | bool valid_{false}; |
110 | | }; |
111 | | |
112 | | //! Loader for native previews |
113 | | class LoaderNative : public Loader { |
114 | | public: |
115 | | //! Constructor |
116 | | LoaderNative(PreviewId id, const Image& image, int parIdx); |
117 | | |
118 | | //! Get properties of a preview image with given params |
119 | | [[nodiscard]] PreviewProperties getProperties() const override; |
120 | | |
121 | | //! Get a buffer that contains the preview image |
122 | | [[nodiscard]] DataBuf getData() const override; |
123 | | |
124 | | //! Read preview image dimensions |
125 | | bool readDimensions() override; |
126 | | |
127 | | protected: |
128 | | //! Native preview information |
129 | | NativePreview nativePreview_; |
130 | | }; |
131 | | |
132 | | //! Function to create new LoaderNative |
133 | | Loader::UniquePtr createLoaderNative(PreviewId id, const Image& image, int parIdx); |
134 | | |
135 | | //! Loader for Jpeg previews that are not read into ExifData directly |
136 | | class LoaderExifJpeg : public Loader { |
137 | | public: |
138 | | //! Constructor |
139 | | LoaderExifJpeg(PreviewId id, const Image& image, int parIdx); |
140 | | |
141 | | //! Get properties of a preview image with given params |
142 | | [[nodiscard]] PreviewProperties getProperties() const override; |
143 | | |
144 | | //! Get a buffer that contains the preview image |
145 | | [[nodiscard]] DataBuf getData() const override; |
146 | | |
147 | | //! Read preview image dimensions |
148 | | bool readDimensions() override; |
149 | | |
150 | | protected: |
151 | | //! Structure that lists offset/size tag pairs |
152 | | struct Param { |
153 | | const char* offsetKey_; //!< Offset tag |
154 | | const char* sizeKey_; //!< Size tag |
155 | | const char* baseOffsetKey_; //!< Tag that holds base offset or 0 |
156 | | }; |
157 | | |
158 | | //! Table that holds all possible offset/size pairs. parIdx is an index to this table |
159 | | static const Param param_[]; |
160 | | |
161 | | //! Offset value |
162 | | size_t offset_{0}; |
163 | | }; |
164 | | |
165 | | //! Function to create new LoaderExifJpeg |
166 | | Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image& image, int parIdx); |
167 | | |
168 | | //! Loader for Jpeg previews that are read into ExifData |
169 | | class LoaderExifDataJpeg : public Loader { |
170 | | public: |
171 | | //! Constructor |
172 | | LoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx); |
173 | | |
174 | | //! Get properties of a preview image with given params |
175 | | [[nodiscard]] PreviewProperties getProperties() const override; |
176 | | |
177 | | //! Get a buffer that contains the preview image |
178 | | [[nodiscard]] DataBuf getData() const override; |
179 | | |
180 | | //! Read preview image dimensions |
181 | | bool readDimensions() override; |
182 | | |
183 | | protected: |
184 | | //! Structure that lists data/size tag pairs |
185 | | struct Param { |
186 | | const char* dataKey_; //!< Data tag |
187 | | const char* sizeKey_; //!< Size tag |
188 | | }; |
189 | | |
190 | | //! Table that holds all possible data/size pairs. parIdx is an index to this table |
191 | | static const Param param_[]; |
192 | | |
193 | | //! Key that points to the Value that contains the JPEG preview in data area |
194 | | ExifKey dataKey_; |
195 | | }; |
196 | | |
197 | | //! Function to create new LoaderExifDataJpeg |
198 | | Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx); |
199 | | |
200 | | //! Loader for Tiff previews - it can get image data from ExifData or image_.io() as needed |
201 | | class LoaderTiff : public Loader { |
202 | | public: |
203 | | //! Constructor |
204 | | LoaderTiff(PreviewId id, const Image& image, int parIdx); |
205 | | |
206 | | //! Get properties of a preview image with given params |
207 | | [[nodiscard]] PreviewProperties getProperties() const override; |
208 | | |
209 | | //! Get a buffer that contains the preview image |
210 | | [[nodiscard]] DataBuf getData() const override; |
211 | | |
212 | | protected: |
213 | | //! Name of the group that contains the preview image |
214 | | const char* group_; |
215 | | |
216 | | //! Tag that contains image data. Possible values are "StripOffsets" or "TileOffsets" |
217 | | std::string offsetTag_; |
218 | | |
219 | | //! Tag that contains data sizes. Possible values are "StripByteCounts" or "TileByteCounts" |
220 | | std::string sizeTag_; |
221 | | |
222 | | //! Structure that lists preview groups |
223 | | struct Param { |
224 | | const char* group_; //!< Group name |
225 | | const char* checkTag_; //!< Tag to check or NULL |
226 | | const char* checkValue_; //!< The preview image is valid only if the checkTag_ has this value |
227 | | }; |
228 | | |
229 | | //! Table that holds all possible groups. parIdx is an index to this table. |
230 | | static const Param param_[]; |
231 | | }; |
232 | | |
233 | | //! Function to create new LoaderTiff |
234 | | Loader::UniquePtr createLoaderTiff(PreviewId id, const Image& image, int parIdx); |
235 | | |
236 | | //! Loader for JPEG previews stored in the XMP metadata |
237 | | class LoaderXmpJpeg : public Loader { |
238 | | public: |
239 | | //! Constructor |
240 | | LoaderXmpJpeg(PreviewId id, const Image& image, int parIdx); |
241 | | |
242 | | //! Get properties of a preview image with given params |
243 | | [[nodiscard]] PreviewProperties getProperties() const override; |
244 | | |
245 | | //! Get a buffer that contains the preview image |
246 | | [[nodiscard]] DataBuf getData() const override; |
247 | | |
248 | | //! Read preview image dimensions |
249 | | bool readDimensions() override; |
250 | | |
251 | | protected: |
252 | | //! Preview image data |
253 | | DataBuf preview_; |
254 | | }; |
255 | | |
256 | | //! Function to create new LoaderXmpJpeg |
257 | | Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image& image, int parIdx); |
258 | | |
259 | | // ***************************************************************************** |
260 | | // class member definitions |
261 | | |
262 | | const Loader::LoaderList Loader::loaderList_[] = { |
263 | | {nullptr, createLoaderNative, 0}, {nullptr, createLoaderNative, 1}, |
264 | | {nullptr, createLoaderNative, 2}, {nullptr, createLoaderNative, 3}, |
265 | | {nullptr, createLoaderExifDataJpeg, 0}, {nullptr, createLoaderExifDataJpeg, 1}, |
266 | | {nullptr, createLoaderExifDataJpeg, 2}, {nullptr, createLoaderExifDataJpeg, 3}, |
267 | | {nullptr, createLoaderExifDataJpeg, 4}, {nullptr, createLoaderExifDataJpeg, 5}, |
268 | | {nullptr, createLoaderExifDataJpeg, 6}, {nullptr, createLoaderExifDataJpeg, 7}, |
269 | | {nullptr, createLoaderExifDataJpeg, 8}, {"image/x-panasonic-rw2", createLoaderExifDataJpeg, 9}, |
270 | | {nullptr, createLoaderExifDataJpeg, 10}, {nullptr, createLoaderExifDataJpeg, 11}, |
271 | | {nullptr, createLoaderTiff, 0}, {nullptr, createLoaderTiff, 1}, |
272 | | {nullptr, createLoaderTiff, 2}, {nullptr, createLoaderTiff, 3}, |
273 | | {nullptr, createLoaderTiff, 4}, {nullptr, createLoaderTiff, 5}, |
274 | | {nullptr, createLoaderTiff, 6}, {"image/x-canon-cr2", createLoaderTiff, 7}, |
275 | | {nullptr, createLoaderExifJpeg, 0}, {nullptr, createLoaderExifJpeg, 1}, |
276 | | {nullptr, createLoaderExifJpeg, 2}, {nullptr, createLoaderExifJpeg, 3}, |
277 | | {nullptr, createLoaderExifJpeg, 4}, {nullptr, createLoaderExifJpeg, 5}, |
278 | | {nullptr, createLoaderExifJpeg, 6}, {"image/x-canon-cr2", createLoaderExifJpeg, 7}, |
279 | | {nullptr, createLoaderExifJpeg, 8}, {nullptr, createLoaderXmpJpeg, 0}, |
280 | | }; |
281 | | |
282 | | const LoaderExifJpeg::Param LoaderExifJpeg::param_[] = { |
283 | | {"Exif.Image.JPEGInterchangeFormat", "Exif.Image.JPEGInterchangeFormatLength", nullptr}, // 0 |
284 | | {"Exif.SubImage1.JPEGInterchangeFormat", "Exif.SubImage1.JPEGInterchangeFormatLength", nullptr}, // 1 |
285 | | {"Exif.SubImage2.JPEGInterchangeFormat", "Exif.SubImage2.JPEGInterchangeFormatLength", nullptr}, // 2 |
286 | | {"Exif.SubImage3.JPEGInterchangeFormat", "Exif.SubImage3.JPEGInterchangeFormatLength", nullptr}, // 3 |
287 | | {"Exif.SubImage4.JPEGInterchangeFormat", "Exif.SubImage4.JPEGInterchangeFormatLength", nullptr}, // 4 |
288 | | {"Exif.SubThumb1.JPEGInterchangeFormat", "Exif.SubThumb1.JPEGInterchangeFormatLength", nullptr}, // 5 |
289 | | {"Exif.Image2.JPEGInterchangeFormat", "Exif.Image2.JPEGInterchangeFormatLength", nullptr}, // 6 |
290 | | {"Exif.Image.StripOffsets", "Exif.Image.StripByteCounts", nullptr}, // 7 |
291 | | {"Exif.OlympusCs.PreviewImageStart", "Exif.OlympusCs.PreviewImageLength", "Exif.MakerNote.Offset"} // 8 |
292 | | }; |
293 | | |
294 | | const LoaderExifDataJpeg::Param LoaderExifDataJpeg::param_[] = { |
295 | | {"Exif.Thumbnail.JPEGInterchangeFormat", "Exif.Thumbnail.JPEGInterchangeFormatLength"}, // 0 |
296 | | {"Exif.NikonPreview.JPEGInterchangeFormat", "Exif.NikonPreview.JPEGInterchangeFormatLength"}, // 1 |
297 | | {"Exif.Pentax.PreviewOffset", "Exif.Pentax.PreviewLength"}, // 2 |
298 | | {"Exif.PentaxDng.PreviewOffset", "Exif.PentaxDng.PreviewLength"}, // 3 |
299 | | {"Exif.Minolta.ThumbnailOffset", "Exif.Minolta.ThumbnailLength"}, // 4 |
300 | | {"Exif.SonyMinolta.ThumbnailOffset", "Exif.SonyMinolta.ThumbnailLength"}, // 5 |
301 | | {"Exif.Olympus.ThumbnailImage", nullptr}, // 6 |
302 | | {"Exif.Olympus2.ThumbnailImage", nullptr}, // 7 |
303 | | {"Exif.Minolta.Thumbnail", nullptr}, // 8 |
304 | | {"Exif.PanasonicRaw.PreviewImage", nullptr}, // 9 |
305 | | {"Exif.SamsungPreview.JPEGInterchangeFormat", "Exif.SamsungPreview.JPEGInterchangeFormatLength"}, // 10 |
306 | | {"Exif.Casio2.PreviewImage", nullptr} // 11 |
307 | | }; |
308 | | |
309 | | const LoaderTiff::Param LoaderTiff::param_[] = { |
310 | | {"Image", "Exif.Image.NewSubfileType", "1"}, // 0 |
311 | | {"SubImage1", "Exif.SubImage1.NewSubfileType", "1"}, // 1 |
312 | | {"SubImage2", "Exif.SubImage2.NewSubfileType", "1"}, // 2 |
313 | | {"SubImage3", "Exif.SubImage3.NewSubfileType", "1"}, // 3 |
314 | | {"SubImage4", "Exif.SubImage4.NewSubfileType", "1"}, // 4 |
315 | | {"SubThumb1", "Exif.SubThumb1.NewSubfileType", "1"}, // 5 |
316 | | {"Thumbnail", nullptr, nullptr}, // 6 |
317 | | {"Image2", nullptr, nullptr} // 7 |
318 | | }; |
319 | | |
320 | 0 | Loader::UniquePtr Loader::create(PreviewId id, const Image& image) { |
321 | 0 | Loader::UniquePtr loader; |
322 | 0 | if (id < 0 || id >= Loader::getNumLoaders()) |
323 | 0 | return loader; |
324 | | |
325 | 0 | if (loaderList_[id].imageMimeType_ && std::string(loaderList_[id].imageMimeType_) != image.mimeType()) |
326 | 0 | return loader; |
327 | | |
328 | 0 | loader = loaderList_[id].create_(id, image, loaderList_[id].parIdx_); |
329 | 0 | if (!loader->valid()) |
330 | 0 | loader = nullptr; |
331 | |
|
332 | 0 | return loader; |
333 | 0 | } |
334 | | |
335 | 0 | Loader::Loader(PreviewId id, const Image& image) : id_(id), image_(image) { |
336 | 0 | } |
337 | | |
338 | 0 | PreviewProperties Loader::getProperties() const { |
339 | 0 | return {"", "", size_, width_, height_, id_}; |
340 | 0 | } |
341 | | |
342 | 0 | PreviewId Loader::getNumLoaders() { |
343 | 0 | return PreviewId{std::size(loaderList_)}; |
344 | 0 | } |
345 | | |
346 | 0 | LoaderNative::LoaderNative(PreviewId id, const Image& image, int parIdx) : Loader(id, image) { |
347 | 0 | if (0 > parIdx || static_cast<size_t>(parIdx) >= image.nativePreviews().size()) |
348 | 0 | return; |
349 | 0 | nativePreview_ = image.nativePreviews()[parIdx]; |
350 | 0 | width_ = nativePreview_.width_; |
351 | 0 | height_ = nativePreview_.height_; |
352 | 0 | valid_ = true; |
353 | 0 | if (nativePreview_.filter_.empty()) { |
354 | 0 | size_ = nativePreview_.size_; |
355 | 0 | } else { |
356 | 0 | size_ = getData().size(); |
357 | 0 | } |
358 | 0 | } |
359 | | |
360 | 0 | Loader::UniquePtr createLoaderNative(PreviewId id, const Image& image, int parIdx) { |
361 | 0 | return std::make_unique<LoaderNative>(id, image, parIdx); |
362 | 0 | } |
363 | | |
364 | 0 | PreviewProperties LoaderNative::getProperties() const { |
365 | 0 | PreviewProperties prop = Loader::getProperties(); |
366 | 0 | prop.mimeType_ = nativePreview_.mimeType_; |
367 | 0 | if (nativePreview_.mimeType_ == "image/jpeg") { |
368 | 0 | prop.extension_ = ".jpg"; |
369 | 0 | } else if (nativePreview_.mimeType_ == "image/tiff") { |
370 | 0 | prop.extension_ = ".tif"; |
371 | 0 | } else if (nativePreview_.mimeType_ == "image/x-wmf") { |
372 | 0 | prop.extension_ = ".wmf"; |
373 | 0 | } else if (nativePreview_.mimeType_ == "image/x-portable-anymap") { |
374 | 0 | prop.extension_ = ".pnm"; |
375 | 0 | } else { |
376 | 0 | #ifndef SUPPRESS_WARNINGS |
377 | 0 | EXV_WARNING << "Unknown native preview format: " << nativePreview_.mimeType_ << "\n"; |
378 | 0 | #endif |
379 | 0 | prop.extension_ = ".dat"; |
380 | 0 | } |
381 | 0 | return prop; |
382 | 0 | } |
383 | | |
384 | 0 | DataBuf LoaderNative::getData() const { |
385 | 0 | if (!valid()) |
386 | 0 | return {}; |
387 | | |
388 | 0 | BasicIo& io = image_.io(); |
389 | 0 | if (io.open() != 0) { |
390 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); |
391 | 0 | } |
392 | 0 | IoCloser closer(io); |
393 | 0 | const byte* data = io.mmap(); |
394 | 0 | if (io.size() < nativePreview_.position_ + nativePreview_.size_) { |
395 | 0 | #ifndef SUPPRESS_WARNINGS |
396 | 0 | EXV_WARNING << "Invalid native preview position or size.\n"; |
397 | 0 | #endif |
398 | 0 | return {}; |
399 | 0 | } |
400 | 0 | if (nativePreview_.filter_.empty()) { |
401 | 0 | return {data + nativePreview_.position_, nativePreview_.size_}; |
402 | 0 | } |
403 | 0 | if (nativePreview_.filter_ == "hex-ai7thumbnail-pnm") { |
404 | 0 | const DataBuf ai7thumbnail = decodeHex(data + nativePreview_.position_, nativePreview_.size_); |
405 | 0 | const DataBuf rgb = decodeAi7Thumbnail(ai7thumbnail); |
406 | 0 | return makePnm(width_, height_, rgb); |
407 | 0 | } |
408 | 0 | if (nativePreview_.filter_ == "hex-irb") { |
409 | 0 | const DataBuf psData = decodeHex(data + nativePreview_.position_, nativePreview_.size_); |
410 | 0 | const byte* record; |
411 | 0 | uint32_t sizeHdr = 0; |
412 | 0 | uint32_t sizeData = 0; |
413 | 0 | if (Photoshop::locatePreviewIrb(psData.c_data(), psData.size(), &record, sizeHdr, sizeData) != 0) { |
414 | 0 | #ifndef SUPPRESS_WARNINGS |
415 | 0 | EXV_WARNING << "Missing preview IRB in Photoshop EPS preview.\n"; |
416 | 0 | #endif |
417 | 0 | return {}; |
418 | 0 | } |
419 | 0 | Internal::enforce(sizeData >= 28, ErrorCode::kerCorruptedMetadata); |
420 | 0 | return {record + sizeHdr + 28, sizeData - 28}; |
421 | 0 | } |
422 | 0 | throw Error(ErrorCode::kerErrorMessage, "Invalid native preview filter: ", nativePreview_.filter_); |
423 | 0 | } |
424 | | |
425 | 0 | bool LoaderNative::readDimensions() { |
426 | 0 | if (!valid()) |
427 | 0 | return false; |
428 | 0 | if (width_ != 0 || height_ != 0) |
429 | 0 | return true; |
430 | | |
431 | 0 | const DataBuf data = getData(); |
432 | 0 | if (data.empty()) |
433 | 0 | return false; |
434 | | |
435 | 0 | try { |
436 | 0 | auto image = ImageFactory::open(data.c_data(), data.size()); |
437 | 0 | if (!image) |
438 | 0 | return false; |
439 | 0 | image->readMetadata(); |
440 | |
|
441 | 0 | width_ = image->pixelWidth(); |
442 | 0 | height_ = image->pixelHeight(); |
443 | 0 | } catch (const Error& /* error */) { |
444 | 0 | #ifndef SUPPRESS_WARNINGS |
445 | 0 | EXV_WARNING << "Invalid native preview image.\n"; |
446 | 0 | #endif |
447 | 0 | return false; |
448 | 0 | } |
449 | 0 | return true; |
450 | 0 | } |
451 | | |
452 | 0 | LoaderExifJpeg::LoaderExifJpeg(PreviewId id, const Image& image, int parIdx) : Loader(id, image) { |
453 | 0 | const ExifData& exifData = image_.exifData(); |
454 | 0 | auto pos = exifData.findKey(ExifKey(param_[parIdx].offsetKey_)); |
455 | 0 | if (pos != exifData.end() && pos->count() > 0) { |
456 | 0 | offset_ = pos->toUint32(); |
457 | 0 | } |
458 | |
|
459 | 0 | size_ = 0; |
460 | 0 | pos = exifData.findKey(ExifKey(param_[parIdx].sizeKey_)); |
461 | 0 | if (pos != exifData.end() && pos->count() > 0) { |
462 | 0 | size_ = pos->toUint32(); |
463 | 0 | } |
464 | |
|
465 | 0 | if (offset_ == 0 || size_ == 0) |
466 | 0 | return; |
467 | | |
468 | 0 | if (param_[parIdx].baseOffsetKey_) { |
469 | 0 | pos = exifData.findKey(ExifKey(param_[parIdx].baseOffsetKey_)); |
470 | 0 | if (pos != exifData.end() && pos->count() > 0) { |
471 | 0 | offset_ += pos->toUint32(); |
472 | 0 | } |
473 | 0 | } |
474 | |
|
475 | 0 | if (Safe::add(offset_, size_) > image_.io().size()) |
476 | 0 | return; |
477 | | |
478 | 0 | valid_ = true; |
479 | 0 | } |
480 | | |
481 | 0 | Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image& image, int parIdx) { |
482 | 0 | return std::make_unique<LoaderExifJpeg>(id, image, parIdx); |
483 | 0 | } |
484 | | |
485 | 0 | PreviewProperties LoaderExifJpeg::getProperties() const { |
486 | 0 | PreviewProperties prop = Loader::getProperties(); |
487 | 0 | prop.mimeType_ = "image/jpeg"; |
488 | 0 | prop.extension_ = ".jpg"; |
489 | 0 | return prop; |
490 | 0 | } |
491 | | |
492 | 0 | DataBuf LoaderExifJpeg::getData() const { |
493 | 0 | if (!valid()) |
494 | 0 | return {}; |
495 | 0 | BasicIo& io = image_.io(); |
496 | |
|
497 | 0 | if (io.open() != 0) { |
498 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); |
499 | 0 | } |
500 | 0 | IoCloser closer(io); |
501 | |
|
502 | 0 | const Exiv2::byte* base = io.mmap(); |
503 | |
|
504 | 0 | return {base + offset_, size_}; |
505 | 0 | } |
506 | | |
507 | 0 | bool LoaderExifJpeg::readDimensions() { |
508 | 0 | if (!valid()) |
509 | 0 | return false; |
510 | 0 | if (width_ || height_) |
511 | 0 | return true; |
512 | | |
513 | 0 | BasicIo& io = image_.io(); |
514 | |
|
515 | 0 | if (io.open() != 0) { |
516 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); |
517 | 0 | } |
518 | 0 | IoCloser closer(io); |
519 | 0 | const Exiv2::byte* base = io.mmap(); |
520 | |
|
521 | 0 | try { |
522 | 0 | auto image = ImageFactory::open(base + offset_, size_); |
523 | 0 | if (!image) |
524 | 0 | return false; |
525 | 0 | image->readMetadata(); |
526 | |
|
527 | 0 | width_ = image->pixelWidth(); |
528 | 0 | height_ = image->pixelHeight(); |
529 | 0 | } catch (const Error& /* error */) { |
530 | 0 | #ifndef SUPPRESS_WARNINGS |
531 | 0 | EXV_WARNING << "Invalid JPEG preview image.\n"; |
532 | 0 | #endif |
533 | 0 | return false; |
534 | 0 | } |
535 | | |
536 | 0 | return true; |
537 | 0 | } |
538 | | |
539 | | LoaderExifDataJpeg::LoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx) : |
540 | 0 | Loader(id, image), dataKey_(param_[parIdx].dataKey_) { |
541 | 0 | if (auto pos = image_.exifData().findKey(dataKey_); pos != image_.exifData().end()) { |
542 | 0 | size_ = pos->sizeDataArea(); // indirect data |
543 | 0 | if (size_ == 0 && pos->typeId() == undefined) |
544 | 0 | size_ = pos->size(); // direct data |
545 | 0 | } |
546 | |
|
547 | 0 | if (size_ == 0) |
548 | 0 | return; |
549 | | |
550 | 0 | valid_ = true; |
551 | 0 | } |
552 | | |
553 | 0 | Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx) { |
554 | 0 | return std::make_unique<LoaderExifDataJpeg>(id, image, parIdx); |
555 | 0 | } |
556 | | |
557 | 0 | PreviewProperties LoaderExifDataJpeg::getProperties() const { |
558 | 0 | PreviewProperties prop = Loader::getProperties(); |
559 | 0 | prop.mimeType_ = "image/jpeg"; |
560 | 0 | prop.extension_ = ".jpg"; |
561 | 0 | return prop; |
562 | 0 | } |
563 | | |
564 | 0 | DataBuf LoaderExifDataJpeg::getData() const { |
565 | 0 | DataBuf buf; |
566 | |
|
567 | 0 | if (!valid()) |
568 | 0 | return buf; |
569 | | |
570 | 0 | if (auto pos = image_.exifData().findKey(dataKey_); pos != image_.exifData().end()) { |
571 | 0 | buf = pos->dataArea(); // indirect data |
572 | |
|
573 | 0 | if (buf.empty()) { // direct data |
574 | 0 | buf = DataBuf(pos->size()); |
575 | 0 | pos->copy(buf.data(), invalidByteOrder); |
576 | 0 | } |
577 | |
|
578 | 0 | buf.write_uint8(0, 0xff); // fix Minolta thumbnails with invalid jpeg header |
579 | 0 | return buf; |
580 | 0 | } |
581 | | |
582 | 0 | return buf; |
583 | 0 | } |
584 | | |
585 | 0 | bool LoaderExifDataJpeg::readDimensions() { |
586 | 0 | if (!valid()) |
587 | 0 | return false; |
588 | | |
589 | 0 | DataBuf buf = getData(); |
590 | 0 | if (buf.empty()) |
591 | 0 | return false; |
592 | | |
593 | 0 | try { |
594 | 0 | auto image = ImageFactory::open(buf.c_data(), buf.size()); |
595 | 0 | if (!image) |
596 | 0 | return false; |
597 | 0 | image->readMetadata(); |
598 | |
|
599 | 0 | width_ = image->pixelWidth(); |
600 | 0 | height_ = image->pixelHeight(); |
601 | 0 | } catch (const Error& /* error */) { |
602 | 0 | return false; |
603 | 0 | } |
604 | | |
605 | 0 | return true; |
606 | 0 | } |
607 | | |
608 | | LoaderTiff::LoaderTiff(PreviewId id, const Image& image, int parIdx) : |
609 | 0 | Loader(id, image), group_(param_[parIdx].group_) { |
610 | 0 | const ExifData& exifData = image_.exifData(); |
611 | |
|
612 | 0 | size_t offsetCount = 0; |
613 | 0 | ExifData::const_iterator pos; |
614 | | |
615 | | // check if the group_ contains a preview image |
616 | 0 | if (param_[parIdx].checkTag_) { |
617 | 0 | pos = exifData.findKey(ExifKey(param_[parIdx].checkTag_)); |
618 | 0 | if (pos == exifData.end()) |
619 | 0 | return; |
620 | 0 | if (param_[parIdx].checkValue_ && pos->toString() != param_[parIdx].checkValue_) |
621 | 0 | return; |
622 | 0 | } |
623 | | |
624 | 0 | pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".StripOffsets")); |
625 | 0 | if (pos != exifData.end()) { |
626 | 0 | offsetTag_ = "StripOffsets"; |
627 | 0 | sizeTag_ = "StripByteCounts"; |
628 | 0 | offsetCount = pos->value().count(); |
629 | 0 | } else { |
630 | 0 | pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".TileOffsets")); |
631 | 0 | if (pos == exifData.end()) |
632 | 0 | return; |
633 | 0 | offsetTag_ = "TileOffsets"; |
634 | 0 | sizeTag_ = "TileByteCounts"; |
635 | 0 | offsetCount = pos->value().count(); |
636 | 0 | } |
637 | | |
638 | 0 | pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + '.' + sizeTag_)); |
639 | 0 | if (pos == exifData.end()) |
640 | 0 | return; |
641 | 0 | if (offsetCount != pos->value().count()) |
642 | 0 | return; |
643 | 0 | for (size_t i = 0; i < offsetCount; i++) { |
644 | 0 | size_ += pos->toUint32(i); |
645 | 0 | } |
646 | |
|
647 | 0 | if (size_ == 0) |
648 | 0 | return; |
649 | | |
650 | 0 | pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageWidth")); |
651 | 0 | if (pos != exifData.end() && pos->count() > 0) { |
652 | 0 | width_ = pos->toUint32(); |
653 | 0 | } |
654 | |
|
655 | 0 | pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageLength")); |
656 | 0 | if (pos != exifData.end() && pos->count() > 0) { |
657 | 0 | height_ = pos->toUint32(); |
658 | 0 | } |
659 | |
|
660 | 0 | if (width_ == 0 || height_ == 0) |
661 | 0 | return; |
662 | | |
663 | 0 | valid_ = true; |
664 | 0 | } |
665 | | |
666 | 0 | Loader::UniquePtr createLoaderTiff(PreviewId id, const Image& image, int parIdx) { |
667 | 0 | return std::make_unique<LoaderTiff>(id, image, parIdx); |
668 | 0 | } |
669 | | |
670 | 0 | PreviewProperties LoaderTiff::getProperties() const { |
671 | 0 | PreviewProperties prop = Loader::getProperties(); |
672 | 0 | prop.mimeType_ = "image/tiff"; |
673 | 0 | prop.extension_ = ".tif"; |
674 | 0 | return prop; |
675 | 0 | } |
676 | | |
677 | 0 | DataBuf LoaderTiff::getData() const { |
678 | 0 | const ExifData& exifData = image_.exifData(); |
679 | |
|
680 | 0 | ExifData preview; |
681 | | |
682 | | // copy tags |
683 | 0 | for (auto&& pos : exifData) { |
684 | 0 | if (pos.groupName() == group_) { |
685 | | /* |
686 | | Write only the necessary TIFF image tags |
687 | | tags that especially could cause problems are: |
688 | | "NewSubfileType" - the result is no longer a thumbnail, it is a standalone image |
689 | | "Orientation" - this tag typically appears only in the "Image" group. Deleting it ensures |
690 | | consistent result for all previews, including JPEG |
691 | | */ |
692 | 0 | uint16_t tag = pos.tag(); |
693 | 0 | if (tag != 0x00fe && tag != 0x00ff && Internal::isTiffImageTag(tag, IfdId::ifd0Id)) { |
694 | 0 | preview.add(ExifKey(tag, "Image"), &pos.value()); |
695 | 0 | } |
696 | 0 | } |
697 | 0 | } |
698 | |
|
699 | 0 | auto& dataValue = const_cast<Value&>(preview["Exif.Image." + offsetTag_].value()); |
700 | |
|
701 | 0 | if (dataValue.sizeDataArea() == 0) { |
702 | | // image data are not available via exifData, read them from image_.io() |
703 | 0 | BasicIo& io = image_.io(); |
704 | |
|
705 | 0 | if (io.open() != 0) { |
706 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); |
707 | 0 | } |
708 | 0 | IoCloser closer(io); |
709 | |
|
710 | 0 | const Exiv2::byte* base = io.mmap(); |
711 | |
|
712 | 0 | const Value& sizes = preview["Exif.Image." + sizeTag_].value(); |
713 | |
|
714 | 0 | if (sizes.count() == dataValue.count()) { |
715 | 0 | if (sizes.count() == 1) { |
716 | | // this saves one copying of the buffer |
717 | 0 | uint32_t offset = dataValue.toUint32(0); |
718 | 0 | uint32_t size = sizes.toUint32(0); |
719 | 0 | if (Safe::add(offset, size) <= static_cast<uint32_t>(io.size())) |
720 | 0 | dataValue.setDataArea(base + offset, size); |
721 | 0 | } else { |
722 | | // FIXME: the buffer is probably copied twice, it should be optimized |
723 | 0 | Internal::enforce(size_ <= io.size(), ErrorCode::kerCorruptedMetadata); |
724 | 0 | DataBuf buf(size_); |
725 | 0 | uint32_t idxBuf = 0; |
726 | 0 | for (size_t i = 0; i < sizes.count(); i++) { |
727 | 0 | uint32_t offset = dataValue.toUint32(i); |
728 | 0 | uint32_t size = sizes.toUint32(i); |
729 | | |
730 | | // the size_ parameter is originally computed by summing all values inside sizes |
731 | | // see the constructor of LoaderTiff |
732 | | // But e.g in malicious files some of these values could be negative |
733 | | // That's why we check again for each step here to really make sure we don't overstep |
734 | 0 | Internal::enforce(Safe::add(idxBuf, size) <= size_, ErrorCode::kerCorruptedMetadata); |
735 | 0 | if (size != 0 && Safe::add(offset, size) <= static_cast<uint32_t>(io.size())) { |
736 | 0 | std::copy_n(base + offset, size, buf.begin() + idxBuf); |
737 | 0 | } |
738 | |
|
739 | 0 | idxBuf += size; |
740 | 0 | } |
741 | 0 | dataValue.setDataArea(buf.c_data(), buf.size()); |
742 | 0 | } |
743 | 0 | } |
744 | 0 | } |
745 | | |
746 | | // Fix compression value in the CR2 IFD2 image |
747 | 0 | if (0 == strcmp(group_, "Image2") && image_.mimeType() == "image/x-canon-cr2") { |
748 | 0 | preview["Exif.Image.Compression"] = std::uint16_t{1}; |
749 | 0 | } |
750 | | |
751 | | // write new image |
752 | 0 | MemIo mio; |
753 | 0 | IptcData emptyIptc; |
754 | 0 | XmpData emptyXmp; |
755 | 0 | TiffParser::encode(mio, nullptr, 0, Exiv2::littleEndian, preview, emptyIptc, emptyXmp); |
756 | 0 | return {mio.mmap(), mio.size()}; |
757 | 0 | } |
758 | | |
759 | 0 | LoaderXmpJpeg::LoaderXmpJpeg(PreviewId id, const Image& image, int parIdx) : Loader(id, image) { |
760 | 0 | (void)parIdx; |
761 | |
|
762 | 0 | const XmpData& xmpData = image_.xmpData(); |
763 | |
|
764 | 0 | std::string prefix = "xmpGImg"; |
765 | 0 | if (xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/xapGImg:image")) != xmpData.end()) { |
766 | 0 | prefix = "xapGImg"; |
767 | 0 | } |
768 | |
|
769 | 0 | auto imageDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":image")); |
770 | 0 | if (imageDatum == xmpData.end()) |
771 | 0 | return; |
772 | 0 | auto formatDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":format")); |
773 | 0 | if (formatDatum == xmpData.end()) |
774 | 0 | return; |
775 | 0 | auto widthDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":width")); |
776 | 0 | if (widthDatum == xmpData.end()) |
777 | 0 | return; |
778 | 0 | auto heightDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":height")); |
779 | 0 | if (heightDatum == xmpData.end()) |
780 | 0 | return; |
781 | | |
782 | 0 | if (formatDatum->toString() != "JPEG") |
783 | 0 | return; |
784 | | |
785 | 0 | width_ = widthDatum->toUint32(); |
786 | 0 | height_ = heightDatum->toUint32(); |
787 | 0 | preview_ = decodeBase64(imageDatum->toString()); |
788 | 0 | size_ = preview_.size(); |
789 | 0 | valid_ = true; |
790 | 0 | } |
791 | | |
792 | 0 | Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image& image, int parIdx) { |
793 | 0 | return std::make_unique<LoaderXmpJpeg>(id, image, parIdx); |
794 | 0 | } |
795 | | |
796 | 0 | PreviewProperties LoaderXmpJpeg::getProperties() const { |
797 | 0 | PreviewProperties prop = Loader::getProperties(); |
798 | 0 | prop.mimeType_ = "image/jpeg"; |
799 | 0 | prop.extension_ = ".jpg"; |
800 | 0 | return prop; |
801 | 0 | } |
802 | | |
803 | 0 | DataBuf LoaderXmpJpeg::getData() const { |
804 | 0 | if (!valid()) |
805 | 0 | return {}; |
806 | 0 | return {preview_.c_data(), preview_.size()}; |
807 | 0 | } |
808 | | |
809 | 0 | bool LoaderXmpJpeg::readDimensions() { |
810 | 0 | return valid(); |
811 | 0 | } |
812 | | |
813 | 0 | DataBuf decodeHex(const byte* src, size_t srcSize) { |
814 | | // create decoding table |
815 | 0 | byte invalid = 16; |
816 | 0 | std::array<byte, 256> decodeHexTable; |
817 | 0 | decodeHexTable.fill(invalid); |
818 | 0 | for (byte i = 0; i < 10; i++) |
819 | 0 | decodeHexTable[static_cast<byte>('0') + i] = i; |
820 | 0 | for (byte i = 0; i < 6; i++) |
821 | 0 | decodeHexTable[static_cast<byte>('A') + i] = i + 10; |
822 | 0 | for (byte i = 0; i < 6; i++) |
823 | 0 | decodeHexTable[static_cast<byte>('a') + i] = i + 10; |
824 | | |
825 | | // calculate dest size |
826 | 0 | long validSrcSize = 0; |
827 | 0 | for (size_t srcPos = 0; srcPos < srcSize; srcPos++) { |
828 | 0 | if (decodeHexTable[src[srcPos]] != invalid) |
829 | 0 | validSrcSize++; |
830 | 0 | } |
831 | 0 | const size_t destSize = validSrcSize / 2; |
832 | | |
833 | | // allocate dest buffer |
834 | 0 | DataBuf dest(destSize); |
835 | | |
836 | | // decode |
837 | 0 | for (size_t srcPos = 0, destPos = 0; destPos < destSize; destPos++) { |
838 | 0 | byte buffer = 0; |
839 | 0 | for (int bufferPos = 1; bufferPos >= 0 && srcPos < srcSize; srcPos++) { |
840 | 0 | byte srcValue = decodeHexTable[src[srcPos]]; |
841 | 0 | if (srcValue == invalid) |
842 | 0 | continue; |
843 | 0 | buffer |= srcValue << (bufferPos * 4); |
844 | 0 | bufferPos--; |
845 | 0 | } |
846 | 0 | dest.write_uint8(destPos, buffer); |
847 | 0 | } |
848 | 0 | return dest; |
849 | 0 | } |
850 | | |
851 | | const char encodeBase64Table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
852 | | |
853 | 0 | DataBuf decodeBase64(const std::string& src) { |
854 | 0 | DataBuf dest; |
855 | | // create decoding table |
856 | 0 | unsigned long invalid = 64; |
857 | 0 | auto decodeBase64Table = std::vector<unsigned long>(256, invalid); |
858 | 0 | for (unsigned long i = 0; i < 64; i++) |
859 | 0 | decodeBase64Table[static_cast<unsigned char>(encodeBase64Table[i])] = i; |
860 | | |
861 | | // calculate dest size |
862 | 0 | auto validSrcSize = static_cast<unsigned long>( |
863 | 0 | std::count_if(src.begin(), src.end(), [&](unsigned char c) { return decodeBase64Table.at(c) != invalid; })); |
864 | 0 | if (validSrcSize > ULONG_MAX / 3) |
865 | 0 | return dest; // avoid integer overflow |
866 | 0 | const unsigned long destSize = (validSrcSize * 3) / 4; |
867 | | |
868 | | // allocate dest buffer |
869 | 0 | dest = DataBuf(destSize); |
870 | | |
871 | | // decode |
872 | 0 | for (unsigned long srcPos = 0, destPos = 0; destPos < destSize;) { |
873 | 0 | unsigned long buffer = 0; |
874 | 0 | for (int bufferPos = 3; bufferPos >= 0 && srcPos < src.size(); srcPos++) { |
875 | 0 | unsigned long srcValue = decodeBase64Table[static_cast<unsigned char>(src[srcPos])]; |
876 | 0 | if (srcValue == invalid) |
877 | 0 | continue; |
878 | 0 | buffer |= srcValue << (bufferPos * 6); |
879 | 0 | bufferPos--; |
880 | 0 | } |
881 | 0 | for (int bufferPos = 2; bufferPos >= 0 && destPos < destSize; bufferPos--, destPos++) { |
882 | 0 | dest.write_uint8(destPos, static_cast<byte>((buffer >> (bufferPos * 8)))); |
883 | 0 | } |
884 | 0 | } |
885 | 0 | return dest; |
886 | 0 | } |
887 | | |
888 | 0 | DataBuf decodeAi7Thumbnail(const DataBuf& src) { |
889 | 0 | const byte* colorTable = src.c_data(); |
890 | 0 | const size_t colorTableSize = 256 * 3; |
891 | 0 | if (src.size() < colorTableSize) { |
892 | 0 | #ifndef SUPPRESS_WARNINGS |
893 | 0 | EXV_WARNING << "Invalid size of AI7 thumbnail: " << src.size() << "\n"; |
894 | 0 | #endif |
895 | 0 | return {}; |
896 | 0 | } |
897 | 0 | const byte* imageData = src.c_data(colorTableSize); |
898 | 0 | const size_t imageDataSize = src.size() - colorTableSize; |
899 | 0 | const bool rle = (imageDataSize >= 3 && imageData[0] == 'R' && imageData[1] == 'L' && imageData[2] == 'E'); |
900 | 0 | std::string dest; |
901 | 0 | for (size_t i = rle ? 3 : 0; i < imageDataSize;) { |
902 | 0 | byte num = 1; |
903 | 0 | byte value = imageData[i++]; |
904 | 0 | if (rle && value == 0xFD) { |
905 | 0 | if (i >= imageDataSize) { |
906 | 0 | #ifndef SUPPRESS_WARNINGS |
907 | 0 | EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n"; |
908 | 0 | #endif |
909 | 0 | return {}; |
910 | 0 | } |
911 | 0 | value = imageData[i++]; |
912 | 0 | if (value != 0xFD) { |
913 | 0 | if (i >= imageDataSize) { |
914 | 0 | #ifndef SUPPRESS_WARNINGS |
915 | 0 | EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n"; |
916 | 0 | #endif |
917 | 0 | return {}; |
918 | 0 | } |
919 | 0 | num = value; |
920 | 0 | value = imageData[i++]; |
921 | 0 | } |
922 | 0 | } |
923 | 0 | for (; num != 0; num--) { |
924 | 0 | dest.append(reinterpret_cast<const char*>(colorTable + (3 * value)), 3); |
925 | 0 | } |
926 | 0 | } |
927 | 0 | return {reinterpret_cast<const byte*>(dest.data()), dest.size()}; |
928 | 0 | } |
929 | | |
930 | 0 | DataBuf makePnm(size_t width, size_t height, const DataBuf& rgb) { |
931 | 0 | DataBuf dest; |
932 | 0 | if (size_t expectedSize = width * height * 3UL; rgb.size() != expectedSize) { |
933 | 0 | #ifndef SUPPRESS_WARNINGS |
934 | 0 | EXV_WARNING << "Invalid size of preview data. Expected " << expectedSize << " bytes, got " << rgb.size() |
935 | 0 | << " bytes.\n"; |
936 | 0 | #endif |
937 | 0 | return dest; |
938 | 0 | } |
939 | | |
940 | 0 | const std::string header = "P6\n" + std::to_string(width) + " " + std::to_string(height) + "\n255\n"; |
941 | |
|
942 | 0 | dest = DataBuf(header.size() + rgb.size()); |
943 | 0 | std::copy(header.begin(), header.end(), dest.begin()); |
944 | 0 | std::copy(rgb.begin(), rgb.end(), dest.begin() + header.size()); |
945 | 0 | return dest; |
946 | 0 | } |
947 | | |
948 | | } // namespace |
949 | | |
950 | | // ***************************************************************************** |
951 | | // class member definitions |
952 | | namespace Exiv2 { |
953 | | PreviewImage::PreviewImage(PreviewProperties properties, DataBuf&& data) : |
954 | 0 | properties_(std::move(properties)), preview_(std::move(data)) { |
955 | 0 | } |
956 | | |
957 | 0 | PreviewImage::PreviewImage(const PreviewImage& rhs) : properties_(rhs.properties_), preview_(rhs.pData(), rhs.size()) { |
958 | 0 | } |
959 | | |
960 | 0 | PreviewImage& PreviewImage::operator=(const PreviewImage& rhs) { |
961 | 0 | if (this == &rhs) |
962 | 0 | return *this; |
963 | 0 | properties_ = rhs.properties_; |
964 | 0 | preview_ = DataBuf(rhs.pData(), rhs.size()); |
965 | 0 | return *this; |
966 | 0 | } |
967 | | |
968 | | #ifdef EXV_ENABLE_FILESYSTEM |
969 | 0 | size_t PreviewImage::writeFile(const std::string& path) const { |
970 | 0 | std::string name = path + extension(); |
971 | | // Todo: Creating a DataBuf here unnecessarily copies the memory |
972 | 0 | DataBuf buf(pData(), size()); |
973 | 0 | return Exiv2::writeFile(buf, name); |
974 | 0 | } |
975 | | #endif |
976 | | |
977 | 0 | DataBuf PreviewImage::copy() const { |
978 | 0 | return {pData(), size()}; |
979 | 0 | } |
980 | | |
981 | 0 | const byte* PreviewImage::pData() const { |
982 | 0 | return preview_.c_data(); |
983 | 0 | } |
984 | | |
985 | 0 | uint32_t PreviewImage::size() const { |
986 | 0 | return static_cast<uint32_t>(preview_.size()); |
987 | 0 | } |
988 | | |
989 | 0 | const std::string& PreviewImage::mimeType() const { |
990 | 0 | return properties_.mimeType_; |
991 | 0 | } |
992 | | |
993 | 0 | const std::string& PreviewImage::extension() const { |
994 | 0 | return properties_.extension_; |
995 | 0 | } |
996 | | |
997 | 0 | size_t PreviewImage::width() const { |
998 | 0 | return properties_.width_; |
999 | 0 | } |
1000 | | |
1001 | 0 | size_t PreviewImage::height() const { |
1002 | 0 | return properties_.height_; |
1003 | 0 | } |
1004 | | |
1005 | 0 | PreviewId PreviewImage::id() const { |
1006 | 0 | return properties_.id_; |
1007 | 0 | } |
1008 | | |
1009 | 0 | PreviewManager::PreviewManager(const Image& image) : image_(image) { |
1010 | 0 | } |
1011 | | |
1012 | 0 | PreviewPropertiesList PreviewManager::getPreviewProperties() const { |
1013 | 0 | PreviewPropertiesList list; |
1014 | | // go through the loader table and store all successfully created loaders in the list |
1015 | 0 | for (PreviewId id = 0; id < Loader::getNumLoaders(); ++id) { |
1016 | 0 | auto loader = Loader::create(id, image_); |
1017 | 0 | if (loader && loader->readDimensions()) { |
1018 | 0 | PreviewProperties props = loader->getProperties(); |
1019 | 0 | DataBuf buf = loader->getData(); // #16 getPreviewImage() |
1020 | 0 | props.size_ = buf.size(); // update the size |
1021 | 0 | list.push_back(std::move(props)); |
1022 | 0 | } |
1023 | 0 | } |
1024 | 0 | std::sort(list.begin(), list.end(), |
1025 | 0 | [](const auto& lhs, const auto& rhs) { return lhs.width_ * lhs.height_ < rhs.width_ * rhs.height_; }); |
1026 | |
|
1027 | 0 | return list; |
1028 | 0 | } |
1029 | | |
1030 | 0 | PreviewImage PreviewManager::getPreviewImage(const PreviewProperties& properties) const { |
1031 | 0 | auto loader = Loader::create(properties.id_, image_); |
1032 | 0 | DataBuf buf; |
1033 | 0 | if (loader) { |
1034 | 0 | buf = loader->getData(); |
1035 | 0 | } |
1036 | |
|
1037 | 0 | return {properties, std::move(buf)}; |
1038 | 0 | } |
1039 | | } // namespace Exiv2 |