代码之家  ›  专栏  ›  技术社区  ›  Reza.Ab

使用具有图像全分辨率的bezierpath遮罩图像

  •  1
  • Reza.Ab  · 技术社区  · 6 年前

    enter image description here 您好,我有一个路径(形状)和一个高分辨率图像。我将高分辨率图像设置为AspectFit,在绘制路径的视图中,我想用路径遮罩图像,但以图像的全分辨率,而不是以我们看到路径的分辨率。问题是,如果我不将它们放大以获得高分辨率的掩蔽效果,它会非常有效,但如果我这样做,一切都会一团糟。面具被拉长了,起源没有意义。

    enter image description here

    我所希望的就是能够以相同的图像纵横比(在图像的全分辨率下)放大路径,并正确定位路径,以便能够正确遮罩高分辨率图像。 我试过这个:

    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

    2 回复  |  直到 6 年前
        1
  •  2
  •   rob mayoff    6 年前

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

    • 您有一个包含图像的图像视图,该图像可能已使用 UIViewContentModeScaleAspectFit .
    • 您有一个bezier路径,其点位于该图像视图的几何体(坐标系)中。

    现在,您需要创建图像的副本,以其原始分辨率,由贝塞尔路径遮罩。

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

    1. 创建一个足够大的图形渲染器,以便在不缩放的情况下将图像绘制到其中。此渲染器的几何体是图像的几何体。
    2. 将bezier路径从视图几何体转换为渲染器几何体。
    3. 将变换后的路径应用于渲染器的片段区域。
    4. 将图像(未转换)绘制到渲染器中。

    第二步很难,因为我们必须想出正确的 CGAffineTransform . 在aspect fit场景中,变换不仅需要缩放图像,还可能沿x轴或y轴(但不能同时沿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
    

    有了它,我们可以编写屏蔽图像的代码。在我的测试应用程序中,我有一个子类 UIImageView 已命名 PathEditingView 处理贝塞尔路径编辑的。因此,我的视图控制器会创建如下蒙版图像:

    - (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];
        }];
    }
    

    看起来是这样的:

    masking demo

    当然,很难说输出的图像是全分辨率的。让我们通过将输出图像裁剪到bezier路径的边界框来修复此问题:

    - (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];
        }];
    }
    

    遮罩和裁剪的效果如下:

    masking and cropping demo

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

        2
  •  1
  •   Reza.Ab    6 年前

    作为第二个答案,我使用了这段代码,为了更好地理解,您也可以在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;
    }