¿Cómo puedo enmascarar un UIImageView?

100

Estoy tratando de enmascarar una imagen con algo como esto:

imagen para enmascarar

¿Sería tan amable de ayudarme?

Estoy usando este código:

- (void) viewDidLoad {
    UIImage *OrigImage = [UIImage imageNamed:@"dogs.png"];
    UIImage *mask = [UIImage imageNamed:@"mask.png"];
    UIImage *maskedImage = [self maskImage:OrigImage withMask:mask];
    myUIIMage.image = maskedImage;
}
Mc.Lover
fuente
27
Soy la persona que escribió el tutorial original. La imagen de la máscara es solo una imagen simple en escala de grises que creé en Photoshop. No tiene nada de especial. El área negra se convierte en la parte "transparente" de la máscara. Tenga en cuenta que cualquier tono de gris se interpreta como un grado de opacidad. De esta manera, las máscaras también pueden ser degradados, lo que es útil para crear bordes más suaves alrededor de una máscara.
ra9r
Esto tiene que ser del mismo tamaño, ¿verdad?
KarenAnne
código hiper-elegante, gracias. solo un enlace útil si alguien necesita recortar una imagen antes de enmascarar ... stackoverflow.com/questions/17712797/…
Fattie
Alguna idea sobre esto ... stackoverflow.com/questions/28122370/…
Milan patel
1
Le insto a que cambie la respuesta seleccionada por la que tenga casi 3 veces más votos a favor. En mi opinión, es mejor en casi todos los sentidos.
Albert Renshaw

Respuestas:

163

Existe una forma más sencilla.

#import <QuartzCore/QuartzCore.h>
// remember to include Framework as well

CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:@"mask.png"] CGImage];
mask.frame = CGRectMake(0, 0, <img_width>, <img_height>);
yourImageView.layer.mask = mask;
yourImageView.layer.masksToBounds = YES;

Para Swift 4 y más, siga el código a continuación

let mask = CALayer()
mask.contents =  [ UIImage(named: "right_challenge_bg")?.cgImage] as Any
mask.frame = CGRect(x: 0, y: 0, width: leftBGImage.frame.size.width, height: leftBGImage.frame.size.height)
leftBGImage.layer.mask = mask
leftBGImage.layer.masksToBounds = true
Bartosz Ciechanowski
fuente
1
gracias pero puse este código viewDidLoadpero no pasó nada!
Mc.Lover
6
Debe establecer la propiedad de la capa para usar la máscara, es decir, [yourImageView.layer setMasksToBounds: YES];
Wolfgang Schreurs
3
Tenga en cuenta que esto es necesario para ser un UIImageView para esta solución. La otra solución funciona solo con UIImage.
adriendenat
1
Probé este enfoque, pero parece que no funciona en iOS 8
bdv
2
Para el código Swift, cuando se coloca en mask.contents, debe CGImage. No [CGImage]. Si usa corchetes [], significa Array.
strawnut
59

El tutorial usa este método con dos parámetros: imagey maskImage, estos debes establecerlos cuando llames al método. Un ejemplo de llamada podría verse así, asumiendo que el método está en la misma clase y las imágenes están en su paquete:

Nota: sorprendentemente, las imágenes ni siquiera tienen que ser del mismo tamaño.

...
UIImage *image = [UIImage imageNamed:@"dogs.png"];
UIImage *mask = [UIImage imageNamed:@"mask.png"];

// result of the masking method
UIImage *maskedImage = [self maskImage:image withMask:mask];

...

- (UIImage*) maskImage:(UIImage *)image withMask:(UIImage *)maskImage {

    CGImageRef maskRef = maskImage.CGImage; 

    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
        CGImageGetHeight(maskRef),
        CGImageGetBitsPerComponent(maskRef),
        CGImageGetBitsPerPixel(maskRef),
        CGImageGetBytesPerRow(maskRef),
        CGImageGetDataProvider(maskRef), NULL, false);

    CGImageRef maskedImageRef = CGImageCreateWithMask([image CGImage], mask);
    UIImage *maskedImage = [UIImage imageWithCGImage:maskedImageRef];

    CGImageRelease(mask);
    CGImageRelease(maskedImageRef);

    // returns new image with mask applied
    return maskedImage;
}

