【问题标题】:PDF generated from postscript vs. Quartz 2D从 postscript 与 Quartz 2D 生成的 PDF
【发布时间】:2014-04-22 03:10:55
【问题描述】:

我正在开发一个程序,该程序使用 Quartz 2D 和 Objective C 将针线图案输出为 PDF 文件。我使用了另一个用 Python 编码的程序,它输出 postscript 文件,当我打开它们时将它们转换为 PDF预览。由于第二个应用程序是开源的,我已经能够检查我用于布局我的 PDF 的设置是否相同,特别是正方形的大小和它们之间的间隙大小。

在下图中,另一个程序的输出在左侧,而我的在右侧,两者都是实际大小。我遇到的问题是,在实际大小下,我的输出中的间隙线是断断续续的,而在另一个中,所有的间隙都可以看到。我想知道是否有人知道允许这样做的 postscript 文件的渲染差异。我可以放大我的输出并显示差距,但我不明白为什么会有这种差异。

正方形设置为 8 像素宽和高,在两个应用程序中它们之间有 1 像素的间隙,每 10 个正方形有 2 像素宽的间隙,我的设置为不使用抗锯齿。通过我的输出,我尝试直接绘制到 CGPDFContext 并绘制到 CGLayerRef 然后将图层绘制到 PDF 上下文,但我得到了相同的结果。我使用整数值来定位布局,并且我很确定我已经避免尝试将正方形放置在像素位置的小数部分中。

我也尝试将输出绘制到 CGBitmapContext,然后将生成的位图绘制到 PDF 上下文,但放大后会产生可怕的伪影,因为它是被放大的光栅。

我注意到的最后一个区别是 postscript 生成的 PDF 的文件大小比我制作的要小得多,我认为这可能与我绘制的路径有关,因为它说要绘制到PDF 上下文将绘图记录为写入文件的一系列 PDF 绘图命令,我想这与仅显示图像相比会占用相当多的空间。

我已经在下面包含了我的代码来生成我的 PDF,以防它会有所帮助,但我真的只是想知道 postscript 和 Quartz 之间是否存在可以解释这些差异的渲染差异,以及是否有办法制作我的输出匹配。

