【问题标题】:Image File to PDF Stream Conversion图像文件到 PDF 流转换
【发布时间】:2011-12-05 19:39:27
【问题描述】:

我正在创建一个例程,根据来自 Adob​​e 的 this document 中的信息编写一个简单的 PDF 文档。事实证明,为文本和形状创建流很简单,但我一直坚持插入图像。

谁能简单解释一下如何将文件(任何图像格式,如 gif、bmp、jpg 等都可以)转换为 PDF 流?请注意,我不想创建整个 PDF 文件,只是文件中的一个流。

对于我可用的应用程序,无法查看它在其他地方是如何完成的,因为整个流是从头到尾进行编码的,而我正在尝试解决这种编码方法。

虽然我不想重新发明整个创建 PDF 文件轮,但我确实想了解它的这个特定部分是如何工作的,所以不想使用库(因此没有提及我的语言的原因正在使用)。

【问题讨论】:

    标签: pdf pdf-generation


    【解决方案1】:

    您需要在内容流中使用Do 运算符。例如

    ....    /Im1 Do .......
    

    Im1 引用页面资源字典中的 XObject 资源

    例如,

    In the page dictionary ...
    << 
    
    ...
    /Contents 1 0 R
    /Resources << /XObject << /Im1 2 0 R >> >>
    ...
    >>
    

    Object 2 0 R 将是一个图像 XObject:

    2 0 obj << /Type /XObject /Subtype /Image /Width 100 /Height 100 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Length 10000 /Filter /DCTDecode >>
    stream
    JPEG DATA HERE
    endstream
    endobj
    

    几点说明: - 要定位和缩放图像,您必须使用cm 运算符设置当前图形矩阵。例如

    150 0 0 150 100 100 cm
    

    将图像定位在(100,100),并使图像150变宽,150变高。

    • 您不仅限于 JPEG - 您可以使用 JPEG2000(使用 /Filter=/JPXDecode)或位图像素数据(省略过滤器)

    • 包含所有这些的规范部分是 8.9

    • 我没有尝试过 LZW 解码 - 我猜这可能适用于 GIF

    • 您通常在显示图像时将图形状态推入堆栈。例如

      q a b c d e f cm /Im1 Do Q

    q 和 Q 运算符推送和弹出图形状态(重要的是 cm 运算符!)

    【讨论】:

    • 非常感谢你,你是明星!你的回答正是我所需要的。
    • 很晚的问题;但是如何从图像中获取流数据本身呢?你说位图像素数据是什么意思?
    【解决方案2】:

    可以在这里找到一个简单的 C# 程序,用于根据上述内容从 jpg 创建 pdf。

    注意“流”这个词和实际的 jpg-stream 必须用 \n(或 \r\n)分隔的细节!!!

    最好的问候 埃斯克·拉恩

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Drawing;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void WriStr(FileStream Out, string s)
            {
                Out.Write(System.Text.Encoding.ASCII.GetBytes(s), 0, s.Length);
            }
            static void Main(string[] args)
            {
    
                string InJpg = @"InFile.JPG";
                string OutPdf = @"OutFile.pdf";
    
                byte[] buffer = new byte[8192];
                var stream = File.OpenRead(InJpg); // The easiest way to get the metadata is to temporaryly load it as a BMP
                Bitmap bmp = (Bitmap)Bitmap.FromStream(stream);
                int w = bmp.Width; String wf = (w * 72 / bmp.HorizontalResolution).ToString().Replace(",", ".");
                int h = bmp.Height; ; string hf = (h * 72 / bmp.VerticalResolution).ToString().Replace(",", ".");
                stream.Close();
    
                FileStream Out = File.Create(OutPdf);
    
                var lens = new List<long>();
    
                WriStr(Out, "%PDF-1.5\r\n");
    
                lens.Add(Out.Position);
                WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Type /Catalog\r\n/Pages 2 0 R>>\r\nendobj\r\n");
    
                lens.Add(Out.Position);
                WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Count 1/Kids [ <<\r\n" +
                            "/Type /Page\r\n" +
                            "/Parent 2 0 R\r\n" +
                            "/MediaBox [0 0 " + wf + " " + hf + "]\r\n" +
                            "/Resources<<  /ProcSet [/PDF /ImageC]\r\n /XObject <</Im1 4 0 R >>  >>\r\n" +
                            "/Contents 3 0 R\r\n" +
                            ">>\r\n ]\r\n" +
                            ">>\r\nendobj\r\n");
    
                string X = "\r\n" +
                    "q\r\n" +
                    "" + wf + " 0 0 " + hf + " 0 0 cm\r\n" +
                    "/Im1 Do\r\n" +
                    "Q\r\n";
                lens.Add(Out.Position);
                WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Length " + X.Length.ToString() + ">>" +
                            "stream" + X + "endstream\r\n" +
                            "endobj\r\n");
                lens.Add(Out.Position);
                WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Name /Im1" +
                            "/Type /XObject\r\n" +
                            "/Subtype /Image\r\n" +
                            "/Width " + w.ToString() +
                            "/Height " + h.ToString() +
                            "/Length 5 0 R\r\n" +
                            "/Filter /DCTDecode\r\n" +
                            "/ColorSpace /DeviceRGB\r\n" +
                            "/BitsPerComponent 8\r\n" +
                            ">> stream\r\n");
                long Siz = Out.Position;
                var in1 = File.OpenRead(InJpg);
                while (true)
                {
                    var len = in1.Read(buffer, 0, buffer.Length);
                    if (len != 0) Out.Write(buffer, 0, len); else break;
                }
                in1.Close();
                Siz = Out.Position - Siz;
                WriStr(Out, "\r\nendstream\r\n" +
                            "endobj\r\n");
    
                lens.Add(Out.Position);
                WriStr(Out, lens.Count.ToString() + " 0 obj " + Siz.ToString() + " endobj\r\n");
    
                long startxref = Out.Position;
    
                WriStr(Out, "xref\r\n" +
                            "0 " + (lens.Count + 1).ToString() + "\r\n" +
                            "0000000000 65535 f\r\n");
                foreach (var L in lens)
                    WriStr(Out, (10000000000 + L).ToString().Substring(1) + " 00000 n\r\n");
                WriStr(Out, "trailer\r\n" +
                            "<<\r\n" +
                            "  /Size " + (lens.Count + 1).ToString() + "\r\n" +
                            "  /Root 1 0 R\r\n" +
                            ">>\r\n" +
                            "startxref\r\n" +
                            startxref.ToString() + "\r\n%%EOF");
                Out.Close();
            }
        }
    }
    

    添加 2016-04-07:

    这是一个带有 cmets 的更高版本,支持缩放和多 JPG 页面以及一个完整的程序 main-wrapper(附加功能很容易添加,如果省略会很遗憾......)

    using System;
    using System.Collections.Generic;
    //using System.Linq;
    using System.Text;
    using System.Drawing;
    using System.IO;
    
    namespace Jpg2Pdfdir
    {
        class Program
        {
            static void WriStr(FileStream Out, string s, params object[] args)
            {
                s = string.Format(s, args);
                Out.Write(System.Text.Encoding.ASCII.GetBytes(s), 0, s.Length);
            }
            //Combined from http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf
    
            /// <summary>
            /// Create a pdf from a list of jpgs, optionally stretching&compressing them. (Note the scaling is a display&print thing only, the jpg_stream itself is included unchanged) 
            /// </summary>
            /// <param name="InJpgs">List of Jpg (full)names</param>
            /// <param name="OutPdf">Name of the pdf to create</param>
            /// <param name="StretchWs">For each jpg the width-scaling factor, fall back to the last given, and if none to 1.0</param>
            /// <param name="StretchHs">For each jpg the height scalling, none positive or missing value is replaced with the width scale value (to keep aspect ratio)</param>
            static void JpgToPdf(List<string> InJpgs, string OutPdf, List<Double> StretchWs, List<Double> StretchHs)
            {
                if (StretchWs==null || StretchWs.Count==0)StretchWs=new List<double>{1.0}; //default to unchanged
                if (StretchHs==null)StretchHs=new List<double>{}; //Default to all with same aspect ratio
    
                byte[] buffer = new byte[8192];
                int[] ws = new int[InJpgs.Count];
                int[] hs = new int[InJpgs.Count];
                string[] wfs = new string[InJpgs.Count];
                string[] hfs = new string[InJpgs.Count];
                for (int i=0;i<InJpgs.Count;i++) {
                    double StretchW=i<StretchWs.Count?StretchWs[i]:StretchWs[StretchWs.Count-1]; // Fall back to the last
                    double StretchH=i<StretchHs.Count && 0<StretchHs[i]?StretchHs[i]:StretchW; //Fall back to same X-Y scale.
                    System.IO.FileStream stream = File.OpenRead(InJpgs[i]);
                    // The easiest way to get the metadata is to temporaryly load the file, ignoring the ImageData!
                    using (Image Img = Image.FromStream(stream,false, false)) { //Last parameter: vaildateImageData=FALSE
                        ws[i] = Img.Width ; wfs[i] = (ws[i] * StretchW * 72 / Img.HorizontalResolution).ToString(System.Globalization.CultureInfo.InvariantCulture);
                        hs[i] = Img.Height; hfs[i] = (hs[i] * StretchH * 72 / Img.VerticalResolution  ).ToString(System.Globalization.CultureInfo.InvariantCulture);
                    }
                    stream.Close();
                }
    
                FileStream Out = File.Create(OutPdf);
    
                //Holds the object-positions (Or lengths before)
                var lens = new List<long>();
    
                //Must have header
                WriStr(Out, "%PDF-1.5\r\n");
    
                //Obj 1 The catalog, pointing to the pages in object 2
                lens.Add(Out.Position);
                WriStr(Out, "{0} 0 obj " + "<</Type /Catalog\r\n/Pages 2 0 R>>\r\nendobj\r\n", lens.Count);
    
                //Obj 2 The pageS, with inline object for the Kids object of type Page
                //Note the size in the MediaBox, The resource for the image in object 4 (Streams can not be inline objects)
                //And the Contents in object 3, and that the Parent of the Page points back to object 2 self.
                lens.Add(Out.Position);
                String Pages = "";
                for (int i = 0; i < InJpgs.Count; i++) {
                    Pages+= "<<\r\n"+
                            "/Type /Page\r\n" +
                            "/Parent 2 0 R\r\n" +
                            "/MediaBox [0 0 " + wfs[i] + " " + hfs[i] + "]\r\n" +
                            "/Resources << /XObject <</Im"+(1+i).ToString()+" "+(4+3*i).ToString()+" 0 R >>  >>\r\n" +
                            "/Contents "+(3+3*i).ToString()+" 0 R\r\n" +
                            ">>\r\n";
                }
                WriStr(Out, "{0} 0 obj <</Type /Pages /Count {1} /Kids [{2}]\r\n" +
                            ">>\r\nendobj\r\n", lens.Count, InJpgs.Count, Pages);
    
                for (int i = 0; i < InJpgs.Count; i++) {
    
                    // Obj 3+3i. The command stream to do the image Im# in a string, so the length can be evaluated. Note this is WITHOUT the leading and trailing CRLF 
                    string X =
                        "q\r\n" +
                        "" + wfs[i] + " 0 0 " + hfs[i] + " 0 0 cm\r\n" +
                        "/Im"+(1+i).ToString()+" Do\r\n" +
                        "Q";
                    lens.Add(Out.Position);
                    WriStr(Out, lens.Count.ToString() + " 0 obj <</Length {0}>> stream\r\n" +
                                "{1}\r\n" +
                                "endstream\r\n" +
                                "endobj\r\n", X.Length, X);
    
                    // Obj 4+3i of type XObject containing the jpg-stream, and with a reference to the length that will be stored in object 5 when known
                    lens.Add(Out.Position);
                    WriStr(Out, "{0} 0 obj <</Name /Im{1}" +
                                "/Type /XObject\r\n" +
                                "/Subtype /Image\r\n" +
                                "/Width {2}"+ 
                                "/Height {3}"+
                                "/Length {4} 0 R\r\n" +
                                "/Filter /DCTDecode\r\n" +
                                "/ColorSpace /DeviceRGB\r\n" +
                                "/BitsPerComponent 8\r\n" +
                                ">> stream\r\n", lens.Count, 1+i, ws[i], hs[i], 5+3*i);
                    long Siz = Out.Position;
                    var in1 = File.OpenRead(InJpgs[i]);
                    while (true)
                    {
                        var len = in1.Read(buffer, 0, buffer.Length);
                        if (len != 0) Out.Write(buffer, 0, len); else break;
                    }
                    in1.Close();
                    Siz = Out.Position - Siz; // The difference is the stream-length
                    WriStr(Out, "\r\nendstream\r\n" +
                                "endobj\r\n");
    
                    // Obj 5+3i the stream length (not known at the time of the begining of object 4
                    lens.Add(Out.Position);
                    WriStr(Out, "{0} 0 obj {1} endobj\r\n",lens.Count ,Siz);
    
                }
                //Pointer for XREF-table saved
                long startxref = Out.Position;
    
                //The XREF table, note the zero'th object, it is the free-object-list not used here
                WriStr(Out, "xref\r\n" +
                            "0 {0}\r\n" +
                            "0000000000 65535 f\r\n", lens.Count+1);
                //Position of each object saved entered in the XREF
                foreach (var L in lens)
                    WriStr(Out, (10000000000 + L).ToString().Substring(1) + " 00000 n\r\n");
                //The trailer, pointing to object 1 as the Root
                //and the saved startxref last, judt before the %%EOF marker
                WriStr(Out, "trailer\r\n" +
                            "<<\r\n" +
                            "  /Size {0}\r\n" +
                            "  /Root 1 0 R\r\n" +
                            ">>\r\n" +
                            "startxref\r\n", lens.Count+1);
                WriStr(Out, startxref.ToString() + "\r\n" +
                            "%%EOF");
                Out.Close();
            }
    
    
    
            static void Main(string[] args)
            {
                if (0==args.Length)  { 
                    Console.WriteLine("Call with {JpgName [ScaleXY | ScaleW ScaleH] } [OutputName] , OutputName defaults to first .jpg -> .pdf");
                    return;
                }
                List<string> basejpgs = new List<string>();
                double WrkDouble;
                List<double> ScaFacWs = new List<double>();
                List<double> ScaFacHs = new List<double>();
                int i = 0;
                while(i<args.Length && System.IO.File.Exists(args[i]) && System.IO.Path.GetExtension(args[i]).ToLower()==".jpg") {
                    basejpgs.Add(args[i]);
                    i++;
                    if (i<args.Length && Double.TryParse(args[i], out WrkDouble)) {
                        i++;
                    } else {
                        WrkDouble=1.0; //Default to 1x
                    }
                    ScaFacWs.Add(WrkDouble);
                    if (i < args.Length && Double.TryParse(args[i], out WrkDouble))
                    {
                        i++;
                    } else {
                        WrkDouble=-1; //Default to same x-y scale
                    }
                    ScaFacHs.Add(WrkDouble);
                }
                //if (basejpgs.Count==0) basejpgs.Add("Red16x16.jPg"); //####DEBUG####
                string destpdf = basejpgs[0];
                if (i<args.Length && (System.IO.Path.GetExtension(args[i]).ToLower()==".pdf" || System.IO.Path.GetExtension(args[i])=="")) { 
                    destpdf=args[i];
                    i++;
                }
                if (i<args.Length)  { 
                    Console.WriteLine("Too many arguments, or could not decode???");
                }
                destpdf = System.IO.Path.ChangeExtension(destpdf, ".PDF");
                JpgToPdf(basejpgs, destpdf, ScaFacWs, ScaFacHs);
            }
        }
    }
    

    【讨论】:

    • 非常有用的代码!如果您能评论这些步骤,那就太好了。
    • 谢谢。有用的代码。访问者可以参考这篇文章,这篇文章可能会用于创建更复杂的 PDF 生成代码:codeproject.com/Articles/18623/Add-Images-and-Textboxes-to-PDF
    • 不错的链接,谢谢!我只是把上面的图像用作一个古老程序制作的发票的背景,所以只需要将 JPG 转换为 PDF,所以从来没有真正概括它 - 也没有制作有助于解码我正在做的事情的 cmets。 .. 哈哈
    • ....那是我没有添加评论然后我后来做了,只是添加了带有 cmets 和附加功能的代码
    猜你喜欢
    • 2013-08-13
    • 2012-07-28
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 2011-09-03
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多