iOS: how to scale up image nicely? - ios

I'm having trouble scaling up an image in a nice way. I'm displaying an image in a UIImageView. But while the image is downloading from the server, I'd like to display a blurred version of the image. The server quickly gives me a small version of the image that looks like this:
Magnified, it looks like this:
I'd like to be able to draw it like this (which is how Chrome draws it when I scale the image up):
But when I put the image into a UIImageView and set imageview.layer.magnificationFilter to kCAFilterLinear or kCAFilterTrilinear, I get this:
... which is ugly - notice the hard bands of light and dark.
I've tried using CIFilter with CILanczosScaleTransform, and that looks promising, but I get an ugly border effect:
Well - any advice? Anybody done this nicely? I can try filters forever, thought I'd see if anybody's cracked this...
EDIT: I tried bbarnhart's suggestion. I get this - nicer, but the blur radius is too large or something:
EDIT 2: #bbarnhart, I can now get this, much closer!

Using the blur effect on your UIImageView might give you something closer to what you want.
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
blurView.frame = yourImageView.bounds
yourImageView.addSubview(blurView)
Also check out bluuur
Update to include a playground:
import UIKit
import XCPlayground
let image = UIImage(named: "pixels")
let view = UIView(frame: CGRect(x: 0, y: 0, width: 346, height: 431))
let imageView = UIImageView(image: image)
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
blurView.frame = imageView.bounds
imageView.addSubview(blurView)
view.addSubview(imageView)
XCPlaygroundPage.currentPage.liveView = view

Related

addSublayer not displaying image

I'm having trouble getting an image in a CAlayer to display in an imageView. I can get it to work with a CAGradientLayer, but as soon as I change it to a CALayer with an image, nothing seems to work. It doesn't even display part of the image as I would expect it to, if the frame was shifted.
I've made sure the image name is correct and exists. For background: testImageView is the only imageView and covers the entire screen.
let strokeLayer = CALayer()
strokeLayer.contents = UIImage(named: "test.png")
strokeLayer.frame = CGRect(x: 0, y: 0, width: testImageView.frame.width, height: testImageView.frame.height)
testImageView.layer.addSublayer(strokeLayer)
Any help is appreciated!
Figured out I was just missing .cgImage at the end of the second line
If you want to display a image in CALayer you have to pass as
CGImage.strokeLayer.contents = UIImage(named: "test.png")?.cgImage

Shape/mask on UIImage?

