You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1162 lines
42 KiB
C++

// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2004-2021 Exiv2 authors
* This program is part of the Exiv2 distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
*/
// *****************************************************************************
// included header files
#include "config.h"
#include <array>
#include <climits>
#include <string>
#include "preview.hpp"
#include "futils.hpp"
#include "enforce.hpp"
#include "safe_op.hpp"
#include "image.hpp"
#include "cr2image.hpp"
#include "jpgimage.hpp"
#include "tiffimage.hpp"
#include "tiffimage_int.hpp"
#include "unused.h"
// *****************************************************************************
namespace {
using namespace Exiv2;
using Exiv2::byte;
/*!
17 years ago
@brief Compare two preview images by number of pixels, if width and height
of both lhs and rhs are available or else by size.
Return true if lhs is smaller than rhs.
*/
bool cmpPreviewProperties(
const PreviewProperties& lhs,
const PreviewProperties& rhs
)
{
17 years ago
uint32_t l = lhs.width_ * lhs.height_;
uint32_t r = rhs.width_ * rhs.height_;
return l < r;
}
/*!
@brief Decode a Hex string.
*/
DataBuf decodeHex(const byte *src, long srcSize);
/*!
@brief Decode a Base64 string.
*/
DataBuf decodeBase64(const std::string &src);
/*!
@brief Decode an Illustrator thumbnail that follows after %AI7_Thumbnail.
*/
DataBuf decodeAi7Thumbnail(const DataBuf &src);
/*!
@brief Create a PNM image from raw RGB data.
*/
DataBuf makePnm(uint32_t width, uint32_t height, const DataBuf &rgb);
/*!
Base class for image loaders. Provides virtual methods for reading properties
and DataBuf.
*/
class Loader {
public:
//! Virtual destructor.
virtual ~Loader() = default;
//! Loader auto pointer
using UniquePtr = std::unique_ptr<Loader>;
//! Create a Loader subclass for requested id
static UniquePtr create(PreviewId id, const Image &image);
//! Check if a preview image with given params exists in the image
17 years ago
virtual bool valid() const { return valid_; }
//! Get properties of a preview image with given params
virtual PreviewProperties getProperties() const;
//! Get a buffer that contains the preview image
virtual DataBuf getData() const = 0;
//! Read preview image dimensions when they are not available directly
17 years ago
virtual bool readDimensions() { return true; }
//! A number of image loaders configured in the loaderList_ table
static PreviewId getNumLoaders();
protected:
//! Constructor. Sets all image properies to unknown.
Loader(PreviewId id, const Image &image);
//! Functions that creates a loader from given parameters
using CreateFunc = UniquePtr (*)(PreviewId, const Image &, int);
//! Structure to list possible loaders
struct LoaderList {
const char *imageMimeType_; //!< Image type for which the loader is valid, 0 matches all images
CreateFunc create_; //!< Function that creates particular loader instance
int parIdx_; //!< Parameter that is passed into CreateFunc
};
//! Table that lists possible loaders. PreviewId is an index to this table.
static const LoaderList loaderList_[];
//! Identifies preview image type
PreviewId id_;
//! Source image reference
const Image &image_;
//! Preview image width
17 years ago
uint32_t width_;
//! Preview image length
17 years ago
uint32_t height_;
//! Preview image size in bytes
17 years ago
uint32_t size_;
//! True if the source image contains a preview image of given type
bool valid_;
};
//! Loader for native previews
class LoaderNative : public Loader {
public:
//! Constructor
LoaderNative(PreviewId id, const Image &image, int parIdx);
//! Get properties of a preview image with given params
PreviewProperties getProperties() const override;
//! Get a buffer that contains the preview image
DataBuf getData() const override;
//! Read preview image dimensions
bool readDimensions() override;
protected:
//! Native preview information
NativePreview nativePreview_;
};
//! Function to create new LoaderNative
Loader::UniquePtr createLoaderNative(PreviewId id, const Image &image, int parIdx);
//! Loader for Jpeg previews that are not read into ExifData directly
class LoaderExifJpeg : public Loader {
public:
//! Constructor
LoaderExifJpeg(PreviewId id, const Image &image, int parIdx);
//! Get properties of a preview image with given params
PreviewProperties getProperties() const override;
//! Get a buffer that contains the preview image
DataBuf getData() const override;
//! Read preview image dimensions
bool readDimensions() override;
protected:
//! Structure that lists offset/size tag pairs
struct Param {
const char* offsetKey_; //!< Offset tag
const char* sizeKey_; //!< Size tag
const char* baseOffsetKey_; //!< Tag that holds base offset or 0
};
//! Table that holds all possible offset/size pairs. parIdx is an index to this table
static const Param param_[];
//! Offset value
17 years ago
uint32_t offset_;
};
//! Function to create new LoaderExifJpeg
Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image &image, int parIdx);
//! Loader for Jpeg previews that are read into ExifData
class LoaderExifDataJpeg : public Loader {
public:
//! Constructor
LoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx);
//! Get properties of a preview image with given params
PreviewProperties getProperties() const override;
//! Get a buffer that contains the preview image
DataBuf getData() const override;
//! Read preview image dimensions
bool readDimensions() override;
protected:
//! Structure that lists data/size tag pairs
struct Param {
const char* dataKey_; //!< Data tag
const char* sizeKey_; //!< Size tag
};
//! Table that holds all possible data/size pairs. parIdx is an index to this table
static const Param param_[];
//! Key that points to the Value that contains the JPEG preview in data area
ExifKey dataKey_;
};
//! Function to create new LoaderExifDataJpeg
Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx);
//! Loader for Tiff previews - it can get image data from ExifData or image_.io() as needed
class LoaderTiff : public Loader {
public:
//! Constructor
LoaderTiff(PreviewId id, const Image &image, int parIdx);
//! Get properties of a preview image with given params
PreviewProperties getProperties() const override;
//! Get a buffer that contains the preview image
DataBuf getData() const override;
protected:
//! Name of the group that contains the preview image
const char *group_;
//! Tag that contains image data. Possible values are "StripOffsets" or "TileOffsets"
std::string offsetTag_;
//! Tag that contains data sizes. Possible values are "StripByteCounts" or "TileByteCounts"
std::string sizeTag_;
//! Structure that lists preview groups
struct Param {
const char* group_; //!< Group name
const char* checkTag_; //!< Tag to check or NULL
const char* checkValue_; //!< The preview image is valid only if the checkTag_ has this value
};
//! Table that holds all possible groups. parIdx is an index to this table.
static const Param param_[];
};
//! Function to create new LoaderTiff
Loader::UniquePtr createLoaderTiff(PreviewId id, const Image &image, int parIdx);
//! Loader for JPEG previews stored in the XMP metadata
class LoaderXmpJpeg : public Loader {
public:
//! Constructor
LoaderXmpJpeg(PreviewId id, const Image &image, int parIdx);
//! Get properties of a preview image with given params
PreviewProperties getProperties() const override;
//! Get a buffer that contains the preview image
DataBuf getData() const override;
//! Read preview image dimensions
bool readDimensions() override;
protected:
//! Preview image data
DataBuf preview_;
};
//! Function to create new LoaderXmpJpeg
Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image &image, int parIdx);
// *****************************************************************************
// class member definitions
const Loader::LoaderList Loader::loaderList_[] = {
{ nullptr, createLoaderNative, 0 },
{ nullptr, createLoaderNative, 1 },
{ nullptr, createLoaderNative, 2 },
{ nullptr, createLoaderNative, 3 },
{ nullptr, createLoaderExifDataJpeg, 0 },
{ nullptr, createLoaderExifDataJpeg, 1 },
{ nullptr, createLoaderExifDataJpeg, 2 },
{ nullptr, createLoaderExifDataJpeg, 3 },
{ nullptr, createLoaderExifDataJpeg, 4 },
{ nullptr, createLoaderExifDataJpeg, 5 },
{ nullptr, createLoaderExifDataJpeg, 6 },
{ nullptr, createLoaderExifDataJpeg, 7 },
{ nullptr, createLoaderExifDataJpeg, 8 },
{ "image/x-panasonic-rw2", createLoaderExifDataJpeg, 9 },
{ nullptr, createLoaderExifDataJpeg,10 },
{ nullptr, createLoaderExifDataJpeg,11 },
{ nullptr, createLoaderTiff, 0 },
{ nullptr, createLoaderTiff, 1 },
{ nullptr, createLoaderTiff, 2 },
{ nullptr, createLoaderTiff, 3 },
{ nullptr, createLoaderTiff, 4 },
{ nullptr, createLoaderTiff, 5 },
{ nullptr, createLoaderTiff, 6 },
{ "image/x-canon-cr2", createLoaderTiff, 7 },
{ nullptr, createLoaderExifJpeg, 0 },
{ nullptr, createLoaderExifJpeg, 1 },
{ nullptr, createLoaderExifJpeg, 2 },
{ nullptr, createLoaderExifJpeg, 3 },
{ nullptr, createLoaderExifJpeg, 4 },
{ nullptr, createLoaderExifJpeg, 5 },
{ nullptr, createLoaderExifJpeg, 6 },
{ "image/x-canon-cr2", createLoaderExifJpeg, 7 },
{ nullptr, createLoaderExifJpeg, 8 },
{ nullptr, createLoaderXmpJpeg, 0 }
17 years ago
};
const LoaderExifJpeg::Param LoaderExifJpeg::param_[] = {
{ "Exif.Image.JPEGInterchangeFormat", "Exif.Image.JPEGInterchangeFormatLength", nullptr }, // 0
{ "Exif.SubImage1.JPEGInterchangeFormat", "Exif.SubImage1.JPEGInterchangeFormatLength", nullptr }, // 1
{ "Exif.SubImage2.JPEGInterchangeFormat", "Exif.SubImage2.JPEGInterchangeFormatLength", nullptr }, // 2
{ "Exif.SubImage3.JPEGInterchangeFormat", "Exif.SubImage3.JPEGInterchangeFormatLength", nullptr }, // 3
{ "Exif.SubImage4.JPEGInterchangeFormat", "Exif.SubImage4.JPEGInterchangeFormatLength", nullptr }, // 4
{ "Exif.SubThumb1.JPEGInterchangeFormat", "Exif.SubThumb1.JPEGInterchangeFormatLength", nullptr }, // 5
{ "Exif.Image2.JPEGInterchangeFormat", "Exif.Image2.JPEGInterchangeFormatLength", nullptr }, // 6
{ "Exif.Image.StripOffsets", "Exif.Image.StripByteCounts", nullptr }, // 7
{ "Exif.OlympusCs.PreviewImageStart", "Exif.OlympusCs.PreviewImageLength", "Exif.MakerNote.Offset"} // 8
17 years ago
};
const LoaderExifDataJpeg::Param LoaderExifDataJpeg::param_[] = {
{ "Exif.Thumbnail.JPEGInterchangeFormat", "Exif.Thumbnail.JPEGInterchangeFormatLength" }, // 0
{ "Exif.NikonPreview.JPEGInterchangeFormat", "Exif.NikonPreview.JPEGInterchangeFormatLength" }, // 1
{ "Exif.Pentax.PreviewOffset", "Exif.Pentax.PreviewLength" }, // 2
{ "Exif.PentaxDng.PreviewOffset", "Exif.PentaxDng.PreviewLength" }, // 3
{ "Exif.Minolta.ThumbnailOffset", "Exif.Minolta.ThumbnailLength" }, // 4
{ "Exif.SonyMinolta.ThumbnailOffset", "Exif.SonyMinolta.ThumbnailLength" }, // 5
{ "Exif.Olympus.ThumbnailImage", nullptr }, // 6
{ "Exif.Olympus2.ThumbnailImage", nullptr }, // 7
{ "Exif.Minolta.Thumbnail", nullptr }, // 8
{ "Exif.PanasonicRaw.PreviewImage", nullptr }, // 9
{ "Exif.SamsungPreview.JPEGInterchangeFormat", "Exif.SamsungPreview.JPEGInterchangeFormatLength" }, // 10
{ "Exif.Casio2.PreviewImage", nullptr } // 11
17 years ago
};
const LoaderTiff::Param LoaderTiff::param_[] = {
{ "Image", "Exif.Image.NewSubfileType", "1" }, // 0
{ "SubImage1", "Exif.SubImage1.NewSubfileType", "1" }, // 1
{ "SubImage2", "Exif.SubImage2.NewSubfileType", "1" }, // 2
{ "SubImage3", "Exif.SubImage3.NewSubfileType", "1" }, // 3
{ "SubImage4", "Exif.SubImage4.NewSubfileType", "1" }, // 4
{ "SubThumb1", "Exif.SubThumb1.NewSubfileType", "1" }, // 5
{ "Thumbnail", nullptr, nullptr }, // 6
{ "Image2", nullptr, nullptr } // 7
17 years ago
};
Loader::UniquePtr Loader::create(PreviewId id, const Image &image)
{
17 years ago
if (id < 0 || id >= Loader::getNumLoaders())
return UniquePtr();
17 years ago
if (loaderList_[id].imageMimeType_ &&
std::string(loaderList_[id].imageMimeType_) != image.mimeType())
return UniquePtr();
UniquePtr loader = loaderList_[id].create_(id, image, loaderList_[id].parIdx_);
17 years ago
if (loader.get() && !loader->valid()) loader.reset();
return loader;
}
Loader::Loader(PreviewId id, const Image &image)
17 years ago
: id_(id), image_(image),
width_(0), height_(0),
size_(0),
valid_(false)
{
}
PreviewProperties Loader::getProperties() const
{
PreviewProperties prop;
prop.id_ = id_;
17 years ago
prop.size_ = size_;
prop.width_ = width_;
prop.height_ = height_;
return prop;
}
PreviewId Loader::getNumLoaders()
{
return static_cast<PreviewId> EXV_COUNTOF(loaderList_);
}
LoaderNative::LoaderNative(PreviewId id, const Image &image, int parIdx)
: Loader(id, image)
{
if (!(0 <= parIdx && static_cast<size_t>(parIdx) < image.nativePreviews().size())) return;
nativePreview_ = image.nativePreviews()[parIdx];
width_ = nativePreview_.width_;
height_ = nativePreview_.height_;
valid_ = true;
if (nativePreview_.filter_.empty()) {
size_ = nativePreview_.size_;
14 years ago
} else {
size_ = getData().size_;
}
}
Loader::UniquePtr createLoaderNative(PreviewId id, const Image &image, int parIdx)
{
return Loader::UniquePtr(new LoaderNative(id, image, parIdx));
}
PreviewProperties LoaderNative::getProperties() const
{
PreviewProperties prop = Loader::getProperties();
prop.mimeType_ = nativePreview_.mimeType_;
if (nativePreview_.mimeType_ == "image/jpeg") {
prop.extension_ = ".jpg";
} else if (nativePreview_.mimeType_ == "image/tiff") {
prop.extension_ = ".tif";
} else if (nativePreview_.mimeType_ == "image/x-wmf") {
prop.extension_ = ".wmf";
} else if (nativePreview_.mimeType_ == "image/x-portable-anymap") {
prop.extension_ = ".pnm";
} else {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Unknown native preview format: " << nativePreview_.mimeType_ << "\n";
#endif
prop.extension_ = ".dat";
}
#ifdef EXV_UNICODE_PATH
prop.wextension_ = s2ws(prop.extension_);
#endif
return prop;
}
DataBuf LoaderNative::getData() const
{
if (!valid()) return DataBuf();
BasicIo &io = image_.io();
if (io.open() != 0) {
throw Error(kerDataSourceOpenFailed, io.path(), strError());
}
IoCloser closer(io);
const byte* data = io.mmap();
if (static_cast<long>(io.size()) < nativePreview_.position_ + static_cast<long>(nativePreview_.size_)) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Invalid native preview position or size.\n";
#endif
return DataBuf();
}
if (nativePreview_.filter_.empty()) {
return DataBuf(data + nativePreview_.position_, static_cast<long>(nativePreview_.size_));
}
if (nativePreview_.filter_ == "hex-ai7thumbnail-pnm") {
const DataBuf ai7thumbnail = decodeHex(data + nativePreview_.position_, static_cast<long>(nativePreview_.size_));
const DataBuf rgb = decodeAi7Thumbnail(ai7thumbnail);
return makePnm(width_, height_, rgb);
}
if (nativePreview_.filter_ == "hex-irb") {
const DataBuf psData = decodeHex(data + nativePreview_.position_, static_cast<long>(nativePreview_.size_));
const byte *record;
uint32_t sizeHdr = 0;
uint32_t sizeData = 0;
if (Photoshop::locatePreviewIrb(psData.pData_, psData.size_, &record, &sizeHdr, &sizeData) != 0) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Missing preview IRB in Photoshop EPS preview.\n";
#endif
return DataBuf();
}
return DataBuf(record + sizeHdr + 28, sizeData - 28);
}
throw Error(kerErrorMessage, "Invalid native preview filter: " + nativePreview_.filter_);
}
bool LoaderNative::readDimensions()
{
if (!valid()) return false;
if (width_ != 0 || height_ != 0) return true;
const DataBuf data = getData();
if (data.size_ == 0) return false;
try {
Image::UniquePtr image = ImageFactory::open(data.pData_, data.size_);
if (image.get() == nullptr) return false;
image->readMetadata();
width_ = image->pixelWidth();
height_ = image->pixelHeight();
} catch (const AnyError& /* error */) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Invalid native preview image.\n";
#endif
return false;
}
return true;
}
LoaderExifJpeg::LoaderExifJpeg(PreviewId id, const Image &image, int parIdx) : Loader(id, image), offset_(0)
{
const ExifData &exifData = image_.exifData();
auto pos = exifData.findKey(ExifKey(param_[parIdx].offsetKey_));
if (pos != exifData.end() && pos->count() > 0) {
offset_ = pos->toLong();
}
size_ = 0;
pos = exifData.findKey(ExifKey(param_[parIdx].sizeKey_));
if (pos != exifData.end() && pos->count() > 0) {
size_ = pos->toLong();
}
17 years ago
if (offset_ == 0 || size_ == 0) return;
if (param_[parIdx].baseOffsetKey_) {
pos = exifData.findKey(ExifKey(param_[parIdx].baseOffsetKey_));
if (pos != exifData.end() && pos->count() > 0) {
offset_ += pos->toLong();
}
}
if (Safe::add(offset_, size_) > static_cast<uint32_t>(image_.io().size()))
return;
valid_ = true;
}
Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image &image, int parIdx)
{
return Loader::UniquePtr(new LoaderExifJpeg(id, image, parIdx));
}
PreviewProperties LoaderExifJpeg::getProperties() const
{
PreviewProperties prop = Loader::getProperties();
prop.mimeType_ = "image/jpeg";
prop.extension_ = ".jpg";
#ifdef EXV_UNICODE_PATH
prop.wextension_ = EXV_WIDEN(".jpg");
#endif
return prop;
}
DataBuf LoaderExifJpeg::getData() const
{
if (!valid()) return DataBuf();
BasicIo &io = image_.io();
if (io.open() != 0) {
throw Error(kerDataSourceOpenFailed, io.path(), strError());
}
IoCloser closer(io);
const Exiv2::byte* base = io.mmap();
17 years ago
return DataBuf(base + offset_, size_);
}
bool LoaderExifJpeg::readDimensions()
{
if (!valid()) return false;
if (width_ || height_) return true;
BasicIo &io = image_.io();
if (io.open() != 0) {
throw Error(kerDataSourceOpenFailed, io.path(), strError());
}
IoCloser closer(io);
const Exiv2::byte* base = io.mmap();
try {
Image::UniquePtr image = ImageFactory::open(base + offset_, size_);
if (image.get() == nullptr) return false;
image->readMetadata();
width_ = image->pixelWidth();
height_ = image->pixelHeight();
}
catch (const AnyError& /* error */ ) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Invalid JPEG preview image.\n";
#endif
return false;
}
return true;
}
LoaderExifDataJpeg::LoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx)
: Loader(id, image),
dataKey_(param_[parIdx].dataKey_)
{
auto pos = image_.exifData().findKey(dataKey_);
if (pos != image_.exifData().end()) {
size_ = pos->sizeDataArea(); // indirect data
if (size_ == 0 && pos->typeId() == undefined)
size_ = pos->size(); // direct data
}
17 years ago
if (size_ == 0) return;
valid_ = true;
}
Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx)
{
return Loader::UniquePtr(new LoaderExifDataJpeg(id, image, parIdx));
}
PreviewProperties LoaderExifDataJpeg::getProperties() const
{
PreviewProperties prop = Loader::getProperties();
prop.mimeType_ = "image/jpeg";
prop.extension_ = ".jpg";
#ifdef EXV_UNICODE_PATH
prop.wextension_ = EXV_WIDEN(".jpg");
#endif
return prop;
}
DataBuf LoaderExifDataJpeg::getData() const
{
if (!valid()) return DataBuf();
auto pos = image_.exifData().findKey(dataKey_);
if (pos != image_.exifData().end()) {
DataBuf buf = pos->dataArea(); // indirect data
if (buf.size_ == 0) { // direct data
buf = DataBuf(pos->size());
pos->copy(buf.pData_, invalidByteOrder);
}
buf.pData_[0] = 0xff; // fix Minolta thumbnails with invalid jpeg header
return buf;
}
return DataBuf();
}
bool LoaderExifDataJpeg::readDimensions()
{
if (!valid()) return false;
DataBuf buf = getData();
if (buf.size_ == 0) return false;
try {
Image::UniquePtr image = ImageFactory::open(buf.pData_, buf.size_);
if (image.get() == nullptr) return false;
image->readMetadata();
width_ = image->pixelWidth();
height_ = image->pixelHeight();
}
catch (const AnyError& /* error */ ) {
return false;
}
return true;
}
LoaderTiff::LoaderTiff(PreviewId id, const Image &image, int parIdx)
17 years ago
: Loader(id, image),
group_(param_[parIdx].group_)
{
const ExifData &exifData = image_.exifData();
int offsetCount = 0;
ExifData::const_iterator pos;
// check if the group_ contains a preview image
if (param_[parIdx].checkTag_) {
pos = exifData.findKey(ExifKey(param_[parIdx].checkTag_));
if (pos == exifData.end()) return;
if (param_[parIdx].checkValue_ && pos->toString() != param_[parIdx].checkValue_) return;
}
pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".StripOffsets"));
if (pos != exifData.end()) {
offsetTag_ = "StripOffsets";
sizeTag_ = "StripByteCounts";
offsetCount = pos->value().count();
}
else {
pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".TileOffsets"));
if (pos == exifData.end()) return;
offsetTag_ = "TileOffsets";
sizeTag_ = "TileByteCounts";
offsetCount = pos->value().count();
}
pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + '.' + sizeTag_));
if (pos == exifData.end()) return;
if (offsetCount != pos->value().count()) return;
for (int i = 0; i < offsetCount; i++) {
size_ += pos->toLong(i);
}
17 years ago
if (size_ == 0) return;
pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageWidth"));
if (pos != exifData.end() && pos->count() > 0) {
width_ = pos->toLong();
}
pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageLength"));
if (pos != exifData.end() && pos->count() > 0) {
height_ = pos->toLong();
}
if (width_ == 0 || height_ == 0) return;
valid_ = true;
}
Loader::UniquePtr createLoaderTiff(PreviewId id, const Image &image, int parIdx)
{
return Loader::UniquePtr(new LoaderTiff(id, image, parIdx));
}
PreviewProperties LoaderTiff::getProperties() const
{
PreviewProperties prop = Loader::getProperties();
prop.mimeType_ = "image/tiff";
prop.extension_ = ".tif";
#ifdef EXV_UNICODE_PATH
prop.wextension_ = EXV_WIDEN(".tif");
#endif
return prop;
}
DataBuf LoaderTiff::getData() const
{
const ExifData &exifData = image_.exifData();
ExifData preview;
// copy tags
for (auto &&pos : exifData) {
if (pos.groupName() == group_) {
/*
Write only the necessary TIFF image tags
tags that especially could cause problems are:
"NewSubfileType" - the result is no longer a thumbnail, it is a standalone image
"Orientation" - this tag typically appears only in the "Image" group. Deleting it ensures
consistent result for all previews, including JPEG
*/
uint16_t tag = pos.tag();
if (tag != 0x00fe && tag != 0x00ff && Internal::isTiffImageTag(tag, Internal::ifd0Id)) {
preview.add(ExifKey(tag, "Image"), &pos.value());
}
}
}
Value &dataValue = const_cast<Value&>(preview["Exif.Image." + offsetTag_].value());
if (dataValue.sizeDataArea() == 0) {
// image data are not available via exifData, read them from image_.io()
BasicIo &io = image_.io();
if (io.open() != 0) {
throw Error(kerDataSourceOpenFailed, io.path(), strError());
}
IoCloser closer(io);
const Exiv2::byte* base = io.mmap();
const Value &sizes = preview["Exif.Image." + sizeTag_].value();
if (sizes.count() == dataValue.count()) {
if (sizes.count() == 1) {
// this saves one copying of the buffer
uint32_t offset = dataValue.toLong(0);
uint32_t size = sizes.toLong(0);
if (Safe::add(offset, size) <= static_cast<uint32_t>(io.size()))
dataValue.setDataArea(base + offset, size);
}
else {
// FIXME: the buffer is probably copied twice, it should be optimized
enforce(size_ <= static_cast<uint32_t>(io.size()), kerCorruptedMetadata);
DataBuf buf(size_);
uint32_t idxBuf = 0;
for (long i = 0; i < sizes.count(); i++) {
uint32_t offset = dataValue.toLong(i);
uint32_t size = sizes.toLong(i);
enforce(Safe::add(idxBuf, size) < size_, kerCorruptedMetadata);
if (size!=0 && Safe::add(offset, size) <= static_cast<uint32_t>(io.size()))
memcpy(&buf.pData_[idxBuf], base + offset, size);
idxBuf += size;
}
dataValue.setDataArea(buf.pData_, buf.size_);
}
}
}
// Fix compression value in the CR2 IFD2 image
if (0 == strcmp(group_, "Image2") && image_.mimeType() == "image/x-canon-cr2") {
preview["Exif.Image.Compression"] = uint16_t(1);
}
// write new image
MemIo mio;
IptcData emptyIptc;
XmpData emptyXmp;
TiffParser::encode(mio, nullptr, 0, Exiv2::littleEndian, preview, emptyIptc, emptyXmp);
return DataBuf(mio.mmap(), static_cast<long>(mio.size()));
}
LoaderXmpJpeg::LoaderXmpJpeg(PreviewId id, const Image &image, int parIdx)
: Loader(id, image)
{
(void)parIdx;
const XmpData &xmpData = image_.xmpData();
std::string prefix = "xmpGImg";
if (xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/xapGImg:image")) != xmpData.end()) {
prefix = "xapGImg";
}
auto imageDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":image"));
if (imageDatum == xmpData.end()) return;
auto formatDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":format"));
if (formatDatum == xmpData.end()) return;
auto widthDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":width"));
if (widthDatum == xmpData.end()) return;
auto heightDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":height"));
if (heightDatum == xmpData.end()) return;
if (formatDatum->toString() != "JPEG") return;
width_ = widthDatum->toLong();
height_ = heightDatum->toLong();
preview_ = decodeBase64(imageDatum->toString());
size_ = static_cast<uint32_t>(preview_.size_);
valid_ = true;
}
Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image &image, int parIdx)
{
return Loader::UniquePtr(new LoaderXmpJpeg(id, image, parIdx));
}
PreviewProperties LoaderXmpJpeg::getProperties() const
{
PreviewProperties prop = Loader::getProperties();
prop.mimeType_ = "image/jpeg";
prop.extension_ = ".jpg";
#ifdef EXV_UNICODE_PATH
prop.wextension_ = EXV_WIDEN(".jpg");
#endif
return prop;
}
DataBuf LoaderXmpJpeg::getData() const
{
if (!valid()) return DataBuf();
return DataBuf(preview_.pData_, preview_.size_);
}
bool LoaderXmpJpeg::readDimensions()
{
return valid();
}
DataBuf decodeHex(const byte *src, long srcSize)
{
// create decoding table
byte invalid = 16;
std::array<byte, 256> decodeHexTable;
decodeHexTable.fill(invalid);
for (byte i = 0; i < 10; i++) decodeHexTable[static_cast<byte>('0') + i] = i;
for (byte i = 0; i < 6; i++) decodeHexTable[static_cast<byte>('A') + i] = i + 10;
for (byte i = 0; i < 6; i++) decodeHexTable[static_cast<byte>('a') + i] = i + 10;
// calculate dest size
long validSrcSize = 0;
for (long srcPos = 0; srcPos < srcSize; srcPos++) {
if (decodeHexTable[src[srcPos]] != invalid) validSrcSize++;
}
const long destSize = validSrcSize / 2;
// allocate dest buffer
DataBuf dest(destSize);
// decode
for (long srcPos = 0, destPos = 0; destPos < destSize; destPos++) {
byte buffer = 0;
for (int bufferPos = 1; bufferPos >= 0 && srcPos < srcSize; srcPos++) {
byte srcValue = decodeHexTable[src[srcPos]];
if (srcValue == invalid) continue;
buffer |= srcValue << (bufferPos * 4);
bufferPos--;
}
dest.pData_[destPos] = buffer;
}
return dest;
}
const char encodeBase64Table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
DataBuf decodeBase64(const std::string& src)
{
const size_t srcSize = src.size();
// create decoding table
unsigned long invalid = 64;
std::array<unsigned long, 256> decodeBase64Table;
decodeBase64Table.fill(invalid);
for (unsigned long i = 0; i < 64; i++)
decodeBase64Table[static_cast<unsigned char>(encodeBase64Table[i])] = i;
// calculate dest size
unsigned long validSrcSize = 0;
for (unsigned long srcPos = 0; srcPos < srcSize; srcPos++) {
if (decodeBase64Table[static_cast<unsigned char>(src[srcPos])] != invalid)
validSrcSize++;
}
if (validSrcSize > ULONG_MAX / 3) return DataBuf(); // avoid integer overflow
const unsigned long destSize = (validSrcSize * 3) / 4;
// allocate dest buffer
if (destSize > LONG_MAX) return DataBuf(); // avoid integer overflow
DataBuf dest(static_cast<long>(destSize));
// decode
for (unsigned long srcPos = 0, destPos = 0; destPos < destSize;) {
unsigned long buffer = 0;
for (int bufferPos = 3; bufferPos >= 0 && srcPos < srcSize; srcPos++) {
unsigned long srcValue = decodeBase64Table[static_cast<unsigned char>(src[srcPos])];
if (srcValue == invalid) continue;
buffer |= srcValue << (bufferPos * 6);
bufferPos--;
}
for (int bufferPos = 2; bufferPos >= 0 && destPos < destSize; bufferPos--, destPos++) {
dest.pData_[destPos] = static_cast<byte>((buffer >> (bufferPos * 8)) & 0xFF);
}
}
return dest;
}
DataBuf decodeAi7Thumbnail(const DataBuf &src)
{
const byte *colorTable = src.pData_;
const long colorTableSize = 256 * 3;
if (src.size_ < colorTableSize) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Invalid size of AI7 thumbnail: " << src.size_ << "\n";
#endif
return DataBuf();
}
const byte *imageData = src.pData_ + colorTableSize;
const long imageDataSize = src.size_ - colorTableSize;
const bool rle = (imageDataSize >= 3 && imageData[0] == 'R' && imageData[1] == 'L' && imageData[2] == 'E');
std::string dest;
for (long i = rle ? 3 : 0; i < imageDataSize;) {
byte num = 1;
byte value = imageData[i++];
if (rle && value == 0xFD) {
if (i >= imageDataSize) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n";
#endif
return DataBuf();
}
value = imageData[i++];
if (value != 0xFD) {
if (i >= imageDataSize) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n";
#endif
return DataBuf();
}
num = value;
value = imageData[i++];
}
}
for (; num != 0; num--) {
dest.append(reinterpret_cast<const char*>(colorTable + (3*value)), 3);
}
}
return DataBuf(reinterpret_cast<const byte*>(dest.data()), static_cast<long>(dest.size()));
}
DataBuf makePnm(uint32_t width, uint32_t height, const DataBuf &rgb)
{
Fix issues detected with PVS-Studio + other little improvements (#1689) * avoid re-declaration of constant variables * Replace pthreads critical section with std::mutex * ci - better naming * cmake - increase minimum version to 3.11. Use project DESCRIPTION * fix - do not treat string::find() return type as bool * remove conditions that were always true * remove condition that were always false * Remove EXV_HAVE_GMTIME_R which is not used anymore * pixelWidth_ was inherited from Exiv2::Image The width & height variables in the TiffImage class need to be mutable to be able to change their values on the getters pixelHeight() and pixelWidth() ... Do not ask me why ... * Remove superfluous if * pvs:V766 item with identical key added already * pvs:V730 not all members were initialized (time) * pvs:V730 not all members are initialized * pvs:v668 no point in testing pointer against null after new * pvs:V1048 variable assigned the same value * replace c-style dynamic vector with std one * pvs:547 fakeData is always true * Remove useless constructor in derived class * pvs:V690 modern way to disable copy-ctor * Replace malloc/free with new/delete. No need to check for null * pvs:V1028 cast operands and not result * Remove custom MIN/MAX functions * pvs:V595 pointer used before verified against null * pvs: index used before being checked * pvs:V1028 possible overflow. Cast operands * pvs:v575 potential null pointer passed to other functions * pvs:V547 deal with always true/false expressions * pvs:V560 part of conditional expressions always false or true * pvs:V701 possible break in realloc -> move to std::vector * Make some classes 'final' * Replace sprintf with std::to_string() * fix compilation on windows
4 years ago
const long expectedSize = static_cast<long>(width) * static_cast<long>(height) * 3L;
if (rgb.size_ != expectedSize) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Invalid size of preview data. Expected " << expectedSize << " bytes, got " << rgb.size_ << " bytes.\n";
#endif
return DataBuf();
}
const std::string header = "P6\n" + toString(width) + " " + toString(height) + "\n255\n";
const auto headerBytes = reinterpret_cast<const byte *>(header.data());
DataBuf dest(static_cast<long>(header.size() + rgb.size_));
std::copy(headerBytes, headerBytes + header.size(), dest.pData_);
std::copy(rgb.pData_, rgb.pData_ + rgb.size_, dest.pData_ + header.size());
return dest;
}
} // namespace
// *****************************************************************************
// class member definitions
namespace Exiv2 {
PreviewImage::PreviewImage(PreviewProperties properties, DataBuf data)
: properties_(std::move(properties)), pData_(data.pData_), size_(data.size_)
{
std::pair<byte*, long> ret = data.release();
UNUSED(ret);
}
PreviewImage::~PreviewImage()
{
delete[] pData_;
}
PreviewImage::PreviewImage(const PreviewImage &rhs)
: properties_(rhs.properties_), pData_(new byte[rhs.size_]), size_(rhs.size_)
{
memcpy(pData_, rhs.pData_, rhs.size_);
}
PreviewImage& PreviewImage::operator=(const PreviewImage& rhs)
{
if (this == &rhs) return *this;
if (rhs.size_ > size_) {
delete[] pData_;
pData_ = new byte[rhs.size_];
}
properties_ = rhs.properties_;
memcpy(pData_, rhs.pData_, rhs.size_);
size_ = rhs.size_;
return *this;
}
long PreviewImage::writeFile(const std::string& path) const
{
std::string name = path + extension();
// Todo: Creating a DataBuf here unnecessarily copies the memory
DataBuf buf(pData_, size_);
return Exiv2::writeFile(buf, name);
}
#ifdef EXV_UNICODE_PATH
long PreviewImage::writeFile(const std::wstring& wpath) const
{
std::wstring name = wpath + wextension();
// Todo: Creating a DataBuf here unnecessarily copies the memory
DataBuf buf(pData_, size_);
return Exiv2::writeFile(buf, name);
}
#endif
DataBuf PreviewImage::copy() const
{
return DataBuf(pData_, size_);
}
const byte* PreviewImage::pData() const
{
return pData_;
}
uint32_t PreviewImage::size() const
{
return size_;
}
std::string PreviewImage::mimeType() const
{
return properties_.mimeType_;
}
std::string PreviewImage::extension() const
{
return properties_.extension_;
}
#ifdef EXV_UNICODE_PATH
std::wstring PreviewImage::wextension() const
{
return properties_.wextension_;
}
#endif
uint32_t PreviewImage::width() const
{
return properties_.width_;
}
uint32_t PreviewImage::height() const
{
return properties_.height_;
}
PreviewId PreviewImage::id() const
{
return properties_.id_;
}
PreviewManager::PreviewManager(const Image& image)
17 years ago
: image_(image)
{
}
PreviewPropertiesList PreviewManager::getPreviewProperties() const
{
PreviewPropertiesList list;
// go through the loader table and store all successfully created loaders in the list
for (PreviewId id = 0; id < Loader::getNumLoaders(); ++id) {
Loader::UniquePtr loader = Loader::create(id, image_);
if (loader.get() && loader->readDimensions()) {
PreviewProperties props = loader->getProperties();
DataBuf buf = loader->getData(); // #16 getPreviewImage()
props.size_ = buf.size_; // update the size
list.push_back(props) ;
}
}
std::sort(list.begin(), list.end(), cmpPreviewProperties);
return list;
}
PreviewImage PreviewManager::getPreviewImage(const PreviewProperties &properties) const
{
Loader::UniquePtr loader = Loader::create(properties.id_, image_);
DataBuf buf;
if (loader.get()) {
buf = loader->getData();
}
return PreviewImage(properties, buf);
}
} // namespace Exiv2