使用 CoreGraphics CGContextDrawImage 在 CATiledLayer 中绘图

Posted

技术标签:

【中文标题】使用 CoreGraphics CGContextDrawImage 在 CATiledLayer 中绘图【英文标题】:Drawing in CATiledLayer with CoreGraphics CGContextDrawImage 【发布时间】:2011-05-03 07:45:19 【问题描述】:

我想在 iPhone OS 3.1.3 中使用 CATiledLayer,为此,-(void)drawLayer:(CALayer *)layer inContext:(CGContext)context 中的所有绘图都必须仅使用 coregraphics 完成。

现在我遇到了 iPhone 上翻转坐标系的问题,有一些建议如何使用变换来解决它:

Image is drawn upside down CATiledLayer or CALayer not working

我的问题是我无法让它工作。我开始使用 PhotoScroller 示例代码并仅用 coregraphics 调用替换绘图方法。看起来是这样的

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context 
CGContextSaveGState(context);
CGRect rect = CGContextGetClipBoundingBox(context);
CGFloat scale = CGContextGetCTM(context).a;
CGContextConcatCTM(context, CGAffineTransformMakeTranslation(0.f, rect.size.height));
CGContextConcatCTM(context, CGAffineTransformMakeScale(1.f, -1.f));

CATiledLayer *tiledLayer = (CATiledLayer *)layer;
CGSize tileSize = tiledLayer.tileSize;


tileSize.width /= scale;
tileSize.height /= scale;
// calculate the rows and columns of tiles that intersect the rect we have been asked to draw
int firstCol = floorf(CGRectGetMinX(rect) / tileSize.width);
int lastCol = floorf((CGRectGetMaxX(rect)-1) / tileSize.width);
int firstRow = floorf(CGRectGetMinY(rect) / tileSize.height);
int lastRow = floorf((CGRectGetMaxY(rect)-1) / tileSize.height);
for (int row = firstRow; row <= lastRow; row++) 
    for (int col = firstCol; col <= lastCol; col++) 
     //   if (row == 0 ) continue;
        UIImage *tile = [self tileForScale:scale row:row col:col];
        CGImageRef tileRef = [tile CGImage];
        CGRect tileRect = CGRectMake(tileSize.width * col, tileSize.height * row,
                                     tileSize.width, tileSize.height);
        // if the tile would stick outside of our bounds, we need to truncate it so as to avoid
        // stretching out the partial tiles at the right and bottom edges
        tileRect = CGRectIntersection(self.bounds, tileRect);
        NSLog(@"row:%d, col:%d, x:%.0f y:%.0f, height:%.0f, width:%.0f", row, col,tileRect.origin.x, tileRect.origin.y, tileRect.size.height, tileRect.size.width);

        //[tile drawInRect:tileRect];
        CGContextDrawImage(context, tileRect, tileRef);
// just to draw the row and column index in the tile and mark the origin of the tile with a red line        
        if (self.annotates) 
            CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor]CGColor]);
            CGContextSetLineWidth(context, 6.0 / scale);
            CGContextStrokeRect(context, tileRect);
            CGContextSetStrokeColorWithColor(context, [[UIColor redColor]CGColor]);
            CGContextMoveToPoint(context, tileRect.origin.x, tileRect.origin.y);
            CGContextAddLineToPoint(context, tileRect.origin.x+100.f, tileRect.origin.y+100.f);
            CGContextStrokePath(context);
            CGContextSetStrokeColorWithColor(context, [[UIColor redColor]CGColor]);
            CGContextSetFillColorWithColor(context, [[UIColor whiteColor]CGColor]);
            CGContextSelectFont(context, "Courier", 128, kCGEncodingMacRoman);
            CGContextSetTextDrawingMode(context, kCGTextFill);
            CGContextSetShouldAntialias(context, true);
            char text[30]; 
            int length = sprintf(text,"row:%d col:%d",row,col);
            CGContextSaveGState(context);
            CGContextShowTextAtPoint(context, tileRect.origin.x+110.f,tileRect.origin.y+100.f, text, length);
            CGContextRestoreGState(context);
        
    


CGContextRestoreGState(context);

如您所见,我使用缩放变换来反转坐标系,并使用平移变换将原点移动到左下角。图像正确绘制,但仅绘制第一行图块。我认为平移操作或图块坐标的计算方式存在问题。

