C#: Retrieve JPEG Comment (not Exif) - c#

I've been stumped trying to figure this one out.
I'm trying to retrieve the "Jpeg Comment" out of a jpg file via C#.
The code below works but I need the basic comment NOT the Exif comment.
I'm using FastStone Image Viewer to set the basic comment. Help me retrieve it.
I can use the commandline program exiv2 to verify that the comment is there.
exiv2 -pc c:\test.jpg (it spits out the basic comment).
exiv2 -pa c:\test.jpg (it spits out the EXIF comment)
I've used several C# libs to get at it but they get the EXIF data.
Image x = Image.FromFile(#"c:\test.jpg");
PropertyItem prop;
prop = x.GetPropertyItem(0x9286);
string Comment = Encoding.ASCII.GetString(prop.Value);

You could refer to this link.
(Thanks for those who already have answered the same question, although the answer was quite right but not 100% to solve this problem.)
Here are three steps you need to do:
Be aware that you should have the Jpeg file cloned.
Set the comment of the cloned file.
Replace the file by deleting the original jpeg file.
Here is the sample code:
public void addImageComment(string imageFlePath, string comments)
{
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.Comment = 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);
}
}
}
}
}

You can do this quite simply with the MetadataExtractor library (available via NuGet):
JpegCommentDirectory jpegCommentDirectory = ImageMetadataReader.ReadMetadata(imagePath)
.OfType<JpegCommentDirectory>()
.FirstOrDefault();
string comment = jpegCommentDirectory?.GetDescription(JpegCommentDirectory.TagComment);

Related

How to change default image folder on Xamarin forms (android)

I am having the issue of "Canvas Drawing too large bitmaps". After a quick search, I found the following thread, which promptly helped me know what is the issue.
The solution is to put the image in drawable-xxhdpi/ instead of simply drawable/. And here lies the issue: the image is not static, it is imported when I need it. As such, I do not chose where the image ends up stored. It store itself in drawable. Is there 1) A solution to chose which folder to use, or 2) a way to tell it not get the image if it's too heavy?
var file = new SmbFile(path, auth);
try
{
if (file.Exists())
{
// Get readable stream.
var readStream = file.GetInputStream();
//Create reading buffer.
MemoryStream memStream = new MemoryStream();
//Get bytes.
((Stream)readStream).CopyTo(memStream);
var stream1 = new MemoryStream(memStream.ToArray());
if (stream1.Length < 120188100)
{
//Save image
ProductImage = ImageSource.FromStream(() => stream1);
//Dispose readable stream.
readStream.Dispose();
InfoColSpan = 1;
}
else
{
Common.AlertError("Image trop lourde pour l'affichage");
}
}
}

Google drive upload an image from memory stream

Good day,
I have this code which upload an image to Google drive from file, everything works well:
// Create a new file on Google Drive
using (var fsSource = new FileStream(UploadFileName, FileMode.Open, FileAccess.Read))
{
// Create a new file, with metadata and stream.
var request = service.Files.Create(fileMetadata, fsSource, "image/jpg");
request.Fields = "*";
var results = await request.UploadAsync(CancellationToken.None);
}
Now I want to do some image manipulation before uploading so that I could convert the image to jpeg if the image is in another format (png or bmp for example) or resize the image, so I changed the file to stream for manipulation, I don't want to save it locally again because the code could be used on a website on mobiles, that's why I am saving to stream.
using (MemoryStream ms = new MemoryStream())
{
Image img = Image.FromFile(uploadfileName);
img.Save(ms, ImageFormat.Jpeg);
}
How can I now upload this ms stream to Google Drive?
Thanks for any clue, I'm not an expect in field.
Thanks all for assistance.
The answer suggested by canton7 works:
Just set ms.Position = 0, so that the next read starts reading from the beginning of the stream, then use it in place of your fsSource in your first snippet

How can I read file properties/metadata?

