使用bezierpath屏蔽图像,图像的全分辨率

时间:2018-02-03 17:19:12

标签: ios objective-c uiimage uibezierpath cgcontext

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 个答案:

答案 0 :(得分:2)

如果我理解你的问题:

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

现在你想要创建一个原始分辨率的图像副本,用bezier路径掩盖。

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

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

    有了这个,我们可以编写掩盖图像的代码。在我的测试应用程序中,我有一个UIImageView的子类,名为PathEditingView,用于处理bezier路径编辑。所以我的视图控制器创建了这样的蒙版图像:

    - (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

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

    - (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

    您可以在此演示中看到输出图像比输入视图中显示的更详细,因为它是以输入图像的全分辨率生成的。

答案 1 :(得分:1)

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

解决问题的代码部分:

bash