【问题标题】:Merge background color and placeholder image in one image将背景颜色和占位符图像合并到一张图像中
【发布时间】:2021-09-25 21:06:46
【问题描述】:

我正在构建一种刮刮卡体验,其中我有背景颜色和顶部的透明 png。 在此之下,我有带有刮刮卡内容的实际图像。 我想将背景颜色和透明图像组合为一个 uiimage,这样当我刮擦它时,我能够看到以下内容。

我尝试为实际图像添加背景颜色,但是当我刮擦它时,我看不到内容。而是背景颜色开始清除透明图像。

我已经编写了以下代码,用于在触摸后清除图像区域:

- (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);
            //            CGContextMoveToPoint(ctx, prevPoint.x, prevPoint.y);
            //            CGContextAddLineToPoint(ctx, rect.origin.x, rect.origin.y);
            
            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);
        }
    }
    
    // was _tilesFilled changed?
    if(tempFilled != _tilesFilled) {
        [_delegate mdScratchImageView:self didChangeMaskingProgress:self.maskingProgress];
    }
    
    CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
    UIImage *image = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);
    return image;
}

/*
 * filling tile with one ellipse
 */
-(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++;
    }
}

/*
 * filling tile with line
 */
-(void)fillTileWithTwoPoints:(CGPoint)begin end:(CGPoint)end{
    CGFloat incrementerForx,incrementerFory;
    static const FillTileWithPointFunc fillTileFunc = (FillTileWithPointFunc) [self methodForSelector:@selector(fillTileWithPoint:)];
    
    /* incrementers - about size of a tile */
    incrementerForx = (begin.x < end.x ? 1 : -1) * self.image.size.width / _tilesX;
    incrementerFory = (begin.y < end.y ? 1 : -1) * self.image.size.height / _tilesY;
    
    // iterate on points between begin and end
    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);
}

【问题讨论】:

  • 您能展示一下您是如何构建实际图像的吗?如果我的理解正确,您的半透明图像会被清除,而不是图像的背景颜色?
  • 您想从一个纯色矩形开始,然后“显示”图像吗?或者,您想从一张图片开始,然后“展示不同的图片”?
  • @SeaSpell UIImage *info_icon = [UIImage imageNamed:@"scratchcard_placeholder"]; UIImage *imageResize = [self scaleImage:info_icon toSize:viewSize]; _imageScratched = [[UIImageView alloc] initWithImage:imageResize]; _imageScratched.backgroundColor = UIColor.blue
  • @DonMag 我想在用户将手指放在卡片上的任何位置开始显示图像。因此,理想情况下,图像和背景颜色应该被视为一种颜色。
  • 好吧..你在哪里填充和抚摸?这有什么作用 [self.maskedMatrix setValue:1 forCoordinates:x y:y];

标签: ios objective-c uiimage


【解决方案1】:

您可能想要做的是使用图层蒙版。

屏蔽层时,来自 Apple 的文档:

图层的 Alpha 通道决定了图层的内容和背景有多少可以透过。完全或部分不透明的像素允许底层内容显示出来,但完全透明的像素会阻挡该内容。

因此,您需要使用路径来掩盖您的黑眼圈。

但是,要获得“刮掉”效果,您需要使用清晰笔划来绘制路径...这是 CAShapeLayer 无法完成的。

因此,我们将使用自定义 CALayer 子类。

MyShapeLayer.h

//
//  MyShapeLayer.h
//

#import <QuartzCore/QuartzCore.h>

@interface MyShapeLayer : CALayer

@property(nonatomic) CGPathRef path;

@end

MyShapeLayer.m

//
//  MyShapeLayer.m
//

#import <UIKit/UIKit.h>
#import "MyShapeLayer.h"

@implementation MyShapeLayer

- (void)drawInContext:(CGContextRef)inContext {
    // fill entire layer with solid color
    CGContextSetGrayFillColor(inContext, 0.0, 1.0);
    CGContextFillRect(inContext, self.bounds);
    
    // we want to "clear" the stroke
    CGContextSetStrokeColorWithColor(inContext, [UIColor clearColor].CGColor);
    // any color will work, as the mask uses the alpha value
    CGContextSetFillColorWithColor(inContext, [UIColor whiteColor].CGColor);
    // adjust drawing-line-width as desired
    CGContextSetLineWidth(inContext, 60.0);

    CGContextSetLineCap(inContext, kCGLineCapRound);
    CGContextSetLineJoin(inContext, kCGLineJoinRound);
    CGContextAddPath(inContext, self.path);
    CGContextSetBlendMode(inContext, kCGBlendModeSourceIn);
    CGContextDrawPath(inContext, kCGPathFillStroke);
}

@end

现在我们可以创建一个UIView 子类来在CAShapeLayer 上绘制一个实心圆路径,并用我们的MyShapeLayer 遮盖它。

ScratchOffView.h

//
//  ScratchOffView.h
//

#import <UIKit/UIKit.h>

@interface ScratchOffView : UIView

@property (assign, readwrite) CGFloat expandedBounds;

@end

ScratchOffView.m

//
//  ScratchOffView.m
//

#import "ScratchOffView.h"

#import "MyShapeLayer.h"

@interface ScratchOffView()

@property (strong, nonatomic) UIBezierPath *maskPath;
@property (strong, nonatomic) MyShapeLayer *maskLayer;
@property (strong, nonatomic) CAShapeLayer *scratchOffShapeLayer;
@property (strong, nonatomic) CALayer *scratchOffLayer;

@end

