Zmiana koloru UIImage

100

Próbuję zmienić kolor UIImage. Mój kod:

-(UIImage *)coloredImage:(UIImage *)firstImage withColor:(UIColor *)color {
    UIGraphicsBeginImageContext(firstImage.size);

    CGContextRef context = UIGraphicsGetCurrentContext();
    [color setFill];

    CGContextTranslateCTM(context, 0, firstImage.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    CGContextSetBlendMode(context, kCGBlendModeCopy);
    CGRect rect = CGRectMake(0, 0, firstImage.size.width, firstImage.size.height);
    CGContextDrawImage(context, rect, firstImage.CGImage);

    CGContextClipToMask(context, rect, firstImage.CGImage);
    CGContextAddRect(context, rect);
    CGContextDrawPath(context,kCGPathElementMoveToPoint);

    UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return coloredImg;
}

Ten kod działa, ale uzyskany obraz nie jest tak dobry, jak powinien: granice pikseli zwracanego obrazu są przerywane i nie są tak gładkie, jak na moim pierwszym obrazie. Jak mogę rozwiązać ten problem?

RomanHouse
źródło
1
możliwy duplikat zmiany koloru UIImage?
idmean

Odpowiedzi:

264

Od iOS 7 jest to najprostszy sposób na zrobienie tego.

Cel C:

theImageView.image = [theImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[theImageView setTintColor:[UIColor redColor]];

Swift 2.0:

theImageView.image = theImageView.image?.imageWithRenderingMode(.AlwaysTemplate) 
theImageView.tintColor = UIColor.magentaColor()

Swift 4.0:

theImageView.image = theImageView.image?.withRenderingMode(.alwaysTemplate) 
theImageView.tintColor = .magenta

Storyboard:

Najpierw skonfiguruj obraz jako szablon (na prawym pasku - Renderuj jako) w swoich zasobach. Wtedy kolor obrazu byłby zastosowanym kolorem odcienia. wprowadź opis obrazu tutaj

Ankish Jain
źródło
„UIImageRenderingModeAlwaysTemplate: Zawsze rysuj obraz jako obraz szablonu, ignorując jego informacje o kolorze”. Miły!
Tieme
3
In Swift 2.0+theImageView.image? = (theImageView.image?.imageWithRenderingMode(.AlwaysTemplate))! theImageView.tintColor = UIColor.magentaColor()
ColossalChris
@AnkishJain Czy są jakieś problemy z wydajnością związane z tym podejściem?
Ríomhaire,
3
Nie zmienia to koloru obrazu, ale raczej instruuje widok, aby renderował go z innym odcieniem (kolorem).
Steve Kuo
@Womble nie do końca. Możesz tego użyć naprawdę dla każdego UIImage. img = [img imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; [button setTintColor:[UIColor redColor]]; [button setImage:img forState:UIControlStateNormal];@Ankish thanks!
Motasim
31

To jest prawie odpowiedź powyżej, ale nieco skrócona. To tylko traktuje obraz jako maskę i nie "rozmnaża się" ani nie koloruje obrazu.

Cel C:

    UIColor *color = <# UIColor #>;
    UIImage *image = <# UIImage #>;// Image to mask with
    UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [color setFill];
    CGContextTranslateCTM(context, 0, image.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextClipToMask(context, CGRectMake(0, 0, image.size.width, image.size.height), [image CGImage]);
    CGContextFillRect(context, CGRectMake(0, 0, image.size.width, image.size.height));

    UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

Szybki:

    let color: UIColor = <# UIColor #>
    let image: UIImage = <# UIImage #> // Image to mask with
    UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
    let context = UIGraphicsGetCurrentContext()
    color.setFill()
    context?.translateBy(x: 0, y: image.size.height)
    context?.scaleBy(x: 1.0, y: -1.0)
    context?.clip(to: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height), mask: image.cgImage!)
    context?.fill(CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
    let coloredImg = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
user1270061
źródło
2
Powinieneś użyć, UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);ponieważ twoja wersja stworzy tylko grafikę inną niż siatkówka.
Nikola Lajic
user1270061 jest sposobem na to, z mojego doświadczenia. Druga odpowiedź z „paleniem” wymaga widocznie obrazu źródłowego o określonym kolorze. Ten po prostu wykorzystuje wartości alfa w pikselach źródłowych i łączy je z pożądanym kolorem - idealnie.
Jason
Idealnie - tylko odpowiedź, która działała dobrze dla mnie. (16 maja ')
trdavidson
10

Innym sposobem zabarwienia obrazu jest po prostu pomnożenie go przez stały kolor. Czasami jest to preferowane, ponieważ nie „podnosi” wartości kolorów w czarnych obszarach; utrzymuje względne natężenia obrazu na tym samym poziomie. Stosowanie nakładki jako odcienia ma tendencję do spłaszczania kontrastu.

Oto kod, którego używam:

UIImage *MultiplyImageByConstantColor( UIImage *image, UIColor *color ) {

    CGSize backgroundSize = image.size;
    UIGraphicsBeginImageContext(backgroundSize);

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGRect backgroundRect;
    backgroundRect.size = backgroundSize;
    backgroundRect.origin.x = 0;
    backgroundRect.origin.y = 0;

    CGFloat r,g,b,a;
    [color getRed:&r green:&g blue:&b alpha:&a];
    CGContextSetRGBFillColor(ctx, r, g, b, a);
    CGContextFillRect(ctx, backgroundRect);

    CGRect imageRect;
    imageRect.size = image.size;
    imageRect.origin.x = (backgroundSize.width - image.size.width)/2;
    imageRect.origin.y = (backgroundSize.height - image.size.height)/2;

    // Unflip the image
    CGContextTranslateCTM(ctx, 0, backgroundSize.height);
    CGContextScaleCTM(ctx, 1.0, -1.0);

    CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
    CGContextDrawImage(ctx, imageRect, image.CGImage);

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return newImage;
}

Szybka wersja

extension UIImage{

    static func multiplyImageByConstantColor(image:UIImage,color:UIColor)->UIImage{
        let backgroundSize = image.size
        UIGraphicsBeginImageContext(backgroundSize)

        let ctx = UIGraphicsGetCurrentContext()

        var backgroundRect=CGRect()
        backgroundRect.size = backgroundSize
        backgroundRect.origin.x = 0
        backgroundRect.origin.y = 0

        var r:CGFloat
        var g:CGFloat
        var b:CGFloat
        var a:CGFloat
        color.getRed(&r, green: &g, blue: &b, alpha: &a)
        CGContextSetRGBFillColor(ctx, r, g, b, a)
        CGContextFillRect(ctx, backgroundRect)

        var imageRect=CGRect()
        imageRect.size = image.size
        imageRect.origin.x = (backgroundSize.width - image.size.width)/2
        imageRect.origin.y = (backgroundSize.height - image.size.height)/2

        // Unflip the image
        CGContextTranslateCTM(ctx, 0, backgroundSize.height)
        CGContextScaleCTM(ctx, 1.0, -1.0)

        CGContextSetBlendMode(ctx, .Multiply)
        CGContextDrawImage(ctx, imageRect, image.CGImage)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage
    }
}
Anna Dickinson
źródło
Dokładnie rozwiązanie, którego szukałem! Dzięki!
nas
Co jeśli chcę stworzyć aplikację do wirtualnego malowania ścian? to działa, a co jeśli chcesz po prostu pokolorować ściany na obrazie. Proszę spojrzeć na ten link: - stackoverflow.com/questions/27482508/…
Shailesh
Działa to znacznie lepiej niż niektóre inne dostępne rozwiązania.
Matt Hudson,
3
To zabarwiło dla mnie tylko tło obiektu, a nie sam obiekt.
user3562927
7

W Swift 3.0

imageView.image? = (imageView.image?.withRenderingMode(.alwaysTemplate))!
imageView.tintColor = UIColor.magenta

W Swift 2.0

yourImage.image? = (yourImage.image?.imageWithRenderingMode(.AlwaysTemplate))!
yourImage.tintColor = UIColor.magentaColor()

Ciesz się pionierami Swift

ColossalChris
źródło
5
Kod jest w porządku, ale post dotyczy UIImage. Nie zawsze mamy do czynienia z UIImageViews.
HenryRootTwo
6

Swift 4.2 Rozwiązanie

extension UIImage {
    func withColor(_ color: UIColor) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        guard let ctx = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return self }
        color.setFill()
        ctx.translateBy(x: 0, y: size.height)
        ctx.scaleBy(x: 1.0, y: -1.0)
        ctx.clip(to: CGRect(x: 0, y: 0, width: size.width, height: size.height), mask: cgImage)
        ctx.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
        guard let colored = UIGraphicsGetImageFromCurrentImageContext() else { return self }
        UIGraphicsEndImageContext()
        return colored
    }
}

// Usage:
// let redImage = UIImage().withColor(.red)
Charlton Provatas
źródło
5

Począwszy od iOS 10 możesz używać UIGraphicsImageRenderer:

extension UIImage {

    func colored(_ color: UIColor) -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { context in
            color.setFill()
            self.draw(at: .zero)
            context.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height), blendMode: .sourceAtop)
        }
    }

}
Murlakatam
źródło
3