看起来是这样的:

我对所有这些转换有点困惑。

奖金问题: 如何处理核心图形中的视网膜显示图片?

编辑: 为了让它在视网膜显示器上工作,我只是从示例代码中采用原始方法来提供图像:

- (UIImage *)tileForScale:(CGFloat)scale row:(int)row col:(int)col

// we use "imageWithContentsOfFile:" instead of "imageNamed:" here because we don't want UIImage to cache our tiles
NSString *tileName = [NSString stringWithFormat:@"%@_%d_%d_%d", imageName, (int)(scale * 1000), col, row];
NSString *path = [[NSBundle mainBundle] pathForResource:tileName ofType:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:path];
return image;

原则上显示的比例会被忽略,因为 Core Graphics 以像素而不是点工作,因此当要求绘制更多像素时,会使用更多 CATiledLayers(或子图层)来填充屏幕。

非常感谢 托马斯

【问题讨论】:

【参考方案1】:

Thomas,我从关注 WWDC 2010 ScrollView 演讲开始,关于在 ios 3.x 中使用 drawLayer:inContext 的文档很少或根本没有。当我将演示代码从 drawRect: 移到 drawLayer:inContext: 时,我遇到了和你一样的问题。

一些调查显示,在drawLayer:inContext: 内,从CGContextGetClipBoundingBox(context) 返回的rect 的大小和偏移量正是您想要绘制的内容。drawRect: 为您提供了整个范围。

了解这一点后,您可以删除行和列迭代,以及边缘图块的 CGRect 交集,并在翻译上下文后直接绘制到矩形。

这是我最终得到的结果:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 
  CGRect rect = CGContextGetClipBoundingBox(ctx);
  CGFloat scale = CGContextGetCTM(ctx).a;

  CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
  CGSize tileSize = tiledLayer.tileSize;
  tileSize.width /= scale;
  tileSize.height /= scale;

  int col = floorf((CGRectGetMaxX(rect)-1) / tileSize.width);
  int row = floorf((CGRectGetMaxY(rect)-1) / tileSize.height);

  CGImageRef image = [self imageForScale:scale row:row col:col];

  if(NULL != image) 
    CGContextTranslateCTM(ctx, 0.0, rect.size.height);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    rect = CGContextGetClipBoundingBox(ctx);

    CGContextDrawImage(ctx, rect, image);
    CGImageRelease(image);
  

注意rectTranslateCTMScaleCTM 之后重新定义。

这里是我的imageForScale:row:col函数供参考:

- (CGImageRef) imageForScale:(CGFloat)scale row:(int)row col:(int)col 
  CGImageRef image = NULL;
  CGDataProviderRef provider = NULL;
  NSString *filename = [NSString stringWithFormat:@"img_name_here%0.0f_%d_%d",ceilf(scale * 100),col,row];
  NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"png"];

  if(path != nil) 
    NSURL *imageURL = [NSURL fileURLWithPath:path];

    provider = CGDataProviderCreateWithURL((CFURLRef)imageURL);

    image = CGImageCreateWithPNGDataProvider(provider,NULL,FALSE,kCGRenderingIntentDefault); 
    CFRelease(provider);
  
  return image;

为了正确支持高分辨率图形,这两个功能还有一些工作要做,但它在除 iPhone 4 之外的所有设备上看起来都不错。

【讨论】:

感谢您的回答。不能只使用 [[UIImage imageNamed:@"imageName"] CGImage] 方法为视网膜显示获取正确的图像,因为 UIImage 方法应该获得正确的分辨率? 真的很好用!谢谢!我基本上使用从示例代码中获取的方法来提供图像图块,因此在视网膜显示器上它只是以不同的缩放比例显示更多图块来绘制图像。我将方法添加到原始问题中。

以上是关于使用 CoreGraphics CGContextDrawImage 在 CATiledLayer 中绘图的主要内容,如果未能解决你的问题,请参考以下文章

使用 CoreGraphics CGContextDrawImage 在 CATiledLayer 中绘图

CGContext:如何擦除位图上下文之外的像素(例如 kCGBlendModeClear)?

与核心图形的加法混合

CGContextRef详解

iOS绘图CGContextRef详解

用 CGPathRef 屏蔽 CGContext?