Маскировка изображения с использованием пути безье с полным разрешением изображения

введите здесь описание изображения Привет, у меня есть путь (форма) и изображение с высоким разрешением. Я делаю изображение с высоким разрешением AspectFit внутри представления, на котором я рисую путь, и я хочу замаскировать изображение с помощью пути, но с полным разрешением изображения, а не с разрешением, в котором мы видим путь. Проблема в том, что он отлично работает, когда я не масштабирую их для маскировки с высоким разрешением, но когда я это делаю, все испортится. Маска растягивается, и происхождение не имеет смысла.

введите здесь описание изображения

Все, что я хочу, - это иметь возможность масштабировать путь с тем же соотношением сторон изображения (при полном разрешении изображения) и правильно позиционировать его, чтобы он мог правильно маскировать изображение с высоким разрешением. Я пробовал это:

Маскирование CGContext с помощью CGPathRef?

и это

Создание маски с помощью CGImageMaskCreate полностью черное (iphone)

и это

Клипировать UIImage в UIBezierPath (без маскирования)

ни один из них не работает правильно, когда я пытаюсь замаскировать изображение высокого качества (больше, чем разрешение экрана)

EDIT Я разместил рабочий проект, чтобы показать проблему между маскировкой нормального качества (при разрешении экрана) и маскировкой высокого качества (при разрешении изображения) на github. Я был бы очень признателен за любую помощь. https://github.com/Reza-Abdolahi/HighResMasking


person Reza.Ab    schedule 03.02.2018    source источник


Ответы (2)


Если я правильно понял ваш вопрос:

  • У вас есть представление изображения, содержащее изображение, которое могло быть уменьшено (или даже увеличено) с помощью 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

Вооружившись этим, мы можем написать код, маскирующий изображение. В моем тестовом приложении у меня есть подкласс 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];
    }];
}

И это выглядит так:

демонстрация маскировки

Конечно, трудно сказать, что выходное изображение имеет полное разрешение. Давайте исправим это, обрезав выходное изображение до ограничивающей рамки пути Безье:

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

Маскирование и обрезка вместе выглядят так:

демонстрация маскирования и кадрирования

Вы можете видеть в этой демонстрации, что выходное изображение имеет гораздо больше деталей, чем было видно во входном представлении, потому что оно было создано с полным разрешением входного изображения.

person rob mayoff    schedule 11.02.2018
comment
Прекрасная попытка!!!! ты легенда!! Я также заставил свой код работать после того, как потратил столько времени с треском и нашел более простое решение, которое я придумал, которое тоже работает, и я также опубликую его на том же github, который я разместил выше. @роб майофф - person Reza.Ab; 11.02.2018
comment
Это буквально идеальный мужчина! Я сомневаюсь, что мой способ всегда будет работать, так как мне нужно будет изменить масштаб моего контекста на основе моего image.scale, поэтому я думаю, что безопаснее использовать ваш подход. - person Reza.Ab; 19.02.2018

В качестве вторичного ответа я заставил его работать с этим кодом, и для лучшего понимания вы также можете получить рабочий проект на 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;
}
person Reza.Ab    schedule 11.02.2018