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
}
}
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:
Add the two views to a parent view (with the opaque view below the transparent view)
Convert the parent view (and all of its subviews) to a UIImage
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).
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
}
Related
I need to add a drop shadow on my image, not the image view. Is there anyway to do that? I know I can add shadow to imageView like -
imageView.layer.masksToBounds true
imageView.layer.shadowColor = UIColor.gray.cgColor
imageView.layer.shadowOffset = CGSizeMake(0, 1)
imageView.layer.shadowOpacity = 1
imageView.layer.shadowRadius = 1.0
but I need to add the shadow to the image, not imageView. Does anyone have any clue?
I think you can use CIFilter in Core Image. CIFilter is an image processor that produces an image by manipulating one or more input images or by generating new image data.
You can check various references here.
I think you can CIHighlightShadowAdjust
CIHighlightShadowAdjust is the properties you use to configure a highlight-shadow adjust filter.
Just for #dfd.
So, I went and had a look at Create new UIImage by adding shadow to existing UIImage. After scrolling down a bit, I started to find several Swift based solutions. Intrigued, I threw them into a Playground to see what they could do.
I settled on this solution...
import UIKit
extension UIImage {
/// Returns a new image with the specified shadow properties.
/// This will increase the size of the image to fit the shadow and the original image.
func withShadow(blur: CGFloat = 6, offset: CGSize = .zero, color: UIColor = UIColor(white: 0, alpha: 0.8)) -> UIImage {
let shadowRect = CGRect(
x: offset.width - blur,
y: offset.height - blur,
width: size.width + blur * 2,
height: size.height + blur * 2
)
UIGraphicsBeginImageContextWithOptions(
CGSize(
width: max(shadowRect.maxX, size.width) - min(shadowRect.minX, 0),
height: max(shadowRect.maxY, size.height) - min(shadowRect.minY, 0)
),
false, 0
)
let context = UIGraphicsGetCurrentContext()!
context.setShadow(
offset: offset,
blur: blur,
color: color.cgColor
)
draw(
in: CGRect(
x: max(0, -shadowRect.origin.x),
y: max(0, -shadowRect.origin.y),
width: size.width,
height: size.height
)
)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
let sourceImage = UIImage(named: "LogoSmall.png")!
let shadowed = sourceImage.withShadow(blur: 6, color: .red)
But wait, that's not a drop shadow, it's an outline!
đŸ™„ Apparently we need to hand hold everybody now days...
Changing the parameters to ...
let shadowed = sourceImage.withShadow(blur: 6, offset: CGSize(width: 5, height: 5), color: .red)
Produces a drop shadow. I like the solution because it doesn't make assumptions and provides a suitable number of parameters to change the output as desired.
I liked the solution so much, I copied the extension into my personal library, never know when it might come in handy.
Remember, in order to produce this style of image, the original image needs to be transparent.
A little bit like...
Remember, iOS has been around a LONG time, ObjC has been around even longer. You're likely to come across many solutions which are only presented in ObjC, which means, it's still important to have the skill/ability to at least read the code. If we're lucky, other members of the community will produce suitable Swift variants, but this isn't always possible.
I'm sure I don't need to go to the extent of writing a full tutorial on how to include images in Playground, there are plenty of examples about that đŸ˜‰
I'm trying to create a table view (custom class not UITableView) where its results have a gradient effect like in the example image below:
What I tried:
I successfully managed to add the correct gradient as a background to every table cell but I need it to be the color of the text of each label not the background of each cell. Question. (I asked this one.)
FAILED ATEMPT:
Creating a custom gradient image and adding it as colorWithPatternImage: to each label but since the gradient is one every cell looks the same. Question.
FAILED ATEMPT:
Last thing to try:
Suppose you have two colors, color1 and color2. A gradient can result by mixing these colors. In the picture above color1 = purple and color2 = orange. It would be easy to create a gradient effect by dividing the gradient in sections based on the number of results and then find the average color of each section and use it as the text color of each corresponding result.
For example:
5 results = 5 divisions.
division1 = purple
division2 = less purple, more orange
division3 = equal purple, equal orange
division4 = least purple, most orange
division5 = orange
The result is not as detailed because each text is a solid color but it is equally impressive when the text is small:
The problem is, for two colors like these:
Purple: 128.0, 0.0, 255.0
Orange: 255.0, 128.0, 0.0
How do you divide it in 5 sections and find the average of each section?
I could do this using the eyedropper tool in pixelmator but only if I knew the fixed number of results, won't work with 6 results.
I can't approach it with math, I don't know where to begin.
Any ideas?
You can use math on the rgb values of the colors.
To get the rgb values, you can use the getRed:green:blue:alpha method on UIColor. Then all you have to do is average the colors together based on how many sections you need.
Here is a function that should return an array of colors based on a start and end color, and how many divisions you need.
Solution
func divideColors(firstColor: UIColor, secondColor: UIColor, sections: Int) -> [UIColor] {
// get rgb values from the colors
var firstRed: CGFloat = 0; var firstGreen: CGFloat = 0; var firstBlue: CGFloat = 0; var firstAlpha: CGFloat = 0;
firstColor.getRed(&firstRed, green: &firstGreen, blue: &firstBlue, alpha: &firstAlpha)
var secondRed: CGFloat = 0; var secondGreen: CGFloat = 0; var secondBlue: CGFloat = 0; var secondAlpha: CGFloat = 0;
secondColor.getRed(&secondRed, green: &secondGreen, blue: &secondBlue, alpha: &secondAlpha)
// function to mix the colors
func mix(_ first: CGFloat, _ second: CGFloat, ratio: CGFloat) -> CGFloat { return first + ratio * (second - first) }
// variable setup
var colors = [UIColor]()
let ratioPerSection = 1.0 / CGFloat(sections)
// mix the colors for each section
for section in 0 ..< sections {
let newRed = mix(firstRed, secondRed, ratio: ratioPerSection * CGFloat(section))
let newGreen = mix(firstGreen, secondGreen, ratio: ratioPerSection * CGFloat(section))
let newBlue = mix(firstBlue, secondBlue, ratio: ratioPerSection * CGFloat(section))
let newAlpha = mix(firstAlpha, secondAlpha, ratio: ratioPerSection * CGFloat(section))
let newColor = UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: newAlpha)
colors.append(newColor)
}
return colors
}
Your question is tagged as Objective-C, but it should be easy enough to convert the Swift code above to Objective-C since you would use the same UIColor API.
Here is some code to test the above function (perfect for a Swift playground).
Testing Code
let sections = 5
let view = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 50))
for (index, color) in divideColors(firstColor: UIColor.purple, secondColor: UIColor.orange, sections: sections).enumerated() {
let v = UIView(frame: CGRect(x: CGFloat(index) * 250 / CGFloat(sections), y: 0, width: 250 / CGFloat(sections), height: 50))
v.backgroundColor = color
view.addSubview(v)
}
view.backgroundColor = .white
Test Result
It also works for any number of sections and different colors!
So you want this:
There are several ways you could implement this. Here's a way that's pixel-perfect (it draws the gradient through the labels instead of making each label a solid color).
Make a subclass of UILabel. In your subclass, override drawTextInRect: to draw the gradient.
Let's declare the subclass like this:
IB_DESIGNABLE
#interface GradientLabel: UILabel
#property (nonatomic, weak) IBOutlet UIView *gradientCoverageView;
#property (nonatomic, strong) IBInspectable UIColor *startColor;
#property (nonatomic, strong) IBInspectable UIColor *endColor;
#end
Connect the gradientCoverageView outlet to a view that covers the entire area of the gradient—so, a view that covers all five of the labels. This could be the superview of the labels, or it could just be a hidden view that you have set up to (invisibly) fill the same area.
Set startColor to purple and endColor to orange.
We'll draw the gradient-filled text (in our drawTextInRect: override) in three steps:
Call super to just draw the text normally.
Set the graphics context blend mode kCGBlendModeSourceIn. This blend mode tells Core Graphics to draw only where the context has already been drawn, and to overwrite whatever was drawn there. Thus Core Graphics treats the text drawn by super as a mask.
Draw the gradient with the start point at the top of the coverage view and the end point at the bottom of the coverage view, not at the top and bottom of the current label. Thus the gradient spans the entire coverage view, but is masked to only show up where the text of the current label was drawn in step 1.
Here's the implementation:
#implementation GradientLabel
- (void)drawTextInRect:(CGRect)rect {
[super drawTextInRect:rect];
CGContextRef gc = UIGraphicsGetCurrentContext();
NSArray *colors = #[(__bridge id)self.startColor.CGColor, (__bridge id)self.endColor.CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(CGBitmapContextGetColorSpace(gc), (__bridge CFArrayRef)colors, NULL);
UIView *coverageView = self.gradientCoverageView ?: self;
CGRect coverageRect = [coverageView convertRect:coverageView.bounds toView:self];
CGPoint startPoint = coverageRect.origin;
CGPoint endPoint = { coverageRect.origin.x, CGRectGetMaxY(coverageRect) };
CGContextSaveGState(gc); {
CGContextSetBlendMode(gc, kCGBlendModeSourceIn);
CGContextDrawLinearGradient(gc, gradient, startPoint, endPoint, 0);
} CGContextRestoreGState(gc);
CGGradientRelease(gradient);
}
#end
Here's the layout in my storyboard:
For all five of the GradientLabel instances, I set the startColor to purple and the endColor to orange and I connected the gradientCoverageView outlet to the enclosing stack view.
Well, here is how to do the solid-color mathy bit at the end:
If you have n + 1 colors, for 0 <= m <= n:
color.red[m] = (purple.red * (m/n)) + (orange.red * ((n-m)/n);
color.green[m] = (purple.green * (m/n)) + (orange.green * ((n-m)/n));
color.blue[m] = (purple.blue * (m/n)) + (orange.blue * ((n-m)/n));
Hope this helps.
For Xamarin iOS C#
public static List<UIColor> DividedColors(UIColor firstColor, UIColor secondColor, int sections)
{
nfloat firstRed = 0;
nfloat firstGreen = 0;
nfloat firstBlue = 0;
nfloat firstAlpha = 0;
firstColor.GetRGBA(out firstRed, out firstGreen, out firstBlue, out firstAlpha);
nfloat secondRed = 0;
nfloat secondGreen = 0;
nfloat secondBlue = 0;
nfloat secondAlpha = 0;
secondColor.GetRGBA(out secondRed, out secondGreen, out secondBlue, out secondAlpha);
nfloat Mix(nfloat first, nfloat second, nfloat ratio)
{
return first + ratio * (second - first);
}
List<UIColor> colors = new List<UIColor>();
var ratioPerSection = 1.0f / sections;
for (int i = 0; i < sections; i++)
{
var newRed = Mix(firstRed, secondRed, ratioPerSection * i);
var newGreen = Mix(firstGreen, secondGreen, ratioPerSection * i);
var newBlue = Mix(firstBlue, secondBlue, ratioPerSection * i);
var newAlpha = Mix(firstAlpha, secondAlpha, ratioPerSection * i);
var newColor = new UIColor(newRed, newGreen, newBlue, newAlpha);
colors.Add(newColor);
}
return colors;
}
I am attempting to convert a string containing a color in the Generic RGB color space into UIColor in Swift. For example, a typical string would look like this:
0.121569 0.129412 0.156863 1
Using the color picker in macOS, I discovered that these values are using the Generic RGB color space.
However, when I attempt to convert these values into UIColor, it uses the sRGB color space.
let red = CGFloat((components[0] as NSString).doubleValue)
let green = CGFloat((components[1] as NSString).doubleValue)
let blue = CGFloat((components[2] as NSString).doubleValue)
let alpha = CGFloat((components[3] as NSString).doubleValue)
print(UIColor(red: red, green: green, blue: blue, alpha: alpha))
// Log Result: NSCustomColorSpace sRGB IEC61966-2.1 colorspace 0.121569 0.129412 0.156863 1
Hence, a different color is displayed in my application. I confirmed this by changing the color space in Color Picker to IEC61966-2.1 and it indeed displayed different values:
Any idea how I would convert the Generic RGB values into the correct UIColor values?
EDIT For clarification, I am unable to change the color values in the string into another scheme as I am reading the colors from an external source in an XML file
Color conversion by way of color space is performed at the level of CGColor. Example:
let sp = CGColorSpace(name:CGColorSpace.genericRGBLinear)!
let comps : [CGFloat] = [0.121569, 0.129412, 0.156863, 1]
let c = CGColor(colorSpace: sp, components: comps)!
let sp2 = CGColorSpace(name:CGColorSpace.sRGB)!
let c2 = c.converted(to: sp2, intent: .relativeColorimetric, options: nil)!
let color = UIColor(cgColor: c2)
EDIT I think the premise of your original problem is erroneous. You are trying, it turns out, to use the numbers in an Xcode FontAndColorThemes file. Those numbers are sRGB, not generic RGB.
To prove it, I ran this code:
let sp = CGColorSpace(name:CGColorSpace.sRGB)!
let comps : [CGFloat] = [0.0, 0.456, 0.0, 1]
let c = CGColor(colorSpace: sp, components: comps)!
let v1 = UIView(frame:CGRect(x: 50, y: 50, width: 50, height: 50))
v1.backgroundColor = UIColor(cgColor:c)
self.view.addSubview(v1)
That color is taken from the Default color theme's Comment color. Well, the result is identical to the Comment color, as this screen shot demonstrates:
I get the same answer when I use the "eyedropper" tool as when I simply open the color swatch to read the inspector. And I get the same answer when I use the "eyedropper" tool on Xcode's swatch and on my iOS swatch. This seems to me to prove that these colors were always sRGB.
It's easy to blur a portion of the view, keeping in mind that if the contents of views behind change, the blur changes too in realtime.
My questions
How to make an invert effect, and you can put it over a view and the contents behind would have inverted colors
How to add an effect that would know the average color of the pixels behind?
In general, How to access the pixels and manipulate them?
My question is not about UIImageView, asking about UIView in general..
there are libraries that does something similar, but they are so slow and don't run as smooth as blur!
Thanks.
If you know how to code a CIColorKernel, you'll have what you need.
Core Image has several blur filters, all of which use the GPU, which will give you the performance you need.
The CIAreaAverage will give you the average color for a specified rectangular area.
Core Image Filters
Here is about the simplest CIColorKernel you can write. It swaps the red and green value for every pixel in an image (note the "grba" instead of "rgba"):
kernel vec4 swapRedAndGreenAmount(__sample s) {
return s.grba;
}
To put this into a CIColorKernel, just use this line of code:
let swapKernel = CIKernel(string:
"kernel vec4 swapRedAndGreenAmount(__sample s) {" +
"return s.grba;" +
"}"
#tww003 has good code to convert a view's layer into a UIImage. Assuming you call your image myUiImage, to execute this swapKernel, you can:
let myInputCi = CIImage(image: myUiImage)
let myOutputCi = swapKernel.apply(withExtent: myInputCi, arguments: myInputCi)
Let myNewImage = UIImage(ciImage: myOutputCi)
That's about it. You can do alot more (including using CoreGraphics, etc.) but this is a good start.
One last note, you can chain individual filters (including hand-written color, warp, and general kernels). If you want, you can chain your color average over the underlying view with a blur and do whatever kind of inversion you wish as a single filter/effect.
I don't think I can fully answer your question, but maybe I can point you in the right direction.
Apple has some documentation on accessing the pixels data from CGImages, but of course that requires that you have an image to work with in the first place. Fortunately, you can create an image from a UIView like this:
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
From this image you created, you'll be able to manipulate the pixel data how you want to. It may not be the cleanest way to solve your problem, but maybe it's something worth exploring.
Unfortunately, the link I provided is written in Objective-C and is a few years old, but maybe you can figure out how to make good use of it.
1st I will recommend you to extend UIImageView for this purpose. ref
Good ref by Joe
You have to override drawRect method
import UIKit
#IBDesignable class PortholeView: UIView {
#IBInspectable var innerCornerRadius: CGFloat = 10.0
#IBInspectable var inset: CGFloat = 20.0
#IBInspectable var fillColor: UIColor = UIColor.grayColor()
#IBInspectable var strokeWidth: CGFloat = 5.0
#IBInspectable var strokeColor: UIColor = UIColor.blackColor()
override func drawRect(rect: CGRect) {
super.drawRect(rect:rect)
// Prep constants
let roundRectWidth = rect.width - (2 * inset)
let roundRectHeight = rect.height - (2 * inset)
// Use EvenOdd rule to subtract portalRect from outerFill
// (See https://stackoverflow.com/questions/14141081/uiview-drawrect-draw-the-inverted-pixels-make-a-hole-a-window-negative-space)
let outterFill = UIBezierPath(rect: rect)
let portalRect = CGRectMake(
rect.origin.x + inset,
rect.origin.y + inset,
roundRectWidth,
roundRectHeight)
fillColor.setFill()
let portal = UIBezierPath(roundedRect: portalRect, cornerRadius: innerCornerRadius)
outterFill.appendPath(portal)
outterFill.usesEvenOddFillRule = true
outterFill.fill()
strokeColor.setStroke()
portal.lineWidth = strokeWidth
portal.stroke()
}
}
Your answer is here
The function that apple has already put in the phone that is in general>accessibility>invert colors, can I somehow use that in my program so for say when the user touches the screen the colors invert?
I don't know of a way to do this automatically, but you could invert colors yourself using an extension on UIColor and accessing the subviews?
extension UIColor {
var inverted: UIColor {
var r: CGFloat = 0.0, g: CGFloat = 0.0, b: CGFloat = 0.0, a: CGFloat = 0.0
self.getRed(&r, green: &g, blue: &b, alpha: &a)
return UIColor(red: (1 - r), green: (1 - g), blue: (1 - b), alpha: a) // Assuming you want the same alpha value.
}
}
And then if you want to update specific properties of the views you could do something like this:
view.subviews.map { $0.backgroundColor = $0.backgroundColor.invertedColor }
// And so on to change things like the tint color, text color, etc.
Sorry, I don't know a way to do this directly but till then this is better than nothing I guess.
I know of no such API call, and frankly I'd be surprised if it was available. Generally, Apple does not provide system-wide settings to individual applications. However, you could implement this yourself—but only for your own app.
Now this can be done with a buid-in method from SwiftUI; colorInvert().
The colorInvert() modifier inverts all of the colors in a view so that each color displays as its complementary color. For example, blue converts to yellow, and white converts to black.
See: https://developer.apple.com/documentation/swiftui/view/colorinvert()