Extra white space when get visible content of UIScrollView - swift

There is a UIImageView inside a UIScrollView which user can scale it down and up and then save it:
The area under the navigation bar and top of the tab bar is UIScrollView frame.
When user hits Done, image will be save in camera roll. It's what saved there (Photos App):
I have no idea what is this empty space in the saved image.
It's my code to save the image:
UIGraphicsBeginImageContextWithOptions(scrollView.frame.size, false, 0.0)
let rect = CGRectMake(0, scrollView.frame.origin.y, scrollView.frame.size.width, scrollView.frame.size.height)
self.view.drawViewHierarchyInRect(rect, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
let imageData = UIImageJPEGRepresentation(image, 1)
let compressedJPGImage = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(compressedJPGImage!, nil, nil, nil)
What I want to save is exactly the visible region of UIScrollView.

I had to use CGContextTranslateCTM() to translate the rectangle:
let screenRect: CGRect = scrollView.bounds
UIGraphicsBeginImageContext(screenRect.size)
let ctx: CGContextRef = UIGraphicsGetCurrentContext()!
CGContextTranslateCTM(ctx,0,-scrollView.frame.origin.y)
UIColor.blackColor().set()
CGContextFillRect(ctx, screenRect)
view.layer.renderInContext(ctx)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

Related

Crop Image so It Becomes Full Screen like Snapchat

If you upload a picture to Snapchat (that isn't already full screen), it will zoom in and crop the photo so that it becomes full screen. I am able to do this in my ImageView using autoresizing masks, but I need to be able to save the image in this cropped state and I can't figure out how to do it.
This is how I am able to display the image (selected from camera roll) in the image view how I want it
let imgView = UIImageView(image: image)
imgView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleRightMargin, .flexibleLeftMargin, .flexibleTopMargin]
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
imgView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
self.view.addSubview(imgView)
This turns a non-full screen photo and displays it full screen with the propping zoom/crop. How can I now save the photo as a full screen photo?
you can capture an image from given views.
func image(with view: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
view.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
let img = image(with: YourImageView)

Getting masked layer as UIImage on Swift on top of UIImageView

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.

Image shown in imageView loses aspect ratio and becomes squished when saved to camera roll

I'm making an image app that can put simple frames over images loaded into the imageView and save them as a new image for practice, because I'm very new to swift and trying to learn. These screenshots show the frame hidden because the problem is with the image loaded behind the frame.
My rep isn't high enough to post images but the specific issue is: Image shown in imageView loses aspect ratio and becomes squished widthwise when saved to camera roll.
I have my imageView's constraints set to maintain a specific aspect ratio with any sized device so it grows and shrinks accordingly. I also have it's content mode set to aspect fill via IB.
The aspect fill works exactly how I'd expect until I save the image. When I hit save the image inside the image view instantly squishes width wise and loses its aspect ratio.
I import the image to the imageView with this:
func importPicture() {
let picker = UIImagePickerController()
picker.allowsEditing = true
picker.delegate = self
present(picker, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else { return }
dismiss(animated: true)
currentImage = image
unchangedImage = image
self.imageView.image = currentImage
}
Then I draw:
func drawImagesAndText() {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: imageView.bounds.size.width, height: imageView.bounds.size.height))
img = renderer.image { ctx in
let bgImage = currentImage
bgImage?.draw(in: CGRect(x: 0, y: 0, width: imageView.bounds.size.width, height: imageView.bounds.size.height))
let frame = UIImage(named: "5x4frame")
frame?.draw(in: CGRect(x: 0, y: 0, width: imageView.bounds.size.width, height: imageView.bounds.size.height))
}
imageView.image = img
}
This was the only way I could think to do it to allow the image to be drawn while also having the image view dynamic.
this is the save function I'm using, I have this function tied to a "save button's" outlet from IB.
UIImageWriteToSavedPhotosAlbum(img, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
thanks for any and all advice

Apply CIFilters on UI elements

I want to apply an CIFilter on an UI element. I tried to apply it onto the views layer via the .filters member. However the filter won`t get applied.
Here's an approach: use UIGraphicsGetImageFromCurrentImageContext to generate a UIImage, apply the filter to that and overlay an image view containing the filtered image over your original component.
Here's a way to do that with a blur (taken from my blog):
Getting a blurred representation of a UIView is pretty simple: I need to begin an image context, use the view's layer's renderInContext method to render into the context and then get a UIImage from the context:
UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
Once I have the image populated, it's a fairly standard workflow to apply a Gaussian blur to it:
guard let blur = CIFilter(name: "CIGaussianBlur") else
{
return
}
blur.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blur.setValue(blurRadius, forKey: kCIInputRadiusKey)
let ciContext = CIContext(options: nil)
let result = blur.valueForKey(kCIOutputImageKey) as! CIImage!
let boundingRect = CGRect(x: -blurRadius * 4,
y: -blurRadius * 4,
width: frame.width + (blurRadius * 8),
height: frame.height + (blurRadius * 8))
let cgImage = ciContext.createCGImage(result, fromRect: boundingRect)
let filteredImage = UIImage(CGImage: cgImage)
A blurred image will be larger than its input image, so I need to be explicit about the size I require in createCGImage.
The next step is to add a UIImageView to my view and hide all the other views. I've subclassed UIImageView to BlurOverlay so that when it comes to removing it, I can be sure I'm not removing an existing UIImageView:
let blurOverlay = BlurOverlay()
blurOverlay.frame = boundingRect
blurOverlay.image = filteredImage
subviews.forEach{ $0.hidden = true }
addSubview(blurOverlay)
When it comes to de-blurring, I want to ensure the last subview is one of my BlurOverlay remove it and unhide the existing views:
func unBlur()
{
if let blurOverlay = subviews.last as? BlurOverlay
{
blurOverlay.removeFromSuperview()
subviews.forEach{ $0.hidden = false }
}
}
Finally, to see if a UIView is currently blurred, I just need to see if its last subview is a BlurOverlay:
var isBlurred: Bool
{
return subviews.last is BlurOverlay
}

UIImage appears differently in UIImageView than it does when saved to disk

I'm having a problem where a UIImage that I build in a playground appears correctly when I inspect it in a UIImageView within the playground...
...but incorrectly when I save it to disk.
Here is the code I'm using to build/inspect/save the UIImage:
import UIKit
import XCPlayground
// Size of view and layers
let size = CGSize(width: 180, height: 180)
// Create a layer
let layer = CALayer()
layer.frame = CGRect(origin: CGPointZero, size: size)
layer.backgroundColor = UIColor.blackColor().CGColor
// And a sublayer
let sublayer = CALayer()
sublayer.frame = CGRect(origin: CGPointZero, size: size)
sublayer.cornerRadius = 180
sublayer.backgroundColor = UIColor.whiteColor().CGColor
layer.addSublayer(sublayer)
// Render the layer into an image
UIGraphicsBeginImageContext(size)
layer.renderInContext(UIGraphicsGetCurrentContext())
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// Inspect the image within the playground
let view = UIImageView(image: im)
XCPShowView("Container View", view: view)
view.layer.addSublayer(layer)
// Save the image to disk
let data = NSData(data: UIImagePNGRepresentation(im)!)
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
var docs: String = paths[0] as String
let fullPath = docs.stringByAppendingPathComponent("icon.png")
let result = data.writeToFile(fullPath, atomically: true)
How can I render the image to disk to reflect what I see in the UIImageView?
The petal shaped thing is what I expect to see if the radius is larger than what makes a circle. If you click the "Show result" icon in playground next to the let view = UIImageView(image: im) line, it will show your image the exact same way as it is stored on disk. See below. I changed the colors while experimenting with it, but otherwise it is your code...
So I think that what is shown in XCPShowView("Container View", view: view) is incorrect, not the other way around.