I have two vector images. I want to merge them so the dots appear on top of the first image.
The solutions for regular png assets don't work, as they use the actual size of the image, and the combined image comes out very blurry.
One solution that I found was placing a second UIImageView (same size, centred) on top of the first one, but it seems really dumb.
I need to make this inside the app because I also want to have the squared coloured differently (change the tint of the vector), and then merged with the black border image. So I can have 3 assets in my project (square, border and dots).
Figured out a way to do this in an extension to UIImageView. Surprised that I couldn't find this anywhere.
extension UIImageView {
func mergeTwoPDF(one: UIImage, two: UIImage) {
UIGraphicsBeginImageContextWithOptions(self.frame.size, false, UIScreen.main.scale)
let areaSize = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
one.draw(in: areaSize, blendMode: .normal, alpha: 1.0)
two.draw(in: areaSize, blendMode: .normal, alpha: 1.0)
//If you want to merge more than 2 images, just add them to the func parameters and repeat the line above with them
let mergedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
self.image = mergedImage
}
}
I suggest using sublayers:
["firstOne", "secondOne"].forEach {
let myLayer = CALayer()
let myImage = UIImage(named: $0)?.cgImage
myLayer.frame = myView.bounds
myLayer.contents = myImage
myView.layer.addSublayer(myLayer)
}
Or create a custom layer class
Related
I have a pattern PNG image, with black dots on transparent background, and I need to change dot's color at custom colors, for example yellow
I'm trying to change tint color before drawing it, but image is still black on transparent
let templateImage = UIImage(named: "spray7")!.withTintColor(.yellow, renderingMode: .alwaysTemplate)
let image = templateImage.cgImage
context.draw(image!, in: CGRect(x: 0, y: 0, width: 8, height: 8))
and context is a CGContext
Any adviсe will be helpful!
correct way is to use
let templateImage = UIImage(named: "spray7")!.tinted(with: .yellow)
instead
I'm trying to get the UIImage of the mask that I applied to a UIImageView.
I'm adding the mask using UIBezierPath and want the actual masked layer as UIImage, not the whole image. Think of it as a crop feature.
I'm cropping the image using:
func cropImage() {
shapeLayer.fillColor = UIColor.black.cgColor
viewSource.imageView.layer.mask = shapeLayer
viewSource.imageView.layer.masksToBounds = true
UIGraphicsBeginImageContextWithOptions(viewSource.imageView.bounds.size, false, 1)
viewSource.imageView.layer.render(in: UIGraphicsGetCurrentContext()!)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.completionObservable.onNext(newImage)
}
This eventually gives me the masked image on top of the old dimensions (the initial imageView width and height). But I want to have only the masked image, excluding the white background around them.
The screens are as shown:
I know what you mean now. Here is the answer, just update the size of imageContext.
UIGraphicsBeginImageContextWithOptions((shapeLayer.path?.boundingBoxOfPath)!.size, false, 1)
If it's not so simple, can try CIImage pipeline to achieve.
let context = CIContext()
let m1 = newImage?.cgImage
let m = CIImage.init(cgImage: m1!)
let bounds = imageView.layer.bounds
let cgImage = context.createCGImage(m, from: CGRect.init(x: 0, y: bounds.size.height, width: bounds.size.width, height: bounds.size.height))
let newUIImage = UIImage.init(cgImage: cgImage!)
You may need to adjust transform.
I want draw image pattern like star, one can change the color for that. Can I change the color of Image itself without using image view? I saw many answer changing tint color of UIImageview, but the effect is not applied to UIImage
Thank you all for the answers, but I got the solution working.
func changePatternImageColor() {
patternImage = UIImage(named: "pattern_star.png")! //make sure to reinitialize the original image here every time you change the color
UIGraphicsBeginImageContext(patternImage.size)
let context = UIGraphicsGetCurrentContext()
let color = UIColor(red: self.red, green: self.green, blue: self.blue, alpha: self.opacity)
color.setFill()
context?.translateBy(x: 0, y: patternImage.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
//set the blend mode to color burn, and the original image
context?.setBlendMode(CGBlendMode.exclusion) //this does the magic.
let rect = CGRect(x: 0, y: 0, width: patternImage.size.height, height: patternImage.size.height)
context?.draw(patternImage.cgImage!, in: rect)
//set the mask that matches the shape of the image, then draw color burn a colored rectangle
context?.clip(to: rect, mask: patternImage.cgImage!)
context?.addRect(rect)
context?.drawPath(using: CGPathDrawingMode.fill)
patternImage = UIGraphicsGetImageFromCurrentImageContext()!
image_ref = patternImage.cgImage
UIGraphicsEndImageContext()
}
The patternImage is the the image added in asset.
P.S. the patternImage added in asset SHOULD be in black color
tintColor is not only a property of a UIImageView, it is a property of a UIView, as long as your image is in a UIView, you can use UIView.tintColor.
I am trying to make a gradient and use it as an alpha mask. Right now, I am able to make an image similar to this (from black to transparent):
This is the code which I use to make all this:
private func createImage(width: CGFloat, height: CGFloat) -> CGImageRef?{
if let ciFilter = CIFilter(name: "CILinearGradient"){
let ciContext = CIContext()
ciFilter.setDefaults()
let startColor = CIColor(red: 0, green: 0, blue: 0, alpha: 0)
let endColor = CIColor(red: 0, green: 0, blue: 0, alpha: 1)
let startVector = CIVector(x: 0, y: height-10)
let endVector = CIVector(x: 0, y: height-22)
ciFilter.setValue(startColor, forKey: "inputColor0")
ciFilter.setValue(endColor, forKey: "inputColor1")
ciFilter.setValue(startVector, forKey: "inputPoint0")
ciFilter.setValue(endVector, forKey: "inputPoint1")
if let outputImage = ciFilter.outputImage {
let cgImage:CGImageRef = ciContext.createCGImage(outputImage, fromRect: CGRect(x: 0, y: 0, width: width, height: height))
return cgImage
}
}
return nil
}
For me, this way works pretty much perfect because I create a mask only once in my code (when GUI element is created) so performance is not affected at all.
The thing is that I actually need a resulting image (gradient texture) to look like this (gradients should have fixed height, but size of black area may vary):
Because CILinearGradient filter doesn't have an inputImage parameter, I can't chain two filters one after another. But rather I have to create an image, and apply a filter, then to create another image, and apply a new filter.
I solved this just like described above. Also I had to extend a process in three steps (create upper gradient, create middle, black area and create lower gradient) and then to combine all that into one image.
I wonder is there anything about CILinearGradient filter I am not aware of ? Maybe there is an easier way to attack this problem and make a complex gradient?
Note that I am using SpriteKit and solving this with CAGradientLayer is not a way I want to go.
I'm not sure Core Image is the right tool for the job here — you'd probably be better off using CGGradient functions to draw your image.
Aside: What you're asking would be possible but cumbersome in CoreImage. Think of it by analogy to what you'd do to create these images as a user of Photoshop or other common graphics software... the CoreImage way would be something like this:
Create a layer for the upper gradient, fill the entire canvas with your gradient, and transform the layer to the proper position and size
Create a layer for the middle black portion, fill the entire canvas with black, and transform the layer to the proper position and size
Ditto #1, but for the bottom gradient.
You could do this with a combination of generator filters, transform filters, and compositing filters...
gradient -> transform -\
}-> composite -\
solid color -> transform -/ \
}-> composite
gradient -> transform ————————————————--/
But setting that up would be ugly.
Likewise, in Photoshop you could use selection tools and fill/gradient tools to create your gradient-fill-gradient design completely within a single layer... that's what drawing a single image using CoreGraphics is analogous to.
So, how to do that single-pass CG drawing? Something like this...
func createImage(width: CGFloat, _ height: CGFloat) -> CGImage {
let gradientOffset: CGFloat = 10 // white at bottom and top before/after gradient
let gradientLength: CGFloat = 12 // height of gradient region
// create colors and gradient for drawing with
let space = CGColorSpaceCreateDeviceRGB()
let startColor = UIColor.whiteColor().CGColor
let endColor = UIColor.blackColor().CGColor
let colors: CFArray = [startColor, endColor]
let gradient = CGGradientCreateWithColors(space, colors, [0,1])
// start an image context
UIGraphicsBeginImageContext(CGSize(width: width, height: height))
defer { UIGraphicsEndImageContext() }
let context = UIGraphicsGetCurrentContext()
// fill the whole thing with white (that'll show through at the ends when done)
CGContextSetFillColorWithColor(context, startColor)
CGContextFillRect(context, CGRect(x: 0, y: 0, width: width, height: height))
// draw top gradient
CGContextDrawLinearGradient(context, gradient, CGPoint(x: 0, y: gradientOffset), CGPoint(x: 0, y: gradientOffset + gradientLength), [])
// fill solid black middle
CGContextSetFillColorWithColor(context, endColor)
CGContextFillRect(context, CGRect(x: 0, y: gradientOffset + gradientLength, width: width, height: height - 2 * gradientOffset - 2 * gradientLength))
// draw bottom gradient
CGContextDrawLinearGradient(context, gradient, CGPoint(x: 0, y: height - gradientOffset), CGPoint(x: 0, y: height - gradientOffset - gradientLength), [])
return CGBitmapContextCreateImage(context)!
}
I'm programming in Swift. I want to mask an image using CALayer and UIImage. I'm creating my mask image programmatically. The created mask image is a UIImage and works fine when I view it on its own. But when I use it as a mask the whole screen becomes white. I suspect that my problem is in configuring the CALayer object. I would appreciate your help. Thanks!
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var maskImageSize = CGSizeMake(self.imageView.frame.width, self.imageView.frame.height)
UIGraphicsBeginImageContextWithOptions(maskImageSize, false, 0.0)
var color = UIColor(white: 1.0, alpha: 1.0)
color.setFill()
var rect = CGRectMake(0, 0, self.imageView.frame.width, self.imageView.frame.height)
UIRectFill(rect)
color = UIColor(white: 0.0, alpha: 1.0)
color.setFill()
rect = CGRectMake((self.imageView.frame.width/2)-100, (self.imageView.frame.height/2)-100, 200, 200)
UIRectFill(rect)
var maskImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
var maskLayer = CALayer()
maskLayer.contents = maskImage
maskLayer.contentsRect = CGRectMake(0, 0, self.imageView.bounds.width, self.imageView.bounds.height)
self.imageView.image = UIImage(named: "pictobemasked.png")
self.imageView.layer.mask = maskLayer;
}
}
Unfortunately you've asked your question rather badly - you have not said what it is that you are actually trying to do! It looks, however, as if you might be trying to punch a rectangular hole in your image view using a mask. If so, your code has at least three huge flaws.
One reason your code is not working is that a mask is based on transparency, not on color. You are using an opaque white and an opaque black, which are both opaque, so there is no difference there. You need your two colors to be like this:
var color = UIColor(white: 1.0, alpha: 1.0)
// ... and then, later ...
color = UIColor(white: 1.0, alpha: 0.0)
The second problem is that your layer has no size. You need to give it one:
var maskLayer = CALayer()
maskLayer.frame = CGRectMake(
0, 0, self.imageView.bounds.width, self.imageView.bounds.height)
The third and biggest problem is that your mask image is never getting into your mask layer, because you have forgotten to extract its CGImage:
maskLayer.contents = maskImage.CGImage
That last one is really the killer, because if you set the contents to a UIImage without extracting its CGImage, the image fails silently to get into the layer. There is no error message, no crash - and no image.
Making those three corrections in your code, I was able to make the mask punch a rectangular hole in an image. So if that's your purpose, those changes will achieve it.