Azure Database and Photos - c#

I have a database set up in Azure, and along with some other data types, it will house some pictures. The column to hold the photos is an Image data type. My insert looks like this (converts the pic to a binary file)
public void Insert()
{
string filepath = "C:\\PictureFolder\\MyPhoto.jpg";
byte[] Pic = File.ReadAllBytes(filepath);
DataContex oDc = new DataContex();
tblShip otblShip = new tblShip;
otblShip.Id = Guid.NewGuid();
otblShip.Name = "Enterprise";
otblShip.Picture = Pic;
oDc.tblShips.InsertOnSubmit(oMyTable);
oDc.SubmitChanges();
}
The insert works, when I check my table in Azure, a binary value is inserted in the Picture column. How do I get it back, and how do I display an actual photo in my WPF interface?

I agree with #CSharpRocks that, since you're already using Azure, it'll be very convenient for you to do some research and store your pictures on a BlobStorage storage account. Storage accounts have many advantages when compared to traditional database storage.
You can find more about storage accounts and how to get started with it here.
But this question is more about how do you retrieve the image and show it in your WPF application, as per you own question. So let's see:
You already have you image stored on your database, so you can fetch the related tblShip entity, using the primary key value (or any other query):
Assuming you have this somewhere in your xaml view:
<Image Name="imgControl" ... />
You could show your image like this:
private void DisplayImageFromDb(Guid id)
{
using (var context = new DataContext())
{
var item = context
.tblShips
.AsNoTracking()
.FirstOrDefault(x => x.Id == id);
if (item == null)
throw new Exception("Image could not be found!");
//Convert the byte[] to a BitmapImage
BitmapImage img = new BitmapImage();
MemoryStream ms = new MemoryStream(item.Picture);
img.BeginInit();
img.StreamSource = ms;
img.EndInit();
//assign the image to the Source property of the Image Control.
imgControl.Source = img;
}
}
This will work ok, but It'll be better if you use a more WPF (MVVM) oriented way. So let's say you have you xaml view, but your are using MVVM. The Image control is therefore bound to a viewmodel property:
<Image Grid.Row="1"
Source="{Binding ImageSource, Converter={StaticResource ByteArrayToImageConverter}}"/>
In your view's resources you declare the converter:
<Windows.Resources>
<local:ByteArrayToImageConverter x:Key="ByteArrayToImageConverter" />
</Window.Resources>
Here is the implementation for the converter:
using System;
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace WpfApp1
{
public class ByteArrayToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;
//Convert the byte[] to a BitmapImage
BitmapImage img = new BitmapImage();
MemoryStream ms = new MemoryStream((byte[])value);
img.BeginInit();
img.StreamSource = ms;
img.EndInit();
return img;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Now you have a clean separation of concerns so your UI is who actually is showing the image and your viewmodel should only load it from the database and manipulate the domain entity:
Your viewmodel must declare ImageSource property used for binding:
private byte[] imageSource;
public byte[] ImageSource
{
get { return imageSource; }
set
{
imageSource = value;
//must implement property changed notifications
OnPropertyChanged(new PropertyChangedEventArgs("ImageSource"));
}
}
After all this refactoring, the method in the viewmodel, that loads the image can be implemented like this:
private void DisplayImageFromDb(int id)
{
using (var context = new DataContext())
{
var item = context
.tblShips
.AsNoTracking()
.FirstOrDefault(x => x.Id == id);
if (item == null)
throw new Exception("Image could not be found!");
//Assign the property and let the binding do the work
ImageSource = item.Picture;
}
}
You'll certainly get the same result, but you now have a very well separated app that will allow to be more easily maintained as it evolves.
Hope this helps!

Everything's hard coded here, but here is how I move a 100 x 100 pixel image
to and from the Db. I do understand there are better ways to store images, but for my purposes, this works great!
public void InsertPhotoToDb()
{
string filepath = "C:\\TFS\\Enterprise.jpg";
byte[] Pic = File.ReadAllBytes(filepath);
ArmadaDataContext oDc = new ArmadaDataContext();
tblPictureTest otblPictureTest = new tblPictureTest();
otblPictureTest.Id = Guid.NewGuid();
otblPictureTest.FileName = "Enterprise";
otblPictureTest.Photo = Pic;
oDc.tblPictureTests.InsertOnSubmit(otblPictureTest);
oDc.SubmitChanges();
oDc = null;
}
private void DisplayImageFromDb()
{
using (var oDc = new ArmadaDataContext())
{
var item = oDc
.tblPictureTests
.FirstOrDefault(x => x.FileName == "Enterprise"); // Retrieves using the filename
// If retrieving using the GUID, use the line below instead.
// .FirstOrDefault(x => x.Id == Guid.Parse("58b44a51-0627-43fe-9563-983aebdcda3a"));
if (item == null)
throw new Exception("Image could not be found!");
//Convert the byte[] to a BitmapImage
BitmapImage img = new BitmapImage();
MemoryStream ms = new MemoryStream(item.Photo.ToArray());
img.BeginInit();
img.StreamSource = ms;
img.EndInit();
//assign the image to the Source property of the Image box in the UI.
imgPhoto.Source = img;
}
}

Related

How can i convert a file .png to a Bitmap? Nothing works for me. Error "'Failed to decode image. The provided image must be a Bitmap.'" Xamarin.forms

I'm working with Xamarin in the part of Xamarin.Forms i need to convert a file ("image.png") to a Bitmap because when project run its enter in "break mode" and show me this message "Java.Lang.IllegalArgumentException: 'Failed to decode image. The provided image must be a Bitmap.'". So i tried to convert the file in many ways like:
1_ Using methods from System.Drawing.Bitmap but it's show this exception "This operation is not supported in this platform"
2_ Cannot use Android.Graphics because i'm working in xamarin.forms not in xamarin.android.
3_ I tried to convert the "image.png" into a base64 or byte[] array and then into a bitmap but the problem is the same like in first problem because to convert from byte[] array or base64 to a Bitmap i have use methods from System.Drawing.Bitmap.
4_ I tried using the library SkiaSharp but i don't have success because i don't found so much information about how to convert .png to SKBitmap and then convert SKBitmap to Bitmap (even i don't know if this is possible).
5_ Finally i converted "image.png" to a "image.bmp" with an app and use the file .bmp in my project but it doesn't work too.
the "image.png" i have to save in a string variable, well that's the idea.
If you have a solution with SkiaSharp o whatever i will glad
EDIT
here is a part of my code, i just save in a variable the image
Icon = "pin_blue.png";
//i can't use a path because in xamarin you have many size from the same
//image for the different size of the screen
EDIT 2 This is my method to show the pins in google maps
private void ShowPins(List<PointsOfInterest> resultPointsOfInterest)
{
if (resultPointsOfInterest != null && resultPointsOfInterest.Any())
{
var location = Geolocation.GetLastKnownLocationAsync();
PointsOfInterest position = new PointsOfInterest();
if (location != null)
{
position.ccsm0166latitud = location.Result.Latitude;
position.ccsm0166longitud = location.Result.Longitude;
}
else {
position = resultPointsOfInterest.First();
}
//Distance = Distance.FromKilometers(new Random().Next(23,35));
Distance = Distance.FromKilometers(3);
Position = new Position(position.ccsm0166latitud, position.ccsm0166longitud);
PinsFiltered= Pins = new List<PinCustom>(resultPointsOfInterest.Select(
x => new PinCustom()
{
Position =
new Position(x.ccsm0166latitud, x.ccsm0166longitud),
Address = x.ccsm0166direccion,
Label = x.ccsm0166nombre,
Type = PinType.Place,
TypePointOfInterest = x.ccsm0166tipositio,
IconBM = Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branch ? PinCustom.ConvertToBitmap("pin_blue.png") :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branch ? "pin_blue.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branchWithExtendedHours ? "pin_black.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branchWithExtendedHours2 ? "pin_black.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branchWithExtendedHours3 ? "pin_black.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.selfServiceTerminal ? "pin_green.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.atmServBox ? "pin_yellow.png" : string.Empty
}).ToList());
}
else
{
Pins = new List<PinCustom>();
}
}
This is the class Pin where i save the image
public class PinCustom : Pin
{
public string TypePointOfInterest { get; set; }
public string Icon { get; set; }
public Bitmap { get; set; }
//Here i create this variable to save a bitmap but i don't know if i do the things well
}
this is the icon .png i want to show in googlemaps
Pin Blue Image
To open your file and convert it to byte array:
string directory = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, Android.OS.Environment.DirectoryDownloads);
// or your image directory, just replace it
var stream = File.OpenWrite(Path.Combine(directory, "image.png"));
byte[] buff = ConvertStreamToByteArray(stream);
stream.Close();
public static byte[] ConvertStreamToByteArray(Stream stream)
{
byte[] byteArray = new byte[16 * 1024];
using (MemoryStream mStream = new MemoryStream())
{
int bit;
while ((bit = stream.Read(byteArray, 0, byteArray.Length)) > 0)
{
mStream.Write(byteArray, 0, bit);
}
return mStream.ToArray();
}
}
then, to pass this byte array to SKBitmap:
SKBitmap bmp = SKBitmap.Decode(buff);
EDIT:
If you want to try without ConvertStreamToByteArray():
byte[] buff = null;
stream.Write(buff, 0, buff.Length);
This Write will depend of the type of your stream. In this example, I'm using FileStream.
EDIT 2:
string resourceID = "image.png";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
resourceBitmap = SKBitmap.Decode(stream);
SKBitmap bmp = SKImage.FromBitmap(resourceBitmap);
}
and then you can use this bitmap to draw and fill your SKCanvas:
canvas.DrawBitmap(bmp, 0, 0);
Use ffmpeg
command for this: ffmpeg -i image.png image.bmp
i have 5 types of pins (pins are the image .png) when i put the pins
in format .png
if you want to custom the pins in your map, you can simply use Custom Renderers to achieve this.
The icon used to represent a marker can be customized by calling the MarkerOptions.SetIcon method. This can be accomplished by overriding the CreateMarker method, which is invoked for each Pin that's added to the map:
protected override MarkerOptions CreateMarker(Pin pin)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
return marker;
}
If you want to display different icons, you can refer to the following code:
protected override MarkerOptions CreateMarker(Pin pin)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
var customPin = (Element as CustomMap).CustomPins.FirstOrDefault(p => p.Position == pin.Position);
if (customPin != null)
{
if (customPin.IconType == "corporate")
{
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.corporate));
}
else if (customPin.IconType == "pickup")
{
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.bus));
}
}
return marker;
}
For more, check:Customizing the Marker.