(上传者说我需要至少 10 个声望才能发布图片,但我再次拥有此链接 http://i.stack.imgur.com/nr588.jpg,postscript 输出在左侧,我的 Quartz 输出在右侧,在我的输出中,网格线是间歇)

-(void)makePDF:(NSImage*)image withPixelArray:(unsigned char *)rawData{

NSString *currentUserHomeDirectory = NSHomeDirectory();

currentUserHomeDirectory = [currentUserHomeDirectory stringByAppendingString:@"/Desktop/"];
currentUserHomeDirectory = [currentUserHomeDirectory stringByAppendingString:[image name]];
currentUserHomeDirectory = [currentUserHomeDirectory stringByAppendingPathExtension:@"pdf"];

CGContextRef pdfContext;
CFStringRef path;
CFURLRef url;

int width = 792;
int height = 612;

CFMutableDictionaryRef myDictionary = NULL;
CFMutableDictionaryRef pageDictionary = NULL;

const char *filename = [currentUserHomeDirectory UTF8String];
path = CFStringCreateWithCString (NULL, filename,
                                  kCFStringEncodingUTF8);
url = CFURLCreateWithFileSystemPath (NULL, path,
                                     kCFURLPOSIXPathStyle, 0);
CFRelease (path);
myDictionary = CFDictionaryCreateMutable(NULL, 0,
                                         &kCFTypeDictionaryKeyCallBacks,
                                         &kCFTypeDictionaryValueCallBacks);

CGRect pageRect = CGRectMake(0, 0, width, height);

pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary);

const CGFloat whitePoint[3]= {0.95047, 1.0, 1.08883};

const CGFloat blackPoint[3]={0,0,0};
const CGFloat gammavalues[3] = {2.2,2.2,2.2};
const CGFloat matrix[9] = {0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.072175, 0.0193339, 0.119192, 0.9503041};

CGColorSpaceRef myColorSpace = CGColorSpaceCreateCalibratedRGB(&whitePoint[3], &blackPoint[3], &gammavalues[3], &matrix[9]);

CGContextSetFillColorSpace (
                            pdfContext,
                            myColorSpace
                            );

int annotationNumber =0;
int match=0;

CFRelease(myDictionary);
CFRelease(url);
pageDictionary = CFDictionaryCreateMutable(NULL, 0,
                                           &kCFTypeDictionaryKeyCallBacks,
                                           &kCFTypeDictionaryValueCallBacks);

CFDataRef boxData = CFDataCreate(NULL,(const UInt8 *)&pageRect, sizeof (CGRect));
CFDictionarySetValue(pageDictionary, kCGPDFContextMediaBox, boxData);

int m = 0;
int sidestep = 0;
int downstep = 0;
int maxc = 0;
int maxr = 0;
int columnsPerPage = 70;
int rowsPerPage = 60;

int symbolSize = 8;
int gapSize=1;


CGContextSetShouldAntialias(pdfContext, NO);

int pages = ceil([image size].width/columnsPerPage) * ceil([image size].height/rowsPerPage);

for (int g=0; g<pages; g++) {

    int offsetX = 32;
    int offsetY = 32;

    if (sidestep == ceil([image size].width/columnsPerPage)-1) {
        maxc=[image size].width-sidestep*columnsPerPage;
    }else {
        maxc=columnsPerPage;
    }

    if (downstep == ceil([image size].height/rowsPerPage)-1) {
        maxr=[image size].height-downstep*rowsPerPage;
    }else {
        maxr=rowsPerPage;
    }

    CGPDFContextBeginPage (pdfContext, pageDictionary);
    CGContextTranslateCTM(pdfContext, 0.0, 612);
    CGContextScaleCTM(pdfContext, 1.0, -1.0);
    CGContextSetShouldAntialias(pdfContext, NO);
    int r=0;

    while (r<maxr){
        int c=0;
        while (c<maxc){
            m = sidestep*columnsPerPage+c+downstep*[image size].width*rowsPerPage+r*[image size].width;

            //Reset offsetX
            if (c==0) {
                offsetX=32;
            }
            //Increase offset for gridlines
            if (c==0 && r%10==0&&r!=0) {
                offsetY+=2;
            }
            if (c%10==0&&c!=0) {
                offsetX+=2;
            }
            //DRAW SQUARES

            CGContextSetRGBFillColor (pdfContext, (double)rawData[m*4]/255.,(double) rawData[m*4+1]/255., (double)rawData[m*4+2]/255., 1);

            CGContextFillRect (pdfContext, CGRectMake (c*(symbolSize+gapSize)+offsetX, r*(symbolSize+gapSize)+offsetY, symbolSize, symbolSize ));

            if ([usedColorsPaths count]!=0) {

                for (int z=0; z<[usedColorsPaths count]; z++) {


                    if ([[[usedColorsPaths allKeys] objectAtIndex:z] isEqualToString:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4], rawData[m*4+1], rawData[m*4+2]]]) {
                        match=1;

                        if (rawData[m*4+3] == 0) {

                            CGContextDrawLayerAtPoint (pdfContext, CGPointMake(c*(symbolSize+1)+offsetX-2, r*(symbolSize+1)+offsetY-2), [Anotations colorAnnotations:pdfContext :[[usedColorsPaths objectForKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4],rawData[m*4+1],rawData[m*4+2]]]intValue] :symbolSize+4 :0]);

                        }
                        else{

                            CGContextDrawLayerAtPoint (pdfContext, CGPointMake(c*(symbolSize+1)+offsetX, r*(symbolSize+1)+offsetY),[Anotations colorAnnotations:pdfContext :[[usedColorsPaths objectForKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4], rawData[m*4+1], rawData[m*4+2]]] intValue] :symbolSize :0]);
                        }
                        break;
                    }

                }
                if (match==0) {
                    if (rawData[m*4+3] == 0) {
                        [usedColorsPaths setObject:[NSNumber numberWithInt:455] forKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4], rawData[m*4+1], rawData[m*4+2]]];

                        CGContextDrawLayerAtPoint (pdfContext, CGPointMake(c*(symbolSize+1)+offsetX-2, r*(symbolSize+1)+offsetY-2), [Anotations colorAnnotations:pdfContext :[[usedColorsPaths objectForKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4],rawData[m*4+1],rawData[m*4+2]]]intValue] :symbolSize+4 :0]);

                    }
                    else{
                        [usedColorsPaths setObject:[NSNumber numberWithInt:annotationNumber] forKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4], rawData[m*4+1], rawData[m*4+2]]];

                        CGContextDrawLayerAtPoint (pdfContext, CGPointMake(c*(symbolSize+1)+offsetX, r*(symbolSize+1)+offsetY), [Anotations colorAnnotations:pdfContext :[[usedColorsPaths objectForKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4],rawData[m*4+1],rawData[m*4+2]]]intValue] :symbolSize :0]);
                    }



                    annotationNumber++;

                    if (annotationNumber==9) {
                        annotationNumber=0;
                    }

                }
                match=0;
            }
            if ([usedColorsPaths count]==0) {

                if (rawData[m*4+3] == 0) {
                    [usedColorsPaths setObject:[NSNumber numberWithInt:455] forKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4], rawData[m*4+1], rawData[m*4+2]]];

                    CGContextDrawLayerAtPoint (pdfContext, CGPointMake(c*(symbolSize+1)+offsetX-2, r*(symbolSize+1)+offsetY-2), [Anotations colorAnnotations:pdfContext :[[usedColorsPaths objectForKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4],rawData[m*4+1],rawData[m*4+2]]]intValue] :symbolSize+4 :0]);
                }
                else{

                    [usedColorsPaths setObject:[NSNumber numberWithInt:annotationNumber] forKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4], rawData[m*4+1], rawData[m*4+2]]];

                    CGContextDrawLayerAtPoint (pdfContext, CGPointMake(c*(symbolSize+1)+offsetX, r*(symbolSize+1)+offsetY), [Anotations colorAnnotations:pdfContext :[[usedColorsPaths objectForKey:[NSString stringWithFormat:@"%i,%i,%i",rawData[m*4],rawData[m*4+1],rawData[m*4+2]]]intValue] :symbolSize :0]);
                }

                annotationNumber++;

            }


            c++;

        }

        r++;
    }
    sidestep++;
    if (sidestep == ceil([image size].width/columnsPerPage)) {
        sidestep=0;
        downstep+=1;
    }
    CGContextSaveGState(pdfContext);

    CGPDFContextEndPage (pdfContext);

}