@implementation ScratchOffView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {
    
    _maskPath = [UIBezierPath new];
    _maskLayer = [MyShapeLayer new];
    _scratchOffLayer = [CALayer new];
    _scratchOffShapeLayer = [CAShapeLayer new];
    
    // Important, otherwise you will get a black rectangle
    _maskLayer.opaque = NO;

    // add the layer holding the shape to "Scratch Off"
    [self.layer addSublayer:_scratchOffShapeLayer];
    
    UIColor *c = [UIColor colorWithRed:50.0 / 255.0 green:150.0 / 255.0 blue:140.0 / 255.0 alpha:1.0];
    [_scratchOffShapeLayer setFillColor:c.CGColor];
    
    // set the mask layer
    [_scratchOffShapeLayer setMask:_maskLayer];
    
    // default 0.0 == no expanded bounds for touch
    _expandedBounds = 0.0;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    [_maskLayer setFrame:[self bounds]];
    [_scratchOffShapeLayer setFrame:[self bounds]];
    
    UIBezierPath *b = [UIBezierPath bezierPathWithOvalInRect:[self bounds]];
    [_scratchOffShapeLayer setPath:b.CGPath];
    
    // triggers drawInContext
    [_maskLayer setNeedsDisplay];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    [_maskPath moveToPoint:currentPoint];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    // add line to our maskPath
    [_maskPath addLineToPoint:currentPoint];
    // update the mask layer path
    [_maskLayer setPath:_maskPath.CGPath];
    // triggers drawInContext
    [_maskLayer setNeedsDisplay];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // accept touch if within expanded bounds
    //  setting _expandedBounds to a Positive number allows the
    //  touches to start outside the frame
    CGRect r = CGRectInset([self bounds], -_expandedBounds, -_expandedBounds);
    return CGRectContainsPoint(r, point);
}

@end

请注意,我们添加了一个属性:expandedBounds。由于只有在此视图上开始时才会注册触摸,因此我们可以(实际上)扩展视图的边界,以便用户可以触摸并“拖入圆圈中”。

这是一个完整的示例实现。为了尝试匹配您的问题,我使用这张图片 (420 x 460 pixels) 作为“背景”图片:

并且这张图片 (284 x 284 pixels) 作为“在刮掉的圆圈下显示的图片”(透明区域是我们希望圆圈的大小):

ScratchOffTestViewController.h

//
//  ScratchOffTestViewController.h
//

#import <UIKit/UIKit.h>

@interface ScratchOffTestViewController : UIViewController

@end

ScratchOffTestViewController.m

//
//  ScratchOffTestViewController.m
//

#import "ScratchOffTestViewController.h"
#import "ScratchOffView.h"

@interface ScratchOffTestViewController ()

@property (strong, nonatomic) ScratchOffView *scratchOffView;

@end

@implementation ScratchOffTestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    // create the Scratch Off View
    _scratchOffView = [ScratchOffView new];
    
    // load background and giftBox image
    UIImage *bkgImage = [UIImage imageNamed:@"backgroundImage"];
    UIImage *giftBoxImage = [UIImage imageNamed:@"giftBox"];
    if (!bkgImage || !giftBoxImage) {
        NSLog(@"Could not load images!!!");
        return;
    }
    
    UIImageView *bkgImageView = [UIImageView new];
    UIImageView *giftImageView = [UIImageView new];
    
    bkgImageView.image = bkgImage;
    giftImageView.image = giftBoxImage;
    
    bkgImageView.translatesAutoresizingMaskIntoConstraints = NO;
    giftImageView.translatesAutoresizingMaskIntoConstraints = NO;
    _scratchOffView.translatesAutoresizingMaskIntoConstraints = NO;

    [self.view addSubview:bkgImageView];
    [self.view addSubview:giftImageView];
    [self.view addSubview:_scratchOffView];
    
    UILayoutGuide *g = [self.view safeAreaLayoutGuide];
    [NSLayoutConstraint activateConstraints:@[
        
        // constrain background image view to background image size
        [bkgImageView.widthAnchor constraintEqualToConstant:bkgImage.size.width],
        [bkgImageView.heightAnchor constraintEqualToConstant:bkgImage.size.height],
        // centered
        [bkgImageView.centerXAnchor constraintEqualToAnchor:g.centerXAnchor],
        [bkgImageView.centerYAnchor constraintEqualToAnchor:g.centerYAnchor],

        // constrain giftBox image view to giftBox image size
        [giftImageView.widthAnchor constraintEqualToConstant:giftBoxImage.size.width],
        [giftImageView.heightAnchor constraintEqualToConstant:giftBoxImage.size.height],
        // centered horizontally, and a little above vertically
        [giftImageView.centerXAnchor constraintEqualToAnchor:bkgImageView.centerXAnchor],
        [giftImageView.centerYAnchor constraintEqualToAnchor:bkgImageView.centerYAnchor],
        
        // constrain Scratch Off View to giftImageView
        [_scratchOffView.widthAnchor constraintEqualToAnchor:giftImageView.widthAnchor],
        [_scratchOffView.heightAnchor constraintEqualToAnchor:giftImageView.widthAnchor],
        [_scratchOffView.centerXAnchor constraintEqualToAnchor:giftImageView.centerXAnchor],
        [_scratchOffView.centerYAnchor constraintEqualToAnchor:giftImageView.centerYAnchor],
        
    ]];
    
    // expand the touch bounds of the Scratch Off View by 80-pts
    _scratchOffView.expandedBounds = 80.0;
    
    return;

}

@end

在开始时,我们会看到:

在圆圈上触摸拖动一点后,我们看到了:

如果我们继续拖动触摸,深绿色的圆圈最终会完全消失——我们将“刮掉它”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-08
    • 2020-09-25
    • 1970-01-01
    • 2012-06-04
    • 2011-07-22
    • 1970-01-01
    • 1970-01-01
    • 2011-12-18
    相关资源
    最近更新 更多