Example #1
0
        public static void CompareJpegMetaData(JpegMetaData meta1, JpegMetaData meta2, string filename, bool GpsIfdExists, bool ExifIfdExists = true)
        {
            Debug.WriteLine("file: " + filename);
            TestUtil.AreEqual(meta1.App1Data, meta2.App1Data, filename + " App1 data");
            TestUtil.AreEqual(meta1.PrimaryIfd, meta2.PrimaryIfd, filename + "Primary IFD");

            if (ExifIfdExists)
            {
                TestUtil.AreEqual(meta1.ExifIfd, meta2.ExifIfd, filename + "Exif IFD");
            }
            else
            {
                Assert.IsNull(meta1.ExifIfd);
                Assert.IsNull(meta2.ExifIfd);
            }

            if (GpsIfdExists)
            {
                TestUtil.AreEqual(meta1.GpsIfd, meta2.GpsIfd, filename + "Gps IFD");
            }
            else
            {
                Assert.IsNull(meta1.GpsIfd);
                Assert.IsNull(meta2.GpsIfd);
            }
        }
 /// <summary>
 /// Set exif data to Jpeg file asynchronously.
 /// Mess of this library; everything are caused by crazy Exif format.
 /// Note that this function overwrites ALL Exif data in the given image.
 /// </summary>
 /// <param name="OriginalImage">Target image</param>
 /// <param name="MetaData">An Exif data which will be added to the image.</param>
 /// <returns>New image</returns>
 public static async Task<byte[]> SetMetaDataAsync(byte[] OriginalImage, JpegMetaData MetaData)
 {
     // It seems thrown exceptions will be raised by this "async" ...
     return await Task<byte[]>.Run(async () =>
     {
         return SetMetaData(OriginalImage, MetaData);
     }).ConfigureAwait(false);
 }
        /// <summary>
        /// Set exif data to Jpeg file; It blocks thread.
        /// Mess of this library; everything are caused by crazy Exif format.
        /// Note that this function overwrites ALL Exif data in the given image.
        /// </summary>
        /// <param name="OriginalImage">Target image</param>
        /// <param name="MetaData">An Exif data which will be added to the image.</param>
        /// <returns>New image</returns>
        public static byte[] SetMetaData(byte[] OriginalImage, JpegMetaData MetaData)
        {
            var OriginalMetaData = JpegMetaDataParser.ParseImage(OriginalImage);
            // Debug.WriteLine("Original image size: " + OriginalImage.Length.ToString("X"));

            // It seems there's a bug handling little endian jpeg image on WP OS.
            var OutputImageMetadataEndian = Definitions.Endian.Big;

            var App0Offset = 0;

            if (Util.GetUIntValue(OriginalImage, 2, 2, Definitions.Endian.Big) == Definitions.APP0_MARKER)
            {
                App0Offset = 2 + (int)Util.GetUIntValue(OriginalImage, 4, 2, Definitions.Endian.Big);
                // Debug.WriteLine("APP0 marker detected. " + App0Offset);
            }

            // Note: App1 size and ID are fixed to Big endian.
            UInt32 OriginalApp1DataSize = Util.GetUIntValue(OriginalImage, 4 + App0Offset, 2, Definitions.Endian.Big);
            // Debug.WriteLine("Original App1 size: " + OriginalApp1DataSize.ToString("X"));

            // 0-5 byte: Exif identify code. E, x, i, f, \0, \0
            // 6-13 byte: TIFF header. endian, 0x002A, Offset to Primary IFD in 4 bytes. generally, 8 is set as the offset.
            var OriginalApp1Data = new byte[OriginalApp1DataSize];
            Array.Copy(OriginalImage, 6 + App0Offset, OriginalApp1Data, 0, (int)OriginalApp1DataSize);

            var NewApp1Data = CreateApp1Data(OriginalMetaData, MetaData, OriginalApp1Data, OutputImageMetadataEndian);

            // Only size of App1 data is different.
            var NewImage = new byte[OriginalImage.Length - OriginalApp1DataSize + NewApp1Data.Length];
            // Debug.WriteLine("New image size: " + NewImage.Length.ToString("X"));

            // Copy SOI, App1 marker
            var newImageHeader = new byte[4] { 0xFF, 0xD8, 0xFF, 0xE1 };
            Array.Copy(newImageHeader, 0, NewImage, 0, 2 + 2);

            // Important note again: App 1 data size is stored in Big endian.
            var App1SizeData = Util.ToByte((UInt32)NewApp1Data.Length, 2, Definitions.Endian.Big);
            Array.Copy(App1SizeData, 0, NewImage, 4, 2);

            // After that, copy App1 data to new image.
            Array.Copy(NewApp1Data, 0, NewImage, 6, NewApp1Data.Length);

            // At last, copy body from original image.
            Array.Copy(OriginalImage, 2 + 2 + 2 + App0Offset + (int)OriginalApp1DataSize, NewImage, 2 + 2 + 2 + NewApp1Data.Length, OriginalImage.Length - 2 - 2 - 2 - App0Offset - (int)OriginalApp1DataSize);

            return NewImage;
        }
 /// <summary>
 /// Set exif data to Jpeg file asynchronously.
 /// Mess of this library; everything are caused by crazy Exif format.
 /// Note that this function overwrites ALL Exif data in the given image.
 /// </summary>
 /// <param name="OriginalImage">Target image. This will be disposed.</param>
 /// <param name="MetaData">An Exif data which will be added to the image.</param>
 /// <returns>New image on new stream.</returns>
 public static async Task<Stream> SetMetaDataAsync(Stream OriginalImage, JpegMetaData MetaData)
 {
     return await Task<Stream>.Run(async () =>
     {
         return SetMetaData(OriginalImage, MetaData);
     }).ConfigureAwait(false);
 }
        /// <summary>
        /// Create app1 data section in byte array.
        /// Needs many informations....
        /// </summary>
        /// <param name="OriginalMetaData">metadata from original image.</param>
        /// <param name="NewMetaData">metadata which will be recorded in app1 data section</param>
        /// <param name="OriginalApp1Data">Original app1 data. Other than Exif meta data will be copied to new one</param>
        /// <param name="OutputImageMetadataEndian">Endian which will be used in Exif meta data. for WP OS, Big endian is recommended.</param>
        /// <returns></returns>
        private static byte[] CreateApp1Data(JpegMetaData OriginalMetaData, JpegMetaData NewMetaData, byte[] OriginalApp1Data, Definitions.Endian OutputImageMetadataEndian)
        {

            if (NewMetaData.ExifIfd != null && !NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.EXIF_IFD_POINTER_TAG))
            {
                // Add Exif IFD section's pointer entry as dummy.
                NewMetaData.PrimaryIfd.Entries.Add(
                    Definitions.EXIF_IFD_POINTER_TAG,
                    new Entry()
                    {
                        Tag = Definitions.EXIF_IFD_POINTER_TAG,
                        Type = Entry.EntryType.Long,
                        Count = 1,
                        value = new byte[] { 0, 0, 0, 0 }
                    });
            }

            if (NewMetaData.GpsIfd != null && !NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.GPS_IFD_POINTER_TAG))
            {
                // Add GPS IFD section's pointer entry as dummy
                NewMetaData.PrimaryIfd.Entries.Add(
                    Definitions.GPS_IFD_POINTER_TAG,
                    new Entry()
                    {
                        Tag = Definitions.GPS_IFD_POINTER_TAG,
                        Type = Entry.EntryType.Long,
                        Count = 1,
                        value = new byte[] { 0, 0, 0, 0 }
                    });
            }


            byte[] primaryIfd = IfdComposer.ComposeIfdsection(NewMetaData.PrimaryIfd, OutputImageMetadataEndian);
            byte[] exifIfd = new byte[] { };
            byte[] gpsIfd = new byte[] { };
            if (NewMetaData.ExifIfd != null)
            {
                exifIfd = IfdComposer.ComposeIfdsection(NewMetaData.ExifIfd, OutputImageMetadataEndian);
            }
            if (NewMetaData.GpsIfd != null)
            {
                gpsIfd = IfdComposer.ComposeIfdsection(NewMetaData.GpsIfd, OutputImageMetadataEndian);
            }

            // Debug.WriteLine("Size fixed. Primary: " + primaryIfd.Length.ToString("X") + " exif: " + exifIfd.Length.ToString("X") + " gps: " + gpsIfd.Length.ToString("X"));

            // after size fixed, set each IFD sections' offset.
            NewMetaData.PrimaryIfd.Offset = 8; // fixed value             

            // now it's possible to calcurate pointer to Exif/GPS IFD
            var exifIfdPointer = 8 + primaryIfd.Length;
            var gpsIfdPointer = 8 + primaryIfd.Length + exifIfd.Length;

            if (NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.EXIF_IFD_POINTER_TAG))
            {
                NewMetaData.PrimaryIfd.Entries.Remove(Definitions.EXIF_IFD_POINTER_TAG);
            }

            var exifIfdPointerEntry = new Entry()
            {
                Tag = Definitions.EXIF_IFD_POINTER_TAG,
                Type = Entry.EntryType.Long,
                Count = 1,
            };
            exifIfdPointerEntry.UIntValues = new UInt32[] { (UInt32)exifIfdPointer };
            NewMetaData.PrimaryIfd.Entries.Add(Definitions.EXIF_IFD_POINTER_TAG, exifIfdPointerEntry);

            if (NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.GPS_IFD_POINTER_TAG))
            {
                NewMetaData.PrimaryIfd.Entries.Remove(Definitions.GPS_IFD_POINTER_TAG);
            }

            var gpsIfdPointerEntry = new Entry()
            {
                Tag = Definitions.GPS_IFD_POINTER_TAG,
                Type = Entry.EntryType.Long,
                Count = 1,
            };
            gpsIfdPointerEntry.UIntValues = new UInt32[] { (UInt32)gpsIfdPointer };
            NewMetaData.PrimaryIfd.Entries.Add(Definitions.GPS_IFD_POINTER_TAG, gpsIfdPointerEntry);

            var nextIfdPointer = 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length;
            NewMetaData.PrimaryIfd.NextIfdPointer = (UInt32)nextIfdPointer;

            // finally, create byte array again
            primaryIfd = IfdComposer.ComposeIfdsection(NewMetaData.PrimaryIfd, OutputImageMetadataEndian);


            if (NewMetaData.ExifIfd != null)
            {
                NewMetaData.ExifIfd.Offset = 8 + (UInt32)primaryIfd.Length;
                exifIfd = IfdComposer.ComposeIfdsection(NewMetaData.ExifIfd, OutputImageMetadataEndian);
            }


            if (NewMetaData.GpsIfd != null)
            {
                NewMetaData.GpsIfd.Offset = 8 + (UInt32)primaryIfd.Length + (UInt32)exifIfd.Length;
                gpsIfd = IfdComposer.ComposeIfdsection(NewMetaData.GpsIfd, OutputImageMetadataEndian);
            }

            // 1st IFD data (after primary IFD data) should be kept
            var Original1stIfdData = new byte[OriginalApp1Data.Length - OriginalMetaData.PrimaryIfd.NextIfdPointer];
            Array.Copy(OriginalApp1Data, (int)OriginalMetaData.PrimaryIfd.NextIfdPointer, Original1stIfdData, 0, Original1stIfdData.Length);

            // Build App1 section. From "Exif\0\0" to end of thumbnail data (1st IFD)
            // Exif00 + TIFF header + 3 IFD sections (Primary, Exif, GPS) + 1st IFD data from original data
            // new app1 size may overfrow from 0xFFFF bytes. in this case, it should be limited up to 0xFFFF byte.
            var NewApp1DataSize = Math.Min(0xFFFF, 6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length + Original1stIfdData.Length);
            var NewApp1DataOverflowSize = 0;
            if (NewApp1DataSize == 0xFFFF)
            {
                NewApp1DataOverflowSize = (6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length + Original1stIfdData.Length) - 0xFFFF;
                Debug.WriteLine("App1 overflow size: " + NewApp1DataOverflowSize);
            }
            var NewApp1Data = new byte[NewApp1DataSize];
            // var NewApp1Data = new byte[6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length];
            // Debug.WriteLine("New App1 size: " + NewApp1Data.Length.ToString("X"));

            // Array.Copy(OriginalApp1Data, 0, NewApp1Data, 0, 6 + 8);
            Array.Copy(OriginalApp1Data, 0, NewApp1Data, 0, 6); // only EXIF00 should be copied.

            var endianSection = Util.ToByte(0x4d4d, 2, OutputImageMetadataEndian);
            Array.Copy(endianSection, 0, NewApp1Data, 6, 2);

            var magicNumber = Util.ToByte(0x002A, 2, OutputImageMetadataEndian);
            Array.Copy(magicNumber, 0, NewApp1Data, 8, 2);

            var primaryIfdOffset = Util.ToByte(8, 4, OutputImageMetadataEndian);
            Array.Copy(primaryIfdOffset, 0, NewApp1Data, 10, 4);

            Array.Copy(primaryIfd, 0, NewApp1Data, 6 + 8, primaryIfd.Length);
            Array.Copy(exifIfd, 0, NewApp1Data, 6 + 8 + primaryIfd.Length, exifIfd.Length);
            Array.Copy(gpsIfd, 0, NewApp1Data, 6 + 8 + primaryIfd.Length + exifIfd.Length, gpsIfd.Length);
            Array.Copy(Original1stIfdData, 0, NewApp1Data, 6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length, Original1stIfdData.Length - NewApp1DataOverflowSize);

            return NewApp1Data;
        }
        /// <summary>
        /// Set exif data to Jpeg file; It blocks thread.
        /// Mess of this library; everything are caused by crazy Exif format.
        /// Note that this function overwrites ALL Exif data in the given image.
        /// </summary>
        /// <param name="OriginalImage">Target image. This will be disposed.</param>
        /// <param name="MetaData">An Exif data which will be added to the image.</param>
        /// <returns>New image on new stream.</returns>
        public static Stream SetMetaData(Stream OriginalImage, JpegMetaData MetaData)
        {
            OriginalImage.Seek(0, SeekOrigin.Begin);

            var OriginalMetaData = JpegMetaDataParser.ParseImage(OriginalImage);
            var OutputImageMetaDataEndian = Definitions.Endian.Big;

            var endian = Definitions.Endian.Big;

            OriginalImage.Position = 2;
            var FirstMarker = new byte[2];
            OriginalImage.Read(FirstMarker, 0, 2);
            var App0Offset = 0;
            if (Util.GetUIntValue(FirstMarker, 0, 2, endian) == Definitions.APP0_MARKER)
            {
                var App0SizeData = new byte[2];
                OriginalImage.Read(App0SizeData, 0, 2);
                App0Offset = 2 + (int)Util.GetUIntValue(App0SizeData, 0, 2, endian);
            }

            OriginalImage.Position = 4 + App0Offset;
            var app1sizeData = new byte[2];
            OriginalImage.Read(app1sizeData, 0, 2);

            var OriginalApp1Size = Util.GetUIntValue(app1sizeData, 0, 2, endian);
            OriginalImage.Position = 6 + App0Offset;

            var OriginalApp1Data = new byte[OriginalApp1Size];
            OriginalImage.Read(OriginalApp1Data, 0, (int)OriginalApp1Size);

            var NewApp1Data = CreateApp1Data(OriginalMetaData, MetaData, OriginalApp1Data, OutputImageMetaDataEndian);

            // first 6 bytes;
            var NewImageHeader = new byte[6] { 0xFF, 0xD8, 0xFF, 0xE1, 0x0, 0x0 }; // No APP0 section will be stored on new image.

            // Important note again: App 1 data size is stored in Big endian.
            var App1SizeData = Util.ToByte((UInt32)NewApp1Data.Length, 2, Definitions.Endian.Big);
            Array.Copy(App1SizeData, 0, NewImageHeader, 4, 2);

            Util.DumpByteArrayAll(NewImageHeader);

            var NewImage = new MemoryStream();
            NewImage.Write(NewImageHeader, 0, 6);

            NewImage.Write(NewApp1Data, 0, NewApp1Data.Length);

            // forward pointer to start position of original body data
            OriginalImage.Position = 6 + App0Offset + OriginalApp1Size;

            // Copy all other data from original image.
            int BufSize = 4096;
            byte[] buffer = new byte[BufSize];
            int numBytes;
            int count = 0;
            while ((numBytes = OriginalImage.Read(buffer, 0, BufSize)) > 0)
            {
                NewImage.Write(buffer, 0, numBytes);
                count += numBytes;
            }

            NewImage.Seek(0, SeekOrigin.Begin);

            // Debug.WriteLine("image size: " + NewImage.Length);
            OriginalImage.Dispose();
            return NewImage;
        }