CGContextRelease(pdfContext);
CFRelease(pageDictionary);
CFRelease(boxData);}

【问题讨论】:

    标签: objective-c macos pdf-generation quartz-2d postscript


    【解决方案1】:

    我需要查看这两个 PDF 文件才能判断为什么大小不同,或者为什么行间距是断断续续的,但这里有几点:

    文件大小不同的最可能原因是一个 PDF 文件压缩了内容流(您所指的绘图操作),而另一个没有。

    通常,发出一系列绘图操作比包含位图图像更紧凑,除非图像分辨率非常低。 RGB 图像的每个图像样本占用 3 个字节。如果您考虑 300 dpi 的 1 英寸正方形图像,即 300x300x3 字节或 270,000 字节。如果图像都是一种颜色(参见下面的示例),我可以用 22 字节的 PDF 绘图操作来描述它。

    您不能以像素为单位指定正方形或任何其他图形特征的大小。 PDF 是基于矢量的可缩放格式,而不是位图格式。我不在 Mac 上工作,所以我无法评论您的示例代码,但我怀疑您将媒体宽度和高度(以磅为单位)与像素混淆,这些并不相同。宽度和高度描述了媒体大小,在将 PDF 文件渲染为位图之前不涉及像素,当时设备的分辨率决定了每个点中有多少像素。

    让我们考虑一个一英寸见方的 PDF;宽度为 72,高度为 72。我将用纯红色填充该矩形,其 PDF 操作将是:

    1 0 0 rg
    0 0 72 72 re
    

    因此将非描边颜色设置为 RGB (1, 0, 0),然后从 0, 0(左下角)开始并延伸 72 点宽和 72 点高(每个方向一英寸)填充一个具有该颜色的矩形。

    我在我的 PC 上的屏幕上看到,一英寸见方被渲染为 96 像素 x 96 像素。现在我在带有视网膜显示器的 iPad 上查看它,正方形被渲染为 264 x 264。最后我将它打印到我的激光打印机上,现在正方形被渲染为 600 x 600 像素。PDF 内容没有改变,但是像素数肯定有。正方形当然太简单了,我可以用圆形代替,显然高分辨率显示器会有更平滑的曲线。当然,如果我确实使用了图像,曲线的平滑度会被“烘焙”,如果分辨率发生变化,渲染 PDF 的设备将无法更改它,它所能做的就是丢弃图像样本以进行渲染或插值新的渲染。缩小时看起来参差不齐,放大时看起来很模糊。矢量表示保持平滑,仅受当前分辨率的限制。

    PDF 的重点是 PDF 不限于一种分辨率,它可以打印到所有分辨率,并且每个设备上的输出应该相同(尽可能)。

    现在我怀疑问题是您“使用整数值来定位布局”,您不能这样做并获得正确(即预期)的结果。您应该使用实数进行布局,这样您可以更好地控制位置。请记住,您不是处理单个像素,您是在坐标系中定位图形,分辨率仅在渲染(即查看或打印)PDF 文件时发挥作用。您需要抛开对像素的担忧,只关注定位。

    【讨论】:

    • 感谢您的回答。我使用 Adob​​e Illustrator 做了一些工作,所以我了解了如何使用点而不是像素来制作矢量,但是在制作 PDF 上下文时,我需要提供一个以点为单位表示页面大小的矩形,然后绘制到该上下文,指定绘制点。我想我误用了术语,称它们为像素,对此感到抱歉。如果有办法让我分享 PDF 示例,我会很高兴,但我提供的图片是示例的直接截图。似乎在 PDF 中绘图很难显示上瘾之间的空间
    • 将文件粘贴到 Dropbox 并在此处分享 URL ?你使用的坐标应该是实数(不是整数),如果你这样做,那么它应该都能正常工作(当然,不是 Mac 程序员,我对 Quartz 一无所知)。虽然可以想象 Quartz 将您限制为整数坐标,但这似乎不太可能。您是否尝试过简单地使用实际值?哦,如果在放大时出现预期的间隙,那么它是由将输出缩小到间隙远小于 1 像素宽的点引起的查看器的伪影。
    • 感谢您的帮助,但事实证明这是一个抗锯齿问题。如果没有抗锯齿,在放大视图时,程序在渲染单点间隙时会出现问题。解决方法是重新打开抗锯齿,输出就像一个魅力。感谢您抽出宝贵时间查看此问题,抱歉,我忽略了这么简单的事情。
    【解决方案2】:

    所以,在搞砸了一天之后,我想我发现我的问题是关闭抗锯齿。我以为我想要更清晰的绘图,但由于 PDF 包含矢量图形,抗锯齿没问题,放大图形可以让它们保持清晰。

    我做的第一件事是在预览中,我转到预览>首选项>PDF 并选择“将 100% 比例定义为:1 点等于 1 个屏幕像素”。在我关闭抗锯齿的情况下执行此操作会导致我的图像按我想要的方式显示,但放大它,预览似乎很难决定何时绘制 1 个像素的间隙。

    然后我通过删除关闭抗锯齿的调用来更改我的代码,并且我的输出完美呈现,因此抗锯齿解决了我的问题。很尴尬,我花了三天时间才弄清楚,但我很高兴这个简单的解决方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-24
      • 1970-01-01
      • 2011-12-09
      • 1970-01-01
      • 2011-05-28
      • 1970-01-01
      • 2011-04-04
      • 1970-01-01
      相关资源
      最近更新 更多