Entity Framework updating byte[] improperly

I'm updating image for the record in the database (the server we use doesn't allow image saving in file system) and it updates the image for the first time, but if I want to re-update a picture shortly, even despite the fact that all the code executes without errors, the byte[] in the database is not updated. If I use breakponts and debug the code - the data is being saved.
The first suspicion was the MemoryStream, the fact that it might not have read everything and there is malformed data that is being discarded from the DB, so I moved out the definition for fileBytes and fileName, as well as service call and return, but it didn't help.
It seems as if the EF was caching the data, but I have no proof for the statement at this moment
public ImageResponse Post(IFormFile file, int targetId)
{
// TODO save content Type
// TODO save content size
if (file == null)
{
throw new Exception();
}
long size = file.Length;
if (size > 0)
{
byte[] fileBytes;
string fileName;
using (var memoryStream = new MemoryStream())
{
file.CopyTo(memoryStream);
fileBytes = memoryStream.ToArray();
fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
}
this.targetService.AddImage(new TargetImage { TargetId = targetId, ImageFile = fileBytes });
return new ImageResponse
{
Size = size
};
}
else
{
throw new Exception();
}
}
public GeneralResult AddImage(TargetImage targetImage)
{
var entity = this.targets.Find(targetImage.TargetId) ??
throw new Exception();
entity.TargetPictureUrl = targetImage.ImageFile;
this.targets.Save(entity);
return new GeneralResult { Success = true };
}
Under the hood it looks like this:
public void Save(DatabaseEntity entity)
{
if (entity is T)
{
this.Save((T)entity);
}
else
{
throw new ApplicationException($"{typeof(T).Name} is required instance type.");
}
}
and the "entities" is:
private DbSet<T> entities;
protected virtual DbSet<T> Entities => this.entities ?? (this.entities = this.context.Set<T>());

