Molecule EMF export problem

User 1a8d11549a

24-03-2011 17:32:13

Hi,


I am exploring the possibilites to use MarvinSketch for exporting reaction sketches into a .NET WPF application using an EMF to XAML converter. This works nicely when consuming the Marvin clipboard sketch EMF. However, when trying to retrieve the EMF stream directly, using the function molecule.toBinFormat("emf") , the result is a XAML image with all bonds rendered properly, but all atom labels located totally off at the top left corner in position (0,0) on top of each other. Here's the code I'm using:


Dim mol As Molecule = mSketchPane.getMol()
Return mol.toBinFormat("emf")


The question is: Since it seems that the clipboard EMF stream is not identical with the one obtained from molecule.toBinFormat("emf"), how can I alternatively retrieve an EMF stream from a sketch pane, which is the same as the clipboard one? There must be some internal functionality providing the clipboard with these data. Thanks for your help!

ChemAxon bd13b5bd77

24-03-2011 21:53:59

Hi John,


 


maybe the easiest I found for you is to use/reference ChemAxon.NET wrapper libraries:


         var Logger logger= new ChemAxon.NET.Base.Logger.NullLogger();
         var renderer = new ChemAxon.NET.IKVM.Marvin.Paint.MarvinMoleculeRenderer(logger);
         var msdi = new ChemAxon.NET.Base.Marvin.Paint.MarvinStructureDrawingInfo();


         int width = 500, height = 500;


         using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb)) {
            using (var bitmapGraphics = System.Drawing.Graphics.FromImage(bmp)) {
               using (MemoryStream emfStream = new MemoryStream()) {
                  using (var emf = new Metafile(emfStream, bitmapGraphics.GetHdc(), new RectangleF(0, 0, width, height), MetafileFrameUnit.Pixel)) {


                     msdi.Size = new System.Drawing.Size(width, height);
                     //msdi.StructureDrawingSettings.ExplicitHydrogens = true;
                     // addmore settings here
                     renderer.DrawMolecule(rxnMolecule, emf, msdi);
                  }
                  string file = @"C:\Users\h\Work\xx.emf";
                  File.WriteAllBytes(file, emfStream.ToArray());
               }
            }
         }


 


Viktor

ChemAxon bd13b5bd77

24-03-2011 22:11:14

Small explanation of my code is:


we need a renderer object and a drawing info,


- renderer is defined to render the moleculeon an image (bitmap, or emf) with the specific drawing settings
- drawing info is defined to store the drawing settings (please check what you can set in it)


next section is all about how to create an emf stream.


C# using block = try-finally-dispose in VB.nET you should findsomething similar.


the last step is to draw the molecule on an EMF graphics with the drawing info


renderer.DrawMolecule(rxnMolecule, emf, msdi);


at the end the using block will close the emf graphics (image) but the recorded emf statements can be serialized out from the underlying stream.


Writing out the emf picture could have been done by the default Image.Save as well but in the case of emf pictures I prefer the low level stream serialization, it is more reliable in my opinion.


 


 


 

ChemAxon bd13b5bd77

24-03-2011 22:17:38

.toBinary("emf") is a legacy code from java, which produces nice emf pictures on mac or linux but on WIndows we recommend the GDI+ based solutions.


Further alternatives I could also add (we have other components for EMF generation) but they work from string (mrv), which requires a MolImport first.


In the recommended way you can render your memory object (RXNMolecule) directly without any extra export-import steps.


 

User 1a8d11549a

25-03-2011 09:55:35

Wow, that was a prompt answer (and competent, as usual...). I'm really impressed, Victor. - After conversion to VB.NET everything works fine, that's absolutely great!


There's just a little grain of salt left, at least from the WPF programming perspective. Since WPF is completely independent fom Windows Forms, using the System.Drawing namespace means to load the whole Windows Forms overhead for these few lines. If some alternative ways not using System.Drawing would be possible, that would be perfect. - Otherwise it obviously wouldn't mean the end of the world ...

ChemAxon bd13b5bd77

25-03-2011 10:18:00

Unfortunatelly your task is to produce EMF, under .NET. Which means to me that the task itself requires the System.Drawing.Imaging namespace anyway. Is that correct?


If I share the other solutions with you would mean also overhead on two frontages, 1. import/export and System.Drawing again becauseof our custom graphics.


But do not worry ikvm prompt loads the whole dll architecture unfortunately, sketcher (MSketchPane) does this also.


The trick I could also suggest that you should take this image generation code out and separate/encapsulate into a different module and try to load it on a separate thread.


 

User 1a8d11549a

25-03-2011 10:36:54

Ok, thanks for clarifying this. Since the Windows Forms overhead is loaded anyway in this or the other way, then we obviously don't need any alternative here, and I will move on with your code as is.


Thanks again for all of your help.

ChemAxon bd13b5bd77

25-03-2011 10:47:02

Please let me know if you have further questions/problems!

User 7f33ec9a5c

14-07-2011 16:42:56

Hi,  since toBInFormat works on .NET for the other image formats (like .png) I found it easier just to have Marvin render the image as a .png, then use GDI+ to change the .png to a .jpeg.


This has the additional advantage for us that we can use the format string(http://www.chemaxon.com/marvin/help/formats/images-doc.html), instead of needing to pass individual format parameters.


The code below slips into the generate_image.jsp example that ChemAxon has posted on their website.


// Generating the image
byte[] b = new byte[0];
 


//if statement as bug workaround.
if (sFormat.StartsWith("jpeg"))
{

 //BUG FIX BECAUSE .JPG WILL NOT RENDER USING V 5.5.0.1510 OF JCHEM.
 sFormat = sFormat.Replace("jpeg", "png"); //we are going to make a .png instead,


  byte[] bPNGout = oMol.toBinFormat(sFormat);


  //then convert this to a .jpeg using GDI+


  MemoryStream bPNGstream = new MemoryStream(bPNGout);


Bitmap oIntermediateImage = new Bitmap(bPNGstream);

b = StreamJPG100(oIntermediateImage);
// end of bug fix stuff
}
else
{
b = oMol.toBinFormat(sFormat);
}


 




//WHERE THE StreamJPG100 function is:


      /// <summary>
       /// Translate a bitmap to a .jpeg at 
       ///  the highest possible resolution.
       /// </summary>
       /// <param name="bmp"></param>
       /// <returns></returns>
       private byte[] StreamJPG100(Bitmap bmp) 
       {
           byte[] abRC = new byte[0];

           //blank in case something goes wiggy
           Stream oOutput = new MemoryStream();

           EncoderParameters encoderParameters = new EncoderParameters(1); 
           encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);

           //make a .jpeg stream of the highest possble quality
           bmp.Save(oOutput, GetEncoder(ImageFormat.Jpeg), encoderParameters);

           oOutput.Position = 0;
           using (BinaryReader br = new BinaryReader(oOutput)) 
           {
               abRC = br.ReadBytes((int)oOutput.Length); 
           } 

           return abRC;
       }

       public static ImageCodecInfo GetEncoder(ImageFormat format) 
       {
           ImageCodecInfo oCodec = null;

           ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); 

           foreach (ImageCodecInfo codec in codecs) 
           { 
               if (codec.FormatID == format.Guid) 
               {
                   oCodec = codec;
                   break;
               } 
           }

           return oCodec; 
       }