I wanted read system file properties like the same shown in the picture below, specifically the Title and Copyright properties. How can I do this?
Generally you can use System.Diagnostics,
FileVersionInfo info = FileVersionInfo.GetVersionInfo("path\to\file");
Then examine the FileDescription and LegalCopyright properties. However for images the case is different, you need to extract the bitmap metadata explicitly. Consider,
using (Stream fs = File.Open("path\to\file", FileMode.Open, FileAccess.ReadWrite))
{
BitmapDecoder decoder =
BitmapDecoder.Create(
fs, BitmapCreateOptions.None, BitmapCacheOption.Default);
BitmapFrame frame = decoder.Frames[0]; // the first frame with the metadata
BitmapMetadata metadata = frame.Metadata as BitmapMetadata;
if (metadata != null)
{
// examine metadata.Title, metadata.Copyright
}
fs.Close();
}
You can find all the properties listed at the BitmapMetadata Class documentation on MSDN.
You would need to load the image into a Bitmap, and access the PropertyItems.
Bitmap image = new Bitmap("YOUR IMAGE PATH HERE");
PropertyItem[] propItems = image.PropertyItems;
The following would get the manufacturer...
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
string manufacturer = encoding.GetString(propItems[1].Value);
You need to do similar conversions depending on what details you are after.
foreach (PropertyItem item in propItems)
{
Console.WriteLine("ID : " + item.Id + " , VALUE : " + encoding.GetString(item.Value));
}

Image/ImageSource/Interop Image to bytearray

I'm developing a control where user can set an image and i want this this to be as user friendly as possible - so support for copy & paste, drag & drop.
I've got this part working using IDataObjects, testing for fileformats of FileDrop, FileContents (eg from outlook), and bitmap eg:
private void GetImageFromIDataObject(IDataObject myIDO)
{
string[] dataformats = myIDO.GetFormats();
Boolean GotImage = false;
foreach (string df in dataformats)
{
if (df == DataFormats.FileDrop)
{
// code here
}
if (df == DataFormats.Bitmap)
{
// Source of my problem here... this gets & displays image but
// how do I then convert from here ?
ImageSource myIS = Utilities.MyImaging.ImageFromClipboardDib();
ImgPerson.Source = myIS;
}
}
}
The ImageFromClipboard code is Thomas Levesque's as referenced in the answer to this SO question wpf InteropBitmap to bitmap
http://www.thomaslevesque.com/2009/02/05/wpf-paste-an-image-from-the-clipboard/
No matter how I get the image onto ImgPerson, this part is working fine; image displays nicely.
When user presses save I need to convert the image to a bytearray and send to a WCF server which will save to server - as in, reconstruct the bytearray into an image and save it in a folder.
For all formats of drag & drop, copy & paste the image is some form of System.Windows.Media.Imaging.BitmapImage.
Except for those involving the clipboard which using Thomas's code becomes System.Windows.Media.Imaging.BitmapFrameDecode.
If I avoid Thomas's code and use:
BitmapSource myBS = Clipboard.GetImage();
ImgPerson.Source = myBS;
I get a System.Windows.Interop.InteropBitmap.
I can't figure out how to work with these; to get them into a bytearray so I can pass to WCF for reconstruction and saving to folder.
Try this piece of code
public byte[] ImageToBytes(BitmapImage imgSource)
{
MemoryStream objMS = new MemoryStream();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(imgSource));
encoder.Save(objMS);
return objMS.GetBuffer();
}
You can also use JpegBitmapEncoder, BmpBitmapEncoder based on your requirements.
byte[] arr = ImageToBytes(ImgPerson.Source as BitmapImage);
I can't believe I didn't see this SO question but the this is essentially the same as my question:
WPF: System.Windows.Interop.InteropBitmap to System.Drawing.Bitmap
The answer being:
BitmapSource bmpSource = msg.ThumbnailSource as BitmapSource;
MemoryStream ms = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmpSource));
encoder.Save(ms);
ms.Seek(0, SeekOrigin.Begin);
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(ms);
So similar in execution to Nitesh's answer but crucially, works with an inter-op bitmap.

InPlaceBitmapMetadataWriter.TrySave() returns true but does nothing

