【问题标题】:Masking an image using bezierpath with image's full resolution使用具有图像全分辨率的 bezierpath 屏蔽图像
【发布时间】:2018-07-13 23:22:28
【问题描述】:

嗨,我有一个路径(形状)和一个高分辨率图像。我将高分辨率图像设置为在我绘制路径的视图内的 AspectFit,我想用路径掩盖图像,但以图像的全分辨率,而不是我们看到路径的分辨率。问题是,当我不将它们放大以进行高分辨率遮罩时,它可以完美地工作,但是当我这样做时,一切都搞砸了。蒙版被拉伸,原点没有意义。

我想要的只是能够以相同的图像纵横比(以图像的全分辨率)放大路径并正确定位它,以便它可以正确掩盖高分辨率图像。 我试过这个:

Masking CGContext with a CGPathRef?

还有这个

Creating mask with CGImageMaskCreate is all black (iphone)

还有这个

Clip UIImage to UIBezierPath (not masking)

当我尝试屏蔽高质量图像(大于屏幕分辨率)时,这些都不能正常工作

编辑 我在 github 上发布了一个工作项目,以显示正常质量遮罩(以屏幕分辨率)和高质量遮罩(以图像分辨率)之间的问题。我真的很感激任何帮助。 https://github.com/Reza-Abdolahi/HighResMasking