How to serve PNG to HtmlImageLoadEventArgs callback as byte[]?

I am using HTML Renderer to create a PDF. I implemented an image provider to server images (PNG). Its HandleImageLoad is properly called but the provided imgae is not shown in the PDF but something that looks like a clock(?) image.
This is the setup:
ImageProvider imgProvider = new ImageProvider();
PdfSharp.Pdf.PdfDocument pdf = TheArtOfDev.HtmlRenderer.PdfSharp.PdfGenerator.GeneratePdf(
HTML, PdfSharp.PageSize.A4, 20,
null, null, imgProvider.HandleImageLoad);
public void HandleImageLoad(object sender, HtmlImageLoadEventArgs args)
{
var img = LoadFromSource(args.Src);
if(img == null)
{
args.Handled = false;
args.Callback();
Log.Error("Missing image: {0}", args.Src);
}
else
{
args.Handled = true;
args.Callback(img.Data);
}
}
The variable img has stored a PNG as byte[] in the member named Data. Do I need any transformation or intermediary object to render the image propery as image?
Ok, I found it out. The image type must be of type PdfSharp.Drawing.XImage. The easiest way is to load the PNG fila as a System.Drawing.Image.
using (var mem = new MemoryStream(img.Data))
{
System.Drawing.Image sysImg = System.Drawing.Image.FromStream(mem, false, true);
var ximg = PdfSharp.Drawing.XImage.FromGdiPlusImage(sysImg);
args.Callback(ximg);
}