Jeśli nie musisz robić tego programowo, możesz to zrobić za pomocą interfejsu użytkownika Xcode.

Jeśli przejdziesz do obrazu w folderze zasobów graficznych, otwórz inspektora po prawej stronie. Pojawi się menu „Renderuj jako” z następującymi opcjami:

  1. Domyślna
  2. Oryginalny
  3. Szablon

Po wybraniu szablonu możesz zmienić tintColor obrazu w dowolny sposób - niezależnie od tego, czy używasz interfejsu użytkownika scenorysu Xcode, czy programowo.

wprowadź opis obrazu tutaj

Zobacz ten obraz:

wprowadź opis obrazu tutaj

Mario Guzman
źródło
Czy używasz XCODE 8?
doxsi
2

Oto moja adaptacja odpowiedzi @ Anny. Dwie kluczowe kwestie:

  • Użyj destinationIntrybu mieszania
  • Zadzwoń, UIGraphicsBeginImageContextWithOptions(backgroundSize, false, UIScreen.main.scale)aby uzyskać płynny obraz

Kod w Swift 3 :

extension UIImage {

    static func coloredImage(image: UIImage?, color: UIColor) -> UIImage? {

        guard let image = image else {
            return nil
        }

        let backgroundSize = image.size
        UIGraphicsBeginImageContextWithOptions(backgroundSize, false, UIScreen.main.scale)

        let ctx = UIGraphicsGetCurrentContext()!

        var backgroundRect=CGRect()
        backgroundRect.size = backgroundSize
        backgroundRect.origin.x = 0
        backgroundRect.origin.y = 0

        var r:CGFloat = 0
        var g:CGFloat = 0
        var b:CGFloat = 0
        var a:CGFloat = 0
        color.getRed(&r, green: &g, blue: &b, alpha: &a)
        ctx.setFillColor(red: r, green: g, blue: b, alpha: a)
        ctx.fill(backgroundRect)

        var imageRect = CGRect()
        imageRect.size = image.size
        imageRect.origin.x = (backgroundSize.width - image.size.width) / 2
        imageRect.origin.y = (backgroundSize.height - image.size.height) / 2

        // Unflip the image
        ctx.translateBy(x: 0, y: backgroundSize.height)
        ctx.scaleBy(x: 1.0, y: -1.0)

        ctx.setBlendMode(.destinationIn)
        ctx.draw(image.cgImage!, in: imageRect)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }
}
algrid
źródło
2
dlaczego przekazujesz obraz podczas rozszerzania UIImage? powinieneś usunąć statyczne słowo kluczowe swojej metody i po prostu użyć selfwewnątrz swojej metody i usunąć niepotrzebny parametr obrazu
Leo Dabus
1

