[C#] 如何讀取相片Metadata

Metadata 是內容的描述性資訊 (Descriptive information), 例如檔案的建立日期, 作者等. 而圖像則有其自己的metadata, 主要分三類:

  • Technical Metadata:
    主要為圖片的技術相關資訊, 例如光圈, 快門, ISO, 等.  除了相片外, 亦會有攝影器材的資訊, 如相機, 鏡頭等. 這類metadata 亦被統稱為Exif.
  • Descriptive Metadata:
    通常為圖片的內容描述. 如圖片的主題, 檢簽, 位置等.
  • Administrative Metadata:
    若圖片有申請知識產權(Intellengent Property, IP), 則在此會有版權資訊, 使用守則. 而這類Metadata 則以IPTC 形式出現.

在示範中, 會取出相中所有meta data 出來.

先建立Enum PropertyTypeID, 以表示該property 所代表的metadata.
PropertyTagID.cs

public enum PropertyTagID
    {
        PropertyTagGpsVer = 0x0000,
        PropertyTagGpsLatitudeRef = 0x0001,
        PropertyTagGpsLatitude = 0x0002,
        PropertyTagGpsLongitudeRef = 0x0003,
        PropertyTagGpsLongitude = 0x0004,
        PropertyTagGpsAltitudeRef = 0x0005,
        PropertyTagGpsAltitude = 0x0006,
        PropertyTagGpsGpsTime = 0x0007,
        PropertyTagGpsGpsSatellites = 0x0008,
        PropertyTagGpsGpsStatus = 0x0009,
        PropertyTagGpsGpsMeasureMode = 0x000A,
        PropertyTagGpsGpsDop = 0x000B,
        PropertyTagGpsSpeedRef = 0x000C,
        PropertyTagGpsSpeed = 0x000D,
        PropertyTagGpsTrackRef = 0x000E,
        PropertyTagGpsTrack = 0x000F,
        PropertyTagGpsImgDirRef = 0x0010,
        PropertyTagGpsImgDir = 0x0011,
        PropertyTagGpsMapDatum = 0x0012,
        PropertyTagGpsDestLatRef = 0x0013,
        PropertyTagGpsDestLat = 0x0014,
        PropertyTagGpsDestLongRef = 0x0015,
        PropertyTagGpsDestLong = 0x0016,
        PropertyTagGpsDestBearRef = 0x0017,
        PropertyTagGpsDestBear = 0x0018,
        PropertyTagGpsDestDistRef = 0x0019,
        PropertyTagGpsDestDist = 0x001A,
        PropertyTagNewSubfileType = 0x00FE,
        PropertyTagSubfileType = 0x00FF,
        PropertyTagImageWidth = 0x0100,
        PropertyTagImageHeight = 0x0101,
        PropertyTagBitsPerSample = 0x0102,
        PropertyTagCompression = 0x0103,
        PropertyTagPhotometricInterp = 0x0106,
        PropertyTagThreshHolding = 0x0107,
        PropertyTagCellWidth = 0x0108,
        PropertyTagCellHeight = 0x0109,
        PropertyTagFillOrder = 0x010A,
        PropertyTagDocumentName = 0x010D,
        PropertyTagImageDescription = 0x010E,
        PropertyTagEquipMake = 0x010F,
        PropertyTagEquipModel = 0x0110,
        PropertyTagStripOffsets = 0x0111,
        PropertyTagOrientation = 0x0112,
        PropertyTagSamplesPerPixel = 0x0115,
        PropertyTagRowsPerStrip = 0x0116,
        PropertyTagStripBytesCount = 0x0117,
        PropertyTagMinSampleValue = 0x0118,
        PropertyTagMaxSampleValue = 0x0119,
        PropertyTagXResolution = 0x011A,
        PropertyTagYResolution = 0x011B,
        PropertyTagPlanarConfig = 0x011C,
        PropertyTagPageName = 0x011D,
        PropertyTagXPosition = 0x011E,
        PropertyTagYPosition = 0x011F,
        PropertyTagFreeOffset = 0x0120,
        PropertyTagFreeByteCounts = 0x0121,
        PropertyTagGrayResponseUnit = 0x0122,
        PropertyTagGrayResponseCurve = 0x0123,
        PropertyTagT4Option = 0x0124,
        PropertyTagT6Option = 0x0125,
        PropertyTagResolutionUnit = 0x0128,
        PropertyTagPageNumber = 0x0129,
        PropertyTagTransferFunction = 0x012D,
        PropertyTagSoftwareUsed = 0x0131,
        PropertyTagDateTime = 0x0132,
        PropertyTagArtist = 0x013B,
        PropertyTagHostComputer = 0x013C,
        PropertyTagPredictor = 0x013D,
        PropertyTagWhitePoint = 0x013E,
        PropertyTagPrimaryChromaticities = 0x013F,
        PropertyTagColorMap = 0x0140,
        PropertyTagHalftoneHints = 0x0141,
        PropertyTagTileWidth = 0x0142,
        PropertyTagTileLength = 0x0143,
        PropertyTagTileOffset = 0x0144,
        PropertyTagTileByteCounts = 0x0145,
        PropertyTagInkSet = 0x014C,
        PropertyTagInkNames = 0x014D,
        PropertyTagNumberOfInks = 0x014E,
        PropertyTagDotRange = 0x0150,
        PropertyTagTargetPrinter = 0x0151,
        PropertyTagExtraSamples = 0x0152,
        PropertyTagSampleFormat = 0x0153,
        PropertyTagSMinSampleValue = 0x0154,
        PropertyTagSMaxSampleValue = 0x0155,
        PropertyTagTransferRange = 0x0156,
        PropertyTagJPEGProc = 0x0200,
        PropertyTagJPEGInterFormat = 0x0201,
        PropertyTagJPEGInterLength = 0x0202,
        PropertyTagJPEGRestartInterval = 0x0203,
        PropertyTagJPEGLosslessPredictors = 0x0205,
        PropertyTagJPEGPointTransforms = 0x0206,
        PropertyTagJPEGQTables = 0x0207,
        PropertyTagJPEGDCTables = 0x0208,
        PropertyTagJPEGACTables = 0x0209,
        PropertyTagYCbCrCoefficients = 0x0211,
        PropertyTagYCbCrSubsampling = 0x0212,
        PropertyTagYCbCrPositioning = 0x0213,
        PropertyTagREFBlackWhite = 0x0214,
        PropertyTagGamma = 0x0301,
        PropertyTagICCProfileDescriptor = 0x0302,
        PropertyTagSRGBRenderingIntent = 0x0303,
        PropertyTagImageTitle = 0x0320,
        PropertyTagResolutionXUnit = 0x5001,
        PropertyTagResolutionYUnit = 0x5002,
        PropertyTagResolutionXLengthUnit = 0x5003,
        PropertyTagResolutionYLengthUnit = 0x5004,
        PropertyTagPrintFlags = 0x5005,
        PropertyTagPrintFlagsVersion = 0x5006,
        PropertyTagPrintFlagsCrop = 0x5007,
        PropertyTagPrintFlagsBleedWidth = 0x5008,
        PropertyTagPrintFlagsBleedWidthScale = 0x5009,
        PropertyTagHalftoneLPI = 0x500A,
        PropertyTagHalftoneLPIUnit = 0x500B,
        PropertyTagHalftoneDegree = 0x500C,
        PropertyTagHalftoneShape = 0x500D,
        PropertyTagHalftoneMisc = 0x500E,
        PropertyTagHalftoneScreen = 0x500F,
        PropertyTagJPEGQuality = 0x5010,
        PropertyTagGridSize = 0x5011,
        PropertyTagThumbnailFormat = 0x5012,
        PropertyTagThumbnailWidth = 0x5013,
        PropertyTagThumbnailHeight = 0x5014,
        PropertyTagThumbnailColorDepth = 0x5015,
        PropertyTagThumbnailPlanes = 0x5016,
        PropertyTagThumbnailRawBytes = 0x5017,
        PropertyTagThumbnailSize = 0x5018,
        PropertyTagThumbnailCompressedSize = 0x5019,
        PropertyTagColorTransferFunction = 0x501A,
        PropertyTagThumbnailData = 0x501B,
        PropertyTagThumbnailImageWidth = 0x5020,
        PropertyTagThumbnailImageHeight = 0x5021,
        PropertyTagThumbnailBitsPerSample = 0x5022,
        PropertyTagThumbnailCompression = 0x5023,
        PropertyTagThumbnailPhotometricInterp = 0x5024,
        PropertyTagThumbnailImageDescription = 0x5025,
        PropertyTagThumbnailEquipMake = 0x5026,
        PropertyTagThumbnailEquipModel = 0x5027,
        PropertyTagThumbnailStripOffsets = 0x5028,
        PropertyTagThumbnailOrientation = 0x5029,
        PropertyTagThumbnailSamplesPerPixel = 0x502A,
        PropertyTagThumbnailRowsPerStrip = 0x502B,
        PropertyTagThumbnailStripBytesCount = 0x502C,
        PropertyTagThumbnailResolutionX = 0x502D,
        PropertyTagThumbnailResolutionY = 0x502E,
        PropertyTagThumbnailPlanarConfig = 0x502F,
        PropertyTagThumbnailResolutionUnit = 0x5030,
        PropertyTagThumbnailTransferFunction = 0x5031,
        PropertyTagThumbnailSoftwareUsed = 0x5032,
        PropertyTagThumbnailDateTime = 0x5033,
        PropertyTagThumbnailArtist = 0x5034,
        PropertyTagThumbnailWhitePoint = 0x5035,
        PropertyTagThumbnailPrimaryChromaticities = 0x5036,
        PropertyTagThumbnailYCbCrCoefficients = 0x5037,
        PropertyTagThumbnailYCbCrSubsampling = 0x5038,
        PropertyTagThumbnailYCbCrPositioning = 0x5039,
        PropertyTagThumbnailRefBlackWhite = 0x503A,
        PropertyTagThumbnailCopyRight = 0x503B,
        PropertyTagLuminanceTable = 0x5090,
        PropertyTagChrominanceTable = 0x5091,
        PropertyTagFrameDelay = 0x5100,
        PropertyTagLoopCount = 0x5101,
        PropertyTagGlobalPalette = 0x5102,
        PropertyTagIndexBackground = 0x5103,
        PropertyTagIndexTransparent = 0x5104,
        PropertyTagPixelUnit = 0x5110,
        PropertyTagPixelPerUnitX = 0x5111,
        PropertyTagPixelPerUnitY = 0x5112,
        PropertyTagPaletteHistogram = 0x5113,
        PropertyTagCopyright = 0x8298,
        PropertyTagExifExposureTime = 0x829A,
        PropertyTagExifFNumber = 0x829D,
        PropertyTagExifIFD = 0x8769,
        PropertyTagICCProfile = 0x8773,
        PropertyTagExifExposureProg = 0x8822,
        PropertyTagExifSpectralSense = 0x8824,
        PropertyTagGpsIFD = 0x8825,
        PropertyTagExifISOSpeed = 0x8827,
        PropertyTagExifOECF = 0x8828,
        PropertyTagExifVer = 0x9000,
        PropertyTagExifDTOrig = 0x9003,
        PropertyTagExifDTDigitized = 0x9004,
        PropertyTagExifCompConfig = 0x9101,
        PropertyTagExifCompBPP = 0x9102,
        PropertyTagExifShutterSpeed = 0x9201,
        PropertyTagExifAperture = 0x9202,
        PropertyTagExifBrightness = 0x9203,
        PropertyTagExifExposureBias = 0x9204,
        PropertyTagExifMaxAperture = 0x9205,
        PropertyTagExifSubjectDist = 0x9206,
        PropertyTagExifMeteringMode = 0x9207,
        PropertyTagExifLightSource = 0x9208,
        PropertyTagExifFlash = 0x9209,
        PropertyTagExifFocalLength = 0x920A,
        PropertyTagExifMakerNote = 0x927C,
        PropertyTagExifUserComment = 0x9286,
        PropertyTagExifDTSubsec = 0x9290,
        PropertyTagExifDTOrigSS = 0x9291,
        PropertyTagExifDTDigSS = 0x9292,
        PropertyTagExifFPXVer = 0xA000,
        PropertyTagExifColorSpace = 0xA001,
        PropertyTagExifPixXDim = 0xA002,
        PropertyTagExifPixYDim = 0xA003,
        PropertyTagExifRelatedWav = 0xA004,
        PropertyTagExifInterop = 0xA005,
        PropertyTagExifFlashEnergy = 0xA20B,
        PropertyTagExifSpatialFR = 0xA20C,
        PropertyTagExifFocalXRes = 0xA20E,
        PropertyTagExifFocalYRes = 0xA20F,
        PropertyTagExifFocalResUnit = 0xA210,
        PropertyTagExifSubjectLoc = 0xA214,
        PropertyTagExifExposureIndex = 0xA215,
        PropertyTagExifSensingMethod = 0xA217,
        PropertyTagExifFileSource = 0xA300,
        PropertyTagExifSceneType = 0xA301,
        PropertyTagExifCfaPattern = 0xA302,
    }

另一個Enum PropertyTagType, 用作表示該metadata 的value type.

public enum PropertyTagType
    {
        PixelFormat4bppIndexed = 0,
        Byte = 1,
        ASCII = 2,
        Short = 3,
        Long = 4,
        Rational = 5,
        Undefined = 7,
        SLONG = 9,
        SRational = 10
    }

因為metadata 中會有份數, 所以利用Internal class Fraction, 表示.

internal class Fraction
    {
        #region Class constructor
        public Fraction(int numerator, int denumerator)
        {
            Numerator = numerator;
            Denumerator = denumerator;
        }

        public Fraction(uint numerator, uint denumerator)
        {
            Numerator = Convert.ToInt32(numerator);
            Denumerator = Convert.ToInt32(denumerator);
        }

        public Fraction(int numerator)
        {
            Numerator = numerator;
            Denumerator = 1;
        }
        #endregion

        #region Numerator
        private int numerator;
        public int Numerator
        {
            get
            {
                return numerator;
            }
            set
            {
                numerator = value;
            }
        }
        #endregion

        #region Denumerator
        private int denumerator;
        public int Denumerator
        {
            get
            {
                return denumerator;
            }
            set
            {
                denumerator = value;
            }
        }
        #endregion

        #region ToString override
        public override string ToString()
        {
            if (Denumerator == 1)
                return String.Format("{0}", Numerator);
            else
                return String.Format("{0}/{1}", Numerator, Denumerator);
        }
        #endregion
    }

Class PropertyTypeValue, 用作metadata 的轉換. 因為取出來的raw value 為Byte Array, 所以須要透過PropertyType 決定如何轉換成presentable 的格式.

public class PropertyTagValue
    {
        public static object GetValueObject(PropertyItem property)
        {
            if (property == null)
                return null;
            switch ((PropertyTagType)property.Type)
            {
                //ASCII
                case PropertyTagType.ASCII:
                    ASCIIEncoding encoding = new ASCIIEncoding();
                    return encoding.GetString(property.Value, 0, property.Len - 1);
                //BYTE
                case PropertyTagType.Byte:
                    if (property.Len == 1)
                        return property.Value[0];
                    else
                        return property.Value;
                //LONG
                case PropertyTagType.Long:
                    uint[] resultLong = new uint[property.Len / 4];
                    for (int i = 0; i < resultLong.Length; i++)
                        resultLong[i] = BitConverter.ToUInt32(property.Value, i * 4);
                    if (resultLong.Length == 1)
                        return resultLong[0];
                    else
                        return resultLong;
                //SHORT
                case PropertyTagType.Short:
                    ushort[] resultShort = new ushort[property.Len / 2];
                    for (int i = 0; i < resultShort.Length; i++)
                        resultShort[i] = BitConverter.ToUInt16(property.Value, i * 2);
                    if (resultShort.Length == 1)
                        return resultShort[0];
                    else
                        return resultShort;
                //SLONG
                case PropertyTagType.SLONG:
                    int[] resultSLong = new int[property.Len / 4];
                    for (int i = 0; i < resultSLong.Length; i++)
                        resultSLong[i] = BitConverter.ToInt32(property.Value, i * 4);
                    if (resultSLong.Length == 1)
                        return resultSLong[0];
                    else
                        return resultSLong;
                //RATIONAL
                case PropertyTagType.Rational:
                    Fraction[] resultRational = new Fraction[property.Len / 8];
                    uint uNumerator;
                    uint uDenumerator;
                    for (int i = 0; i < resultRational.Length; i++)
                    {
                        uNumerator = BitConverter.ToUInt32(property.Value, i * 8);
                        uDenumerator = BitConverter.ToUInt32(property.Value, i * 8 + 4);
                        resultRational[i] = new Fraction(uNumerator, uDenumerator);
                    }
                    if (resultRational.Length == 1)
                        return resultRational[0];
                    else
                        return resultRational;
                //SRATIONAL
                case PropertyTagType.SRational:
                    Fraction[] resultSRational = new Fraction[property.Len / 8];
                    int sNumerator;
                    int sDenumerator;
                    for (int i = 0; i < resultSRational.Length; i++)
                    {
                        sNumerator = BitConverter.ToInt32(property.Value, i * 8);
                        sDenumerator = BitConverter.ToInt32(property.Value, i * 8 + 4);
                        resultSRational[i] = new Fraction(sNumerator, sDenumerator);
                    }
                    if (resultSRational.Length == 1)
                        return resultSRational[0];
                    else
                        return resultSRational;
                //UNDEFINE
                default:
                    if (property.Len == 1)
                        return property.Value[0];
                    else
                        return property.Value;
            }
        }
    }

最後用Class Photo 將讀取圖片, 並將所有metadata取出來.

 public class Photo
    {
        private Image _image;
        public Photo(string path)
        {
            this._image = new Bitmap(path);
        }

        public Dictionary<string, object> GetMetadata()
        {
            Dictionary<string, object> result = new Dictionary<string, object>();
            List<PropertyItem> propertyItems = this._image.PropertyItems.ToList();
            propertyItems.ForEach(delegate (PropertyItem item)
            {
                PropertyTagID key = (PropertyTagID)item.Id;
                object value = PropertyTagValue.GetValueObject(item);
                string displayKey = Enum.GetName(typeof(PropertyTagID), key);
                if (!String.IsNullOrEmpty(displayKey))
                {
                    result.Add(displayKey, value);
                }
            });
            return result;
        }
    }

利用WPF implement的UI 示範如下.

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btmImage_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog data = new OpenFileDialog();
            data.Title = "Open First Image File";
            data.Filter = "JPG files (*.jpg*)|*.jpg";
            data.FilterIndex = 1;
            data.RestoreDirectory = true;
            if (data.ShowDialog().Value)
            {
                string filePath = data.FileName;
                txtImgPath.Text = filePath;
                Photo photo = new Photo(filePath);
                Dictionary<string, object> metadata = photo.GetMetadata();
                DataGridMetadata.ItemsSource = metadata;
            }
        }
    }

執行時, 見到圖片及相關metadata, 是為成功.

Reference

About C.H. Ling 262 Articles
a .net / Java developer from Hong Kong and currently located in United Kingdom. Thanks for Google because it solve many technical problems so I build this blog as return. Besides coding and trying advance technology, hiking and traveling is other favorite to me, so I will write down something what I see and what I feel during it. Happy reading!!!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.