How do I read and write XMP metadata in C#? - c#

I have this method for resizing images, and I have managed to input all of the metadata into the new image except for the XMP data. Now, I can only find topics on how manage the XMP part in C++ but I need it in C#. The closest I've gotten is the xmp-sharp project which is based on some old port of Adobe's SDK, but I can't get that working for me. The MetaDataExtractor project gives me the same results - that is, file format/encoding not supported. I've tried this with .jpg, .png and .tif files.
Is there no good way of reading and writing XMP in C#?
Here is my code if it's of any help (omitting all irrelevant parts):
public Task<Stream> Resize(Size size, Stream image)
{
using (var bitmap = Image.FromStream(image))
{
var newSize = new Size(size.Width, size.Height);
var ms = new MemoryStream();
using (var bmPhoto = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format24bppRgb))
{
// This saves all metadata except XMP
foreach (var id in bitmap.PropertyIdList)
bmPhoto.SetPropertyItem(bitmap.GetPropertyItem(id));
// Trying to use xmp-sharp for the XMP part
try
{
IXmpMeta xmp = XmpMetaFactory.Parse(image);
}
catch (XmpException e)
{
// Here, I always get "Unsupported Encoding, XML parsing failure"
}
// Trying to use MetadataExtractor for the XMP part
try
{
var xmpDirs = ImageMetadataReader.ReadMetadata(image).Where(d => d.Name == "XMP");
}
catch (Exception e)
{
// Here, I always get "File format is not supported"
}
// more code to modify image and save to stream
}
ms.Position = 0;
return Task.FromResult<Stream>(ms);
}
}

The reason you get "File format is not supported" is because you already consumed the image from the stream when you called Image.FromStream(image) in the first few lines.
If you don't do that, you should find that you can read out the XMP just fine.
var xmp = ImageMetadataReader.ReadMetadata(stream).OfType<XmpDirectory().FirstOrDefault();
If your stream is seekable, you might be able to seek back to the origin (using the Seek method, or by setting Position to zero.)

Related

Exporting a 3D double array to a tiff image stack in C# [duplicate]

I load a multiframe TIFF from a Stream in my C# application, and then save it using the Image.Save method. However, this only saves the TIFF with the first frame - how can I get it to save a multiframe tiff?
Since you don't provide any detailed information... just some general tips:
Multi-Frame TIFF are very complex files - for example every frame can have a different encoding... a single Bitmap/Image can't hold all frames with all relevant information (like encoding and similar) of such a file, only one at a time.
For loading you need to set parameter which tells the class which frame to load, otherwise it just loads the first... for some code see here.
Similar problems arise when saving multi-frame TIFFs - here you need to work with EncoderParameters and use SaveAdd etc. - for some working code see here.
Since the link to code provided by #Yahia is broken I have decided to post the code I ended up using.
In my case, the multi-frame TIFF already exists and all I need to do is to load the image, rotate by EXIF (if necessary) and save. I won't post the EXIF rotation code here, since it does not relate to this question.
using (Image img = System.Drawing.Image.FromStream(sourceStream))
{
using (FileStream fileStream = System.IO.File.Create(filePath))
{
int pages = img.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page);
if (pages == 1)
{
img.Save(fileStream, img.RawFormat); // if there is just one page, just save the file
}
else
{
var encoder = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders().First(x => x.MimeType == fileInfo.MediaType);
var encoderParams = new System.Drawing.Imaging.EncoderParameters(1);
encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(System.Drawing.Imaging.EncoderValue.MultiFrame));
img.Save(fileStream, encoder, encoderParams); // save the first image with MultiFrame parameter
for (int f = 1; f < pages; f++)
{
img.SelectActiveFrame(FrameDimension.Page, f); // select active page (System.Drawing.Image.FromStream loads the first one by default)
encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(System.Drawing.Imaging.EncoderValue.FrameDimensionPage));
img.SaveAdd(img, encoderParams); // save add with FrameDimensionPage parameter
}
}
}
}
sourceStream is a System.IO.MemoryStream which holds the byte array of the file content
filePath is absolute path to cache directory (something like 'C:/Cache/multiframe.tiff')
fileInfo is a model holding the actual byte array, fileName, mediaType and other data

c# Novacode.Picture to System.Drawing.Image

