Taking a screenshot and adding a text/watermark on top in Swift - ios

Im trying to allow the ability for a user to share a screenshot of an App and share it. I have got the taking of a screenshot and sharing but was interested in finding out if anyone knew how to add a layer of text on top of that screenshot when it is being taken, kind of like a watermark.
Here I take the screenshot:
let layer = UIApplication.sharedApplication().keyWindow!.layer
let scale = UIScreen.mainScreen().scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
layer.renderInContext(UIGraphicsGetCurrentContext()!)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let croppedImage = self.cropImage(screenshot)
let activityViewController = UIActivityViewController(activityItems: [croppedImage], applicationActivities: nil)
self.presentViewController(activityViewController, animated: true, completion: nil)
I crop the screenshot image in this function so it just shows middle of screen:
func cropImage(screenshot: UIImage) -> UIImage {
let scale = screenshot.scale
let imgSize = screenshot.size
let screenHeight = UIScreen.mainScreen().bounds.height
let bound = self.view.bounds.height
let navHeight = self.navigationController!.navigationBar.frame.height
let bottomBarHeight = screenHeight - navHeight - bound
let crop = CGRectMake(0, 200, //"start" at the upper-left corner
(imgSize.width - 1) * scale, //include half the width of the whole screen
(imgSize.height - bottomBarHeight - 300) * scale) //include the height of the navigationBar and the height of view
let cgImage = CGImageCreateWithImageInRect(screenshot.CGImage, crop)
let image: UIImage = UIImage(CGImage: cgImage!)
return image
}

Yes it is possible to do that. Try adding subview to your image view like below
let newImageView = UIImageView(image : croppedImage)
let labelView = UILabel(frame: CGRectMake(30, 30, 100, 20)) //adjust frame to change position of water mark or text
labelView.text = "my text"
newImageView.addSubview(labelView)
UIGraphicsBeginImageContext(newImageView.frame.size)
newImageView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let watermarkedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

Related

Swift 5: Better way/approach to add image border on photo editing app?

In case the title doesn't make sense, i'm trying to make a photo editing app where user can add border to their photo. For now, i'm testing a white border.
here is a gif sample of the app. (see how slow the slider is. It's meant to be smooth like any other slider.)
Gif sample
My approach was, to render the white background to the image's size, and then render the image n% smaller to shrink it hence the border.
But i have come to a problem where when i'm testing on my device (iphone 7 plus) the slider was so laggy and slow as if it's taking so much time to compute the function.
Here are the codes for the function. This function serves as blend the background with the foreground. Background being plain white colour.
blendImages is a function located on my adjustmentEngine class.
func blendImages(backgroundImg: UIImage,foregroundImg: UIImage) -> Data? {
// size variable
let contentSizeH = foregroundImg.size.height
let contentSizeW = foregroundImg.size.width
// the magic. how the image will scale in the view.
let topImageH = foregroundImg.size.height - (foregroundImg.size.height * imgSizeMultiplier)
let topImageW = foregroundImg.size.width - (foregroundImg.size.width * imgSizeMultiplier)
let bottomImage = backgroundImg
let topImage = foregroundImg
let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width : contentSizeW, height: contentSizeH))
let imgView2 = UIImageView(frame: CGRect(x: 0, y: 0, width: topImageW, height: topImageH))
// - Set Content mode to what you desire
imgView.contentMode = .scaleAspectFill
imgView2.contentMode = .scaleAspectFit
// - Set Images
imgView.image = bottomImage
imgView2.image = topImage
imgView2.center = imgView.center
// - Create UIView
let contentView = UIView(frame: CGRect(x: 0, y: 0, width: contentSizeW, height: contentSizeH))
contentView.addSubview(imgView)
contentView.addSubview(imgView2)
// - Set Size
let size = CGSize(width: contentSizeW, height: contentSizeH)
UIGraphicsBeginImageContextWithOptions(size, true, 0)
contentView.drawHierarchy(in: contentView.bounds, afterScreenUpdates: true)
guard let i = UIGraphicsGetImageFromCurrentImageContext(),
let data = i.jpegData(compressionQuality: 1.0)
else {return nil}
UIGraphicsEndImageContext()
return data
}
Below are the function i called to render it into uiImageView
guard let image = image else { return }
let borderColor = UIColor.white.image()
self.adjustmentEngine.borderColor = borderColor
self.adjustmentEngine.image = image
guard let combinedImageData: Data = self.adjustmentEngine.blendImages(backgroundImg: borderColor, foregroundImg: image) else {return}
let combinedImage = UIImage(data: combinedImageData)
self.imageView.image = combinedImage
This function will get the image and blend it with a new background colour for the border.
And finally, below are the codes for the slider's didChange function.
#IBAction func sliderDidChange(_ sender: UISlider) {
print(sender.value)
let borderColor = adjustmentEngine.borderColor
let image = adjustmentEngine.image
adjustmentEngine.imgSizeMultiplier = CGFloat(sender.value)
guard let combinedImageData: Data = self.adjustmentEngine.blendImages(backgroundImg: borderColor, foregroundImg: image) else {return}
let combinedImage = UIImage(data: combinedImageData)
self.imageView.image = combinedImage
}
So the question is, Is there a better way or optimised way to do this? Or a better approach?

