0

我一直在 Visual Studio 2012 中的 ASP.NET MVC 应用程序 (C#) 中工作。我用 MS Charts 创建了几个报告(在我使用 iTextSharp 的 pdf 文件中显示它们)。但是,当我的图表放大 200% 时,它们的质量很差。因此,我想以 WMF 格式保存我的图表(我发现 iTextSharp 不支持 SVG 格式)。为了以 WMF 格式保存我的图表,我使用了以下代码:

 using (var chartimage = new MemoryStream())
            {

                chartCentersByYear.SaveImage(chartimage, ChartImageFormat.Png);
                using (var imageSys = System.Drawing.Image.FromStream(chartimage))
                {

                    var image = iTextSharp.text.Image.GetInstance(imageSys, System.Drawing.Imaging.ImageFormat.Wmf);
                    image.ScalePercent(50f);

                    image.SetAbsolutePosition(document.LeftMargin + 40, document.BottomMargin + 95);

                    document.Add(image);
                }

            }

但我收到了一条错误消息:

var image = iTextSharp.text.Image.GetInstance(imageSys, System.Drawing.Imaging.ImageFormat.Wmf);

错误是:“值不能为空。参数名称:编码器”。如何以 WMF 格式保存图表?预先感谢您的任何帮助。

4

2 回答 2

2

注意:此答案使用 iText7 for .NET,而不是已弃用的 iTextSharp

这是控制台应用程序中功能的一个工作示例。这会将 mschart 放置为具有完美质量的矢量图像,无论您放大多少。

static void Main(string[] args) {

    // Create a chart.
    Chart chart1 = new Chart();
    chart1.ChartAreas.Add(new ChartArea());
    Series series1 = new Series();
    series1.Points.Add(new DataPoint(1, 2));
    series1.Points.Add(new DataPoint(2, 4));
    series1.Points.Add(new DataPoint(3, 1));
    chart1.Series.Add(series1);

    // Save the chart to an in-memory (emf) metafile.
    Metafile metafile;
    using (MemoryStream str1 = new MemoryStream()) {
        chart1.SaveImage(str1, ChartImageFormat.Emf);

        byte[] emfBytes = str1.ToArray();
        IntPtr hemf = SetEnhMetaFileBits((uint) emfBytes.Length, emfBytes);
        metafile = new Metafile(hemf, true);
    }

    // Convert the metafile to a WMF and write to a file.
    byte[] wmfBytes = GetWmfByteArray(metafile);
    File.WriteAllBytes(@"c:\temp\test.wmf", wmfBytes);

    // Initialize the document.
    PdfWriter writer = new PdfWriter(@"c:\temp\test.pdf");
    PdfDocument pdf = new PdfDocument(writer);
    Document document = new Document(pdf);

    // Add the image.
    PdfFormXObject xObject1 = new PdfFormXObject(new WmfImageData(@"c:\temp\test.wmf"), pdf);
    Image img1 = new Image(xObject1);
    document.Add(img1);

    document.Close();
}

[DllImport("gdiplus.dll", SetLastError = true)]
static extern int GdipEmfToWmfBits(int hEmf, int uBufferSize, byte[] bBuffer, int iMappingMode, EmfToWmfBitsFlags flags);
[DllImport("gdi32.dll")]
public static extern IntPtr SetEnhMetaFileBits(uint cbBuffer, byte[] lpBuffer);

[Flags]
private enum EmfToWmfBitsFlags
{
    EmfToWmfBitsFlagsDefault = 0x00000000,
    EmfToWmfBitsFlagsEmbedEmf = 0x00000001,
    EmfToWmfBitsFlagsIncludePlaceable = 0x00000002,
    EmfToWmfBitsFlagsNoXORClip = 0x00000004
}

private static byte[] GetWmfByteArray(Metafile mf) {
    const int MM_ANISOTROPIC = 8;
    int handle = mf.GetHenhmetafile().ToInt32();
    int bufferSize = GdipEmfToWmfBits(handle, 0, null, MM_ANISOTROPIC, EmfToWmfBitsFlags.EmfToWmfBitsFlagsIncludePlaceable);
    byte[] buf = new byte[bufferSize];
    GdipEmfToWmfBits(handle, bufferSize, buf, MM_ANISOTROPIC, EmfToWmfBitsFlags.EmfToWmfBitsFlagsIncludePlaceable);
    return buf;
}

这是另一个适用于旧版 iTextSharp 的片段:

// Create a chart.
Chart chart1 = new Chart();
chart1.ChartAreas.Add(new ChartArea());
Series series1 = new Series();
series1.Points.Add(new DataPoint(1, 2));
series1.Points.Add(new DataPoint(2, 4));
series1.Points.Add(new DataPoint(3, 1));
chart1.Series.Add(series1);

// Save to metafile.
chart1.SaveImage(@"c:\temp\test.emf", ChartImageFormat.Emf);

// Convert to WMF.
Metafile mf = new Metafile(@"c:\temp\test.emf");
byte[] arr = GetWmfByteArray(mf);
ImgWMF wmf = new ImgWMF(arr);
iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(wmf);

// Create PDF.
Document document = new Document();
PdfWriter.GetInstance(document, new FileStream(@"c:\temp\test.pdf", FileMode.Create));
document.Open();
document.Add(img);
document.Close();

请注意,由于存在错误,目前没有办法(据我所知)将内存中的 WMF 放置在 iText PDF 中而不从文件中加载它。我提出了一个带有修复的拉取请求,所以希望他们很快就会合并它。

更新!拉取请求已合并,此问题已在iText 7.1.8中修复:您现在可以这样做new WmfImageData(wmfBytes),无需写入文件。

更新(2020 年 4 月):不幸的是,最近的 Windows 更新 (KB4549951) 似乎破坏了 GdipEmfToWmfBits 函数(用于从 EMF 转换为 WMF)。除了卸载更新之外,我还不知道永久的解决方案。

于 2019-07-20T08:35:14.813 回答
1

不幸的是,在此更新中,GdipEmfToWmfBits 函数停止与 EmfToWmfBitsFlagsIncludePlaceable 标志一起正常工作,该标志是具有 PDF 分辨率信息的字节结构,对于 iText 或 iTextSharp 是必需的。我尝试了另一种方法,即在指甲上创建此标题。令我惊讶的是,它奏效了。这是我正在使用的代码片段。

    [StructLayout(LayoutKind.Sequential)]
    private struct PLACEABLEMETAHEADER
    {
        public uint Key; // Magic number (always 9AC6CDD7h)
        public ushort Handle; // Metafile HANDLE number (always 0) /
        public short Left; // Left coordinate in metafile units /
        public short Top; // Top coordinate in metafile units /
        public short Right; // Right coordinate in metafile units /
        public short Bottom; // Bottom coordinate in metafile units /
        public ushort Inch; // Number of metafile units per inch /
        public uint Reserved; // Reserved (always 0) /
        public ushort Checksum; // Checksum value for previous 10 WORDs */
    }

    private byte[] GetMetafileRawData(Metafile metafile)
    {
        byte[] data = null;

        System.Drawing.Imaging.MetafileHeader header = metafile.GetMetafileHeader();

        const int MM_ANISOTROPIC = 8;

        IntPtr handle = metafile.GetHenhmetafile();

        try
        {
            EmfToWmfBitsFlags flag = EmfToWmfBitsFlags.EmfToWmfBitsFlagsIncludePlaceable;

            uint dataSize = GdipEmfToWmfBits(handle, 0, null, MM_ANISOTROPIC, flag);

            if (dataSize == 0) //Not Support EmfToWmfBitsFlagsIncludePlaceable
            {
                flag = EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault;

                dataSize = GdipEmfToWmfBits(handle, 0, null, MM_ANISOTROPIC, flag);
            }

            data = new byte[dataSize];

            GdipEmfToWmfBits(handle, dataSize, data, MM_ANISOTROPIC, flag);

            if (flag == EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault) //Add PlaceableMetaHeader to Byte Array
            {
                List<byte> placeableList = new List<byte>();

                PLACEABLEMETAHEADER placeable = new PLACEABLEMETAHEADER();

                placeable.Key = 0x9AC6CDD7; //Fix
                placeable.Left = (short) header.Bounds.Left;
                placeable.Top = (short) header.Bounds.Top;
                placeable.Right = (short) header.Bounds.Width;
                placeable.Bottom = (short) header.Bounds.Height;
                placeable.Inch = 1440; //Fix?!?!?!?!

                placeable.Checksum = 0;
                placeable.Checksum ^= (ushort) (placeable.Key & 0x0000FFFF);
                placeable.Checksum ^= (ushort) ((placeable.Key & 0xFFFF0000) >> 16);
                placeable.Checksum ^= (ushort) placeable.Handle;
                placeable.Checksum ^= (ushort) placeable.Left;
                placeable.Checksum ^= (ushort) placeable.Top;
                placeable.Checksum ^= (ushort) placeable.Right;
                placeable.Checksum ^= (ushort) placeable.Bottom;
                placeable.Checksum ^= (ushort) placeable.Inch;
                placeable.Checksum ^= (ushort) (placeable.Reserved & 0x0000FFFF);
                placeable.Checksum ^= (ushort) ((placeable.Reserved & 0xFFFF0000) >> 16);

                placeableList.AddRange(this.SerializeSequentialStruct(placeable));
                placeableList.AddRange(data);

                data = placeableList.ToArray();
            }
        }
        finally
        {
            DeleteEnhMetaFile(handle);
        }

        return data;
    }

    private byte[] SerializeSequentialStruct(object @struct)
    {
        byte[] arr = null;

        IntPtr ptr = IntPtr.Zero;

        try
        {
            int size = Marshal.SizeOf(@struct) - 2;
            arr = new byte[size];
            ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(@struct, ptr, true);
            Marshal.Copy(ptr, arr, 0, size);
        }
        catch (Exception e)
        {
            throw e;
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }

        return arr;
    }
于 2020-05-04T19:55:01.873 回答