Jak mogę zamaskować UIImageView?

100

Próbuję zamaskować obraz za pomocą czegoś takiego:

obraz do zamaskowania

Czy mógłbyś mi pomóc?

Używam tego kodu:

- (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
źródło
27
Jestem osobą, która napisała oryginalny tutorial. Obraz maski to po prostu prosty obraz w skali szarości, który utworzyłem w Photoshopie. Nie ma w tym nic specjalnego. Czarny obszar staje się „przezroczystą” częścią maski. Należy pamiętać, że każdy odcień szarości jest interpretowany jako stopień krycia. W ten sposób maski mogą być również gradientami, co jest przydatne do tworzenia bardziej miękkich granic wokół maski.
ra9r
To musi być tego samego rozmiaru, prawda?
KarenAnne
hiper-elegancki kod, dzięki. po prostu przydatny link, jeśli ktoś musi przyciąć obraz przed maskowaniem ... stackoverflow.com/questions/17712797/
Fattie,
Jakieś pomysły na ten temat .. stackoverflow.com/questions/28122370/…
Milan patel
1
Zachęcam do zmiany wybranej odpowiedzi na taką, która ma prawie 3 razy więcej głosów za. Moim zdaniem pod każdym względem jest lepiej.
Albert Renshaw,

Odpowiedzi:

165

Jest łatwiejszy sposób.

#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;

Dla Swift 4 i plus wykonaj poniższy kod

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
źródło
1
dziękuję, ale włączyłem ten kod, viewDidLoadale nic się nie stało!
Mc.Lover,
6
Powinieneś ustawić właściwość warstwy, aby używać maski, tj. [YourImageView.layer setMasksToBounds: YES];
Wolfgang Schreurs
3
Zwróć uwagę, że w tym rozwiązaniu musi to być UIImageView. Drugie rozwiązanie działa tylko z UIImage.
adriendenat
1
Próbowałem tego podejścia, ale wydaje się, że to nie działa w iOS 8
bdv
2
Dla kodu Swift, po umieszczeniu w mask.contents, należy CGImage. Nie [CGImage]. Jeśli używasz nawiasu [], oznacza to Array.
słomkowy
59

Samouczek używa tej metody z dwoma parametrami: imagei maskImage, które należy ustawić, wywołując metodę. Przykładowe wywołanie mogłoby wyglądać następująco, zakładając, że metoda jest w tej samej klasie, a obrazy znajdują się w Twoim pakiecie:

Uwaga - zdumiewające, że zdjęcia nie muszą nawet być tego samego rozmiaru.

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

Po podaniu kodu dodałem do niego kilka liczb jako komentarze w celach informacyjnych. Nadal masz dwie możliwości. Cała ta sprawa to metoda, którą gdzieś wywołujesz. Nie musisz tworzyć obrazów w środku: zmniejsza to możliwość ponownego użycia metody do zera.

Aby kod działał. Zmień nagłówek metody ( 1. ) na

- (UIImage *)maskImageMyImages {

Następnie zmień nazwę zmiennej w 2. na

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

Metoda zwróci zamaskowane obrazy, więc będziesz musiał w którymś miejscu wywołać tę metodę. Czy możesz pokazać nam kod, w którym wywołujesz swoją metodę?

Nick Weaver
źródło
przepraszam! gdzie napisać UIImage *imagekod? !!! ponieważ na -(UIimage *) maskeImagekompilatorze pojawia się błąd redefinicji!
Mc.Lover
przepraszam, czy mógłbyś przesłać mi przykładowy kod? Jestem zdezorientowany: -s
Mc.Lover
1
Niemożliwe, wszystko tam jest. Musisz nam pokazać, jak wywołujesz tę metodę, a przynajmniej część, w której chcesz zamaskować swoje obrazy i pokazać wynik.
Nick Weaver,
moim problemem jest to nazwać, czy powinno być coś takiego? (viewDidLoad): zobacz mój edytowany kod
Mc.Lover,
Wszelkie pomysły na temat odwrotnego maskowania. (Odwrócenie powyższego procesu) Oznacza, że ​​chcę usunąć obszar wypełnienia (uczynić go przezroczystym) i przezroczysty obszar z wypełnieniem.
Milan patel
17

Swift 3 - najprostsze rozwiązanie, jakie znalazłem

Stworzyłem @IBDesignablew tym celu, abyś mógł od razu zobaczyć efekt na swojej planszy.

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

Jak używać

  1. Utwórz nowy plik i wklej powyższy kod.
  2. Dodaj UIImageView do swojej serii ujęć (przypisz obraz, jeśli chcesz).
  3. W Inspektorze tożsamości: Zmień klasę niestandardową na „UIImageViewWithMask” (powyżej niestandardowa nazwa klasy).
  4. W Inspektorze atrybutów: wybierz obraz maski, którego chcesz użyć.

Przykład

Maskowanie na Storyboard

Uwagi

  • Obraz maski powinien mieć przezroczyste tło (PNG) dla części innej niż czarna.
Mark Moeykens
źródło
12

Wypróbowałem oba kody używając CALayer lub CGImageCreateWithMask, ale żaden z nich nie działał dla mnie

Ale okazało się, że problem dotyczy formatu pliku png, a nie kodu!
więc po prostu podziel się moim odkryciem!

Jeśli chcesz użyć

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

musisz użyć 24-bitowego png bez kanału alfa

Jeśli chcesz użyć maski CALayer, musisz użyć (24-bitowego lub 8-bitowego) png z kanałem alfa, gdzie przezroczysta część twojego png będzie maskować obraz (dla gładkiej gradientowej maski alfa ... użyj 24-bitowego png z kanałem alfa)

Ninji
źródło
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;
}

To działa dla mnie.

ZYiOS
źródło
dziękuję, ta odpowiedź jest lepsza niż rozmowy innych o wydajności z większymi i większymi obrazami.
Radu Ursache
1
Wszelkie pomysły na temat odwrotnego maskowania. (Odwrócenie powyższego procesu) Oznacza, że ​​chcę usunąć obszar wypełnienia (uczynić go przezroczystym) i Obszar przezroczysty z wypełnieniem.
Milan patel
Dzięki, ten kod działa lepiej niż pierwszy !! Ale mam dwa problemy: Ostrzeżenie o „kCGImageAlphaPremultipliedLast” (przesyłam do „CGBitmapInfo”, aby to naprawić) ORAZ nie działa w przypadku obrazów siatkówki !! czy możesz mi pomóc? ...
fingerup
@Milanpatel Łatwym rozwiązaniem jest po prostu odwrócenie kolorów na obrazie maski. :)
Dids
@ZyiOS jak mogę dostosować ramkę zamaskowanego obrazu. tzn. użytkownik może zwiększyć lub zmniejszyć rozmiar zamaskowanego obrazu.
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

}

// do użycia

testImage.image = maskImage(image: UIImage(named: "OriginalImage")!, withMask: UIImage(named: "maskImage")!)
Ahmed Safadi
źródło
czy możesz mi dać zdjęcie
Ahmed Safadi,
3

Szybka wersja przyjętego rozwiązania:

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

}

Na wypadek, gdyby ktoś tego potrzebował.

Działa na mnie bez zarzutu.

Juan Valera
źródło
Czy masz wersję Swift 3.0 tego?
Van Du Tran
2

stwierdziliśmy, że poprawna odpowiedź nie działa teraz i zaimplementowaliśmy małą klasę drop-in, która pomaga zamaskować dowolny obraz w UIImageView.

Jest dostępny tutaj: https://github.com/Werbary/WBMaskedImageView

Przykład:

WBMaskedImageView *imgView = [[WBMaskedImageView alloc] initWithFrame:frame];
imgView.originalImage = [UIImage imageNamed:@"original_image.png"];
imgView.maskImage = [UIImage imageNamed:@"mask_image.png"];
werbary
źródło
1
Dobry! To naprawdę pomogło.
abhimuralidharan