Swift UIView with multiply effect - ios

What I've been trying to achieve for a couple of hours is something like the following:
I would like to have a UIImage in the background and then preferably a UIView with a background color of red with some kind of multiply effect (the red area). Is this possible? I've seen a few extensions for UIImage that tints them, but that would only work if I wanted my WHOLE image to have a red multiply color effect.
Thanks

You could just add a red UIView to the top of your UIImageView. Adjust the alpha to make it transparent:
let someView = UIView(frame: someImageView.frame)
someView.backgroundColor = UIColor(colorLiteralRed: 255.0/255.0, green: 0, blue: 0, alpha: 0.5)
someImageView.addSubview(someView)
Using a multiply instead:
let img = UIImage(named: “background”)
let img2 = UIImage(named: “effect”) //Make sure this is your red image same size as the background
let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
UIGraphicsBeginImageContextWithOptions(img.size, true, 0)
let context = UIGraphicsGetCurrentContext()
// fill the background with white so that translucent colors get lighter
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextFillRect(context, rect)
img.drawInRect(rect, blendMode: .Normal, alpha: 1)
img2.drawInRect(rect, blendMode: .Multiply, alpha: 1)
// grab the finished image and return it
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

Swift 3 Extension (thx to #Kex):
extension UIImage{
class func multiply(image:UIImage, color:UIColor) -> UIImage? {
let rect = CGRect(origin: .zero, size: image.size)
//image colored
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//image multiply
UIGraphicsBeginImageContextWithOptions(image.size, true, 0)
let context = UIGraphicsGetCurrentContext()
// fill the background with white so that translucent colors get lighter
context!.setFillColor(UIColor.white.cgColor)
context!.fill(rect)
image.draw(in: rect, blendMode: .normal, alpha: 1)
coloredImage?.draw(in: rect, blendMode: .multiply, alpha: 1)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
Example:
let image = UIImage.multiply(image: sourceImage, color: UIColor.red)

With iOS10 you can now use UIGraphicsImageRenderer to add a partial multiply effect as easy as this:
extension UIImage {
func tinted(_ color: UIColor, percentageFromBottom: CGFloat) -> UIImage? {
let imageRect = CGRect(origin: .zero, size: size)
let colorRect = CGRect(x: 0, y: (1.0 - percentageFromBottom) * size.height, width: size.width, height: percentageFromBottom * size.height)
let renderer = UIGraphicsImageRenderer(size: size)
let tintedImage = renderer.image { context in
color.set()
context.fill(colorRect)
draw(in: imageRect, blendMode: .multiply, alpha: 1)
}
return tintedImage
}
}
You can then use it like this to multiply the lower third of the original with a red multiply:
UIImage(named: "trees")?.tinted(.red, percentageFromBottom: 0.33)
Which results in this:

Related

How to apply scale when drawing and composing UIImage

I have the following functions.
extension UIImage
{
var width: CGFloat
{
return size.width
}
var height: CGFloat
{
return size.height
}
private static func circularImage(diameter: CGFloat, color: UIColor) -> UIImage
{
UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0)
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
let rect = CGRect(x: 0, y: 0, width: diameter, height: diameter)
context.setFillColor(color.cgColor)
context.fillEllipse(in: rect)
context.restoreGState()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
private func addCentered(image: UIImage, tintColor: UIColor) -> UIImage
{
let topImage = image.withTintColor(tintColor, renderingMode: .alwaysTemplate)
let bottomImage = self
UIGraphicsBeginImageContext(size)
let bottomRect = CGRect(x: 0, y: 0, width: bottomImage.width, height: bottomImage.height)
bottomImage.draw(in: bottomRect)
let topRect = CGRect(x: (bottomImage.width - topImage.width) / 2.0,
y: (bottomImage.height - topImage.height) / 2.0,
width: topImage.width,
height: topImage.height)
topImage.draw(in: topRect, blendMode: .normal, alpha: 1.0)
let mergedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return mergedImage
}
}
They work fine, but how do I properly apply UIScreen.main.scale to support retina screens?
I've looked at what's been done here but can't figure it out yet.
Any ideas?
Accessing UIScreen.main.scale itself is a bit problematic, as you have to access it only from main thread (while you usually want to put a heavier image processing on a background thread). So I suggest one of these ways instead.
First of all, you can replace UIGraphicsBeginImageContext(size) with
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
The last argument (0.0) is a scale, and based on docs "if you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen."
If instead you want to retain original image's scale on resulting UIImage, you can do this: after topImage.draw, instead of getting the UIImage with UIGraphicsGetImageFromCurrentImageContext, get CGImage with
let cgImage = context.makeImage()
and then construct UIImage with the scale and orientation of the original image (as opposed to defaults)
let mergedImage = UIImage(
cgImage: cgImage,
scale: image.scale,
orientation: image.opientation)

Set UIImage color

I am on swift 4, and my goal is to change the color of a system UIImage in code. I declare the image with
let defaultImage = UIImage(systemName: "person.circle.fill")
But when I change its tint color with:
let grayDefaultImage = defaultImage?.withTintColor(.gray)
The grayDefaultImage still display the standard blue color. Next I use an extension
//MARK: -UIImage extension
extension UIImage {
/// #Todo: this blurs the image for some reason
func imageWithColor(color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
color.setFill()
let context = UIGraphicsGetCurrentContext()
context?.translateBy(x: 0, y: self.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
context?.setBlendMode(CGBlendMode.normal)
let rect = CGRect(origin: .zero, size: CGSize(width: self.size.width, height: self.size.height))
context?.clip(to: rect, mask: self.cgImage!)
context?.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
And the image renders in a very "pixelated" or "low res" form.
withTintColor(_:) say: The new image uses the same rendering mode as the original image.
So if original image's rendering mode is .alwaysTemplate, means image will ignoring its color information. you need set mode to .alwaysOriginal:
defaultImage?.withTintColor(.gray, renderingMode: .alwaysOriginal)

How to make image to watermarkimage in swift [duplicate]

I know there are several other ways to do this; I don't want to import anything that I don't need to. If someone can help me with his code, that would be great.
Currently, it is only saving the original image without the watermark image.
extension UIImage {
class func imageWithWatermark(image1: UIImageView, image2: UIImageView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(image1.bounds.size, false, 0.0)
image2.layer.renderInContext(UIGraphicsGetCurrentContext()!)
image1.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
}
func addWatermark() {
let newImage = UIImage.imageWithWatermark(imageView, image2: watermarkImageView)
UIImageWriteToSavedPhotosAlbum(newImage, nil, nil, nil)
}
EDIT: I've got the watermark appearing on the saved images.
I had to switch the order of the layers:
image1.layer.renderInContext(UIGraphicsGetCurrentContext()!)
image2.layer.renderInContext(UIGraphicsGetCurrentContext()!)
HOWEVER, it is not appearing in the correct place.It seems to always appear in the center of the image.
If you grab the UIImageViews' images you could use the following concept:
if let img = UIImage(named: "image.png"), img2 = UIImage(named: "watermark.png") {
let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
UIGraphicsBeginImageContextWithOptions(img.size, true, 0)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextFillRect(context, rect)
img.drawInRect(rect, blendMode: .Normal, alpha: 1)
img2.drawInRect(CGRectMake(x,y,width,height), blendMode: .Normal, alpha: 1)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(result, nil, nil, nil)
}
SWIFT 4
Use this
let backgroundImage = imageData!
let watermarkImage = #imageLiteral(resourceName: "jodi_url_icon")
let size = backgroundImage.size
let scale = backgroundImage.scale
UIGraphicsBeginImageContextWithOptions(size, false, scale)
backgroundImage.draw(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
watermarkImage.draw(in: CGRect(x: 10, y: 10, width: size.width, height: size.height - 40))
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Use result to UIImageView, tested.

How to set tint color of an Image

let tintedImage = #imageLiteral(resourceName: "user")
pictureImageView?.image = mainCircleImage.overlayed(with: tintedImage,color: UIColor.orange)
extension UIImage {
func overlayed(with overlay: UIImage,color:UIColor) -> UIImage? {
defer {
UIGraphicsEndImageContext()
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
self.draw(in: CGRect(origin: CGPoint.zero, size: size))
let tintedOverlay = overlay.tintedImageWithColor(color: color)
tintedOverlay.draw(in: CGRect(origin: CGPoint.zero, size: size))
if let image = UIGraphicsGetImageFromCurrentImageContext() {
return image
}
return nil
}
func tint(color: UIColor, blendMode: CGBlendMode) -> UIImage
{
let drawRect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context = UIGraphicsGetCurrentContext()
context!.scaleBy(x: 1.0, y: -1.0)
context!.translateBy(x: 0.0, y: -self.size.height)
context!.clip(to: drawRect, mask: cgImage!)
color.setFill()
UIRectFill(drawRect)
draw(in: drawRect, blendMode: blendMode, alpha: 1.0)
let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return tintedImage!
}
func tintedImageWithColor(color: UIColor) -> UIImage
{
return self.tint(color: color, blendMode: CGBlendMode.multiply)
}
}
I have updated my question according to possible answer
Here is my code for the changing the icon color. In some reason my user icon is not fully filling its color when i change the tint color.
I had added 2 more methods to your extension of UIImagefor tint an Image and added some changes in your overlayed method
extension UIImage {
func overlayed(with overlay: UIImage,color:UIColor) -> UIImage? {
defer {
UIGraphicsEndImageContext()
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
self.draw(in: CGRect(origin: CGPoint.zero, size: size))
let tintedOverlay = overlay.tinted(color: color)
tintedOverlay.draw(in: CGRect(origin: CGPoint.zero, size: size), blendMode: .multiply, alpha: 1.0)
if let image = UIGraphicsGetImageFromCurrentImageContext() {
return image
}
return nil
}
func tinted(color: UIColor) -> UIImage
{
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()
color.setFill()
context!.translateBy(x: 0, y: self.size.height)
context!.scaleBy(x: 1.0, y: -1.0)
context!.setBlendMode(CGBlendMode.colorBurn)
let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context!.draw(self.cgImage!, in: rect)
context!.setBlendMode(CGBlendMode.sourceIn)
context!.addRect(rect)
context!.drawPath(using: CGPathDrawingMode.fill)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return coloredImage!
}
}
The content of Tinted method is from this question How can I color a UIImage in Swift?
answer provided by #HR
Use It
let mainCircleImage = UIImage(named: "actions_menu_edit")?
let tintedImage = UIImage(named: "actions_menu_add")
pictureImageView?.image = mainCircleImage?.overlayed(with: tintedImage!,color: UIColor.red)
UPDATED
Result with the last update

iOS blend mode multiply

I'm looking for a solution to create this red box:
It is using a color filter 'Multiply'. Currently I have found this information:
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_images/dq_images.html
But how can I use something like the multiply effect on a UIView or isn't this possible?
So that the background is a UIImageView and the red box is a UIView with the multiply effect.
This Swift extension did the trick for me.
extension UIImage {
//creates a static image with a color of the requested size
static func fromColor(color: UIColor, size: CGSize) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
func blendWithColorAndRect(blendMode: CGBlendMode, color: UIColor, rect: CGRect) -> UIImage {
let imageColor = UIImage.fromColor(color, size:self.size)
let rectImage = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
UIGraphicsBeginImageContextWithOptions(self.size, true, 0)
let context = UIGraphicsGetCurrentContext()
// fill the background with white so that translucent colors get lighter
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextFillRect(context, rectImage)
self.drawInRect(rectImage, blendMode: .Normal, alpha: 1)
imageColor.drawInRect(rect, blendMode: blendMode, alpha: 1)
// grab the finished image and return it
let result = UIGraphicsGetImageFromCurrentImageContext()
//self.backgroundImageView.image = result
UIGraphicsEndImageContext()
return result
}
}
Swift 3:
static func fromColor(color: UIColor, size: CGSize) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
func blendWithColorAndRect(blendMode: CGBlendMode, color: UIColor, rect: CGRect) -> UIImage {
let imageColor = UIImage.fromColor(color: color, size:self.size)
let rectImage = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
UIGraphicsBeginImageContextWithOptions(self.size, true, 0)
let context = UIGraphicsGetCurrentContext()
// fill the background with white so that translucent colors get lighter
context!.setFillColor(UIColor.white.cgColor)
context!.fill(rectImage)
self.draw(in: rectImage, blendMode: .normal, alpha: 1)
imageColor.draw(in: rect, blendMode: blendMode, alpha: 1)
// grab the finished image and return it
let result = UIGraphicsGetImageFromCurrentImageContext()
//self.backgroundImageView.image = result
UIGraphicsEndImageContext()
return result!
}
But only works for images, I would like to work for videos too :|
I expanded #pegpeg solution and added a gradient overlay to the image instead of a single color
Swift 4:
static func fromGradient(colors: [UIColor], locations: [CGFloat], horizontal: Bool, size: CGSize) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
let colorSpace = CGColorSpaceCreateDeviceRGB()
let cgColors = colors.map {$0.cgColor} as CFArray
let grad = CGGradient(colorsSpace: colorSpace, colors: cgColors , locations: locations)
let startPoint = CGPoint(x: 0, y: 0)
let endPoint = horizontal ? CGPoint(x: size.width, y: 0) : CGPoint(x: 0, y: size.height)
context?.drawLinearGradient(grad!, start: startPoint, end: endPoint, options: .drawsAfterEndLocation)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
func blendWithGradientAndRect(blendMode: CGBlendMode, colors: [UIColor], locations: [CGFloat], horizontal: Bool = false, alpha: CGFloat = 1.0, rect: CGRect) -> UIImage {
let imageColor = UIImage.fromGradient(colors: colors, locations: locations, horizontal: horizontal, size: size)
let rectImage = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
UIGraphicsBeginImageContextWithOptions(self.size, true, 0)
let context = UIGraphicsGetCurrentContext()
// fill the background with white so that translucent colors get lighter
context!.setFillColor(UIColor.white.cgColor)
context!.fill(rectImage)
self.draw(in: rectImage, blendMode: .normal, alpha: 1)
imageColor.draw(in: rect, blendMode: blendMode, alpha: alpha)
// grab the finished image and return it
let result = UIGraphicsGetImageFromCurrentImageContext()
//self.backgroundImageView.image = result
UIGraphicsEndImageContext()
return result!
}
the colors array should be of the same length of the locations array, example:
let newImage = image.blendWithGradientAndRect(blendMode: .multiply,
colors: [.red, .white],
locations: [0,1],
horizontal: true,
alpha: 0.8,
rect: imageRect)

Resources