Draw image and text into a single image - ios

I'm attempting to combine both an image and a text field into a single image whilst still keeping the texts initial positioning.
I'm using UIGraphicsBeginImageContext to create a bitmap context and UIGraphicsGetImageFromCurrentImageContext to draw the final image.
So far, I have the following contained within a function:
let size = CGSize(width: self.takenImage.size.width, height: self.takenImage.size.height)
UIGraphicsBeginImageContextWithOptions(takenImage.size, false, takenImage.scale)
let areaSize = CGRect(x: 0, y: 0, width: self.takenImage.size.width, height: self.takenImage.size.height)
takenImage.draw(in: areaSize)
let imageViewSize = self.takenImage.size
let multiWidth = areaSize.width / imageViewSize.width
let multiHeight = areaSize.height / imageViewSize.height
print ("multiWidth = \(multiWidth)")
print ("multiHeight = \(multiHeight)")
let textSize = CGRect(x: textOverlay.frame.origin.x * multiWidth, y: textOverlay.frame.origin.y * multiHeight, width: textOverlay.frame.size.width * multiWidth, height: textOverlay.frame.size.height * multiHeight)
textOverlay.drawText(in: textSize)
let outputImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
self.finalImage = outputImage
takenImage is the taken image from my camera (never null) and textOverlay is a UITextField containing the wanted text.
I first create the bitmap and draw takenImage using both its original width/height.
If I draw just this to my finalImage, all works fine. The problem stems from trying to add the text and keep it in the same position.
I've tried to create a second CGRect with x, y, w, h coordinates from the UITextField : textOverlay but when viewing the final image, I'm getting weird results.
The images can be seen here.
How would I go about preserving the text's position in the merged image?

you need recalculate area size to draw text.
let areaSize = CGRect(x: 0, y: 0, width: self.takenImage.size.width, height: self.takenImage.size.height)
takenImage.draw(in: areaSize)
let imageViewSize = self.imageView.size
let multiWidth = areaSize.width / imageViewSize.width
let multiHeight = areaSize.height / imageViewSize.height
let textSize = CGRect(x: textOverlay.frame.origin.x * multiWidth, y: textOverlay.frame.origin.y * multiHeight, width: textOverlay.frame.size.width * multiWidth, height: textOverlay.frame.size.height * multiHeight)
textOverlay.drawText(in: textSize)
Here, I use self.imageView - it is imageView contains your takenImage.
Hope it help.

Related

How to make a Vision output display into a UI?