On some .JPG files (EPS previews, generated by Adobe Illustrator) in Windows 7 InPlaceBitmapMetadataWriter.TrySave() returns true after some SetQuery() calls, but does nothing.
Code sample:
BitmapDecoder decoder;
BitmapFrame frame;
BitmapMetadata metadata;
InPlaceBitmapMetadataWriter writer;
decoder = BitmapDecoder.Create(s, BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default);
frame = decoder.Frames[0];
metadata = frame.Metadata as BitmapMetadata;
writer = frame.CreateInPlaceBitmapMetadataWriter();
try {
writer.SetQuery("System.Title", title);
writer.SetQuery(#"/app1/ifd/{ushort=" + exiftagids[0] + "} ", (title + '\0').ToCharArray());
writer.SetQuery(#"/app13/irb/8bimiptc/iptc/object name", title);
return writer.TrySave();
}
catch {
return false;
}
Image sample
You can reproduce problem (if you have Windows 7) by downloading image sample and using this code sample to set title on this image.
Image has enough room for metadata - and this code sample works fine on my WinXP.
Same code works fine on Win7 with other .JPG files.
Any ideas are welcome :)
Two things:
I don't think you will be able to write to your metadata variable just like that, as it will be Frozen. So, you will have to clone it:
BitmapMetadata metadata = frame.Metadata.Clone() as BitmapMetadata;
Padding, you need padding. I found this out after about a day's worth of tinkering around trying to make some code (similar to yours) work. InPlaceBitmapMetadataWriter will not work if there is no metadata padding in your image file. So you need something like:
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
if(frame != null && metadata != null) {
metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", padding);
encoder.Frames.Add(BitmapFrame.Create(frame, frame.Thumbnail, metadata, frame.ColorContexts));
using (Stream outputFile = File.Open(_myoutputpath, FileMode.Create, FileAccess.ReadWrite)) {
encoder.Save(outputFile);
}
}
Now you can use the file located at _myoutputpath which has added metadata padding for your InPlaceBitmapMetadataWriter operations.
This article and attached code should help you out.
Hi I found this article about InPlaceBitmapMetadataWriter where the guy said that TrySave() might corrupt the image and that's why he advised to do TrySave() on the copy of the original file and if this doesn't work, add padding to the copy of original file and than TrySave() again and if it works, delete the original and rename the copy.
I scratched my head and asked myself why I should bother with InPlaceBitmapMetadataWriter and writing 3x original file to the disk in case TrySave() doesn't work because there is not enough padding, if I can clone metadata, write whatever into them and assemble jpeg file right away.
Then I started to think that maybe thanks to InPlaceBitmapMetadataWriter I can edit metadata without losing quality, but it looks like it "just" helps you to write metadata more quickly if there is enough padding.
I wrote a small test where I compress one file many times to see the quality degradation and you can see it in the third-fourth compression, which is very bad.
But luckily, if you always use same QualityLevel with JpegBitmapEncoder there is no degradation.
In this example I rewrite keywords 100x in metadata and the quality seems not to change.
private void LosslessJpegTest() {
var original = "d:\\!test\\TestInTest\\20150205_123011.jpg";
var copy = original;
const BitmapCreateOptions createOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile;
for (int i = 0; i < 100; i++) {
using (Stream originalFileStream = File.Open(copy, FileMode.Open, FileAccess.Read)) {
BitmapDecoder decoder = BitmapDecoder.Create(originalFileStream, createOptions, BitmapCacheOption.None);
if (decoder.CodecInfo == null || !decoder.CodecInfo.FileExtensions.Contains("jpg") || decoder.Frames[0] == null)
continue;
BitmapMetadata metadata = decoder.Frames[0].Metadata == null
? new BitmapMetadata("jpg")
: decoder.Frames[0].Metadata.Clone() as BitmapMetadata;
if (metadata == null) continue;
var keywords = metadata.Keywords == null ? new List<string>() : new List<string>(metadata.Keywords);
keywords.Add($"Keyword {i:000}");
metadata.Keywords = new ReadOnlyCollection<string>(keywords);
JpegBitmapEncoder encoder = new JpegBitmapEncoder {QualityLevel = 80};
encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0], decoder.Frames[0].Thumbnail, metadata,
decoder.Frames[0].ColorContexts));
copy = original.Replace(".", $"_{i:000}.");
using (Stream newFileStream = File.Open(copy, FileMode.Create, FileAccess.ReadWrite)) {
encoder.Save(newFileStream);
}
}
}
}
I still didn't find the answer and has to write wrapper for exiftool instead of using WPF's way to work with metadata...
May be som1 will find it useful.

Categories

Resources