JPEG2000 parser : fix parsing to extract Metadata. Duing a wrong offest between box (in some cases), the jump between box been wrong

First try to write metadata in dedicated box (Exif, Iptc, and Xmp). It doesn't work yet properlly duing a wrong detection of eof. Still todo
This commit is contained in:
HumanDynamo 2008-07-27 20:21:42 +00:00
parent 51a2a03f22
commit 5ff3b4603f
2 changed files with 323 additions and 69 deletions

View File

@ -22,13 +22,16 @@
File: jp2image.cpp
Version: $Rev$
Author(s): Marco Piovanelli, Ovolab (marco)
Author(s): Gilles Caulier (cgilles) <caulier dot gilles at gmail dot com>
History: 12-Mar-2007, marco: created
*/
// *****************************************************************************
#include "rcsid.hpp"
EXIV2_RCSID("@(#) $Id$")
//#define DEBUG 1
// *****************************************************************************
#define DEBUG 1
// *****************************************************************************
// included header files
@ -47,6 +50,7 @@ EXIV2_RCSID("@(#) $Id$")
#include <string>
#include <cstring>
#include <iostream>
#include <cassert>
// JPEG-2000 box types
const uint32_t kJp2BoxTypeJp2Header = 0x6a703268; // 'jp2h'
@ -61,13 +65,18 @@ const char* const kJp2UuidExif = "JpgTiffExif->JP2";
const char* const kJp2UuidIptc = "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38";
const char* const kJp2UuidXmp = "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac";
// See section B.1.1 (JPEG 2000 Signature box) of JPEG-2000 specification
const unsigned char Jp2Signature[12] = { 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a };
//! @cond IGNORE
struct Jp2BoxHeader {
struct Jp2BoxHeader
{
uint32_t boxLength;
uint32_t boxType;
};
struct Jp2ImageHeaderBox {
struct Jp2ImageHeaderBox
{
uint32_t imageHeight;
uint32_t imageWidth;
uint16_t componentCount;
@ -78,37 +87,27 @@ struct Jp2ImageHeaderBox {
uint16_t compressionTypeProfile;
};
struct Jp2UuidBox {
struct Jp2UuidBox
{
uint8_t uuid[16];
};
//! @endcond
// *****************************************************************************
// class member definitions
namespace Exiv2 {
namespace Exiv2
{
Jp2Image::Jp2Image(BasicIo::AutoPtr io)
: Image(ImageType::jp2, mdExif | mdIptc | mdXmp, io)
Jp2Image::Jp2Image(BasicIo::AutoPtr io, bool /*create*/)
: Image(ImageType::jp2, mdExif | mdIptc | mdXmp, io)
{
} // Jp2Image::Jp2Image
void Jp2Image::setExifData(const ExifData& /*exifData*/)
{
// Todo: implement me!
throw(Error(32, "Exif metadata", "JP2"));
}
void Jp2Image::setIptcData(const IptcData& /*iptcData*/)
{
// Todo: implement me!
throw(Error(32, "IPTC metadata", "JP2"));
}
void Jp2Image::setComment(const std::string& /*comment*/)
{
// Todo: implement me!
throw(Error(32, "Image comment", "JP2"));
}
} // Jp2Image::setComment
void Jp2Image::readMetadata()
{
@ -127,39 +126,58 @@ namespace Exiv2 {
throw Error(3, "JPEG-2000");
}
long position = 0;
Jp2BoxHeader box = {0,0};
Jp2BoxHeader subBox = {0,0};
Jp2ImageHeaderBox ihdr = {0,0,0,0,0,0,0,0};
Jp2UuidBox uuid = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
long curOffset = io_->tell();
while (io_->read((byte*)&box, sizeof(box)) == sizeof(box))
{
position = io_->tell();
box.boxLength = getLong((byte*)&box.boxLength, bigEndian);
box.boxType = getLong((byte*)&box.boxType, bigEndian);
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: Position: " << position << "\n";
std::cout << "Exiv2::Jp2Image::readMetadata: Find box type: " << std::string((const char*)&box.boxType)
<< " lenght: " << box.boxLength << "\n";
#endif
box.boxType = getLong((byte*)&box.boxType, bigEndian);
if (box.boxLength == 0)
{
break;
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: Null Box size has been found. "
"This is the last box of file.\n";
#endif
return;
}
switch(box.boxType)
{
case kJp2BoxTypeJp2Header:
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: JP2Header box found\n";
#endif
if (io_->read((byte*)&subBox, sizeof(subBox)) == sizeof(subBox))
{
subBox.boxLength = getLong((byte*)&subBox.boxLength, bigEndian);
subBox.boxType = getLong((byte*)&subBox.boxType, bigEndian);
subBox.boxType = getLong((byte*)&subBox.boxType, bigEndian);
if((subBox.boxType == kJp2BoxTypeImageHeader) && (io_->read((byte*)&ihdr, sizeof(ihdr)) == sizeof(ihdr)))
if((subBox.boxType == kJp2BoxTypeImageHeader) &&
(io_->read((byte*)&ihdr, sizeof(ihdr)) == sizeof(ihdr)))
{
ihdr.imageHeight = getLong((byte*)&ihdr.imageHeight, bigEndian);
ihdr.imageWidth = getLong((byte*)&ihdr.imageWidth, bigEndian);
ihdr.componentCount = getShort((byte*)&ihdr.componentCount, bigEndian);
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: Ihdr data found\n";
#endif
ihdr.imageHeight = getLong((byte*)&ihdr.imageHeight, bigEndian);
ihdr.imageWidth = getLong((byte*)&ihdr.imageWidth, bigEndian);
ihdr.componentCount = getShort((byte*)&ihdr.componentCount, bigEndian);
ihdr.compressionTypeProfile = getShort((byte*)&ihdr.compressionTypeProfile, bigEndian);
pixelWidth_ = ihdr.imageWidth;
pixelWidth_ = ihdr.imageWidth;
pixelHeight_ = ihdr.imageHeight;
}
}
@ -168,17 +186,26 @@ namespace Exiv2 {
case kJp2BoxTypeUuid:
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: UUID box found\n";
#endif
if (io_->read((byte*)&uuid, sizeof(uuid)) == sizeof(uuid))
{
if(memcmp(uuid.uuid, kJp2UuidExif, sizeof(uuid)) == 0)
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: Exif data found\n";
#endif
// we've hit an embedded Exif block
DataBuf rawExif(box.boxLength - (sizeof(box) + sizeof(uuid)));
io_->read(rawExif.pData_, rawExif.size_);
if (io_->error() || io_->eof()) throw Error(14);
ByteOrder bo = ExifParser::decode(exifData_, rawExif.pData_, rawExif.size_);
setByteOrder(bo);
if (rawExif.size_ > 0 && byteOrder() == invalidByteOrder) {
if (rawExif.size_ > 0 && byteOrder() == invalidByteOrder)
{
#ifndef SUPPRESS_WARNINGS
std::cerr << "Warning: Failed to decode Exif metadata.\n";
#endif
@ -188,10 +215,14 @@ namespace Exiv2 {
else if(memcmp(uuid.uuid, kJp2UuidIptc, sizeof(uuid)) == 0)
{
// we've hit an embedded IPTC block
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: Iptc data found\n";
#endif
DataBuf rawIPTC(box.boxLength - (sizeof(box) + sizeof(uuid)));
io_->read(rawIPTC.pData_, rawIPTC.size_);
if (io_->error() || io_->eof()) throw Error(14);
if (IptcParser::decode(iptcData_, rawIPTC.pData_, rawIPTC.size_)) {
if (IptcParser::decode(iptcData_, rawIPTC.pData_, rawIPTC.size_))
{
#ifndef SUPPRESS_WARNINGS
std::cerr << "Warning: Failed to decode IPTC metadata.\n";
#endif
@ -201,11 +232,16 @@ namespace Exiv2 {
else if(memcmp(uuid.uuid, kJp2UuidXmp, sizeof(uuid)) == 0)
{
// we've hit an embedded XMP block
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::readMetadata: Xmp data found\n";
#endif
DataBuf xmpPacket(box.boxLength - (sizeof(box) + sizeof(uuid)));
io_->read(xmpPacket.pData_, xmpPacket.size_);
if (io_->error() || io_->eof()) throw Error(14);
xmpPacket_.assign(reinterpret_cast<char *>(xmpPacket.pData_), xmpPacket.size_);
if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_)) {
if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_))
{
#ifndef SUPPRESS_WARNINGS
std::cerr << "Warning: Failed to decode XMP metadata.\n";
#endif
@ -221,25 +257,244 @@ namespace Exiv2 {
}
}
curOffset += box.boxLength;
if(io_->seek(curOffset, BasicIo::beg) != 0)
{
break; // Todo: should throw an error here
}
// Move to the next box.
io_->seek(position - sizeof(box) + box.boxLength, BasicIo::beg);
if (io_->error() || io_->eof()) throw Error(14);
}
} // Jp2Image::readMetadata
void Jp2Image::writeMetadata()
{
// Todo: implement me!
throw(Error(31, "JP2"));
if (io_->open() != 0)
{
throw Error(9, io_->path(), strError());
}
IoCloser closer(*io_);
BasicIo::AutoPtr tempIo(io_->temporary()); // may throw
assert (tempIo.get() != 0);
doWriteMetadata(*tempIo); // may throw
io_->close();
io_->transfer(*tempIo); // may throw
} // Jp2Image::writeMetadata
void Jp2Image::doWriteMetadata(BasicIo& outIo)
{
if (!io_->isopen()) throw Error(20);
if (!outIo.isopen()) throw Error(21);
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Writing JPEG-2000 file " << io_->path() << "\n";
std::cout << "Exiv2::Jp2Image::doWriteMetadata: tmp file created " << outIo.path() << "\n";
#endif
// Ensure that this is the correct image type
if (!isJp2Type(*io_, true))
{
if (io_->error() || io_->eof()) throw Error(20);
throw Error(22);
}
// Write JPEG2000 Signature.
if (outIo.write(Jp2Signature, 12) != 12) throw Error(21);
Jp2BoxHeader box = {0,0};
Jp2BoxHeader subBox = {0,0};
Jp2ImageHeaderBox ihdr = {0,0,0,0,0,0,0,0};
Jp2UuidBox uuid = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
byte boxDataSize[4];
DataBuf bheaderBuf(8); // Box header : 4 bytes (data size) + 4 bytes (box type).
// FIXME: Andreas, why the loop do not stop when EOF is taken from _io. The loop go out by an exception
// generated by a zero size data read.
while(!io_->eof())
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Position: " << io_->tell() << "\n";
#endif
// Read chunk header.
std::memset(bheaderBuf.pData_, 0x00, bheaderBuf.size_);
long bufRead = io_->read(bheaderBuf.pData_, bheaderBuf.size_);
if (io_->error()) throw Error(14);
if (bufRead != bheaderBuf.size_) throw Error(20);
// Decode box header.
box.boxLength = getLong(bheaderBuf.pData_, bigEndian);
box.boxType = getLong(bheaderBuf.pData_ + 4, bigEndian);
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Find box type: " << bheaderBuf.pData_ + 4
<< " lenght: " << box.boxLength << "\n";
#endif
if (box.boxLength == 0)
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Null Box size has been found. "
"This is the last box of file.\n";
#endif
box.boxLength = io_->size() - io_->tell() + 8;
}
// Read whole box : Box header + Box data (not fixed size - can be null).
DataBuf boxBuf(box.boxLength); // Box header (8 bytes) + box data.
memcpy(boxBuf.pData_, bheaderBuf.pData_, 8); // Copy header.
bufRead = io_->read(boxBuf.pData_ + 8, box.boxLength - 8); // Extract box data.
if (io_->error()) throw Error(14);
if (bufRead != (long)(box.boxLength - 8)) throw Error(20);
switch(box.boxType)
{
case kJp2BoxTypeJp2Header:
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write JP2Header box (lenght: " << box.boxLength << ")\n";
#endif
if (outIo.write(boxBuf.pData_, boxBuf.size_) != boxBuf.size_) throw Error(21);
// Write all updated metadata here, just after JP2Header.
if (exifData_.count() > 0)
{
// Update Exif data to a new UUID box
Blob blob;
ExifParser::encode(blob, littleEndian, exifData_);
if (blob.size())
{
const unsigned char ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
DataBuf rawExif(sizeof(ExifHeader) + blob.size());
memcpy(rawExif.pData_, ExifHeader, sizeof(ExifHeader));
memcpy(rawExif.pData_ + sizeof(ExifHeader), &blob[0], blob.size());
DataBuf boxData(8 + sizeof(kJp2UuidExif) + rawExif.size_);
ul2Data(boxDataSize, boxData.size_, Exiv2::bigEndian);
memcpy(boxData.pData_, boxDataSize, 4);
memcpy(boxData.pData_ + 4, &kJp2BoxTypeUuid, 4);
memcpy(boxData.pData_ + 8, kJp2UuidExif, sizeof(kJp2UuidExif));
memcpy(boxData.pData_ + 8 + sizeof(kJp2UuidExif), rawExif.pData_, rawExif.size_);
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with Exif metadata (lenght: "
<< boxData.size_ << ")\n";
#endif
if (outIo.write(boxData.pData_, boxData.size_) != boxData.size_) throw Error(21);
}
}
if (iptcData_.count() > 0)
{
// Update Iptc data to a new UUID box
DataBuf rawIptc = IptcParser::encode(iptcData_);
if (rawIptc.size_ > 0)
{
DataBuf boxData(8 + sizeof(kJp2UuidIptc) + rawIptc.size_);
ul2Data(boxDataSize, boxData.size_, Exiv2::bigEndian);
memcpy(boxData.pData_, boxDataSize, 4);
memcpy(boxData.pData_ + 4, &kJp2BoxTypeUuid, 4);
memcpy(boxData.pData_ + 8, kJp2UuidIptc, sizeof(kJp2UuidIptc));
memcpy(boxData.pData_ + 8 + sizeof(kJp2UuidIptc), rawIptc.pData_, rawIptc.size_);
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with Iptc metadata (lenght: "
<< boxData.size_ << ")\n";
#endif
if (outIo.write(boxData.pData_, boxData.size_) != boxData.size_) throw Error(21);
}
}
if (writeXmpFromPacket() == false)
{
if (XmpParser::encode(xmpPacket_, xmpData_))
{
#ifndef SUPPRESS_WARNINGS
std::cerr << "Error: Failed to encode XMP metadata.\n";
#endif
}
}
if (xmpPacket_.size() > 0)
{
// Update Xmp data to a new UUID box
DataBuf xmp(reinterpret_cast<const byte*>(xmpPacket_.data()), static_cast<long>(xmpPacket_.size()));
DataBuf boxData(8 + sizeof(kJp2UuidXmp) + xmp.size_);
ul2Data(boxDataSize, boxData.size_, Exiv2::bigEndian);
memcpy(boxData.pData_, boxDataSize, 4);
memcpy(boxData.pData_ + 4, &kJp2BoxTypeUuid, 4);
memcpy(boxData.pData_ + 8, kJp2UuidXmp, sizeof(kJp2UuidXmp));
memcpy(boxData.pData_ + 8 + sizeof(kJp2UuidXmp), xmp.pData_, xmp.size_);
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with XMP metadata (lenght: "
<< boxData.size_ << ")\n";
#endif
if (outIo.write(boxData.pData_, boxData.size_) != boxData.size_) throw Error(21);
}
break;
}
case kJp2BoxTypeUuid:
{
if(memcmp(boxBuf.pData_ + 8, kJp2UuidExif, sizeof(16)) == 0)
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Exif Uuid box\n";
#endif
}
else if(memcmp(boxBuf.pData_ + 8, kJp2UuidIptc, sizeof(16)) == 0)
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Iptc Uuid box\n";
#endif
}
else if(memcmp(boxBuf.pData_ + 8, kJp2UuidXmp, sizeof(16)) == 0)
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Xmp Uuid box\n";
#endif
}
else
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: write Uuid box (lenght: " << box.boxLength << ")\n";
#endif
if (outIo.write(boxBuf.pData_, boxBuf.size_) != boxBuf.size_) throw Error(21);
}
break;
}
default:
{
#ifdef DEBUG
std::cout << "Exiv2::Jp2Image::doWriteMetadata: write box (lenght: " << box.boxLength << ")\n";
#endif
if (outIo.write(boxBuf.pData_, boxBuf.size_) != boxBuf.size_) throw Error(21);
break;
}
}
}
} // Jp2Image::doWriteMetadata
// *************************************************************************
// free functions
Image::AutoPtr newJp2Instance(BasicIo::AutoPtr io, bool /*create*/)
Image::AutoPtr newJp2Instance(BasicIo::AutoPtr io, bool create)
{
Image::AutoPtr image(new Jp2Image(io));
Image::AutoPtr image(new Jp2Image(io, create));
if (!image->good())
{
image.reset();
@ -249,16 +504,14 @@ namespace Exiv2 {
bool isJp2Type(BasicIo& iIo, bool advance)
{
// see section B.1.1 (JPEG 2000 Signature box) of JPEG-2000 specification
const int32_t len = 12;
const unsigned char Jp2Header[len] = { 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a };
byte buf[len];
iIo.read(buf, len);
if (iIo.error() || iIo.eof())
{
return false;
}
bool matched = (memcmp(buf, Jp2Header, len) == 0);
bool matched = (memcmp(buf, Jp2Signature, len) == 0);
if (!advance || !matched)
{
iIo.seek(-len, BasicIo::cur);

View File

@ -23,6 +23,8 @@
@brief JPEG-2000 image, implemented using the following references:
<a href="http://jpeg.org/public/fcd15444-6.pdf">ISO/IEC JTC 1/SC 29/WG1 N2401: JPEG 2000 Part 6 FCD 15444-6</a><br>
@version $Rev$
@author Gilles Caulier (cgilles)
<a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
@author Marco Piovanelli, Ovolab (marco)
<a href="mailto:marco.piovanelli@pobox.com">marco.piovanelli@pobox.com</a>
@date 12-Mar-2007, marco: created
@ -41,27 +43,23 @@
// *****************************************************************************
// namespace extensions
namespace Exiv2 {
namespace Exiv2
{
// *****************************************************************************
// class definitions
// Add JPEG-2000 to the supported image formats
namespace ImageType {
namespace ImageType
{
const int jp2 = 15; //!< JPEG-2000 image type
}
/*!
@brief Class to access JPEG-2000 images.
*/
class Jp2Image : public Image {
//! @name NOT Implemented
//@{
//! Copy constructor
Jp2Image(const Jp2Image& rhs);
//! Assignment operator
Jp2Image& operator=(const Jp2Image& rhs);
//@}
class Jp2Image : public Image
{
public:
//! @name Creators
@ -78,27 +76,13 @@ namespace Exiv2 {
instance after it is passed to this method. Use the Image::io()
method to get a temporary reference.
*/
Jp2Image(BasicIo::AutoPtr io);
Jp2Image(BasicIo::AutoPtr io, bool create);
//@}
//! @name Manipulators
//@{
void readMetadata();
/*!
@brief Todo: Write metadata back to the image. This method is not
yet implemented. Calling it will throw an Error(31).
*/
void writeMetadata();
/*!
@brief Todo: Not supported yet. Calling this function will throw
an instance of Error(32).
*/
void setExifData(const ExifData& exifData);
/*!
@brief Todo: Not supported yet. Calling this function will throw
an instance of Error(32).
*/
void setIptcData(const IptcData& iptcData);
/*!
@brief Todo: Not supported yet(?). Calling this function will throw
an instance of Error(32).
@ -111,6 +95,23 @@ namespace Exiv2 {
std::string mimeType() const { return "image/jp2"; }
//@}
private:
//! @name NOT Implemented
//@{
//! Copy constructor
Jp2Image(const Jp2Image& rhs);
//! Assignment operator
Jp2Image& operator=(const Jp2Image& rhs);
/*!
@brief Provides the main implementation of writeMetadata() by
writing all buffered metadata to the provided BasicIo.
@param oIo BasicIo instance to write to (a temporary location).
@return 4 if opening or writing to the associated BasicIo fails
*/
void doWriteMetadata(BasicIo& oIo);
//@}
}; // class Jp2Image
// *****************************************************************************