This is my first question, so please excuse me if I'm unclear in any way.
I would like to be able to create a new UIImage2 from the contents of a freehand drawn shape over an existing UIImage1 to display over the existing UIImage1.
A visual example: UIImage1 is a photo of a city skyline. The user should be able to draw an outline around a skyscraper and create a new UIImage2 which will be displayed stacked over UIImage1, with the ability to edit (size, color, etc) of UIImage 2.
Drawing lines over a UIImage isn't too difficult, but I've yet to figure out how to capture the content from the UIImage within that freehand drawn shape creating a separate UIImage.
I'd greatly appreciate any help.
If you want to create an image from a masked path, you can:
Create UIBezierPath from user gesture;
Use that bezier path as the CAShapeLayer on the image view;
When the user gesture is done, remove that shape layer, but create new shape layer and use it as a mask; and
Create image from that masked image view using UIGraphicsImageRenderer and drawHierarchy to render whatever should be captured in the image.
For example:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
private var path: UIBezierPath?
private var strokeLayer: CAShapeLayer?
#IBAction func didHandlePan(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: imageView)
switch gesture.state {
case .began:
path = UIBezierPath()
path?.move(to: location)
strokeLayer = CAShapeLayer()
imageView.layer.addSublayer(strokeLayer!)
strokeLayer?.strokeColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1).cgColor
strokeLayer?.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
strokeLayer?.lineWidth = 5
strokeLayer?.path = path?.cgPath
case .changed:
path?.addLine(to: location)
strokeLayer?.path = path?.cgPath
case .cancelled, .ended:
// remove stroke from image view
strokeLayer?.removeFromSuperlayer()
strokeLayer = nil
// mask the image view
let mask = CAShapeLayer()
mask.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
mask.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
mask.lineWidth = 0
mask.path = path?.cgPath
imageView.layer.mask = mask
// get cropped image
let image = imageView?.snapshot
imageView.layer.mask = nil
// perhaps use that image?
imageView.image = image
default: break
}
}
}
By the way, to create UIImage from a view (masked or otherwise), you can use:
extension UIView {
var snapshot: UIImage {
UIGraphicsImageRenderer(bounds: bounds).image { _ in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
}
That yields something like:
Related
I have created a clickable diagram with CAShapeLayers and UIBezierPaths. These shapes have a background-color that I want to fade out to transparent on the sides/edges. All CAShapeLayers have different shapes. Is there a way to achieve this in Swift?
I attached a simple example image of what I'm trying to do:
Right now, I'm creating the CAShapeLayers as follows:
var path = UIBezierPath()
path.moveToPoint(CGPointMake(20, 30))
path.addLineToPoint(CGPointMake(40, 30))
path.addLineToPoint(CGPointMake(50, 60))
path.addLineToPoint(CGPointMake(120, 180))
path.closePath()
var layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.5).CGColor
layer.hidden = true
baseImg.layer.addSublayer(layer)
Is there a way to add a CIGaussianBlur or a UIBlurEffect to these shapes?
I'm trying to port some iOS code for a Mac app. My code is as follows:
func innerRing() {
let innerRing = CAShapeLayer()
let circleRadius: CGFloat = 105.0
innerRing.frame = InnerRingView.bounds
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = CGRectGetMidX(InnerRingView.bounds) - CGRectGetMidX(circleFrame)
circleFrame.origin.y = CGRectGetMidY(InnerRingView.bounds) - CGRectGetMidY(circleFrame)
return circleFrame
}
innerRing.path = UIBezierPath(ovalInRect: circleFrame()).CGPath
innerRing.lineWidth = 3.0
innerRing.strokeStart = 0.0
innerRing.strokeEnd = 1.0
innerRing.fillColor = UIColor.clearColor().CGColor
innerRing.strokeColor = UIColor(red: 147.0/255.0, green: 184.0/255.0, blue: 255.0/255.0, alpha: 1.0).CGColor
InnerRingView.layer.addSublayer(innerRing)
}
This code works very well, especially for adjusting the fill color, stroke color, and stroke start/end.
In my Mac app, I am effectively trying to use the same code, but apply it to an NSImageView (I want it to be able to appear on each row of a table, and I will adjust certain parameters (such as color) based on what that row details.
Could anyone assist with guidance on adding this simple circle to an NSImageView?
Why do you want to use an NSImageView? NSImageView is for displaying images (icons, pictures, etc).
Make yourself a custom NSView instead. Just remember that, unlike UIKit's UIView, NSView doesn't get a layer by default, so you need to tell It to by setting wantsLayer to true.
Like so:
class CircleView: NSView {
lazy var innerRing: CAShapeLayer = {
let innerRing = CAShapeLayer()
let circleRadius: CGFloat = 105.0
innerRing.frame = self.bounds
var circleFrame = CGRect(x: 0, y: 0, width: circleRadius, height: circleRadius)
circleFrame.origin.x = CGRectGetMidX(self.bounds) - CGRectGetMidX(circleFrame)
circleFrame.origin.y = CGRectGetMidY(self.bounds) - CGRectGetMidY(circleFrame)
innerRing.path = CGPathCreateWithEllipseInRect(circleFrame, nil)
innerRing.lineWidth = 3.0
innerRing.strokeStart = 0.0
innerRing.strokeEnd = 1.0
innerRing.fillColor = NSColor.clearColor().CGColor
innerRing.strokeColor = NSColor(red: 147.0/255.0, green: 184.0/255.0, blue: 255.0/255.0, alpha: 1.0).CGColor
return innerRing
}()
override func awakeFromNib() {
super.awakeFromNib()
wantsLayer = true
layer = CALayer()
layer?.addSublayer(innerRing)
}
}
I want to create programatically Hexagon UIButtons with Swift. I have hexagon images as you can see green picture. The edges are transparent, so you shouldn't click that areas. By using default UIButton with "custom" option, these areas are still clickable.
Then add them like image below on UIScrollView. The main thing is that when user clicks one button I should get the correct button's id.
So, can you help me with creating hexagon UIButton or clickable image view? Thanks.
You can extend the UIButton class to not detect touch events at the parts of image that are transparent.
We detect the alpha component of the image at the touch point.
If the alpha component is 0, the hit test will fail.
If the alpha is non zero, hit test will succeed.
The hit test will also fail if the touch is outside the button bounds.
extension UIButton {
func getColourFromPoint(point:CGPoint) -> UIColor {
let colorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)
var pixelData:[UInt8] = [0, 0, 0, 0]
let context = CGBitmapContextCreate(&pixelData, 1, 1, 8, 4, colorSpace, bitmapInfo)
CGContextTranslateCTM(context, -point.x, -point.y);
self.layer.renderInContext(context)
var red:CGFloat = CGFloat(pixelData[0])/CGFloat(255.0)
var green:CGFloat = CGFloat(pixelData[1])/CGFloat(255.0)
var blue:CGFloat = CGFloat(pixelData[2])/CGFloat(255.0)
var alpha:CGFloat = CGFloat(pixelData[3])/CGFloat(255.0)
var color:UIColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
return color
}
public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if (!CGRectContainsPoint(self.bounds, point)) {
return nil
}
else {
let color : UIColor = self.getColourFromPoint(point)
let alpha = CGColorGetAlpha(color.CGColor);
if alpha <= 0.0 {
return nil
}
return self
}
}
}
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.
I could use a few pointers with turning PaintCode files into SKShapeNodes for use in an app I am building. There are two specific issues I am trying to overcome, as outlined below. I am using Xcode 6 beta 4, SpriteKit, Swift and PaintCode for the project. First, the code:
import UIKit
import SpriteKit
class Ad_disclighttop : SKShapeNode {
var ovalPath: UIBezierPath = UIBezierPath()
var oval2Path: UIBezierPath = UIBezierPath()
init() {
super.init()
drawDisc()
self.name = "White Disc"
self.path = ovalPath.CGPath
}
//// Initialization
override class func load() {
}
//// Drawing Methods
func drawDisc() {
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let xFFFFFF66 = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 0.400)
let x00000066 = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 0.400)
//// Gradient Declarations
let markerTopGradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [xFFFFFF66.CGColor, x00000066.CGColor], [0.01, 1])
//// MarkerW_04
//// Oval Drawing
ovalPath = UIBezierPath(ovalInRect: CGRectMake(0, 0, 60, 60))
UIColor.whiteColor().setFill()
ovalPath.fill()
x00000066.setStroke()
ovalPath.lineWidth = 1
ovalPath.stroke()
//// Oval 2 Drawing
oval2Path = UIBezierPath(ovalInRect: CGRectMake(2.5, 2.5, 57, 57))
CGContextSaveGState(context)
oval2Path.addClip()
CGContextDrawRadialGradient(context, markerTopGradient,
CGPointMake(35.87, 38.3), 20.28,
CGPointMake(23.16, 25.59), 40.36,
UInt32(kCGGradientDrawsBeforeStartLocation) | UInt32(kCGGradientDrawsAfterEndLocation))
CGContextRestoreGState(context)
}
}
#objc protocol StyleKitSettableImage {
var image: UIImage! { get set }
}
#objc protocol StyleKitSettableSelectedImage {
var selectedImage: UIImage! { get set }
}
First problem I am encountering is that almost every non-trivial PaintCode file I produce has more than one UIBezierPath in it. The SKShapeNode's path property can only take one path, as far as I know, but perhaps it is possible to chain them some how?
Second problem is that the gradient colors do not seem to work.
The things I did to actual turn this file into an SKShapeNode was to:
1. import SpriteKit
2. Change the class extension from NSObject to SKShapeNode
3. rename the drawing function
4. create an init function for the shape node that sets the name and path
I am happy to receive any tips at all on this type of conversion. Thanks!
Since gradient colors do not work anyway in SKShapNode nodes, I decided to render PNG files from within PaintCode, and I create SKSpriteNodes with them. Not the solution I was hoping for, but it is what it is.