iOS刮刮卡开发:如何合并背景色与透明PNG为单个UIImage?
iOS刮刮卡交互实现方案
效果示意图:
问题根因
直接给承载顶层透明PNG的UIImageView设置背景色时,背景色属于视图层属性,和PNG图片本身是两个独立的渲染单元。你当前的擦除逻辑仅修改UIImage的像素内容,擦除PNG透明区域的像素时,对应位置的视图背景色也会一同暴露为空,无法正常显示下层内容。
1. 预合并背景色与透明PNG为单张刮层图片
调用以下方法将指定背景色和透明PNG合并为一张完整的UIImage,作为后续擦除操作的唯一刮层资源:
- (UIImage *)mergeBackgroundColor:(UIColor *)bgColor withPNG:(UIImage *)pngImage { CGSize size = pngImage.size; CGFloat scale = pngImage.scale; // 这里设为NO是为了保留擦除后的透明通道 UIGraphicsBeginImageContextWithOptions(size, NO, scale); // 先填充背景色 [bgColor setFill]; UIRectFill(CGRectMake(0, 0, size.width, size.height)); // 再绘制顶层透明PNG [pngImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; UIImage *mergedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return mergedImage; }
2. 擦除逻辑适配
你现有触摸擦除代码逻辑基本可用,仅需确认两点配置:
- 初始化存储刮层内容的位图上下文
_imageContext时,使用kCGImageAlphaPremultipliedLast格式,支持alpha通道修改 - 擦除绘制时使用clearColor填充/描边,你当前代码已满足该要求
3. 视图层级配置
- 最下层放置刮开后要展示的内容视图/图片
- 上层放置刮层UIImageView,将第一步生成的合并图片赋值给它的
image属性 - 所有触摸擦除操作仅修改上层刮层ImageView的image内容,擦除区域像素变为透明后即可直接露出下层内容
现有可复用擦除代码
- (UIImage *)addTouches:(NSSet *)touches { CGSize size = CGSizeMake(self.image.size.width * self.image.scale, self.image.size.height * self.image.scale); CGContextRef ctx = _imageContext; CGContextSetFillColorWithColor(ctx,[UIColor clearColor].CGColor); CGContextSetStrokeColorWithColor(ctx,[UIColor colorWithRed:0 green:0 blue:0 alpha:0].CGColor); int tempFilled = _tilesFilled; // process touches for (UITouch *touch in touches) { CGContextBeginPath(ctx); CGPoint touchPoint = [touch locationInView:self]; touchPoint = fromUItoQuartz(touchPoint, self.bounds.size); touchPoint = scalePoint(touchPoint, self.bounds.size, size); if(UITouchPhaseBegan == touch.phase){ [self.touchPoints removeAllObjects]; [self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]]; [self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]]; // on begin, we just draw ellipse CGRect rect = CGRectMake(touchPoint.x - _radius, touchPoint.y - _radius, _radius*2, _radius*2); CGContextAddEllipseInRect(ctx, rect); CGContextFillPath(ctx); static const FillTileWithPointFunc fillTileFunc = (FillTileWithPointFunc) [self methodForSelector:@selector(fillTileWithPoint:)]; (*fillTileFunc)(self,@selector(fillTileWithPoint:),rect.origin); } else if (UITouchPhaseMoved == touch.phase) { [self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]]; // then touch moved, we draw superior-width line CGContextSetStrokeColor(ctx, CGColorGetComponents([UIColor clearColor].CGColor)); CGContextSetLineCap(ctx, kCGLineCapRound); CGContextSetLineWidth(ctx, 2 * _radius); while(self.touchPoints.count > 3){ CGPoint bezier[4]; bezier[0] = ((NSValue*)self.touchPoints[1]).CGPointValue; bezier[3] = ((NSValue*)self.touchPoints[2]).CGPointValue; CGFloat k = 0.3; CGFloat len = sqrt(pow(bezier[3].x - bezier[0].x, 2) + pow(bezier[3].y - bezier[0].y, 2)); bezier[1] = ((NSValue*)self.touchPoints[0]).CGPointValue; bezier[1] = [self normalizeVector:CGPointMake(bezier[0].x - bezier[1].x - (bezier[0].x - bezier[3].x), bezier[0].y - bezier[1].y - (bezier[0].y - bezier[3].y) )]; bezier[1].x *= len * k; bezier[1].y *= len * k; bezier[1].x += bezier[0].x; bezier[1].y += bezier[0].y; bezier[2] = ((NSValue*)self.touchPoints[3]).CGPointValue; bezier[2] = [self normalizeVector:CGPointMake( (bezier[3].x - bezier[2].x) - (bezier[3].x - bezier[0].x), (bezier[3].y - bezier[2].y) - (bezier[3].y - bezier[0].y) )]; bezier[2].x *= len * k; bezier[2].y *= len * k; bezier[2].x += bezier[3].x; bezier[2].y += bezier[3].y; CGContextMoveToPoint(ctx, bezier[0].x, bezier[0].y); CGContextAddCurveToPoint(ctx, bezier[1].x, bezier[1].y, bezier[2].x, bezier[2].y, bezier[3].x, bezier[3].y); [self.touchPoints removeObjectAtIndex:0]; } CGContextStrokePath(ctx); CGPoint prevPoint = [touch previousLocationInView:self]; prevPoint = fromUItoQuartz(prevPoint, self.bounds.size); prevPoint = scalePoint(prevPoint, self.bounds.size, size); static const FillTileWithTwoPointsFunc fillTileFunc = (FillTileWithTwoPointsFunc) [self methodForSelector:@selector(fillTileWithTwoPoints:end:)]; (*fillTileFunc)(self,@selector(fillTileWithTwoPoints:end:),touchPoint, prevPoint); } } if(tempFilled != _tilesFilled) { [_delegate mdScratchImageView:self didChangeMaskingProgress:self.maskingProgress]; } CGImageRef cgImage = CGBitmapContextCreateImage(ctx); UIImage *image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); return image; } -(void)fillTileWithPoint:(CGPoint) point{ size_t x,y; point.x = MAX( MIN(point.x, self.image.size.width - 1) , 0); point.y = MAX( MIN(point.y, self.image.size.height - 1), 0); x = point.x * self.maskedMatrix.max.x / self.image.size.width; y = point.y * self.maskedMatrix.max.y / self.image.size.height; char value = [self.maskedMatrix valueForCoordinates:x y:y]; if (!value){ [self.maskedMatrix setValue:1 forCoordinates:x y:y]; _tilesFilled++; } } -(void)fillTileWithTwoPoints:(CGPoint)begin end:(CGPoint)end{ CGFloat incrementerForx,incrementerFory; static const FillTileWithPointFunc fillTileFunc = (FillTileWithPointFunc) [self methodForSelector:@selector(fillTileWithPoint:)]; incrementerForx = (begin.x < end.x ? 1 : -1) * self.image.size.width / _tilesX; incrementerFory = (begin.y < end.y ? 1 : -1) * self.image.size.height / _tilesY; CGPoint i = begin; while(i.x <= MAX(begin.x, end.x) && i.y <= MAX(begin.y, end.y) && i.x >= MIN(begin.x, end.x) && i.y >= MIN(begin.y, end.y)){ (*fillTileFunc)(self,@selector(fillTileWithPoint:),i); i.x += incrementerForx; i.y += incrementerFory; } (*fillTileFunc)(self,@selector(fillTileWithPoint:),end); }
内容的提问来源于stack exchange,提问作者Aashna Narula
相关产品推荐
相关产品推荐