Después de que proporcionó su código, agregué algunos números como comentarios para referencia. Aún tienes dos opciones. Todo esto es un método, al que estás llamando en alguna parte. No es necesario crear las imágenes que contiene: esto reduce la reutilización del método a cero.

Para que su código funcione. Cambie el encabezado de métodos ( 1. ) a

- (UIImage *)maskImageMyImages {

Luego cambie el nombre de la variable en 2. a

UIImage *maskImage = [UIImage imageNamed:@"mask.png"];

El método devolverá sus imágenes enmascaradas, por lo que tendrá que llamar a este método en algún lugar. ¿Puede mostrarnos el código donde está llamando a su método?

Nick Weaver
fuente
lo siento ! ¿Dónde debería escribir el UIImage *imagecódigo? !!! porque en el -(UIimage *) maskeImagecompilador da error de redefinición!
Mc.Lover
lo siento, ¿podría cargarme un código de muestra? Estoy confundido: -s
Mc.Lover
1
No es posible, todo está ahí. Deberá mostrarnos cómo está llamando a este método, o al menos la parte en la que le gustaría enmascarar sus imágenes y mostrar el resultado.
Nick Weaver
mi problema es llamarlo, ¿debería ser algo así ?? (viewDidLoad): vea mi código editado
Mc.Lover
Cualquier idea sobre el enmascaramiento inverso. (Inverso del proceso anterior). Significa que quiero eliminar el área de relleno (hacerla transparente) y el área transparente con relleno.
Milan patel
16

Swift 3 - La solución más simple que encontré

Creé un @IBDesignablepara esto para que pudieras ver el efecto de inmediato en tu guión gráfico.

import UIKit

@IBDesignable
class UIImageViewWithMask: UIImageView {
    var maskImageView = UIImageView()

    @IBInspectable
    var maskImage: UIImage? {
        didSet {
            maskImageView.image = maskImage
            updateView()
        }
    }

    // This updates mask size when changing device orientation (portrait/landscape)
    override func layoutSubviews() {
        super.layoutSubviews()
        updateView()
    }

    func updateView() {
        if maskImageView.image != nil {
            maskImageView.frame = bounds
            mask = maskImageView
        }
    }
} 

Cómo utilizar

  1. Cree un nuevo archivo y péguelo en el código anterior.
  2. Agregue UIImageView a su guión gráfico (asigne una imagen si lo desea).
  3. En el inspector de identidad: cambie la clase personalizada a "UIImageViewWithMask" (nombre de la clase personalizada arriba).
  4. En el inspector de atributos: seleccione la imagen de máscara que desee utilizar.

Ejemplo

Enmascaramiento en Storyboard

Notas

  • La imagen de la máscara debe tener un fondo transparente (PNG) para la parte no negra.
Mark Moeykens
fuente
12

Probé ambos códigos usando CALayer o CGImageCreateWithMask pero ninguno de ellos no funcionó para mí

¡Pero descubrí que el problema está en el formato de archivo png, NO en el código!
así que solo para compartir mi hallazgo!

Si quieres usar

- (UIImage*) maskImage:(UIImage *)image withMask:(UIImage *)maskImage

debes usar png de 24 bits sin canal alfa

Si desea usar la máscara CALayer, debe usar png (24 bits u 8 bits) con canal alfa donde una parte transparente de su png enmascarará la imagen (para una máscara alfa de degradado suave ... use png de 24 bits con canal alfa)

Ninji
fuente
10
- (UIImage*) maskImage:(UIImage *)image withMask:(UIImage *)maskImage {
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

        CGImageRef maskImageRef = [maskImage CGImage];

        // create a bitmap graphics context the size of the image
        CGContextRef mainViewContentContext = CGBitmapContextCreate (NULL, maskImage.size.width, maskImage.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
        CGColorSpaceRelease(colorSpace);

        if (mainViewContentContext==NULL)
            return NULL;

        CGFloat ratio = 0;

        ratio = maskImage.size.width/ image.size.width;

        if(ratio * image.size.height < maskImage.size.height) {
            ratio = maskImage.size.height/ image.size.height;
        }

        CGRect rect1  = {{0, 0}, {maskImage.size.width, maskImage.size.height}};
        CGRect rect2  = {{-((image.size.width*ratio)-maskImage.size.width)/2 , -((image.size.height*ratio)-maskImage.size.height)/2}, {image.size.width*ratio, image.size.height*ratio}};


        CGContextClipToMask(mainViewContentContext, rect1, maskImageRef);
        CGContextDrawImage(mainViewContentContext, rect2, image.CGImage);


        // Create CGImageRef of the main view bitmap content, and then
        // release that bitmap context
        CGImageRef newImage = CGBitmapContextCreateImage(mainViewContentContext);
        CGContextRelease(mainViewContentContext);

        UIImage *theImage = [UIImage imageWithCGImage:newImage];

        CGImageRelease(newImage);

        // return the image
        return theImage;
}

Esto funciona para mi.

ZYiOS
fuente
gracias, esta respuesta es mejor que otras hablando de rendimiento con más imágenes y más grandes.
Radu Ursache
1
Cualquier idea sobre el enmascaramiento inverso. (Inversa del proceso anterior). Significa que quiero eliminar el área de relleno (hacerla transparente) y el área transparente con relleno.
Milan patel
¡Gracias, este código funciona mejor que el primero! Pero tengo dos problemas: Advertencia sobre "kCGImageAlphaPremultipliedLast" (lanzo a "CGBitmapInfo" para solucionarlo) ¡Y no funciona para imágenes de retina! ¿puedes ayudarme ?? ...
fingerup
@Milanpatel La solución fácil es simplemente invertir sus colores en la imagen de la máscara. :)
Fecha de publicación:
@ZyiOS, ¿cómo puedo personalizar el marco de la imagen enmascarada? es decir, el usuario puede aumentar o disminuir el tamaño de la imagen enmascarada.
Dalvik
8

SWIFT 3 XCODE 8.1

func maskImage(image: UIImage, withMask maskImage: UIImage) -> UIImage {

    let maskRef = maskImage.cgImage

    let mask = CGImage(
        maskWidth: maskRef!.width,
        height: maskRef!.height,
        bitsPerComponent: maskRef!.bitsPerComponent,
        bitsPerPixel: maskRef!.bitsPerPixel,
        bytesPerRow: maskRef!.bytesPerRow,
        provider: maskRef!.dataProvider!,
        decode: nil,
        shouldInterpolate: false)

    let masked = image.cgImage!.masking(mask!)
    let maskedImage = UIImage(cgImage: masked!)

    // No need to release. Core Foundation objects are automatically memory managed.

    return maskedImage

}

// para usar

testImage.image = maskImage(image: UIImage(named: "OriginalImage")!, withMask: UIImage(named: "maskImage")!)
Ahmed Safadi
fuente
¿Puedes darme la imagen
Ahmed Safadi
3

Versión rápida de la solución aceptada:

func maskImage(image: UIImage, withMask maskImage: UIImage) -> UIImage {

    let maskRef = maskImage.CGImage

    let mask = CGImageMaskCreate(
        CGImageGetWidth(maskRef),
        CGImageGetHeight(maskRef),
        CGImageGetBitsPerComponent(maskRef),
        CGImageGetBitsPerPixel(maskRef),
        CGImageGetBytesPerRow(maskRef),
        CGImageGetDataProvider(maskRef),
        nil,
        false)

    let masked = CGImageCreateWithMask(image.CGImage, mask)
    let maskedImage = UIImage(CGImage: masked)!

    // No need to release. Core Foundation objects are automatically memory managed.

    return maskedImage

}

Por si acaso alguien lo necesita.

Me está funcionando a la perfección.

Juan Valera
fuente
¿Tiene una versión Swift 3.0 de esto?
Van Du Tran
2

Descubrimos que la respuesta correcta no funciona ahora e implementamos una pequeña clase desplegable, que ayuda a enmascarar cualquier imagen en UIImageView.

Está disponible aquí: https://github.com/Werbary/WBMaskedImageView

Ejemplo:

WBMaskedImageView *imgView = [[WBMaskedImageView alloc] initWithFrame:frame];
imgView.originalImage = [UIImage imageNamed:@"original_image.png"];
imgView.maskImage = [UIImage imageNamed:@"mask_image.png"];
Werbary
fuente
1
¡Buena! Realmente ayudó.
abhimuralidharan