Windows Forms: How to directly bind a Bitmap to a PictureBox?

I'm just trying to build a small C# .Net 4.0 application using Windows Forms (WPF I don't know at all, Windows Forms at least a little :-) ).
Is it possible to directly bind a System.Drawing.Bitmap object to the Image property of a PictureBox? I tried to use PictureBox.DataBindings.Add(...) but this doesn't seem to work.
How can I do this?
Thanks and best regards,
Oliver
This works for me:
Bitmap bitmapFromFile = new Bitmap("C:\\temp\\test.bmp");
pictureBox1.Image = bitmapFromFile;
or, in one line:
pictureBox1.Image = new Bitmap("C:\\temp\\test.bmp");
You might be overcomplicating this - according to the MSDN documentation, you can simple assign the bitmap directly to the PictureBox.Image property.
You can use the PictureBox.DataBindings.Add(...)
The trick is to create a separate property on the object you are binding to to handle the conversion between null and and empty picture.
I did it this way.
In my form load I used
this.PictureBox.DataBindings.Add(new Binding("Visible", this.bindingSource1, "HasPhoto", false, DataSourceUpdateMode.OnPropertyChanged));
this.PictureBox.DataBindings.Add(new Binding("Image", this.bindingSource1, "MyPhoto",false, DataSourceUpdateMode.OnPropertyChanged));
In my object I have the following
[NotMapped]
public System.Drawing.Image MyPhoto
{
get
{
if (Photo == null)
{
return BlankImage;
}
else
{
if (Photo.Length == 0)
{
return BlankImage;
}
else
{
return byteArrayToImage(Photo);
}
}
}
set
{
if (value == null)
{
Photo = null;
}
else
{
if (value.Height == BlankImage.Height) // cheating
{
Photo = null;
}
else
{
Photo = imageToByteArray(value);
}
}
}
}
[NotMapped]
public Image BlankImage {
get
{
return new Bitmap(1,1);
}
}
public static byte[] imageToByteArray(Image imageIn)
{
MemoryStream ms = new MemoryStream();
imageIn.Save(ms, ImageFormat.Gif);
return ms.ToArray();
}
public static Image byteArrayToImage(byte[] byteArrayIn)
{
MemoryStream ms = new MemoryStream(byteArrayIn);
Image returnImage = Image.FromStream(ms);
return returnImage;
}
You can do:
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap("yourfile.bmp");
picturebox1.Image = bmp;

How to Add 'Comments' to a JPEG File Using C#