I'm reading in a .docx file using the Novacode API, and am unable to create or display any images within the file to a WinForm app due to not being able to convert from a Novacode Picture (pic) or Image to a system image. I've noticed that there's very little info inside the pic itself, with no way to get any pixel data that I can see. So I have been unable to utilize any of the usual conversion ideas.
I've also looked up how Word saves images inside the files as well as Novacode source for any hints and I've come up with nothing.
My question then is is there a way to convert a Novacode Picture to a system one, or should I use something different to gather the image data like OpenXML? If so, would Novacode and OpenXML conflict in any way?
There's also this answer that might be another place to start.
Any help is much appreciated.
Okay. This is what I ended up doing. Thanks to gattsbr for the advice. This only works if you can grab all the images in order, and have descending names for all the images.
using System.IO.Compression; // Had to add an assembly for this
using Novacode;
// Have to specify to remove ambiguous error from Novacode
Dictionary<string, System.Drawing.Image> images = new Dictionary<string, System.Drawing.Image>();
void LoadTree()
{
// In case of previous exception
if(File.Exists("Images.zip")) { File.Delete("Images.zip"); }
// Allow the file to be open while parsing
using(FileStream stream = File.Open("Images.docx", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using(DocX doc = DocX.Load(stream))
{
// Work rest of document
// Still parse here to get the names of the images
// Might have to drag and drop images into the file, rather than insert through Word
foreach(Picture pic in doc.Pictures)
{
string name = pic.Description;
if(null == name) { continue; }
name = name.Substring(name.LastIndexOf("\\") + 1);
name = name.Substring(0, name.Length - 4);
images[name] = null;
}
// Save while still open
doc.SaveAs("Images.zip");
}
}
// Use temp zip directory to extract images
using(ZipArchive zip = ZipFile.OpenRead("Images.zip"))
{
// Gather all image names, in order
// They're retrieved from the bottom up, so reverse
string[] keys = images.Keys.OrderByDescending(o => o).Reverse().ToArray();
for(int i = 1; ; i++)
{
// Also had to add an assembly for ZipArchiveEntry
ZipArchiveEntry entry = zip.GetEntry(String.Format("word/media/image{0}.png", i));
if(null == entry) { break; }
Stream stream = entry.Open();
images[keys[i - 1]] = new Bitmap(stream);
}
}
// Remove temp directory
File.Delete("Images.zip");
}

PDF to bmp Images (12 pages = 12 images)

I have to deconstruct/extract a pdf page by page into bitmap images. This will be done on a server via a web service which I've setup. How do I get this right? It has to be page by page (1 page per image).
I am really stuck and I know one of you geniuses have the answer that I've been looking for.
I have tried: http://www.pdfsharp.net/wiki/ExportImages-sample.ashx Which didn't work correctly.
I am using C#;
The PDF is not password protected;
If this solution could take a Uri as a parameter for the location of the PDF it would be excellent!
The solution should not be reliant on Acrobat PDF Reader at all
I have been struggling for a very long time trying to use MigraDoc and PDFSharp and their alternatives to achieve the aforementioned problem.
ANY help/advice/code would be greatly appreciated!!
Thanks in advance!
LibPdf
This library converts converts PDF file to an image. Supported image formats are PNG and BMP, but you can easily add more.
Usage example:
using (FileStream file = File.OpenRead(#"..\path\to\pdf\file.pdf")) // in file
{
var bytes = new byte[file.Length];
file.Read(bytes, 0, bytes.Length);
using (var pdf = new LibPdf(bytes))
{
byte[] pngBytes = pdf.GetImage(0,ImageType.BMP); // image type
using (var outFile = File.Create(#"..\path\to\pdf\file.bmp")) // out file
{
outFile.Write(pngBytes, 0, pngBytes.Length);
}
}
}
Or Bytescout PDF Renderer SDK
using System;
using Bytescout.PDFRenderer;
namespace PDF2BMP
{
class Program
{
static void Main(string[] args)
{
// Create an instance of Bytescout.PDFRenderer.RasterRenderer object and register it.
RasterRenderer renderer = new RasterRenderer();
renderer.RegistrationName = "demo";
renderer.RegistrationKey = "demo";
// Load PDF document.
renderer.LoadDocumentFromFile("multipage.pdf");
for (int i = 0; i < renderer.GetPageCount(); i++)
{
// Render first page of the document to BMP image file.
renderer.RenderPageToFile(i, RasterOutputFormat.BMP, "image" + i + ".bmp");
}
// Open the first output file in default image viewer.
System.Diagnostics.Process.Start("image0.bmp");
}
}
}

Reduce Quality of Image/Stream Before Saving

I'm trying to take an input stream (a zip file of images) and extract each file. But i must reduce the quality of each image before they are saved (if quality < 100). I have tried the following but it never compresses the image:
public void UnZip(Stream inputStream, string destinationPath, int quality = 80) {
using (var zipStream = new ZipInputStream(inputStream)) {
ZipEntry entry;
while ((entry = zipStream.GetNextEntry()) != null) {
var directoryPath = Path.GetDirectoryName(destinationPath + Path.DirectorySeparatorChar + entry.Name);
var fullPath = directoryPath + Path.DirectorySeparatorChar + Path.GetFileName(entry.Name);
// Create the stream to unzip the file to
using (var stream = new MemoryStream()) {
// Write the zip stream to the stream
if (entry.Size != 0) {
var size = 2048;
var data = new byte[2048];
while (true) {
size = zipStream.Read(data, 0, data.Length);
if (size > 0)
stream.Write(data, 0, size);
else
break;
}
}
// Compress the image and save it to the stream
if (quality < 100)
using (var image = Image.FromStream(stream)) {
var info = ImageCodecInfo.GetImageEncoders();
var #params = new EncoderParameters(1);
#params.Param[0] = new EncoderParameter(Encoder.Quality, quality);
image.Save(stream, info[1], #params);
}
}
// Save the stream to disk
using (var fs = new FileStream(fullPath, FileMode.Create)) {
stream.WriteTo(fs);
}
}
}
}
}
I'd appreciate it if someone could show me what i'm doing wrong. Also any advice on tidying it up would be appreciated as the code's grown abit ugly. Thanks
You really shouldn't be using the same stream to save the compressed image. The MSDN documentation clearly says: "Do not save an image to the same stream that was used to construct the image. Doing so might damage the stream." (MSDN Article on Image.Save(...))
using (var compressedImageStream = new MemoryStream())
{
image.Save(compressedImageStream, info[1], #params);
}
Also, what file format are you encoding into? You haven't specified. You're just getting the second encoder found. You shouldn't rely on the order of the results. Search for a specific codec instead:
var encoder = ImageCodecInfo.GetImageEncoders().Where(x => x.FormatID == ImageFormat.Jpeg.Guid).SingleOrDefault()
... and don't forget to check if the encoder doesn't exist on your system:
if (encoder != null)
{ .. }
The Quality parameter doesn't have meaning for all file formats. I assume you might be working with JPEGs? Also, keep in mind that 100% JPEG Quality != Lossless Image. You can still encode with Quality = 100 and reduce space.
There is no code to compress the image after you've extracted it from the zip stream. All you seem to be doing is getting the unzipped data into a MemoryStream, then proceeding the write the image to the same stream based on quality information (which may or may not compress an image, depending on the codec). I would first recommend not writing to the same stream you're reading from. Also, what "compression" you get out of the Encoder.Quality property depends on the type of image--which you haven't provided any detail on. If the image type supports compression and the incoming image quality is lower than 100 to start, you won't get any reduction in size. Also, you've not provided any information with regard to that. Long story short, you haven't provided enough information for anyone to give you a real answer.

Generating a multipage TIFF is not working

I'm trying to generate a multipage TIFF file from an existing picture using code by Bob Powell:
picture.SelectActiveFrame(FrameDimension.Page, 0);
var image = new Bitmap(picture);
using (var stream = new MemoryStream())
{
ImageCodecInfo codecInfo = null;
foreach (var imageEncoder in ImageCodecInfo.GetImageEncoders())
{
if (imageEncoder.MimeType != "image/tiff") continue;
codecInfo = imageEncoder;
break;
}
var parameters = new EncoderParameters
{
Param = new []
{
new EncoderParameter(Encoder.SaveFlag, (long) EncoderValue.MultiFrame)
}
};
image.Save(stream, codecInfo, parameters);
parameters = new EncoderParameters
{
Param = new[]
{
new EncoderParameter(Encoder.SaveFlag, (long) EncoderValue.FrameDimensionPage)
}
};
for (var i = 1; i < picture.GetFrameCount(FrameDimension.Page); i++)
{
picture.SelectActiveFrame(FrameDimension.Page, i);
var img = new Bitmap(picture);
image.SaveAdd(img, parameters);
}
parameters = new EncoderParameters
{
Param = new[]
{
new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush)
}
};
image.SaveAdd(parameters);
stream.Flush();
}
But it's not working (only the first frame is included in the image) and I don't know why.
What I want to do is to change a particular frame of a TIFF file (add annotations to it).
I don't know if there's a simpler way to do it but what I have in mind is to create a multipage TIFF from the original picture and add my own picture instead of that frame.
[deleted first part after comment]
I'm working with multi-page TIFFs using LibTIFF.NET; I found many quicks in handling of TIFF using the standard libraries (memory related and also consistent crashes on 16-bit gray scale images).
What is your test image? Have you tried a many-frame tiff (preferably with a large '1' on the first frame, a '2 on the next etc; this could help you to be certain on the frame included in the file.
Another useful diagnosis may be tiffdump utility, as included in LibTiff binaries (also for windows). This will tell you exactly what frames you have.
See Using LibTiff from c# to access tiled tiff images
[Edit] If you want to understand the .NET stuff: I've found a new resource on multi-page tiffs using the standard .NET functionality (although I'll stick with LibTIFF.NET): TheCodeProject : Save images into a multi-page TIFF file... If you download it, the code snippet in Form1.cs function saveMultipage(..) is similar (but still slightly different) than your code. Especially the flushing at the end is done in a differnt way, and the file is deleted before the first frame...
[/Edit]
It seems that this process doesn't change image object but it changes the stream so I should get the memory stream buffer and build another image object:
var buffer=stream.GetBuffer();
using(var newStream=new MemoryStream(buffer))
{
var result=Image.FromStream(newStream);
}
Now result will include all frames.

Categories