Opierając się na odpowiedzi @ Anny, przepisuję na Swift 2.2 i obsługuję obraz z kanałem alfa:

static func multiplyImageByConstantColor(image:UIImage,color:UIColor)->UIImage{
    let backgroundSize = image.size
    UIGraphicsBeginImageContext(backgroundSize)

    let ctx = UIGraphicsGetCurrentContext()

    var backgroundRect=CGRect()
    backgroundRect.size = backgroundSize
    backgroundRect.origin.x = 0
    backgroundRect.origin.y = 0

    var r:CGFloat = 0
    var g:CGFloat = 0
    var b:CGFloat = 0
    var a:CGFloat = 0
    color.getRed(&r, green: &g, blue: &b, alpha: &a)
    CGContextSetRGBFillColor(ctx, r, g, b, a)

    // Unflip the image
    CGContextTranslateCTM(ctx, 0, backgroundSize.height)
    CGContextScaleCTM(ctx, 1.0, -1.0)
    CGContextClipToMask(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
    CGContextFillRect(ctx, backgroundRect)

    var imageRect=CGRect()
    imageRect.size = image.size
    imageRect.origin.x = (backgroundSize.width - image.size.width)/2
    imageRect.origin.y = (backgroundSize.height - image.size.height)/2


    CGContextSetBlendMode(ctx, .Multiply)
    CGContextDrawImage(ctx, imageRect, image.CGImage)

    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage
}
Bill Chan
źródło
0

Kod Anny działa dobrze przy kopiowaniu UIImage.image na kolorowym tle .image przy użyciu kCGBlendModeNormal zamiast kCGBlendModeMultiply. Na przykład, self.mainImage.image = [self NormalImageByConstantColor: self.mainImage.image withColor: yourColor];umieści zawartość mainImage.image nad tintą yourColor, zachowując krycie yourColor. To rozwiązało mój problem z umieszczeniem koloru tła z kryciem za obrazem, który ma zostać zapisany w Rolce z aparatu.

Lowell Bahner
źródło
0

Swift 3:

extension UIImage{

    static func multiplyImageByConstantColor(image:UIImage,color:UIColor) -> UIImage{

        let backgroundSize = image.size
        UIGraphicsBeginImageContext(backgroundSize)

        guard let ctx = UIGraphicsGetCurrentContext() else {return image}

        var backgroundRect=CGRect()
        backgroundRect.size = backgroundSize
        backgroundRect.origin.x = 0
        backgroundRect.origin.y = 0

        var r:CGFloat = 0
        var g:CGFloat = 0
        var b:CGFloat = 0
        var a:CGFloat = 0
        color.getRed(&r, green: &g, blue: &b, alpha: &a)
        ctx.setFillColor(red: r, green: g, blue: b, alpha: a)

        // Unflip the image
        ctx.translateBy(x: 0, y: backgroundSize.height)
        ctx.scaleBy(x: 1.0, y: -1.0)
        ctx.clip(to: CGRect(0, 0, image.size.width, image.size.height), mask: image.cgImage!)
        ctx.fill(backgroundRect)


        var imageRect=CGRect()
        imageRect.size = image.size
        imageRect.origin.x = (backgroundSize.width - image.size.width)/2
        imageRect.origin.y = (backgroundSize.height - image.size.height)/2


        ctx.setBlendMode(.multiply)
        ctx.draw(image.cgImage!, in: imageRect)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }
}
Nursultan Askarbekuly
źródło
0

Wersja Swift 3.0 cudownego kodu Anny:

extension UIImage{

    static func multiplyImageByConstantColor(image:UIImage,color:UIColor)-> UIImage {
        let backgroundSize = image.size
        UIGraphicsBeginImageContext(backgroundSize)

        let ctx = UIGraphicsGetCurrentContext()!

        var backgroundRect=CGRect()
        backgroundRect.size = backgroundSize
        backgroundRect.origin.x = 0
        backgroundRect.origin.y = 0

        let myFloatForR = 0
        var r = CGFloat(myFloatForR)
        let myFloatForG = 0
        var g = CGFloat(myFloatForG)
        let myFloatForB = 0
        var b = CGFloat(myFloatForB)
        let myFloatForA = 0
        var a = CGFloat(myFloatForA)

        color.getRed(&r, green: &g, blue: &b, alpha: &a)
        ctx.setFillColor(red: r, green: g, blue: b, alpha: a)
        ctx.fill(backgroundRect)

        var imageRect=CGRect()
        imageRect.size = image.size
        imageRect.origin.x = (backgroundSize.width - image.size.width)/2
        imageRect.origin.y = (backgroundSize.height - image.size.height)/2

        // Unflip the image
        ctx.translateBy(x: 0, y: backgroundSize.height)
        ctx.scaleBy(x: 1.0, y: -1.0)

        ctx.setBlendMode(.multiply)
        ctx.draw(image.cgImage!, in: imageRect)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }
}
Matthieu
źródło
0

IOS 13 i nowsze:

let redImage = image.withTintColor(.red, renderingMode: .alwaysTemplate)

beryl
źródło