Within the property window of a JPEG image, there is a tab called 'Summary'. Within this tab, there is a field called 'Comments' I would like to write some c# code which will add a given string to this field e.g "This is a photo".
Does some kind soul out there know how to do this?
Many thanks.
Based on other answers I wrote the following class which allows various metadata manipulations. You use it like this:
var jpeg = new JpegMetadataAdapter(pathToJpeg);
jpeg.Metadata.Comment = "Some comments";
jpeg.Metadata.Title = "A title";
jpeg.Save(); // Saves the jpeg in-place
jpeg.SaveAs(someNewPath); // Saves with a new path
The differences between my solution and the others are not large. Principally I have refactored this to be cleaner. I also use the higher level properties of BitmapMetadata, rather than the SetQuery method.
Here is the full code, which is licensed under the MIT licence. You will need to add references to PresentationCore, WindowsBase, and System.Xaml.
public class JpegMetadataAdapter
{
private readonly string path;
private BitmapFrame frame;
public readonly BitmapMetadata Metadata;
public JpegMetadataAdapter(string path)
{
this.path = path;
frame = getBitmapFrame(path);
Metadata = (BitmapMetadata)frame.Metadata.Clone();
}
public void Save()
{
SaveAs(path);
}
public void SaveAs(string path)
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(frame, frame.Thumbnail, Metadata, frame.ColorContexts));
using (Stream stream = File.Open(path, FileMode.Create, FileAccess.ReadWrite))
{
encoder.Save(stream);
}
}
private BitmapFrame getBitmapFrame(string path)
{
BitmapDecoder decoder = null;
using (Stream stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
}
return decoder.Frames[0];
}
}
The following code solves my problem and adds comments to a given JPEG image:
public void addImageComment(string imageFlePath, string comments)
{
string jpegDirectory = Path.GetDirectoryName(imageFlePath);
string jpegFileName = Path.GetFileNameWithoutExtension(imageFlePath);
BitmapDecoder decoder = null;
BitmapFrame bitmapFrame = null;
BitmapMetadata metadata = null;
FileInfo originalImage = new FileInfo(imageFlePath);
if (File.Exists(imageFlePath))
{
// load the jpg file with a JpegBitmapDecoder
using (Stream jpegStreamIn = File.Open(imageFlePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
decoder = new JpegBitmapDecoder(jpegStreamIn, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
}
bitmapFrame = decoder.Frames[0];
metadata = (BitmapMetadata)bitmapFrame.Metadata;
if (bitmapFrame != null)
{
BitmapMetadata metaData = (BitmapMetadata)bitmapFrame.Metadata.Clone();
if (metaData != null)
{
// modify the metadata
metaData.SetQuery("/app1/ifd/exif:{uint=40092}", comments);
// get an encoder to create a new jpg file with the new metadata.
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapFrame, bitmapFrame.Thumbnail, metaData, bitmapFrame.ColorContexts));
//string jpegNewFileName = Path.Combine(jpegDirectory, "JpegTemp.jpg");
// Delete the original
originalImage.Delete();
// Save the new image
using (Stream jpegStreamOut = File.Open(imageFlePath, FileMode.CreateNew, FileAccess.ReadWrite))
{
encoder.Save(jpegStreamOut);
}
}
}
}
}
This is essentially a lightly modified version of the code found under the link which Konamiman kindly supplied.
Please be aware that to make this work you will need to add .NET references to PresentationCore and WindowsBase. If using Visual Studio 2008, this can be achieved via the following:
Right click on your project in the Solution Explorer
From the drop down list, select Add 'Reference...'
From the new box which opens, select the '.NET' tab
Scroll to the two references mentioned above and on each, click ok
Many thanks to both danbystrom and Konamiman for your help in this matter. I really appreciate the quick response.
The easy part:
Add this property item:
var data = System.Text.Encoding.UTF8.GetBytes( "Some comments" );
PropertyItem pi;
*** create an empty PropertyItem here
pi.Type = 2;
pi.Id = 37510;
pi.Len = data.Length;
pi.Value = data;
To the Image's PropertItems collection.
The somewhat more cumbersome part:
How do you create a new PropertyItem, since it has no public constructor?
The common "trick" is to have an empty image lying around from which you can steal a PropertyItem. sigh
Thanks to the answers here, I've coded a solution to set a comment using memory only:
public static Image SetImageComment(Image image, string comment) {
using (var memStream = new MemoryStream()) {
image.Save(memStream, ImageFormat.Jpeg);
memStream.Position = 0;
var decoder = new JpegBitmapDecoder(memStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
BitmapMetadata metadata;
if (decoder.Metadata == null) {
metadata = new BitmapMetadata("jpg");
} else {
metadata = decoder.Metadata;
}
metadata.Comment = comment;
var bitmapFrame = decoder.Frames[0];
BitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapFrame, bitmapFrame.Thumbnail, metadata, bitmapFrame.ColorContexts));
var imageStream = new MemoryStream();
encoder.Save(imageStream);
imageStream.Position = 0;
image.Dispose();
image = null;
return Image.FromStream(imageStream);
}
}
Don't forget to dispose the image that is returned by this method. (For example after saving the image to a file)
Thanks to the previous tips I was able to put the following together. I've tested it and it seems to work. One of the biggest stumbling blocks was determining the Id needed for the field you want to assign.
string fileName = "c:/SomeImage.jpg";
// Retrieve the Image
System.Drawing.Image originalImage = System.Drawing.Image.FromFile(fileName);
// Get the list of existing PropertyItems. i.e. the metadata
PropertyItem[] properties = originalImage.PropertyItems;
// Create a bitmap image to assign attributes and do whatever else..
Bitmap bmpImage = new Bitmap((Bitmap)originalImage);
// Don't need this anymore
originalImage.Dispose();
// Get / setup a PropertyItem
PropertyItem item = properties[0]; // We have to copy an existing one since no constructor exists
// This will assign "Joe Doe" to the "Authors" metadata field
string sTmp = "Joe DoeX"; // The X will be replaced with a null. String must be null terminated.
var itemData = System.Text.Encoding.UTF8.GetBytes(sTmp);
itemData[itemData.Length - 1] = 0;// Strings must be null terminated or they will run together
item.Type = 2; //String (ASCII)
item.Id = 315; // Author(s), 315 is mapped to the "Authors" field
item.Len = itemData.Length; // Number of items in the byte array
item.Value = itemData; // The byte array
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
// This will assign "MyApplication" to the "Program Name" field
sTmp = "MyApplicationX";
itemData = System.Text.Encoding.UTF8.GetBytes(sTmp);
itemData[itemData.Length - 1] = 0; // Strings must be null terminated or they will run together
item.Type = 2; //String (ASCII)
item.Id = 305; // Program Name, 305 is mapped to the "Program Name" field
item.Len = itemData.Length;
item.Value = itemData;
bmpImage.SetPropertyItem(item);
// Save the image
bmpImage.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);
//Clean up
bmpImage.Dispose();
A variant on Peter Kistler's solution to set Title, Subject and Comment. I found I had to create items as Unicode byte array (type 1) and the IDs for Title, Subject and Comment are the same as for EXIF XPTitle, XPSubject and XP Comment. sFileOut can be the same as sFile.
public static void SetWindowsTags2(string sFile, string sFileOut, string Title = "", string Subject = "", string Comment = "", bool bShowError = false)
{
try
{
// Retrieve the Image
System.Drawing.Image originalImage = System.Drawing.Image.FromFile(sFile);
// Get the list of existing PropertyItems. i.e. the metadata
PropertyItem[] properties = originalImage.PropertyItems;
/*foreach (PropertyItem propItem in properties)
{
string sTag = System.Text.Encoding.UTF8.GetString(propItem.Value);
string sItem = sTag.Replace("\0", string.Empty);
Debug.Print(propItem.Id.ToString() + ", " + propItem.Type + ", " + sItem);
}*/
// Create a bitmap image to assign attributes and do whatever else..
Bitmap bmpImage = new Bitmap((Bitmap)originalImage);
// Don't need this anymore
originalImage.Dispose();
// Get / setup a PropertyItem
PropertyItem item = properties[0]; // We have to copy an existing one since no constructor exists
var itemData = System.Text.Encoding.Unicode.GetBytes(Title);
itemData[itemData.Length - 1] = 0;// Strings must be null terminated or they will run together
item.Type = 1; //Unicode Byte Array
item.Id = 40091; // Title ID
item.Len = itemData.Length; // Number of items in the byte array
item.Value = itemData; // The byte array
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
itemData = System.Text.Encoding.Unicode.GetBytes(Subject);
itemData[itemData.Length - 1] = 0; // Strings must be null terminated or they will run together
item.Type = 1; //Unicode Byte Array
item.Id = 40095; // subject
item.Len = itemData.Length;
item.Value = itemData;
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
itemData = System.Text.Encoding.Unicode.GetBytes(Comment);
itemData[itemData.Length - 1] = 0; // Strings must be null terminated or they will run together
item.Type = 1; ////Unicode Byte Array
item.Id = 40092; // comment
item.Len = itemData.Length;
item.Value = itemData;
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
// Save the image
bmpImage.Save(sFileOut, System.Drawing.Imaging.ImageFormat.Jpeg);
//Clean up
bmpImage.Dispose();
}
catch (Exception Ex)
{
}
}

Categories

Resources