3

I want to "lighten" a certain color by applying an alpha of 0.3 to it. This is shown on a white background.

I would like to determine the opaque color which corresponds to this semi transparent color shown on a white background. The reason is that I use this as view controllers's background color, and when this background is semitransparent the transitions look bad.

I have not tried anything because have no idea about a reasonable way to approach this except maybe taking a snapshot and get the color of it but this seems a bit of an overkill. Can't find any infos, search results are cluttered with "how to make a background semitransparent" etc

Edit: Putting together imataptool's answer parts (and porting to Swift), this is what I came up with:

extension UIColor {

    static func opaqueColorByDisplayingTransparentColorOnBackground(transparentColor: UIColor, backgroundColor: UIColor) -> UIColor {

        let bgView = UIView(frame: CGRectMake(0, 0, 1, 1))
        bgView.backgroundColor = backgroundColor
        let overlayView = UIView(frame: CGRectMake(0, 0, 1, 1))
        overlayView.backgroundColor = transparentColor
        bgView.addSubview(overlayView)

        let image = UIView.imageWithView(bgView)

        let provider = CGImageGetDataProvider(image.CGImage)
        let pixelData = CGDataProviderCopyData(provider)
        let data = CFDataGetBytePtr(pixelData)

        return UIColor(
            red: CGFloat(data[0]) / 255.0,
            green: CGFloat(data[1]) / 255.0,
            blue: CGFloat(data[2]) / 255.0,
            alpha: 1
        )
    }
}

extension UIView {

    // src http://stackoverflow.com/a/32042439/930450
    class func imageWithView(view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
        view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img
    }
}
User
  • 31,811
  • 40
  • 131
  • 232

2 Answers2

1

Edit 1: This isn't to say this is the best way to achieve your specific goal, but it does solve the problem of getting the RGB of a mix of two colors with a particular opacity. You haven't provided enough information about what exactly you are doing for me to give a more specific answer. However, if this solution does what you need with acceptable performance, excellent, run with it.

Edit 2: Refer to this Q&A for new (ish) methods for rendering UIView as bitmaps as of iOS 7 (so if you are supporting iOS 6 you can ignore this, but that's unlikely). The gist of the article is that you can now use the UIView method -drawViewHierarchyInRect:(CGRect)afterScreenUpdates:(BOOL)

I can't say whether or not there exists a mathematical way to calculate the exact answer, but one way of going about it would be:

  1. Add the two views to a parent view (with the opaque view below the transparent view)
  2. Convert the parent view (and all of its subviews) to a UIImage
  3. Sample the UIImage for the color it is composed of

Doing 1 is trivial. You can do 2 with

UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, [[UIScreen mainScreen] scale]);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage* img = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return img;

Now img contains an image with the color you want. Now you just need to figure out what color it is composed of. I believe you can do that with the code provided in this answer. I'll copy it here for convenience.

CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
const UInt8* data = CFDataGetBytePtr(pixelData);

int pixelInfo = ((image.size.width  * y) + x ) * 4; // The image is png

UInt8 red = data[pixelInfo];         // If you need this info, enable it
UInt8 green = data[(pixelInfo + 1)]; // If you need this info, enable it
UInt8 blue = data[pixelInfo + 2];    // If you need this info, enable itgame
CFRelease(pixelData);

red, green, and blue now contain the RGB values of the color at whatever point you pick in your view. You might as well just go to the middle of it. Also, to improve the performance of this process, you might choose to only image a small subsection of the view in question (maybe just one pixel).

Community
  • 1
  • 1
eric.mitchell
  • 8,817
  • 12
  • 54
  • 92
  • This is what I was thinking, create a snapshot and get color from pixels... I'd put it in an extension, a function color+alpha+bg color-> color, of course use an image which is 1x1, more is not necessary. Wondered if there's no simpler way. But I'll just implement this. Thanks for the details. – User Nov 08 '15 at 12:22
  • Sure. Just one note (I'll edit) there are new methods for rendering views as bitmaps as of iOS 7. There's literature on it. – eric.mitchell Nov 08 '15 at 12:25
  • Ah, yes, you probably mean snapshotViewAfterScreenUpdates & co – User Nov 08 '15 at 12:28
  • Well, actually *not* snapshotViewAfterScreenUpdates. That method isn't recommended for this purpose (at least, Apple uses drawViewHierarchyInRect:afterScreenUpdates: in the 2013 WWDC video). – eric.mitchell Nov 08 '15 at 12:29
  • Ok, I was talking generally about the extension for iOS 7 which has snapshotViewAfterScreenUpdates, resizableSnapshotViewFromRect and drawViewHierarchyInRect. Thanks for the tip though. – User Nov 08 '15 at 12:35
  • 1
    Glad you got a working solution. Swift will never feel natural to me! – eric.mitchell Nov 08 '15 at 14:17
1

If somebody will be looking for more straightforward solution, this post about combining colors applying specific alpha might be helpful.

TLDR

extension UIColor {

    func combining(with color: UIColor, fraction f: CGFloat) -> UIColor {

        let source = components()
        let target = color.components()

        return UIColor(
            red:   interpolate(from: source.r, to: target.r, fraction: f),
            green: interpolate(from: source.g, to: target.g, fraction: f),
            blue:  interpolate(from: source.b, to: target.b, fraction: f),
            alpha: 1
        )
    }

    private typealias Components = (
        r: CGFloat,
        g: CGFloat,
        b: CGFloat,
        a: CGFloat
    )

    private func components() -> Components {

        var result: Components = (0, 0, 0, 0)
        getRed(&result.r, green: &result.g, blue: &result.b, alpha: &result.a)

        return result
    }
}

func interpolate(from a: CGFloat, to b: CGFloat, fraction: CGFloat) -> CGFloat {
    (1 - fraction) * a + fraction * b
}
Zapko
  • 2,461
  • 25
  • 30