How to position image view on top of a photo correctly using Swift

Currently I am able to save the photo with an image on top, however the top image is not located at the coordinate it is when appearing on the screen in a preview view.
fileprivate func mergeUIViews( ) -> UIImage?
{
let bottomImage = photo
let topImage = uiViewInstance.image
let bottomImageHeight = bottomImage.size.height
let bottomImageWidth = bottomImage.size.width
let topImageHeight = uiViewInstance.frame.height
let topImageWidth = uiViewInstance.frame.width
let topImageOrigin = uiViewInstance.frame.origin
let bottomImageSize = CGSize(width: bottomImageWidth, height: bottomImageHeight)
let topImageSize = CGSize(width: topImageWidth, height: topImageHeight)
// Merge images
UIGraphicsBeginImageContextWithOptions(bottomImageSize, false, 0.0)
bottomImage.draw(in: CGRect(origin: CGPoint.zero, size: bottomImageSize))
topImage .draw(in: CGRect(origin: topImageOrigin, size: topImageSize)) // Where I believe the problem exists
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
What works instead of using the actual captured photo's size, is setting the photo size to the view of the view controllers size. This resulted in the desired output of the entire screen showing the merged photos and the proper point for the origin of the overlaid image.
fileprivate func mergeUIViews( ) -> UIImage?
{
let bottomImage = photo
let topImage = uiViewInstance.image
let bottomImageSize = self.view.frame.size
let topImageHeight = uiViewInstance.frame.height
let topImageWidth = uiViewInstance.frame.width
let topImageOrigin = uiViewInstance.frame.origin
let topImageSize = uiViewInstance.frame.size
// Merge images
UIGraphicsBeginImageContextWithOptions(bottomImageSize, false, 0.0)
bottomImage.draw(in: CGRect(origin: CGPoint.zero, size: bottomImageSize))
topImage .draw(in: CGRect(origin: topImageOrigin, size: topImageSize)) // Where I believe the problem exists
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}

After cropping images in Swift I'm getting results tilted with 90 degrees - why?

I'm using a nice github plugin for Swift https://github.com/budidino/ShittyImageCrop responsible for cropping the image.
I need aspect ratio 4:3, so I call this controller like this:
let shittyVC = ShittyImageCropVC(frame: (self.navigationController?.view.frame)!, image: image!, aspectWidth: 3, aspectHeight: 4)
self.navigationController?.present(shittyVC, animated: true, completion: nil)
Now, when I provide horizontal image (wider than taller) - cropped result is fine - I see a photo with aspect ratio 4:3 as an output.
But when I provide vertical image and try to cropp it - I'm seeing tilted output. So for example, when normal photo is like this:
vertical - and tilted - one looks like this:
(sorry for low res here). Why does it get shifted to one side?
I suspect the problem might be somewhere in the logic of the crop-button:
func tappedCrop() {
print("tapped crop")
var imgX: CGFloat = 0
if scrollView.contentOffset.x > 0 {
imgX = scrollView.contentOffset.x / scrollView.zoomScale
}
let gapToTheHole = view.frame.height/2 - holeRect.height/2
var imgY: CGFloat = 0
if scrollView.contentOffset.y + gapToTheHole > 0 {
imgY = (scrollView.contentOffset.y + gapToTheHole) / scrollView.zoomScale
}
let imgW = holeRect.width / scrollView.zoomScale
let imgH = holeRect.height / scrollView.zoomScale
print("IMG x: \(imgX) y: \(imgY) w: \(imgW) h: \(imgH)")
let cropRect = CGRect(x: imgX, y: imgY, width: imgW, height: imgH)
let imageRef = img.cgImage!.cropping(to: cropRect)
let croppedImage = UIImage(cgImage: imageRef!)
var path:String = NSTemporaryDirectory() + "tempFile.jpeg"
if let data = UIImageJPEGRepresentation(croppedImage, 0.95) { //0.4 - compression quality
//print("low compression is here")
try? data.write(to: URL(fileURLWithPath: path), options: [.atomic])
}
self.dismiss(animated: true, completion: nil)
}
ShittyImageCrop saves cropped images directly to your album and I couldn't replicate your issue using vertical images.
I see you used UIImageJPEGRepresentation compared to UIImageWriteToSavedPhotosAlbum from ShittyImageCrop and it seems other people also have problems with image rotation after using UIImageJPEGRepresentation.
Look up iOS UIImagePickerController result image orientation after upload and iOS JPEG images rotated 90 degrees
EDIT
try implementing fixOrientation() from https://stackoverflow.com/a/27775741/611879
add fixOrientation():
func fixOrientation(img:UIImage) -> UIImage {
if (img.imageOrientation == UIImageOrientation.Up) {
return img
}
UIGraphicsBeginImageContextWithOptions(img.size, false, img.scale)
let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
img.drawInRect(rect)
let normalizedImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return normalizedImage
}
and then do it before using UIImageJPEGRepresentation:
if let data = UIImageJPEGRepresentation(fixOrientation(croppedImage), 0.95) {
try? data.write(to: URL(fileURLWithPath: path), options: [.atomic])
}
EDIT 2
please edit the init method of ShittyImageCrop by replacing img = image with:
if (image.imageOrientation != .up) {
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
var rect = CGRect.zero
rect.size = image.size
image.draw(in: rect)
img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
} else {
img = image
}

DrawView save combined image (multiply)

I have 2 UIImageViews - one is at the bottom and shows a default image (like an Photo) - on the second UIImageView where you can draw.
I would like to create an UIImage from both images, and save it as new image.
How can i do that? Ill tried with:
func saveImage() {
print("save combined image")
let topImage = self.canvasView.image
let bottomImage = self.backgroundImageView.image
let size = CGSizeMake(topImage!.size.width, topImage!.size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
[topImage!.drawInRect(CGRectMake(0,0,size.width, topImage!.size.height))];
[bottomImage!.drawInRect(CGRectMake(0,0,size.width, bottomImage!.size.height))];
let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(newImage, self, #selector(CanvasViewController.image(_:didFinishSavingWithError:contextInfo:)), nil)
}
But the result is not correct (stretched and no overlay)
Any ideas?
Ok ill found an solution for that, just need to add "multiply" as blend mode.
let topImage = self.canvasView.image
let bottomImage = self.backgroundImageView.image
let size = CGSizeMake(bottomImage!.size.width, bottomImage!.size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
[bottomImage!.drawInRect(CGRectMake(0,0,size.width, bottomImage!.size.height))];
[topImage!.drawInRect(CGRectMake(0,0,size.width, bottomImage!.size.height), blendMode: CGBlendMode.Multiply , alpha: 1.0)];
let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

Crop right side of UIImage inside UIImageView with a PanGestureRecognizer

I am trying to "crop" an image inside an imageView. Crop in the sense of: If the user pans to the left, the picture gets cropped from the right side to the left. So, when the user pans to the right, the picture will be shown completely, if the user pans to the far left, the picture is not visible.
I tried the following:
#IBAction func handlePanGesture(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(containerView)
// Resize the image View
let image = imageView.image!
let hasAlpha = false
let scale: CGFloat = 0.0
let newRect = CGRectMake(0, 0, image.size.width - translation.x, image.size.height)
UIGraphicsBeginImageContextWithOptions(newRect.size, !hasAlpha, scale)
image.drawAtPoint(CGPointMake(-newRect.origin.x, -newRect.origin.y))
let croppedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
imageView.image = croppedImage
recognizer.setTranslation(CGPointZero, inView: containerView)
}
This kind of works. See a gif here (at the moment this is just applied to the left image).
Can someone point me in the right direction? Why does the image height not stay the same, is that because the contentMode is set to 'Aspect Fit'? Why is it only resizing when you pan to the left? Here is a screenshot of the configuration of the imageView
Thanks for any tips.
I solved it to following way.
I will explain and post the code below. First, I resized the image to fit the imageView. That way I could set the contentMode to .Left. Second I kept two copies of the image. So, one picture can be cropped from right to left and one can be reversed when to user pans from left to right.
My cropping-function from above wouldn't work so I cropped them with CGImageCreateWithImageInRect(_:_:).
Here is the code for the UIPanGestureRecognizer which works for me:
#IBAction func handlePanGesture(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(containerView)
let imageLeft = googleImageView.image!
let imageRef = CGImageCreateCopy(initiallyScaledImage?.CGImage)
let imageRight = UIImage(CGImage: imageRef!, scale: (initiallyScaledImage?.scale)!, orientation: (initiallyScaledImage?.imageOrientation)!)
if translation.x < 0 {
let scale = imageLeft.scale
let newRect = CGRectMake(0, 0, (imageLeft.size.width + translation.x) * scale, imageLeft.size.height * scale)
let imageRef = CGImageCreateWithImageInRect(imageLeft.CGImage, newRect)
if let croppedImage = imageRef {
googleImageView.image = UIImage(CGImage: croppedImage, scale: scale, orientation: imageLeft.imageOrientation)
}
} else if translation.x > 0 {
// Get the rect from above, add translation, set new picture
let scale = imageRight.scale
let newRect = CGRectMake(0, 0, (imageLeft.size.width + translation.x) * scale, imageRight.size.height * scale)
let imageRef = CGImageCreateWithImageInRect(imageRight.CGImage, newRect)
if let uncroppedImage = imageRef {
googleImageView.image = UIImage(CGImage: uncroppedImage, scale: scale, orientation: imageRight.imageOrientation)
}
}
recognizer.setTranslation(CGPointZero, inView: containerView)
}

Resources