diff --git a/src/canonmn_int.cpp b/src/canonmn_int.cpp index d678bd12..8b9e67ba 100644 --- a/src/canonmn_int.cpp +++ b/src/canonmn_int.cpp @@ -406,6 +406,23 @@ namespace Exiv2 { { 2, N_("Adobe RGB") } }; + extern const TagDetails canonAFAreaMode[] = { + { 0, N_("Off (Manual Focus)") }, + { 1, N_("AF Point Expansion (surround)")}, + { 2, N_("Single-point AF") }, + { 4, N_("Multi-point AF") }, + { 5, N_("Face Detect AF") }, + { 6, N_("Face + Tracking") }, + { 7, N_("Zone AF") }, + { 8, N_("AF Point Expansion (4 point)") }, + { 9, N_("Spot AF") }, + { 10, N_("AF Point Expansion (8 point)") }, + { 11, N_("Flexizone Multi (49 point)") }, + { 12, N_("Flexizone Multi (9 point)") }, + { 13, N_("Flexizone Single") }, + { 14, N_("Large Zone AF") }, + }; + // Canon MakerNote Tag Info const TagInfo CanonMakerNote::tagInfo_[] = { TagInfo(0x0000, "0x0000", "0x0000", N_("Unknown"), canonId, makerTags, unsignedShort, -1, printValue), @@ -442,6 +459,21 @@ namespace Exiv2 { TagInfo(0x00c1, "0x00c1", "0x00c1", N_("Unknown"), canonId, makerTags, unsignedShort, -1, printValue), TagInfo(0x00d0, "VRDOffset", N_("VRD Offset"), N_("VRD offset"), canonId, makerTags, unsignedLong, -1, printValue), TagInfo(0x00e0, "SensorInfo", N_("Sensor Info"), N_("Sensor info"), canonId, makerTags, unsignedShort, -1, printValue), + TagInfo(0x2600, "AFInfoSize", N_("AF InfoSize"), N_("AF InfoSize"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2601, "AFAreaMode", N_("AF Area Mode"), N_("AF Area Mode"), canonId, makerTags, signedShort, -1, EXV_PRINT_TAG(canonAFAreaMode)), + TagInfo(0x2602, "AFNumPoints", N_("AF NumPoints"), N_("AF NumPoints"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2603, "AFValidPoints", N_("AF ValidPoints"), N_("AF ValidPoints"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2604, "AFCanonImageWidth", N_("AF ImageWidth"), N_("AF ImageWidth"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2605, "AFCanonImageHeight", N_("AF ImageHeight"), N_("AF ImageHeight"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2606, "AFImageWidth", N_("AF Width"), N_("AF Width"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2607, "AFImageHeight", N_("AF Height"), N_("AF Height"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2608, "AFAreaWidths", N_("AF Area Widths"), N_("AF Area Widths"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x2609, "AFAreaHeights", N_("AF Area Heights"), N_("AF Area Heights"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x260a, "AFXPositions", N_("AF X Positions"), N_("AF X Positions"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x260b, "AFYPositions", N_("AF Y Positions"), N_("AF Y Positions"), canonId, makerTags, signedShort, -1, printValue), + TagInfo(0x260c, "AFPointsInFocus", N_("AF Points in Focus"), N_("AF Points in Focus"), canonId, makerTags, signedShort, -1,printBitmask), + TagInfo(0x260d, "AFPointsSelected", N_("AF Points Selected"), N_("AF Points Selected"), canonId, makerTags, signedShort, -1, printBitmask), + TagInfo(0x260e, "AFPrimaryPoint", N_("AF Primary Point"), N_("AF Primary Point"), canonId, makerTags, signedShort, -1, printBitmask), TagInfo(0x4001, "ColorData", N_("Color Data"), N_("Color data"), canonId, makerTags, unsignedShort, -1, printValue), // End of list marker TagInfo(0xffff, "(UnknownCanonMakerNoteTag)", "(UnknownCanonMakerNoteTag)", N_("Unknown CanonMakerNote tag"), canonId, makerTags, asciiString, -1, printValue) diff --git a/src/exif.cpp b/src/exif.cpp index 26a8a2cd..b1622abe 100644 --- a/src/exif.cpp +++ b/src/exif.cpp @@ -660,7 +660,23 @@ namespace Exiv2 { "Exif.Image.StripByteCounts", "Exif.Image.JPEGInterchangeFormat", "Exif.Image.JPEGInterchangeFormatLength", - "Exif.Image.SubIFDs" + "Exif.Image.SubIFDs", + // Issue 981. Never allow manufactured data to be written + "Exif.Canon.AFInfoSize", + "Exif.Canon.AFAreaMode", + "Exif.Canon.AFNumPoints", + "Exif.Canon.AFValidPoints", + "Exif.Canon.AFCanonImageWidth", + "Exif.Canon.AFCanonImageHeight", + "Exif.Canon.AFImageWidth", + "Exif.Canon.AFImageHeight", + "Exif.Canon.AFAreaWidths", + "Exif.Canon.AFAreaHeights", + "Exif.Canon.AFXPositions", + "Exif.Canon.AFYPositions", + "Exif.Canon.AFPointsInFocus", + "Exif.Canon.AFPointsSelected", + "Exif.Canon.AFPrimaryPoint", }; for (unsigned int i = 0; i < EXV_COUNTOF(filteredIfd0Tags); ++i) { ExifData::iterator pos = ed.findKey(ExifKey(filteredIfd0Tags[i])); diff --git a/src/tags_int.cpp b/src/tags_int.cpp index 8d625964..add81946 100644 --- a/src/tags_int.cpp +++ b/src/tags_int.cpp @@ -1944,7 +1944,7 @@ namespace Exiv2 { TagInfo(0xb002, "MPFImageList", N_("MPFImageList"), N_("MPF Image List"), mpfId, mpfTags, asciiString, 0, printValue), - TagInfo(0xb003, "MPFImageUIDList", N_("MPFImageUIDList "), + TagInfo(0xb003, "MPFImageUIDList", N_("MPFImageUIDList "), N_("MPF Image UID List"), mpfId, mpfTags, unsignedLong, 1, printValue), TagInfo(0xb004, "MPFTotalFrames", N_("MPFTotalFrames"), @@ -2164,6 +2164,32 @@ namespace Exiv2 { return os << value; } + std::ostream& printBitmask(std::ostream& os, const Value& value, const ExifData* metadata) + { + if (value.typeId() == Exiv2::unsignedShort || value.typeId() == Exiv2::signedShort) + { + uint16_t bit = 0; + uint16_t comma = 0; + for (uint16_t i = 0; i < value.count(); i++ ) { // for each element in value array + uint16_t bits = static_cast(value.toLong(i)); + for (uint16_t b = 0; b < 16; ++b) { // for every bit + if (bits & (1 << b)) { + if ( comma++ ) { + os << ","; + } + os << bit; + } + bit++ ; + } + } + // if no bits are set, print "(none)" + if ( !comma ) os << N_("(none)"); + } else { + printValue(os,value,metadata); + } + return os; + } + float fnumber(float apertureValue) { return std::exp(std::log(2.0f) * apertureValue / 2.f); diff --git a/src/tags_int.hpp b/src/tags_int.hpp index 306cdbbc..7ff48b22 100644 --- a/src/tags_int.hpp +++ b/src/tags_int.hpp @@ -441,6 +441,8 @@ namespace Exiv2 { std::ostream& printXmpVersion(std::ostream& os, const Value& value, const ExifData*); //! Print a date following the format YYYY-MM-DDTHH:MM:SSZ std::ostream& printXmpDate(std::ostream& os, const Value& value, const ExifData*); + //! Print a bitmask as (none) | n | n,m... where: (none) = no bits set | n = bit n from left (0=left-most) | n,m.. = multiple bits " + std::ostream& printBitmask(std::ostream& os, const Value& value, const ExifData*); //@} //! Calculate F number from an APEX aperture value diff --git a/src/tiffimage_int.cpp b/src/tiffimage_int.cpp index a052a6a3..3d5173cd 100644 --- a/src/tiffimage_int.cpp +++ b/src/tiffimage_int.cpp @@ -1514,7 +1514,8 @@ namespace Exiv2 { { "*", Tag::all, ignoreId, 0, 0 }, // Do not decode tags with group == ignoreId { "*", 0x02bc, ifd0Id, &TiffDecoder::decodeXmp, 0 /*done before the tree is traversed*/ }, { "*", 0x83bb, ifd0Id, &TiffDecoder::decodeIptc, 0 /*done before the tree is traversed*/ }, - { "*", 0x8649, ifd0Id, &TiffDecoder::decodeIptc, 0 /*done before the tree is traversed*/ } + { "*", 0x8649, ifd0Id, &TiffDecoder::decodeIptc, 0 /*done before the tree is traversed*/ }, + { "*", 0x0026, canonId, &TiffDecoder::decodeCanonAFInfo, 0 /* Exiv2.Canon.AFInfo is read-only */ }, }; DecoderFct TiffMapping::findDecoder(const std::string& make, diff --git a/src/tiffvisitor_int.cpp b/src/tiffvisitor_int.cpp index 1e8615a4..930a75dd 100644 --- a/src/tiffvisitor_int.cpp +++ b/src/tiffvisitor_int.cpp @@ -45,6 +45,7 @@ #include #include #include +#include // ***************************************************************************** namespace { @@ -462,6 +463,78 @@ namespace Exiv2 { } } // TiffMetadataDecoder::decodeIptc + static const TagInfo* findTag(const TagInfo* pList,uint16_t tag) + { + while ( pList->tag_ != 0xffff && pList->tag_ != tag ) pList++; + return pList->tag_ != 0xffff ? pList : NULL; + } + + void TiffDecoder::decodeCanonAFInfo(const TiffEntryBase* object) { + // report Exif.Canon.AFInfo as usual + TiffDecoder::decodeStdTiffEntry(object); + if ( object->pValue()->count() < 3 || object->pValue()->typeId() != unsignedShort ) return; // insufficient data + + // create vector of signedShorts from unsignedShorts in Exif.Canon.AFInfo + std::vector ints; + std::vector uint; + for (int i = 0; i < object->pValue()->count(); i++) { + ints.push_back((int16_t) object->pValue()->toLong(i)); + uint.push_back((uint16_t) object->pValue()->toLong(i)); + } + // Check this is AFInfo2 (ints[0] = bytes in object) + if ( ints[0] != object->pValue()->count()*2 ) return ; + + std::string familyGroup(std::string("Exif.") + groupName(object->group()) + "."); + + const uint16_t nPoints = uint.at(2); + const uint16_t nMasks = (nPoints+15)/(sizeof(uint16_t) * 8); + int nStart = 0; + + struct { + uint16_t tag ; + uint16_t size ; + bool bSigned; + } records[] = { + { 0x2600 , 1 , true }, // AFInfoSize + { 0x2601 , 1 , true }, // AFAreaMode + { 0x2602 , 1 , true }, // AFNumPoints + { 0x2603 , 1 , true }, // AFValidPoints + { 0x2604 , 1 , true }, // AFCanonImageWidth + { 0x2605 , 1 , true }, // AFCanonImageHeight + { 0x2606 , 1 , true }, // AFImageWidth" + { 0x2607 , 1 , true }, // AFImageHeight + { 0x2608 , nPoints , true }, // AFAreaWidths + { 0x2609 , nPoints , true }, // AFAreaHeights + { 0x260a , nPoints , true }, // AFXPositions + { 0x260b , nPoints , true }, // AFYPositions + { 0x260c , nMasks , false }, // AFPointsInFocus + { 0x260d , nMasks , false }, // AFPointsSelected + { 0x260e , nMasks , false }, // AFPrimaryPoint + { 0xffff , 0 , true } // end marker + }; + // check we have enough data! + uint16_t count = 0; + for ( uint16_t i = 0; records[i].tag != 0xffff ; i++) count += records[i].size ; + if ( count > ints.size() ) return ; + + for ( uint16_t i = 0; records[i].tag != 0xffff ; i++) { + const TagInfo* pTags = ExifTags::tagList("Canon") ; + const TagInfo* pTag = findTag(pTags,records[i].tag); + if ( pTag ) { + Exiv2::Value::AutoPtr v = Exiv2::Value::create(records[i].bSigned?Exiv2::signedShort:Exiv2::unsignedShort); + std::ostringstream s; + if ( records[i].bSigned ) { + for ( int16_t k = 0 ; k < records[i].size ; k++ ) s << " " << ints.at(nStart++); + } else { + for ( int16_t k = 0 ; k < records[i].size ; k++ ) s << " " << uint.at(nStart++); + } + + v->read(s.str()); + exifData_[familyGroup + pTag->name_] = *v; + } + } + } + void TiffDecoder::decodeTiffEntry(const TiffEntryBase* object) { assert(object != 0); diff --git a/src/tiffvisitor_int.hpp b/src/tiffvisitor_int.hpp index caddace0..1a82e8f6 100644 --- a/src/tiffvisitor_int.hpp +++ b/src/tiffvisitor_int.hpp @@ -332,6 +332,8 @@ namespace Exiv2 { void decodeIptc(const TiffEntryBase* object); //! Decode XMP packet from an XMLPacket tag void decodeXmp(const TiffEntryBase* object); + //! Decode Exif.Canon.AFInfo + void decodeCanonAFInfo(const TiffEntryBase* object); //@} private: diff --git a/test/data/test_issue_981a.exv b/test/data/test_issue_981a.exv new file mode 100644 index 00000000..8129f681 Binary files /dev/null and b/test/data/test_issue_981a.exv differ diff --git a/test/data/test_issue_981b.exv b/test/data/test_issue_981b.exv new file mode 100644 index 00000000..1fabee90 Binary files /dev/null and b/test/data/test_issue_981b.exv differ diff --git a/test/data/test_issue_981c.exv b/test/data/test_issue_981c.exv new file mode 100644 index 00000000..92d11d4f Binary files /dev/null and b/test/data/test_issue_981c.exv differ diff --git a/test/data/test_issue_981d.exv b/test/data/test_issue_981d.exv new file mode 100644 index 00000000..81d8771b Binary files /dev/null and b/test/data/test_issue_981d.exv differ diff --git a/tests/bugfixes/github/test_issue_981.py b/tests/bugfixes/github/test_issue_981.py new file mode 100644 index 00000000..8ca937de --- /dev/null +++ b/tests/bugfixes/github/test_issue_981.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +from system_tests import CaseMeta, path + +class CanonAfInfoTest(metaclass=CaseMeta): + + filenameA = path("$data_path/test_issue_981a.exv") + filenameB = path("$data_path/test_issue_981b.exv") + filenameC = path("$data_path/test_issue_981c.exv") + filenameC = path("$data_path/test_issue_981c.exv") + filenameD = path("$data_path/test_issue_981d.exv") + commands = ["$exiv2 -pa --grep Canon.AF $filenameA", + "$exiv2 -pa --grep Canon.AF $filenameB", + "$exiv2 -pv --grep Points $filenameC", + "$exiv2 -pt --grep Points $filenameC", + "$exiv2 -pv --grep Primary $filenameD", + "$exiv2 -pt --grep Primary $filenameD", + ] + + stdout = ["""Exif.Canon.AFInfo Short 48 96 2 9 9 4752 3168 4272 2848 115 115 115 162 200 162 115 115 115 153 153 153 105 199 105 153 153 153 64409 64862 64862 0 0 0 674 674 1127 0 321 65215 603 0 64933 321 65215 0 16 256 0 65535 +Exif.Canon.AFInfoSize SShort 1 96 +Exif.Canon.AFAreaMode SShort 1 Single-point AF +Exif.Canon.AFNumPoints SShort 1 9 +Exif.Canon.AFValidPoints SShort 1 9 +Exif.Canon.AFCanonImageWidth SShort 1 4752 +Exif.Canon.AFCanonImageHeight SShort 1 3168 +Exif.Canon.AFImageWidth SShort 1 4272 +Exif.Canon.AFImageHeight SShort 1 2848 +Exif.Canon.AFAreaWidths SShort 9 115 115 115 162 200 162 115 115 115 +Exif.Canon.AFAreaHeights SShort 9 153 153 153 105 199 105 153 153 153 +Exif.Canon.AFXPositions SShort 9 -1127 -674 -674 0 0 0 674 674 1127 +Exif.Canon.AFYPositions SShort 9 0 321 -321 603 0 -603 321 -321 0 +Exif.Canon.AFPointsInFocus Short 1 4 +Exif.Canon.AFPointsSelected Short 1 8 +Exif.Canon.AFPrimaryPoint Short 1 (none) +""" , """Exif.Canon.AFInfo Short 273 546 2 63 61 6720 4480 6720 4480 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 0 0 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 0 0 65200 64790 64435 64099 63764 336 0 65200 64099 63764 1772 1437 1101 746 336 0 1437 1101 746 336 0 65200 64790 64435 336 0 65200 64790 64435 64099 63764 1772 64790 64435 64099 63764 1772 1437 1101 746 63764 1772 1437 1101 746 336 0 65200 1101 746 336 0 65200 64790 64435 64099 336 0 65200 1772 1437 0 0 547 625 625 625 625 821 821 821 308 308 625 625 625 625 547 547 308 308 308 274 274 274 308 308 0 0 0 0 0 0 0 308 65228 65228 65228 65228 0 0 0 0 64911 65228 65228 65228 65228 65262 65262 65262 64911 64911 64989 64989 64989 64911 64911 64911 64715 64715 64715 64911 64911 0 0 0 512 0 0 0 512 0 0 0 0 0 0 65535 +Exif.Canon.AFInfoSize SShort 1 546 +Exif.Canon.AFAreaMode SShort 1 Single-point AF +Exif.Canon.AFNumPoints SShort 1 63 +Exif.Canon.AFValidPoints SShort 1 61 +Exif.Canon.AFCanonImageWidth SShort 1 6720 +Exif.Canon.AFCanonImageHeight SShort 1 4480 +Exif.Canon.AFImageWidth SShort 1 6720 +Exif.Canon.AFImageHeight SShort 1 4480 +Exif.Canon.AFAreaWidths SShort 63 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 0 0 +Exif.Canon.AFAreaHeights SShort 63 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 218 0 0 +Exif.Canon.AFXPositions SShort 63 -336 -746 -1101 -1437 -1772 336 0 -336 -1437 -1772 1772 1437 1101 746 336 0 1437 1101 746 336 0 -336 -746 -1101 336 0 -336 -746 -1101 -1437 -1772 1772 -746 -1101 -1437 -1772 1772 1437 1101 746 -1772 1772 1437 1101 746 336 0 -336 1101 746 336 0 -336 -746 -1101 -1437 336 0 -336 1772 1437 0 0 +Exif.Canon.AFYPositions SShort 63 547 625 625 625 625 821 821 821 308 308 625 625 625 625 547 547 308 308 308 274 274 274 308 308 0 0 0 0 0 0 0 308 -308 -308 -308 -308 0 0 0 0 -625 -308 -308 -308 -308 -274 -274 -274 -625 -625 -547 -547 -547 -625 -625 -625 -821 -821 -821 -625 -625 0 0 +Exif.Canon.AFPointsInFocus Short 4 25 +Exif.Canon.AFPointsSelected Short 4 25 +Exif.Canon.AFPrimaryPoint Short 4 (none) +""","""0x2602 Canon AFNumPoints SShort 1 63 +0x2603 Canon AFValidPoints SShort 1 61 +0x260c Canon AFPointsInFocus Short 4 0 560 57344 0 +0x260d Canon AFPointsSelected Short 4 0 1848 57344 0 +""","""Exif.Canon.AFNumPoints SShort 1 63 +Exif.Canon.AFValidPoints SShort 1 61 +Exif.Canon.AFPointsInFocus Short 4 20,21,25,45,46,47 +Exif.Canon.AFPointsSelected Short 4 19,20,21,24,25,26,45,46,47 +""","""0x260e Canon AFPrimaryPoint Short 4 3608 49152 792 6272 +""","""Exif.Canon.AFPrimaryPoint Short 4 3,4,9,10,11,30,31,35,36,40,41,55,59,60 +""" +] + stderr = [""]*len(commands) + retval = [ 0]*len(commands)