I was wondering how can this be achieved in Xcode? It's part of a profile, so the header is an actual UIImageView, but the curve below it, not sure how to achieve that. Any ideas?
Say that the grey area is built out of a bottom grey rectangle and on top another rectangle with your arc we could do something like this:
Create a UIBezierPath in the shape of a circle:
let path = UIBezierPath(ovalIn:CGRect(x: 0, y: view.bounds.height/2, width: view.bounds.width, height: view.bounds.height)).cgPath
Apply it to the top rectangle.
let overlay = CAShapeLayer()
overlay.path = path
overlay.fillColor = UIColor.gray.cgColor
overlay.shouldRasterize = true
view.layer.addSublayer(overlay)
This will create a perfect circle but you can tweak the CGRect's your liking in order to get the shape you want!
It can be achieved by giving header view UIBezierPath but if you don't want to do that stuff.
I found a cool way of doing.
Your Header view contain a image (Lets say it HeaderImage).
Make a Image of that shape (Lets say it MaskImage).
let path = UIImageView.init(image: #imageLiteral(resourceName: "MaskImage"))
Than apply this mask to Header Image.
HeaderImage.mask = path
Hope it work for you.

Cropping UIImage to custom path and keeping correct resolution?

I have a view (blue background...) which I'll call "main" here, on main I added a UIImageView that I then rotate, pan and scale. On main I have a another subview that shows the cropping area. Anything out of that under the darker area needs to be cropped.
I am trying to figure out how to properly create a cropped image from this state. I want the resulting image to look like this:
I want to make sure to keep the resolution of the image.
Any idea?
I have tried to figure out how to use the layer.mask property of the UIImageView. After some feedback, I think I could have another view (B) on the blue view, on B I would then add the image view, so then I would make sure that B's frame would match the rect of the cropping mask overlay. I think that could work? The only thing is I want to make sure I don't lose resolution.
So, earlier I tried this:
maskShape.frame = imageView.bounds
maskShape.path = UIBezierPath(rect: CGRect(x: 20, y: 20, width: 200, height: 200)).cgPath
imageView.layer.mask = maskShape
The rect was just a test rect and the image would be cropped to that path, but, I wasn't sure how to get a UIImage from all this that could keep the large resolution of the original image
So, I have implemented the method suggested by marco, it all works with the exception of keeping the resolution.
I use this call to take a screenshot of the view the contains the image and I have it clip to bounds:
public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage {
let rendererFormat = UIGraphicsImageRendererFormat.default()
rendererFormat.opaque = isOpaque
let renderer = UIGraphicsImageRenderer(size: bounds.size, format: rendererFormat)
let snapshotImage = renderer.image { _ in
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
}
return snapshotImage
}
The image I get is correct, but is not as sharp as the one I crop.
Hoe can I keep the resolution high?
In your view which keeps the image you must set clipsToBounds to true. Not sure if I got well but I suppose it's your "cropping area"

Sublayer is not showing up after add it

I'm trying to add the image "resizeLayer" over my UIView selectedShape by sublayering it over selectedShape
let sublayer = CALayer()
sublayer.bounds = selectedShape.bounds //even when inserted this line, sublayer still doesn't show up
sublayer.frame = selectedShape.frame
sublayer.contents = UIImage(named: "resizeLayer")
selectedShape?.layer.addSublayer(sublayer)
But when I run my code I don't see the layer at all
I even tried subviewing the image "resizeLayer" over the UIView "selectedShape"
let resizeFrame = UIImageView(image: UIImage(named: "resizeLayer"))
resizeFrame.frame = selectedShape.frame
resizeFrame.contentMode = UIViewContentMode.ScaleAspectFill
selectedShape.addSubview(resizeFrame)
But still, the "resizeLayer" does not show up!
It only shows up if I add the "resizeLayer" to the overall view:
let resizeFrame = UIImageView(image: UIImage(named: "resizeLayer"))
resizeFrame.frame = selectedShape.frame
resizeFrame.contentMode = UIViewContentMode.ScaleAspectFill
selectedShape.addSubview(resizeFrame)
self.view.insertSubview(resizeFrame, aboveSubview: selectedShape) //add this line
Any help on this would be really appreciated!
If it's relevant, this is how I made selectedShape
selectedShape = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
selectedShape.layer.cornerRadius = 10
selectedShape.backgroundColor = UIColor.blueColor()
canvas.addSubview(selectedShape) //canvas is the view I'm adding selectedShape to
This is the image "resizeLayer" that I'm trying to add
The blue square is selectedShape. As you can see the layer is not showing up.
What I want to happen
You are using the entire frame for selectedShape. You should only use the width and the height because x and y should be zero. The image is added to selectedShape so the point (0,0) is the top left of the selectedShape view.
resizeFrame.frame = CGRect(origin: CGPointZero, size: selectedShape.frame.size)
I will admit that this stumped me for longer than it should have.
Not really an answer to his problem, but related and would have saved me time if somone had written this here before. So in hope to help someone and compile a more complete list of problems and their solution:
The addSublayer call needs to be made from the main-thread...
The funny thing is if you do it from a worker-thread it still shows up in the view-hirarchy-debugging with the content you set, but it's not displayed...
I think you should send selectedView to back:
canvas.sendSubviewToBack(selectedShape)

Subtract UIView from another UIView in Swift

I'm sure this is a very simple thing to do, but I can't seem to wrap my head around the logic.
I have two UIViews. One black, semi-transparent and "full-screen" ("overlayView"), another one on top, smaller and resizeable ("cropView"). It's pretty much a crop-view setup, where I want to "dim" out the areas of an underlying image that are not being cropped.
My question is: How do I go about this? I'm sure my approach should be with CALayers and masks, but no matter what I try, I can't get behind the logic.
This is what I have right now:
This is what I would want it to look like:
How do I achieve this result in Swift?
Although you won't find a method such as subtract(...), you can easily build a screen with an overlay and a transparent cut with the following code:
Swift 4.2
private func addOverlayView() {
let overlayView = UIView(frame: self.bounds)
let targetMaskLayer = CAShapeLayer()
let squareSide = frame.width / 1.6
let squareSize = CGSize(width: squareSide, height: squareSide)
let squareOrigin = CGPoint(x: CGFloat(center.x) - (squareSide / 2),
y: CGFloat(center.y) - (squareSide / 2))
let square = UIBezierPath(roundedRect: CGRect(origin: squareOrigin, size: squareSize), cornerRadius: 16)
let path = UIBezierPath(rect: self.bounds)
path.append(square)
targetMaskLayer.path = path.cgPath
// Exclude intersected paths
targetMaskLayer.fillRule = CAShapeLayerFillRule.evenOdd
overlayView.layer.mask = targetMaskLayer
overlayView.clipsToBounds = true
overlayView.alpha = 0.6
overlayView.backgroundColor = UIColor.black
addSubview(overlayView)
}
Just call this method inside your custom view's constructor or inside your ViewController's viewDidLoad().
Walkthrough
First I create a raw overlayView, then a CAShapeLayer which I called "targetMaskLayer". The ultimate goal is to draw a square with the help of UIBezierPath inside that overlayView. After defining the square's dimensions, I set its cgPath as the targetMaskLayer's path.
Now comes an important part:
targetMaskLayer.fillRule = CAShapeLayerFillRule.evenOdd
Here I basically configure the fill rule to exclude the intersection.
Finally, I provide some styling to the overlayView and add it as a subview.
ps.: don't forget to import UIKit
There might be another drawing solution but basically you have 4 areas that need to be handled. Take the square area above and below the space with full width and add the right and left side between them with constraints to eachother.

Resources