You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Objective-C:如何在CALayer中为PDF图标设置自定义绘制颜色?

问题描述

已将黑底透明的PDF图标加载到CGPDFPageRef中,需要在CALayer上以指定颜色绘制。目前能正确缩放和旋转图标,但无法修改其渲染颜色。这是macOS桌面航班地图应用的功能,此前使用PNG图标缩放到40x40时会模糊,因此改用矢量PDF文件。现有代码可正确缩放定位图标,但改色代码块无效,图标仍为黑色透明背景。之前处理PNG时可通过NSImage修改颜色,但不清楚如何对PDF执行类似操作。

现有渲染PDF代码

- (void)drawAircraftInContext:(CGContextRef)context acft:(Aircraft *)acft
{
   CGContextSaveGState (context);

   CGRect pdfRect = CGPDFPageGetBoxRect (acft.pdf, kCGPDFCropBox);

   CGContextTranslateCTM (context, acft.x, acft.y);
   CGContextRotateCTM (context, degreesToRadians (acft.heading));
   CGContextTranslateCTM (context, (_iconSize / 2), (_iconSize / 2));
   CGContextScaleCTM (context, .0 - (_iconSize / pdfRect.size.width), .0 - (_iconSize / pdfRect.size.height));

// 此代码块无效,图标仍为黑色透明背景
   CGContextSetAlpha (context, _radarView.militaryAlpha);
   CGContextSetFillColorWithColor (context, _militaryColour);
   CGContextSetStrokeColorWithColor (context, _militaryColour);
   CGContextSetBlendMode (context, kCGBlendModeMultiply);
// 如何修改图像颜色?

   CGContextDrawPDFPage (context, acft.pdf);

   CGRect rect = CGRectMake (0.0 - (_iconSize / 2.0), 0.0 - (_iconSize / 2.0), _iconSize, _iconSize);

   // 绘制高亮效果
   if (acft == _radarView.selected && !_radarView.showOnlySelectedAircraft)
      [self drawAircraftHighlightInContext:context rect:rect acft:acft];

   CGContextRestoreGState (context);
}

PDF加载代码

// 测试是否可将PDF视为矢量而非图像
if (acft.pdfIcon)
{
    NSURL *nsurl = [NSURL fileURLWithPath:filename isDirectory:NO];
    CFURLRef url = (CFURLRef)CFBridgingRetain(nsurl);
    CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL (url);
    CGPDFPageRef page = CGPDFDocumentGetPage (pdf, 1);

    /* 可能需要基于原PDF创建新CGPDFPageRef并绘制为指定颜色 */

    acft.pdf = page;
}

此前PNG改色代码

image = [[NSImage alloc] initWithContentsOfFile:filename];

/*
 * 我们的PNG图标为黑色,需用指定颜色区分军用、民用或模拟机
 */
[image lockFocus];
[[[NSColor colorWithCGColor:colour] colorWithAlphaComponent:alpha] set];
NSRect imageRect = { NSZeroPoint, [image size] };
NSRectFillUsingOperation (imageRect, NSCompositeSourceIn);
[image unlockFocus];
}

if (image)
{
    CGRect rect = CGRectMake (0.0, 0.0, _iconSize, _iconSize);

    return ([image CGImageForProposedRect:&rect
                                  context:nil
                                    hints:@{}]);
}

return ([image CGImageForProposedRect:&rect
                              context:nil
                                hints:@{}]);
}
解决方案

直接通过CGContext设置颜色混合模式对PDF无效,因为PDF自身的绘制指令会覆盖上下文的颜色设置。可通过以下两种方式实现PDF图标改色:

方法一:将PDF转为NSImage后再改色(复用PNG改色逻辑)

利用NSImage加载PDF,沿用之前PNG的改色流程,既能保留矢量清晰度,又能实现颜色修改:

// 替换原PDF加载逻辑,改为生成带指定颜色的NSImage
if (acft.pdfIcon)
{
    NSURL *pdfURL = [NSURL fileURLWithPath:filename isDirectory:NO];
    NSImage *pdfImage = [[NSImage alloc] initWithContentsOfURL:pdfURL];
    // 设置PDF图像尺寸,确保矢量缩放
    [pdfImage setSize:NSMakeSize(_iconSize, _iconSize)];
    
    // 复用PNG的改色逻辑
    [pdfImage lockFocus];
    [[[NSColor colorWithCGColor:_militaryColour] colorWithAlphaComponent:_radarView.militaryAlpha] set];
    NSRect imageRect = {NSZeroPoint, [pdfImage size]};
    NSRectFillUsingOperation(imageRect, NSCompositeSourceIn);
    [pdfImage unlockFocus];
    
    // 将改色后的NSImage转为CGImage存储,后续绘制用
    CGRect rect = CGRectMake(0.0, 0.0, _iconSize, _iconSize);
    CGImageRef coloredCGImage = [pdfImage CGImageForProposedRect:&rect context:nil hints:@{}];
    acft.coloredCGImage = coloredCGImage; // 需在Aircraft类中新增CGImageRef属性
}