Example #7
0
        /// <summary>
        /// Parse given data, App1 section data.
        /// </summary>
        /// <param name="App1Data">byte array of app1 data which starts from TIFF header.</param>
        /// <returns>all meta data.</returns>
        private static JpegMetaData ParseApp1Data(byte[] App1Data)
        {

            var exif = new JpegMetaData();
            exif.App1Data = App1Data;

            // Check TIFF header.
            var MetaDataEndian = Definitions.Endian.Little;
            if (Util.GetUIntValue(App1Data, 0, 2, Definitions.Endian.Little) == Definitions.TIFF_LITTLE_ENDIAN)
            {
                MetaDataEndian = Definitions.Endian.Little;
                // Debug.WriteLine("This metadata in Little endian");
            }
            else if (Util.GetUIntValue(App1Data, 0, 2, Definitions.Endian.Little) == Definitions.TIFF_BIG_ENDIAN)
            {
                MetaDataEndian = Definitions.Endian.Big;
                // Debug.WriteLine("This metadata in Big endian");
            }
            else
            {
                throw new UnsupportedFileFormatException("Currently, only little endian is supported.");
            }

            if (Util.GetUIntValue(App1Data, 2, 2, MetaDataEndian) != Definitions.TIFF_IDENTIFY_CODE)
            {
                throw new UnsupportedFileFormatException("TIFF identify code (0x2A00) couldn't find.");
            }

            // find out a pointer to 1st IFD
            var PrimaryIfdPointer = Util.GetUIntValue(App1Data, 4, 4, MetaDataEndian);
            // Debug.WriteLine("Primary IFD pointer: " + PrimaryIfdPointer);

            // parse primary (0th) IFD section
            exif.PrimaryIfd = Parser.IfdParser.ParseIfd(App1Data, PrimaryIfdPointer, MetaDataEndian);
            // Debug.WriteLine("Primary offset: " + exif.PrimaryIfd.Offset + " length: " + exif.PrimaryIfd.Length + " next ifd: " + exif.PrimaryIfd.NextIfdPointer);

            // parse Exif IFD section
            if (exif.PrimaryIfd.Entries.ContainsKey(Definitions.EXIF_IFD_POINTER_TAG))
            {
                exif.ExifIfd = Parser.IfdParser.ParseIfd(App1Data, exif.PrimaryIfd.Entries[Definitions.EXIF_IFD_POINTER_TAG].UIntValues[0], MetaDataEndian);
                // Debug.WriteLine("Exif offset: " + exif.ExifIfd.Offset + " length: " + exif.ExifIfd.Length);
            }

            // parse GPS data.
            if (exif.PrimaryIfd.Entries.ContainsKey(Definitions.GPS_IFD_POINTER_TAG))
            {
                exif.GpsIfd = Parser.IfdParser.ParseIfd(App1Data, exif.PrimaryIfd.Entries[Definitions.GPS_IFD_POINTER_TAG].UIntValues[0], MetaDataEndian);
                // Debug.WriteLine("GPS offset: " + exif.GpsIfd.Offset + " length: " + exif.GpsIfd.Length);
            }

            return exif;
        }
Example #8
0
 public static void IsGpsDataAdded(JpegMetaData original, JpegMetaData added)
 {
     if (original.ExifIfd != null && added.ExifIfd != null)
     {
         TestUtil.AreEqual(original.ExifIfd, added.ExifIfd, "Exif IFD");
     }
     Assert.IsTrue(added.PrimaryIfd.Entries.ContainsKey(Definitions.GPS_IFD_POINTER_TAG));
     Assert.IsNotNull(added.GpsIfd);
 }
Example #9
0
 /// <summary>
 /// Remove GPS information from metadata.
 /// </summary>
 /// <param name="meta">Metadata with geotag</param>
 /// <returns></returns>
 public static JpegMetaData RemoveGeoinfo(JpegMetaData meta)
 {
     if (meta.PrimaryIfd.Entries.ContainsKey(Definitions.GPS_IFD_POINTER_TAG))
     {
         meta.PrimaryIfd.Entries.Remove(Definitions.GPS_IFD_POINTER_TAG);
     }
     meta.GpsIfd = null;
     return meta;
 }