【问题讨论】:

    标签: ios objective-c uiimage uibezierpath cgcontext


    【解决方案1】:

    如果我正确理解您的问题:

    • 您的图像视图包含可能已使用 UIViewContentModeScaleAspectFit 缩小(甚至放大)的图像。
    • 您有一个贝塞尔路径,其点位于该图像视图的几何(坐标系)中。

    现在你想创建一个图像的副本,以原始分辨率,被贝塞尔路径掩盖。

    我们可以将图像视为具有自己的几何形状,原点位于图像的左上角,沿每个轴的一个单位为一个点。所以我们需要做的是:

    1. 创建一个足够大的图形渲染器,可以在不缩放的情况下绘制图像。此渲染器的几何图形就是图像的几何图形。
    2. 将贝塞尔路径从视图几何体转换为渲染器几何体。
    3. 将转换后的路径应用到渲染器的剪辑区域。
    4. 将图像(未转换)绘制到渲染器中。

    第 2 步比较难,因为我们必须找到正确的CGAffineTransform。在切面拟合场景中,变换不仅需要缩放图像,还可能沿 x 轴或 y 轴(但不能同时)平移图像。但让我们更笼统地支持其他UIViewContentMode 设置。这是一个类别,可让您向UIImageView 询问将视图几何中的点转换为图像几何中的点的转换:

    @implementation UIImageView (ImageGeometry)
    
    /**
     * Return a transform that converts points in my geometry to points in the
     * image's geometry. The origin of the image's geometry is at its upper
     * left corner, and one unit along each axis is one point in the image.
     */
    - (CGAffineTransform)imageGeometryTransform {
        CGRect viewBounds = self.bounds;
        CGSize viewSize = viewBounds.size;
        CGSize imageSize = self.image.size;
    
        CGFloat xScale = imageSize.width / viewSize.width;
        CGFloat yScale = imageSize.height / viewSize.height;
        CGFloat tx, ty;
        switch (self.contentMode) {
            case UIViewContentModeScaleToFill: tx = 0; ty = 0; break;
            case UIViewContentModeScaleAspectFit:
                if (xScale > yScale) { tx = 0; ty = 0.5; yScale = xScale; }
                else if (xScale < yScale) { tx = 0.5; ty = 0; xScale = yScale; }
                else { tx = 0; ty = 0; }
                break;
            case UIViewContentModeScaleAspectFill:
                if (xScale < yScale) { tx = 0; ty = 0.5; yScale = xScale; }
                else if (xScale > yScale) { tx = 0.5; ty = 0; xScale = yScale; }
                else { tx = 0; ty = 0; imageSize = viewSize; }
                break;
            case UIViewContentModeCenter: tx = 0.5; ty = 0.5; xScale = yScale = 1; break;
            case UIViewContentModeTop: tx = 0.5; ty = 0; xScale = yScale = 1; break;
            case UIViewContentModeBottom: tx = 0.5; ty = 1; xScale = yScale = 1; break;
            case UIViewContentModeLeft: tx = 0; ty = 0.5; xScale = yScale = 1; break;
            case UIViewContentModeRight: tx = 1; ty = 0.5; xScale = yScale = 1; break;
            case UIViewContentModeTopLeft: tx = 0; ty = 0; xScale = yScale = 1; break;
            case UIViewContentModeTopRight: tx = 1; ty = 0; xScale = yScale = 1; break;
            case UIViewContentModeBottomLeft: tx = 0; ty = 1; xScale = yScale = 1; break;
            case UIViewContentModeBottomRight: tx = 1; ty = 1; xScale = yScale = 1; break;
            default: return CGAffineTransformIdentity; // Mode not supported by UIImageView.
        }
    
        tx *= (imageSize.width - xScale * (viewBounds.origin.x + viewSize.width));
        ty *= (imageSize.height - yScale * (viewBounds.origin.y + viewSize.height));
        CGAffineTransform transform = CGAffineTransformMakeTranslation(tx, ty);
        transform = CGAffineTransformScale(transform, xScale, yScale);
        return transform;
    }
    
    @end
    

    有了这个,我们可以编写屏蔽图像的代码。在我的测试应用程序中,我有一个名为 PathEditingViewUIImageView 子类,它处理贝塞尔路径编辑。所以我的视图控制器像这样创建蒙版图像:

    - (UIImage *)maskedImage {
        UIImage *image = self.pathEditingView.image;
        UIGraphicsImageRendererFormat *format = [[UIGraphicsImageRendererFormat alloc] init];
        format.scale = image.scale;
        format.prefersExtendedRange = image.imageRendererFormat.prefersExtendedRange;
        format.opaque = NO;
        UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:image.size format:format];
        return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
            UIBezierPath *path = [self.pathEditingView.path copy];
            [path applyTransform:self.pathEditingView.imageGeometryTransform];
            CGContextRef gc = UIGraphicsGetCurrentContext();
            CGContextAddPath(gc, path.CGPath);
            CGContextClip(gc);
            [image drawAtPoint:CGPointZero];
        }];
    }
    

    它看起来像这样:

    当然,很难说输出图像是全分辨率的。让我们通过将输出图像裁剪到贝塞尔路径的边界框来解决这个问题:

    - (UIImage *)maskedAndCroppedImage {
        UIImage *image = self.pathEditingView.image;
        UIBezierPath *path = [self.pathEditingView.path copy];
        [path applyTransform:self.pathEditingView.imageGeometryTransform];
        CGRect pathBounds = CGPathGetPathBoundingBox(path.CGPath);
        UIGraphicsImageRendererFormat *format = [[UIGraphicsImageRendererFormat alloc] init];
        format.scale = image.scale;
        format.prefersExtendedRange = image.imageRendererFormat.prefersExtendedRange;
        format.opaque = NO;
        UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:pathBounds.size format:format];
        return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
            CGContextRef gc = UIGraphicsGetCurrentContext();
            CGContextTranslateCTM(gc, -pathBounds.origin.x, -pathBounds.origin.y);
            CGContextAddPath(gc, path.CGPath);
            CGContextClip(gc);
            [image drawAtPoint:CGPointZero];
        }];
    }
    

    蒙版和裁剪在一起看起来像这样:

    您可以在此演示中看到,输出图像的细节比在输入视图中可见的要多得多,因为它是在输入图像的全分辨率下生成的。

    【讨论】:

    • 辛苦了!!!!你是个传奇!!在悲惨地浪费了这么多时间并找到了一个我想出的更简单的解决方案之后,我也让我的代码工作了,我也将把它发布在我上面发布的同一个 github 上。 @rob 梅奥夫
    • 简直是完美的男人!我怀疑我的方法总是会奏效,因为我需要根据我的 image.scale 更改上下文的比例,所以我认为采用你的方法更安全。
    【解决方案2】:

    作为次要答案,我让它与这段代码一起工作,为了更好地理解,你也可以在 github 上获取工作项目,看看它是否适用于所有情况。 我的 github 项目: https://github.com/Reza-Abdolahi/HighResMasking

    解决问题的部分代码:

    -(UIImage*)highResolutionMasking{
        NSLog(@"///High quality (Image resolution) masking///////////////////////////////////////////////////");
    
        //1.Rendering the path into an image with the size of _targetBound (which is the size of a device screen sized view in which the path is drawn.)
        CGFloat aspectRatioOfImageBasedOnHeight = _highResolutionImage.size.height/ _highResolutionImage.size.width;
        CGFloat aspectRatioOfTargetBoundBasedOnHeight = _targetBound.size.height/ _targetBound.size.width;
    
        CGFloat pathScalingFactor = 0;
        if ((_highResolutionImage.size.height >= _targetBound.size.height)||
            (_highResolutionImage.size.width  >= _targetBound.size.width)) {
                //Then image is bigger than targetBound
    
                if ((_highResolutionImage.size.height<=_highResolutionImage.size.width)) {
                //The image is Horizontal
    
                    CGFloat newWidthForTargetBound =_highResolutionImage.size.width;
                    CGFloat ratioOfHighresImgWidthToTargetBoundWidth = (_highResolutionImage.size.width/_targetBound.size.width);
                    CGFloat newHeightForTargetBound = _targetBound.size.height* ratioOfHighresImgWidthToTargetBoundWidth;
    
                    _bigTargetBound = CGRectMake(0, 0, newWidthForTargetBound, newHeightForTargetBound);
                    pathScalingFactor = _highResolutionImage.size.width/_targetBound.size.width;
    
                }else if((_highResolutionImage.size.height > _highResolutionImage.size.width)&&
                         (aspectRatioOfImageBasedOnHeight  <= aspectRatioOfTargetBoundBasedOnHeight)){
                    //The image is Vertical but has smaller aspect ratio (based on height) than targetBound
    
                    CGFloat newWidthForTargetBound =_highResolutionImage.size.width;
                    CGFloat ratioOfHighresImgWidthToTargetBoundWidth = (_highResolutionImage.size.width/_targetBound.size.width);
                    CGFloat newHeightForTargetBound = _targetBound.size.height* ratioOfHighresImgWidthToTargetBoundWidth;
    
                    _bigTargetBound = CGRectMake(0, 0, newWidthForTargetBound, newHeightForTargetBound);
                    pathScalingFactor = _highResolutionImage.size.width/_targetBound.size.width;
    
                }else if((_highResolutionImage.size.height > _highResolutionImage.size.width)&&
                         (aspectRatioOfImageBasedOnHeight  > aspectRatioOfTargetBoundBasedOnHeight)){
    
                    CGFloat newHeightForTargetBound =_highResolutionImage.size.height;
                    CGFloat ratioOfHighresImgHeightToTargetBoundHeight = (_highResolutionImage.size.height/_targetBound.size.height);
                    CGFloat newWidthForTargetBound = _targetBound.size.width* ratioOfHighresImgHeightToTargetBoundHeight;
    
                    _bigTargetBound = CGRectMake(0, 0, newWidthForTargetBound, newHeightForTargetBound);
                    pathScalingFactor = _highResolutionImage.size.height/_targetBound.size.height;
                }else{
                    //Do nothing
                }
        }else{
                //Then image is smaller than targetBound
                _bigTargetBound = _imageRect;
                pathScalingFactor =1;
        }
    
        CGSize correctedSize = CGSizeMake(_highResolutionImage.size.width  *_scale,
                                          _highResolutionImage.size.height *_scale);
    
        _bigImageRect= AVMakeRectWithAspectRatioInsideRect(correctedSize,_bigTargetBound);
    
        //Scaling path
        CGAffineTransform scaleTransform = CGAffineTransformIdentity;
        scaleTransform = CGAffineTransformScale(scaleTransform, pathScalingFactor, pathScalingFactor);
    
        CGPathRef scaledCGPath = CGPathCreateCopyByTransformingPath(_examplePath.CGPath,&scaleTransform);
    
        //Render scaled path into image
        UIGraphicsBeginImageContextWithOptions(_bigTargetBound.size, NO, 1.0);
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextAddPath (context, scaledCGPath);
        CGContextSetFillColorWithColor (context, [UIColor redColor].CGColor);
        CGContextSetStrokeColorWithColor (context, [UIColor redColor].CGColor);
        CGContextDrawPath (context, kCGPathFillStroke);
        UIImage * pathImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        NSLog(@"High res pathImage.size: %@",NSStringFromCGSize(pathImage.size));
    
        //Cropping it from targetBound into imageRect
        _maskImage = [self cropThisImage:pathImage toRect:_bigImageRect];
        NSLog(@"High res _croppedRenderedPathImage.size: %@",NSStringFromCGSize(_maskImage.size));
    
        //Masking the high res image with my mask image which both have the same size now.
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGImageRef maskImageRef = [_maskImage CGImage];
        CGContextRef myContext = CGBitmapContextCreate (NULL, _highResolutionImage.size.width, _highResolutionImage.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
        CGColorSpaceRelease(colorSpace);
        if (myContext==NULL)
            return NULL;
    
        CGFloat ratio = 0;
        ratio = _maskImage.size.width/ _highResolutionImage.size.width;
        if(ratio * _highResolutionImage.size.height < _maskImage.size.height) {
            ratio = _maskImage.size.height/ _highResolutionImage.size.height;
        }
    
        CGRect rectForMask  = {{0, 0}, {_maskImage.size.width, _maskImage.size.height}};
        CGRect rectForImageDrawing  = {{-((_highResolutionImage.size.width*ratio)-_maskImage.size.width)/2 , -((_highResolutionImage.size.height*ratio)-_maskImage.size.height)/2},
            {_highResolutionImage.size.width*ratio, _highResolutionImage.size.height*ratio}};
    
        CGContextClipToMask(myContext, rectForMask, maskImageRef);
        CGContextDrawImage(myContext, rectForImageDrawing, _highResolutionImage.CGImage);
        CGImageRef newImage = CGBitmapContextCreateImage(myContext);
        CGContextRelease(myContext);
        UIImage *theImage = [UIImage imageWithCGImage:newImage];
        CGImageRelease(newImage);
        return theImage;
    }
    
    -(UIImage *)cropThisImage:(UIImage*)image toRect:(CGRect)rect{
        CGImageRef subImage = CGImageCreateWithImageInRect(image.CGImage, rect);
        UIImage *croppedImage = [UIImage imageWithCGImage:subImage];
        CGImageRelease(subImage);
        return croppedImage;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多