After I finished the program for geotagging, I had the idea to write this article - so that fewer people attacked the same rake, since there is not so much sensible information on these issues.
So, I am not going to tell you what
geotagging or
EXIF is , you can read about it in Wikipedia. But how to make a program in C #, which would read and write data to EXIF and I'm going to tell.
We will work with photo metadata, as, in my opinion, by the simplest method - via
JpegBitmapDecoder , for this you will need to connect several modules
using System.IO;
using System.Globalization;
using System.Windows.Media.Imaging;
* This source code was highlighted with Source Code Highlighter .
To get started, just open the photo file:
FileStream Foto = File .Open(s, FileMode .Open, FileAccess.Read); // s
BitmapDecoder decoder = JpegBitmapDecoder.Create(Foto, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); //"" decoder
BitmapMetadata TmpImgEXIF = (BitmapMetadata)decoder.Frames[0].Metadata.Clone(); //
* This source code was highlighted with Source Code Highlighter .
It is often necessary to get the date and time of the shot. If you look at
the EXIF specification , then there are several dates (the date and time of taking a picture, creating a file, digitizing, GPS, etc.), and it is still unknown exactly which one of them was recorded by the camera, and which one was ignored. But it does not stand on this especially zamorachivatsya - everything is simple.
Bitmapmetadatacontains properties for obtaining the date and time of shooting, as well as some other parameters (for example, the camera model).
DateTime DateOfShot = Convert .ToDateTime(TmpImgEXIF.DateTaken);
* This source code was highlighted with Source Code Highlighter .
Now let's go directly to the geotag record. To work with any metadata (not only with EXIF), BitmapMetadata contains the
SetQuery and
GetQuery methods . They take as a parameter a query string that determines which metadata field to read or write. There are also
RemoveQuery to remove the field. Let's start with a simple one: add to EXIF a note that we have a northern latitude. In EXIF, it corresponds to the GPS section field with ID = 1, and in C #, the query "/ app1 / ifd / gps / {ushort = 1}" (where to get these queries from then on) and the data type string. If the northern latitude is written down “N”, and the southern one - “S”:
TmpImgEXIF.SetQuery( "/app1/ifd/gps/{ushort=1}" , "N" );
* This source code was highlighted with Source Code Highlighter .
Similarly with longitude:
TmpImgEXIF.SetQuery( "/app1/ifd/gps/{ushort=3}" , "E" );
* This source code was highlighted with Source Code Highlighter .
And the version should be 2.2.0.0:
TmpImgEXIF.SetQuery( "/app1/ifd/gps/{ushort=0}" , "2.2.0.0" );
* This source code was highlighted with Source Code Highlighter .
Now it's more complicated: add altitude above sea level - this is an optional parameter, but using its example, consider working with the
Rational type. If you open
the EXIF specification , you can see there that the altitude is stored in the Rational format. This type expresses real numbers as a simple fraction, so the number 182.053 will be written as 182053/1000 (or 1820530/10000). To work with this type in C #, you can select the
ulong type and use 4 low bytes to store the numerator, and 4 high bytes for the denominator. Here is the function for converting double type to Rational:
private ulong rational( double a)
{
uint denom = 1000;
uint num = ( uint )(a * denom);
ulong tmp;
tmp = ( ulong )denom << 32;
tmp |= ( ulong )num;
return tmp;
}
* This source code was highlighted with Source Code Highlighter .
We write down the height of 95.3m (in EXIF the height above sea level is stored in meters, for example, in contrast to the track .plt, where it is stored in feet).
TmpImgEXIF.SetQuery( "/app1/ifd/gps/{ushort=2}" , rational(95.3));
* This source code was highlighted with Source Code Highlighter .
Well, now add latitude and longitude. It is stored in the form of three Rational, which express degrees, minutes, seconds and fractions of a second. Degrees and minutes are stored in integers (that is, the denominator is 1, but this is not a prerequisite), and seconds are real. Example: 50⁰30'12.345 ″. In C #, these three numbers need to be combined into an array. Here is an example of recording latitude:
ulong [] t = { rational(50), rational(30), rational(12.345) };
TmpImgEXIF.SetQuery( "/app1/ifd/gps/{ushort=2}" , t);
* This source code was highlighted with Source Code Highlighter .
and longitude:
TmpImgEXIF.SetQuery( "/app1/ifd/gps/{ushort=4}" , t);
* This source code was highlighted with Source Code Highlighter .
Well, perhaps that's all. Sometimes it is necessary to translate coordinates from the format of degrees and fractions of a degree obtained from the track file, label, some Internet service and others, since such a format is more convenient for work, but I think that there shouldn't be any problems with such conversion. Just in case, here are the translation formulas:
Degree = Math .Floor( value );
Minute = Math .Floor((( value - Math .Floor( value )) * 60.0));
Second = ((( value - Math .Floor( value )) * 60.0) - Math .Floor((( value - Math .Floor( value )) * 60.0))) * 60;
* This source code was highlighted with Source Code Highlighter .
So, we have a BitmapMetadata object with new entries added (if there was already something recorded before us, it should automatically be replaced with new values). Now we will create a new snapshot file, into which we will transfer everything, except for metadata, from the first file, and take the metadata for those that we have changed.
JpegBitmapEncoder Encoder = new JpegBitmapEncoder(); // Jpeg
Encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0], decoder.Frames[0].Thumbnail, TmpImgEXIF, decoder.Frames[0].ColorContexts)); // ( )
string NewFileName = s + "+GeoTag.jpg" ; // +GeoTag.jpg
using ( Stream jpegStreamOut = File .Open(NewFileName, FileMode .CreateNew, FileAccess.ReadWrite)) //
{
Encoder.Save(jpegStreamOut); //
}
Foto.Close(); //
* This source code was highlighted with Source Code Highlighter .
Now we have a new jpeg-file, but already with a geotag. Please note that the size of the original file and the new one may differ significantly, this is due to the different ways and parameters of image compression. You can work with any EXIF fields (tags) using the SetQuery and GetQuery methods. GetQuery works in a similar way - accepts a query string, returns a value, what type - look in
the EXIF specification . What request, what parameter answers can be found
here . For example, the function of reading the date:
GetQuery( "/app1/ifd/exif:{uint=36867}" );
* This source code was highlighted with Source Code Highlighter .
You can use your own queries as shown
in this example .