如何添加 EXIF 信息以在 .NET 中对图像进行地理标记?
How can I add EXIF information to geotag an Image in .NET?
我在 .NET 中有一个图像。如何通过使用 EXIF 数据对生成图像的经纬度进行编码来对图像进行地理标记,而不需要使用外部库?
在 .NET 2.0 及更高版本中,可以使用 PropertyItems 将 EXIF 信息附加到图像,这些信息转换为单独的 EXIF 字段。这些字段的详细信息可以在 the EXIF 2.3 standard, but we only need five of them to geotag an image. The sample C# code below requires references to System.Drawing, System.Drawing.Imaging, and System.IO. To test it, simply use the line below. You can verify that the image has been correctly geotagged by examining it with this tool(或许多其他字段之一)中找到。
Geotag(new Bitmap(@"C:\path\to\image.jpg"), 34, -118)
.Save(@"C:\path\to\geotagged.jpg", ImageFormat.Jpeg);
下面的代码可能看起来很奇怪,因为 PropertyItem 被重新用于创建新的 PropertyItem。这并不明显,因为改变现有的 PropertyItem(它是一个 class 而不是一个结构)似乎会影响现有的 属性。然而,事实并非如此,这个 hack 是必要的,因为 PropertyItem 没有 public 构造函数。
static Image Geotag(Image original, double lat, double lng)
{
// These constants come from the CIPA DC-008 standard for EXIF 2.3
const short ExifTypeByte = 1;
const short ExifTypeAscii = 2;
const short ExifTypeRational = 5;
const int ExifTagGPSVersionID = 0x0000;
const int ExifTagGPSLatitudeRef = 0x0001;
const int ExifTagGPSLatitude = 0x0002;
const int ExifTagGPSLongitudeRef = 0x0003;
const int ExifTagGPSLongitude = 0x0004;
char latHemisphere = 'N';
if (lat < 0)
{
latHemisphere = 'S';
lat = -lat;
}
char lngHemisphere = 'E';
if (lng < 0)
{
lngHemisphere = 'W';
lng = -lng;
}
MemoryStream ms = new MemoryStream();
original.Save(ms, ImageFormat.Jpeg);
ms.Seek(0, SeekOrigin.Begin);
Image img = Image.FromStream(ms);
AddProperty(img, ExifTagGPSVersionID, ExifTypeByte, new byte[] { 2, 3, 0, 0 });
AddProperty(img, ExifTagGPSLatitudeRef, ExifTypeAscii, new byte[] { (byte)latHemisphere, 0 });
AddProperty(img, ExifTagGPSLatitude, ExifTypeRational, ConvertToRationalTriplet(lat));
AddProperty(img, ExifTagGPSLongitudeRef, ExifTypeAscii, new byte[] { (byte)lngHemisphere, 0 });
AddProperty(img, ExifTagGPSLongitude, ExifTypeRational, ConvertToRationalTriplet(lng));
return img;
}
static byte[] ConvertToRationalTriplet(double value)
{
int degrees = (int)Math.Floor(value);
value = (value - degrees) * 60;
int minutes = (int)Math.Floor(value);
value = (value - minutes) * 60 * 100;
int seconds = (int)Math.Round(value);
byte[] bytes = new byte[3 * 2 * 4]; // Degrees, minutes, and seconds, each with a numerator and a denominator, each composed of 4 bytes
int i = 0;
Array.Copy(BitConverter.GetBytes(degrees), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(1), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(minutes), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(1), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(seconds), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(100), 0, bytes, i, 4);
return bytes;
}
static void AddProperty(Image img, int id, short type, byte[] value)
{
PropertyItem pi = img.PropertyItems[0];
pi.Id = id;
pi.Type = type;
pi.Len = value.Length;
pi.Value = value;
img.SetPropertyItem(pi);
}
我在 .NET 中有一个图像。如何通过使用 EXIF 数据对生成图像的经纬度进行编码来对图像进行地理标记,而不需要使用外部库?
在 .NET 2.0 及更高版本中,可以使用 PropertyItems 将 EXIF 信息附加到图像,这些信息转换为单独的 EXIF 字段。这些字段的详细信息可以在 the EXIF 2.3 standard, but we only need five of them to geotag an image. The sample C# code below requires references to System.Drawing, System.Drawing.Imaging, and System.IO. To test it, simply use the line below. You can verify that the image has been correctly geotagged by examining it with this tool(或许多其他字段之一)中找到。
Geotag(new Bitmap(@"C:\path\to\image.jpg"), 34, -118)
.Save(@"C:\path\to\geotagged.jpg", ImageFormat.Jpeg);
下面的代码可能看起来很奇怪,因为 PropertyItem 被重新用于创建新的 PropertyItem。这并不明显,因为改变现有的 PropertyItem(它是一个 class 而不是一个结构)似乎会影响现有的 属性。然而,事实并非如此,这个 hack 是必要的,因为 PropertyItem 没有 public 构造函数。
static Image Geotag(Image original, double lat, double lng)
{
// These constants come from the CIPA DC-008 standard for EXIF 2.3
const short ExifTypeByte = 1;
const short ExifTypeAscii = 2;
const short ExifTypeRational = 5;
const int ExifTagGPSVersionID = 0x0000;
const int ExifTagGPSLatitudeRef = 0x0001;
const int ExifTagGPSLatitude = 0x0002;
const int ExifTagGPSLongitudeRef = 0x0003;
const int ExifTagGPSLongitude = 0x0004;
char latHemisphere = 'N';
if (lat < 0)
{
latHemisphere = 'S';
lat = -lat;
}
char lngHemisphere = 'E';
if (lng < 0)
{
lngHemisphere = 'W';
lng = -lng;
}
MemoryStream ms = new MemoryStream();
original.Save(ms, ImageFormat.Jpeg);
ms.Seek(0, SeekOrigin.Begin);
Image img = Image.FromStream(ms);
AddProperty(img, ExifTagGPSVersionID, ExifTypeByte, new byte[] { 2, 3, 0, 0 });
AddProperty(img, ExifTagGPSLatitudeRef, ExifTypeAscii, new byte[] { (byte)latHemisphere, 0 });
AddProperty(img, ExifTagGPSLatitude, ExifTypeRational, ConvertToRationalTriplet(lat));
AddProperty(img, ExifTagGPSLongitudeRef, ExifTypeAscii, new byte[] { (byte)lngHemisphere, 0 });
AddProperty(img, ExifTagGPSLongitude, ExifTypeRational, ConvertToRationalTriplet(lng));
return img;
}
static byte[] ConvertToRationalTriplet(double value)
{
int degrees = (int)Math.Floor(value);
value = (value - degrees) * 60;
int minutes = (int)Math.Floor(value);
value = (value - minutes) * 60 * 100;
int seconds = (int)Math.Round(value);
byte[] bytes = new byte[3 * 2 * 4]; // Degrees, minutes, and seconds, each with a numerator and a denominator, each composed of 4 bytes
int i = 0;
Array.Copy(BitConverter.GetBytes(degrees), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(1), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(minutes), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(1), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(seconds), 0, bytes, i, 4); i += 4;
Array.Copy(BitConverter.GetBytes(100), 0, bytes, i, 4);
return bytes;
}
static void AddProperty(Image img, int id, short type, byte[] value)
{
PropertyItem pi = img.PropertyItems[0];
pi.Id = id;
pi.Type = type;
pi.Len = value.Length;
pi.Value = value;
img.SetPropertyItem(pi);
}