Como posso mascarar um UIImageView?

100

Estou tentando mascarar uma imagem com algo assim:

imagem a ser mascarada

Você poderia me ajudar?

Estou 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
fonte
27
Eu sou a pessoa que escreveu o tutorial original. A imagem da máscara é apenas uma imagem simples em tons de cinza que criei no photoshop. Nada de especial nisso. A área preta se torna a parte "transparente" da máscara. Lembre-se de que qualquer tom de cinza é interpretado como um grau de opacidade. Dessa forma, as máscaras também podem ser gradientes, o que é útil para criar bordas mais suaves ao redor de uma máscara.
ra9r
Isso tem que ser do mesmo tamanho, certo?
KarenAnne
código hiperelegante, obrigado. apenas um link útil se alguém precisar cortar uma imagem antes de mascarar ... stackoverflow.com/questions/17712797/…
Fattie
Alguma ideia sobre isso .. stackoverflow.com/questions/28122370/…
Milan Patel
1
Peço que altere a resposta selecionada para aquela com quase 3 vezes mais votos positivos. Na minha opinião, é melhor em quase todos os sentidos.
Albert Renshaw de

Respostas:

163

Existe uma maneira mais fácil.

#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 e mais, siga o código abaixo

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
fonte
1
obrigado, mas eu coloquei esse código, viewDidLoadmas nada aconteceu!
Mc.Lover
6
Você deve definir a propriedade da camada para usar a máscara, ou seja, [yourImageView.layer setMasksToBounds: YES];
Wolfgang Schreurs
3
Observe que é necessário ser um UIImageView para esta solução. A outra solução funciona apenas com um UIImage.
adriendenat
1
Tentei essa abordagem, mas parece que não funciona no iOS 8
bdv
2
Para código Swift, quando colocado em mask.contents, deve CGImage. Não [CGImage]. Se usar colchete [], significa Array.
strawnut
59

O tutorial usa este método com dois parâmetros: imagee maskImage, estes você deve definir ao chamar o método. Um exemplo de chamada poderia ser assim, supondo que o método esteja na mesma classe e as imagens estejam em seu pacote:

Observação - surpreendentemente, as imagens nem precisam ter o mesmo tamanho.

...
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;
}

Depois que você forneceu seu código, adicionei alguns números como comentários para referência. Você ainda tem duas opções. Tudo isso é um método, que você está chamando em algum lugar. Você não precisa criar as imagens dentro dele: isso reduz a reutilização do método a zero.

Para fazer seu código funcionar. Altere os métodos head ( 1. ) para

- (UIImage *)maskImageMyImages {

Em seguida, altere o nome da variável em 2. para

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

O método retornará suas imagens mascaradas, então você terá que chamar este método em algum lugar. Você pode nos mostrar o código onde está chamando seu método?

Nick Weaver
fonte
Desculpe ! onde deve escrever o UIImage *imagecódigo? !!! porque no -(UIimage *) maskeImagecompilador dá erro de redefinição!
Mc.Lover
desculpe, você poderia enviar um código de amostra para mim? Estou confuso: -s
Mc.Lover
1
Não é possível, está tudo aí. Você terá que nos mostrar como está chamando este método, ou pelo menos a parte em que gostaria de mascarar suas imagens e mostrar o resultado.
Nick Weaver
meu problema é chamá-lo, deveria ser algo assim ?? (viewDidLoad): veja meu código editado
Mc.Lover
Qualquer ideia sobre o Mascaramento reverso. (Processo reverso do acima). Significa que eu quero remover a área de preenchimento (torná-la transparente) e área transparente com preenchimento.
Milan Patel
16

Swift 3 - Solução mais simples que encontrei

Criei um @IBDesignablepara isso para que você pudesse ver o efeito imediatamente no seu storyboard.

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

Como usar

  1. Crie um novo arquivo e cole o código acima.
  2. Adicione UIImageView ao seu storyboard (atribua uma imagem se quiser).
  3. No Identity Inspector: Altere a classe personalizada para "UIImageViewWithMask" (nome da classe personalizada acima).
  4. No Attributes Inspector: Selecione a imagem de máscara que deseja usar.

Exemplo

Mascaramento no Storyboard

Notas

  • Sua imagem de máscara deve ter um fundo transparente (PNG) para a parte não preta.
Mark Moeykens
fonte
12

Eu tentei os dois códigos usando CALayer ou CGImageCreateWithMask, mas nenhum deles não funcionou para mim

Mas eu descobri que o problema é com o formato do arquivo png, NÃO com o código !!
então, apenas para compartilhar minha descoberta!

Se você quiser usar

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

você deve usar 24 bits png sem canal alfa

Se você quiser usar a máscara CALayer, você deve usar (24 bits ou 8 bits) png com canal alfa onde parte transparente de seu png irá mascarar a imagem (para máscara alfa de gradiente suave .. use 24 bits png com canal alfa)

Ninji
fonte
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;
}

Isso funciona para mim.

ZYiOS
fonte
obrigado, essa resposta é melhor do que outras falando sobre performance com mais e maiores imagens.
Radu Ursache
1
Alguma ideia sobre o mascaramento reverso. (Processo reverso do processo acima). Significa que eu quero remover a área de preenchimento (torná-la transparente) e a área Transparente com preenchimento.
Milan Patel
Obrigado, este código funciona melhor do que o primeiro !! Mas eu tenho dois problemas: Aviso sobre "kCGImageAlphaPremultipliedLast" (eu transmito para "CGBitmapInfo" para corrigi-lo) E não funciona para imagens de retina !! você pode me ajudar ?? ...
fingerup
@Milanpatel A solução fácil é simplesmente inverter suas cores na imagem da máscara. :)
Fez
@ZyiOS como posso personalizar o quadro da imagem mascarada. ou seja, o usuário pode aumentar ou diminuir o tamanho da imagem mascarada.
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 uso

testImage.image = maskImage(image: UIImage(named: "OriginalImage")!, withMask: UIImage(named: "maskImage")!)
Ahmed Safadi
fonte
você pode me dar a foto
Ahmed Safadi
3

Versão rápida da solução aceita:

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

}

Apenas no caso de alguém precisar.

Está funcionando para mim perfeitamente.

Juan Valera
fonte
Você tem uma versão Swift 3.0 disso?
Van Du Tran
2

descobrimos que a resposta correta não está funcionando agora e implementamos uma pequena classe drop-in, o que ajuda a mascarar qualquer imagem em UIImageView.

Está disponível aqui: https://github.com/Werbary/WBMaskedImageView

Exemplo:

WBMaskedImageView *imgView = [[WBMaskedImageView alloc] initWithFrame:frame];
imgView.originalImage = [UIImage imageNamed:@"original_image.png"];
imgView.maskImage = [UIImage imageNamed:@"mask_image.png"];
werbary
fonte
1
Um bom! Realmente ajudou.
abhimuralidharan