// 修改渲染方法,直接绘制改色后的CGImage
- (void)drawAircraftInContext:(CGContextRef)context acft:(Aircraft *)acft
{
    CGContextSaveGState(context);
    
    CGContextTranslateCTM(context, acft.x, acft.y);
    CGContextRotateCTM(context, degreesToRadians(acft.heading));
    // 调整绘制位置,对应原PDF的缩放逻辑
    CGRect drawRect = CGRectMake(-_iconSize/2, -_iconSize/2, _iconSize, _iconSize);
    CGContextDrawImage(context, drawRect, acft.coloredCGImage);
    
    // 绘制高亮效果(保留原有逻辑)
    if (acft == _radarView.selected && !_radarView.showOnlySelectedAircraft)
    {
        [self drawAircraftHighlightInContext:context rect:drawRect acft:acft];
    }
    
    CGContextRestoreGState(context);
}

方法二:使用CGPDFOperatorOverride拦截PDF绘制指令

通过重写PDF的填充/描边指令,强制使用指定颜色,适合需要直接操作PDF的场景:

// 定义PDF操作拦截回调
static void overrideFillOperator(CGPDFScannerRef scanner, void *info)
{
    CGContextRef context = (CGContextRef)info;
    // 跳过原PDF的颜色设置,强制使用指定颜色
    CGContextSetFillColorWithColor(context, _militaryColour); // 可通过info传递颜色,避免全局变量
    CGContextFillPath(context);
}

static void overrideStrokeOperator(CGPDFScannerRef scanner, void *info)
{
    CGContextRef context = (CGContextRef)info;
    CGContextSetStrokeColorWithColor(context, _militaryColour);
    CGContextStrokePath(context);
}

// 修改渲染方法,添加操作拦截
- (void)drawAircraftInContext:(CGContextRef)context acft:(Aircraft *)acft
{
    CGContextSaveGState(context);
    
    CGRect pdfRect = CGPDFPageGetBoxRect(acft.pdf, kCGPDFCropBox);
    CGContextTranslateCTM(context, acft.x, acft.y);
    CGContextRotateCTM(context, degreesToRadians(acft.heading));
    CGContextTranslateCTM(context, (_iconSize / 2), (_iconSize / 2));
    CGContextScaleCTM(context, -(_iconSize / pdfRect.size.width), -(_iconSize / pdfRect.size.height));
    
    CGContextSetAlpha(context, _radarView.militaryAlpha);
    
    // 创建PDF扫描器,拦截填充和描边操作
    CGPDFContentStreamRef contentStream = CGPDFContentStreamCreateWithPage(acft.pdf);
    CGPDFScannerRef scanner = CGPDFScannerCreate(contentStream, context, (__bridge void *)context);
    
    // 替换默认的填充和描边操作(根据PDF实际使用的操作符添加更多回调)
    CGPDFScannerSetOperatorCallback(scanner, "f", overrideFillOperator);
    CGPDFScannerSetOperatorCallback(scanner, "S", overrideStrokeOperator);
    CGPDFScannerSetOperatorCallback(scanner, "F", overrideFillOperator);
    CGPDFScannerSetOperatorCallback(scanner, "s", overrideStrokeOperator);
    
    // 扫描并执行修改后的PDF指令
    if (!CGPDFScannerScan(scanner))
    {
        // 扫描失败时 fallback 到原绘制逻辑
        CGContextDrawPDFPage(context, acft.pdf);
    }
    
    // 释放资源
    CGPDFScannerRelease(scanner);
    CGPDFContentStreamRelease(contentStream);
    
    // 绘制高亮效果
    CGRect rect = CGRectMake(-_iconSize/2, -_iconSize/2, _iconSize, _iconSize);
    if (acft == _radarView.selected && !_radarView.showOnlySelectedAircraft)
        [self drawAircraftHighlightInContext:context rect:rect acft:acft];
    
    CGContextRestoreGState(context);
}

注意事项

  • 方法一更简单,复用原有PNG改色逻辑,适合快速实现;
  • 方法二更灵活,但需要处理PDF中可能用到的各种绘制操作符;
  • 确保PDF图标是纯黑色+透明背景,改色才能得到正确效果;
  • 记得在Aircraft类中新增对应属性存储改色后的图像,并在合适时机释放CGImageRef避免内存泄漏。

内容的提问来源于stack exchange,提问作者user9398585

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 16:40:54