I am relatively new to coding, and I have recently been working on a program that allows a user to scan a crystal, using the iPhones rear camera, and it will identify what kind of crystal it is. I used CreateML to build the model, and Vision to identify the crystal. What I can't seem to figure out is how to get the results into the UI I built. The results are printing to the Xcode console.
Here's a picture of the Storyboard:
I assume you want to draw a box around the detected crystal?
You should be getting a boundingBox of your crystal that looks something like this:
(0.166666666666667, 0.35, 0.66666666666667, 0.3)
These are "normalized" coordinates, which means that they are relative to the image that you send to Vision. I explain this more in detail here...
What you are used to
What Vision returns
You need to convert these "normalized" coordinates to UIKit coordinates that you can use. To do that, I have this converting function:
func getConvertedRect(boundingBox: CGRect, inImage imageSize: CGSize, containedIn containerSize: CGSize) -> CGRect {
let rectOfImage: CGRect
let imageAspect = imageSize.width / imageSize.height
let containerAspect = containerSize.width / containerSize.height
if imageAspect > containerAspect { /// image extends left and right
let newImageWidth = containerSize.height * imageAspect /// the width of the overflowing image
let newX = -(newImageWidth - containerSize.width) / 2
rectOfImage = CGRect(x: newX, y: 0, width: newImageWidth, height: containerSize.height)
} else { /// image extends top and bottom
let newImageHeight = containerSize.width * (1 / imageAspect) /// the width of the overflowing image
let newY = -(newImageHeight - containerSize.height) / 2
rectOfImage = CGRect(x: 0, y: newY, width: containerSize.width, height: newImageHeight)
}
let newOriginBoundingBox = CGRect(
x: boundingBox.origin.x,
y: 1 - boundingBox.origin.y - boundingBox.height,
width: boundingBox.width,
height: boundingBox.height
)
var convertedRect = VNImageRectForNormalizedRect(newOriginBoundingBox, Int(rectOfImage.width), Int(rectOfImage.height))
/// add the margins
convertedRect.origin.x += rectOfImage.origin.x
convertedRect.origin.y += rectOfImage.origin.y
return convertedRect
}
You can use it like this:
let convertedRect = self.getConvertedRect(
boundingBox: observation.boundingBox,
inImage: image.size, /// image is the image that you feed into Vision
containedIn: self.previewView.bounds.size /// the size of your camera feed's preview view
)
self.drawBoundingBox(rect: convertedRect)
/// draw the rectangle
func drawBoundingBox(rect: CGRect) {
let uiView = UIView(frame: rect)
previewView.addSubview(uiView)
uiView.backgroundColor = UIColor.orange.withAlphaComponent(0.2)
uiView.layer.borderColor = UIColor.orange.cgColor
uiView.layer.borderWidth = 3
}
Result (I'm doing a VNDetectRectanglesRequest):
If you want to "track" the detected object while your phone is moving, check out my answer here

Create a Frame image from single image

I want to make a frame image from single image. Below is the code I'm using
func createFrameFromImage(image:UIImage , size :CGSize) -> UIImage
{
let imageSize = CGSize.init(width: size.width , height: size.height)
UIGraphicsBeginImageContext(imageSize)
let width = imageSize.width
let height = imageSize.height
var letTop = image
let rightTop = rotateImageByAngles(image: &letTop, angles: .pi/2) // correct
let rightBottom = rotateImageByAngles(image: &letTop, angles: -.pi) // correct
let leftBottom = rotateImageByAngles(image: &letTop, angles: -.pi/2) // correct
letTop.draw(in: CGRect(x: 0, y: 0, width: width/2, height: height/2))
rightTop.draw(in: CGRect(x: (width/2) , y: 0, width: width/2, height: height/2))
leftBottom.draw(in: CGRect(x: 0, y: height/2, width: width/2, height: height/2))
rightBottom.draw(in: CGRect(x: (width/2) , y: (height/2), width: width/2, height: height/2))
guard let finalImage = UIGraphicsGetImageFromCurrentImageContext() else { return rightTop }
UIGraphicsEndImageContext()
return finalImage
}
Above function takes one piece of image and create four different images by rotating a single at specific angle and merge them to make a frame image. Issue I'm facing is maintaining image ratio. for ex: if create final image of size 320 * 120 it squeezes image horizontally.Attaching screen shot of output. I want to show this new generated image on wall using ARkit.
Final frame image
Given Image
// Adding Frame
// 1 inch = 72 points
//converting size inch to points to create frame image
let frameWidth = (size.width + 1)
let frameHeight = (size.height + 1)
let imgFrameUnit = UIImage(named: "img.png")!
let imgFrame = Singleton.shared.createFrameFromImage(image: imgFrameUnit, size: CGSize(width: frameWidth , height: frameHeight))
let frame = SCNNode(geometry: SCNPlane(width: ((frameWidth * 2.54) / 100), height: ((frameHeight * 2.54) / 100))) // in meters
frame.geometry?.firstMaterial?.diffuse.contents = imgFrame
frame.name = "frame"
nodeWeCanChange?.addChildNode(frame)
Any Help would be really Appreciated?

How to merge two UIImages while keeping the aspect ratio and size?

The code is added to Github to let you understand the real problem.
This is the hierarchy:
-- ViewController.View P [width: 375, height: 667]
---- UIImageView A [width: 375, height: 667] Name: imgBackground
[A is holding an image of size(1287,1662)]
---- UIImageView B [width: 100, height: 100] Name: imgForeground
[B is holding an image of size(2400,982)]
I am trying to merge A with B but the result is stretched.
This is the merge code:
func mixImagesWith(frontImage:UIImage?, backgroundImage: UIImage?, atPoint point:CGPoint, ofSize signatureSize:CGSize) -> UIImage {
let size = self.imgBackground.frame.size
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
backgroundImage?.draw(in: CGRect.init(x: 0, y: 0, width: size.width, height: size.height))
frontImage?.draw(in: CGRect.init(x: point.x, y: point.y, width: signatureSize.width, height: signatureSize.height))
let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
Note:
.contentMode = .scaleAspectFit
Code works but the result is stretched.
See this line in code, let size = self.imgBackground.frame.size – I need to change this to fix the problem. Find the origin of subview with respect to UIImage size
Here's the screenshot to understand the problem:
What should I do to get the proper output of merge function?
You have two bugs in your code:
You should also calculate aspect for document image to fit it into UIImageView. In mergeImages() replace:
img.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
with:
img.draw(in: getAspectFitFrame(sizeImgView: size, sizeImage: img.size))
When calculating aspect you center image horizontally/vertically if its width/height less then UIImageView width/height. But instead of comparing newWidth and newHeight you should compare factors:
if hfactor > vfactor {
y = (sizeImgView.height - newHeight) / 2
} else {
x = (sizeImgView.width - newWidth) / 2
}
Try bellow code it works for me, hope it works for you too,
func addWaterMarkToImage(img:UIImage, sizeWaterMark:CGRect, waterMarkImage:UIImage, completion : ((UIImage)->())?){
handler = completion
let img2:UIImage = waterMarkImage
let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
UIGraphicsBeginImageContext(img.size)
img.draw(in: rect)
let frameAspect:CGRect = getAspectFitFrame(sizeImgView: sizeWaterMark.size, sizeImage: waterMarkImage.size)
let frameOrig:CGRect = CGRect(x: sizeWaterMark.origin.x+frameAspect.origin.x, y: sizeWaterMark.origin.y+frameAspect.origin.y, width: frameAspect.size.width, height: frameAspect.size.height)
img2.draw(in: frameOrig, blendMode: .normal, alpha: 1)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if handler != nil {
handler!(result!)
}
}
//MARK - Get Aspect Fit frame of UIImage
func getAspectFitFrame(sizeImgView:CGSize, sizeImage:CGSize) -> CGRect{
let imageSize:CGSize = sizeImage
let viewSize:CGSize = sizeImgView
let hfactor : CGFloat = imageSize.width/viewSize.width
let vfactor : CGFloat = imageSize.height/viewSize.height
let factor : CGFloat = max(hfactor, vfactor)
// Divide the size by the greater of the vertical or horizontal shrinkage factor
let newWidth : CGFloat = imageSize.width / factor
let newHeight : CGFloat = imageSize.height / factor
var x:CGFloat = 0.0
var y:CGFloat = 0.0
if newWidth > newHeight{
y = (sizeImgView.height - newHeight)/2
}
if newHeight > newWidth{
x = (sizeImgView.width - newWidth)/2
}
let newRect:CGRect = CGRect(x: x, y: y, width: newWidth, height: newHeight)
return newRect
}

When I draw on the image, the image enlarges

imageView is the photo. I created a copy of the photo called tempImageView and I tried to draw on the tempImageView. However, when I try to draw tempImageView, it enlarges to fill the entire screen.
func createTempImageView(){
tempImageView = UIImageView(frame:CGRect(x: 0, y: 0, width: imageView.image!.size.width, height: imageView.image!.size.height))
tempImageView.center = CGPoint(x: imageView.center.x, y: imageView.center.y)
view.insertSubview(tempImageView, aboveSubview: imageView)
}
Because I set imageView.contentMode = .ScaleAspectFit, the image on screen has a smaller width and height than the original image. However, the imageView.image!.size.width and imageView.image!.size.height refers the original image. Therefore, when I tried to draw on the tempImageView, the image enlarged because tempImageView refers to the original image, which is much larger than the image on screen.
To get the dimensions of the image on screen and to stop the image from enlarging, I did this:
func createTempImageView(){
let widthRatio = imageView.bounds.size.width / imageView.image!.size.width
let heightRatio = imageView.bounds.size.height / imageView.image!.size.height
let scale = min(widthRatio, heightRatio)
let imageWidth = scale * imageView.image!.size.width
let imageHeight = scale * imageView.image!.size.height
tempImageView = UIImageView(frame:CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight))
tempImageView.center = CGPoint(x: imageView.center.x, y: imageView.center.y)
tempImageView.backgroundColor = UIColor.clearColor()
view.insertSubview(tempImageView, aboveSubview: imageView)
}

bubble icon factory for google maps in iOS

I had earlier used the google map util library to plot bubble icon in google maps and now I would like to do the same for iOS
https://developers.google.com/maps/documentation/android/utility/
I found this pod for iOS https://github.com/googlemaps/google-maps-ios-utils
Has anybody used it to create bubble icons and if so how do i create a bubble icons for iOS ?
Unfortunately, there is no such utility for IOS. I needed a similar solution, so I've come with the below.
The below function draws a left arrow and returns an UIImage. Note that one can draw the arrow by using only paths. Also, one can also use a pre created image from assets instead of creating all the time or create one re-use it.
func drawArrow() ->UIImage {
//the color of the arrow
let color = UIColor.yellow
// First create empty image choose the size according to your need.
let width = 84
let height = 24
let size = CGSize( width: width, height: height)
//let image: UIImage = makeEmptyImage(size: size)
let rectangle = CGRect(x: 12, y: 0, width: 72, height: 24)
UIGraphicsBeginImageContextWithOptions( size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
//clear the background
context?.clear(CGRect(x: 0, y: 0, width: width, height: height))
//draw the rectangle
context?.addRect(rectangle)
context?.setFillColor(color.cgColor)
context?.fill(rectangle)
//draw the triangle
context?.beginPath()
context?.move( to: CGPoint(x : 12 ,y : 0) )
context?.addLine(to: CGPoint(x : 0, y : 12) )
context?.addLine(to: CGPoint(x : 12, y : 24) )
context?.closePath()
context?.setFillColor(color.cgColor)
context?.fillPath()
// get the image
let image2 = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image2
}
The next function, takes the image and draw a text onto it.
func drawText(text:NSString, inImage:UIImage) -> UIImage? {
let font = UIFont.systemFont(ofSize: 11)
let color = UIColor.black
let style : NSMutableParagraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
style.alignment = .center
let attributes:NSDictionary = [ NSAttributedString.Key.font : font,
NSAttributedString.Key.paragraphStyle : style,
NSAttributedString.Key.foregroundColor : color
]
let myInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
let newImage = inImage.resizableImage(withCapInsets: myInsets)
print("rect.width \(inImage.size.width) rect.height \(inImage.size.height)")
let textSize = text.size(withAttributes: attributes as? [NSAttributedString.Key : Any] )
let textRect = CGRect(x: 10, y: 5, width: textSize.width, height: textSize.height)
UIGraphicsBeginImageContextWithOptions(CGSize(width: textSize.width + 20, height: textSize.height + 10), false, 0.0)
newImage.draw(in: CGRect(x: 0, y: 0, width: textSize.width + 20 , height: textSize.height + 10))
text.draw(in: textRect.integral, withAttributes: attributes as? [NSAttributedString.Key : Any] )
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resultImage
}
so we can use it like
marker.icon = drawText(text: eLoc.name as NSString, inImage: drawArrow() )
marker.groundAnchor = CGPoint(x: 0, y: 0.5)
//If you need some rotation
//let degrees = CLLocationDegrees(exactly: 90.0)
//marker.rotation = degrees!
groundAnchor moves the position of the marker icon, see this anwser. With degree you can rotate the arrow.
Note : Unfortunately, IOS doesn't support 9-patch images. Therefore, not all of the bubble icon factory can be programmed by using resizableImage, see this nice web site and also see this answer
An example output is;
Have you check out the customize icon documentation for google maps ios sdk? It's a good start.

Resources