从postscript与Quartz 2D生成的PDF

时间:2014-04-22 03:10:55

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

我正在开发一个程序,使用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,后记输出位于左侧,我的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);}

2 个答案:

答案 0 :(得分:0)

我需要看到两个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点高(每个1英寸)方向)用这种颜色填充一个矩形。

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

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

现在我怀疑问题在于你是否使用整数值来定位布局&#34;,你不能这样做并获得正确的(即预期的)结果。您应该使用实数来布局,这样可以更好地控制位置。请记住,处理单个像素,您将图形定位在坐标系统中,分辨率仅在渲染(即查看或打印)PDF文件时起作用。你需要放弃对像素的关注,只关注定位。

答案 1 :(得分:0)

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

我做的第一件事就是预览,我进入预览&gt;首选项&gt; PDF并选择“将100%比例定义为:1点等于1个屏幕像素”。关闭我的antialisaing这样做导致我的图像显示为我想要的,但放大它,似乎预览很难决定何时绘制1像素间隙。

然后我通过删除关闭抗锯齿的调用来改变我的代码并且我的输出渲染完美,因此使用抗锯齿修复了我的问题。非常尴尬,这花了我三天的时间来弄明白,但我很高兴这个简单的修复。