Does anyone have a good way to render PKDrawing to PDF clearly? - ios

I'm trying to share my PKDrawing results via PDF file for email or print. I'm creating the PDF using 2 other PDF files plus the PKDrawing on the top layer. The 2 PDF layers look clear. The PKDrawing however is somewhat grainy, especially compared to a printed screen shot.
Here's my current code:
UIGraphicsBeginPDFContextToFile(filePath, CGRect.zero, nil)
var drawing: PKDrawing = PKDrawing.init()
let markup: Data = self.pages![pagenum].value(forKey: "markup") as! Data
let drawingHeight = self.pages![pagenum].value(forKey: "height") as! CGFloat
let drawingWidth = self.pages![pagenum].value(forKey: "width") as! CGFloat
do {
try drawing = PKDrawing.init(data: markup)
} catch {
print("Error loading drawing")
}
let pageSize = CGSize(width: 0, height: 0)
//let scale: CGFloat = 2.0 THIS VALUE DOESN'T SEEM TO MATTER
let scale: CGFloat = 1.0
if landscapeOrientation {
pageSize = CGSize(width:2048, height: 1408)
} else {
pageSize = CGSize(width: 1536, height: 1920)
}
let canvas = PKCanvasView(frame: CGRect(x: 0.0, y: 0.0, width: drawingWidth, height: drawingHeight))
canvas.drawing = drawing
markupImage = drawing.image(from: canvas.frame, scale: scale)
if pageSize.width != drawingWidth {
canvas.drawing.transform(using: CGAffineTransform(scaleX: pageSize.width / drawingWidth, y: pageSize.height / drawingHeight))
}
//markupImage = drawing.image(from: canvas.frame, scale: self.view.contentScaleFactor) THIS CHANGE MADE A BIG DIFFERENCE
markupImage = drawing.image(from: canvas.frame, scale: 4.0)
UIGraphicsBeginImageContext(pageSize)
backgroundView?.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: pageSize))
templateView?.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: pageSize))
markupImage.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: pageSize))
let pdfImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
let pageRect: CGRect = CGRect(origin: CGPoint.zero, size: pageSize)
UIGraphicsBeginPDFPageWithInfo(pageRect, nil)
let pdfContext: CGContext = UIGraphicsGetCurrentContext()!
pdfContext.scaleBy(x: 1.0, y: 1.0)
pdfImage.draw(at: CGPoint(x: 0.0, y: 0.0))
UIGraphicsEndPDFContext()

Changing my scale from 2.0 to 4.0 cleared it up quite a bit, I hadn't thought to try that. Will edit the code with my change and a comment. Definitely open for other alternatives though if there's a cleaner or clearer way to do this. I'm not the best at resolutions / pixel spaces.

Related

add rounding for the outline swift

I have a code that adds an outline of the required width and color to an image and it works well
But I still need to adjust the rounding for the contour, please tell me in which direction should I start?
There are two functions here that do the job of laying the outline.
func colorized(with color: UIColor = .white) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return self }
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
color.setFill()
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.clip(to: rect, mask: cgImage)
context.fill(rect)
guard let colored = UIGraphicsGetImageFromCurrentImageContext() else { return self }
return colored
}
/**
Returns the stroked version of the fransparent image with the given stroke color and the thickness.
- Parameters:
- color: The colors to user. By defaut, uses the ``UIColor.white`
- thickness: the thickness of the border. Default to `2`
- quality: The number of degrees (out of 360): the smaller the best, but the slower. Defaults to `10`.
- Returns: the stroked version of the image, or self if something was wrong
*/
func stroked(with color: UIColor = .red, thickness: CGFloat = 2, quality: CGFloat = 10) -> UIImage {
guard let cgImage = cgImage else { return self }
// Colorize the stroke image to reflect border color
let strokeImage = colorized(with: color)
guard let strokeCGImage = strokeImage.cgImage else { return self }
/// Rendering quality of the stroke
let step = quality == 0 ? 10 : abs(quality)
let oldRect = CGRect(x: thickness, y: thickness, width: size.width, height: size.height).integral
let newSize = CGSize(width: size.width + 2 * thickness, height: size.height + 2 * thickness)
let translationVector = CGPoint(x: thickness, y: 0)
UIGraphicsBeginImageContextWithOptions(newSize, false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return self }
defer {
UIGraphicsEndImageContext()
}
context.translateBy(x: 0, y: newSize.height)
context.scaleBy(x: 1.0, y: -1.0)
context.interpolationQuality = .high
for angle: CGFloat in stride(from: 0, to: 360, by: step) {
let vector = translationVector.rotated(around: .zero, byDegrees: angle)
let transform = CGAffineTransform(translationX: vector.x, y: vector.y)
context.concatenate(transform)
context.draw(strokeCGImage, in: oldRect)
let resetTransform = CGAffineTransform(translationX: -vector.x, y: -vector.y)
context.concatenate(resetTransform)
}
context.draw(cgImage, in: oldRect)
guard let stroked = UIGraphicsGetImageFromCurrentImageContext() else { return self }
return stroked
}
I tried many options but most of the answers are just adding a conerRadius to the UIImageView (this doesn't work)

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?

Drawing on an image & implementing AspectFill for CGRect. Can it be done?

The following image is a screen grab from an iPad 9.7 simulator, showing the circles overlayed on an underlying live preview. (The live camera view is not there because it's on the Simulator)
On any sort of iPhone the circles clip off at the screen edges, but on the iPad you can see the clipping. I'm sure this is an AspectFit or AspectFill problem but I can't find a solution. Somehow, the code isn't seeing the correct iPad screen size?
This is the code that does it. Firstly the call, then the functions. It all works fine, except for that clipping...
//=======================================
// draw the circles on the image then return
imgOverlay.image = self.drawCirclesOnImage(fromImage: nil, targetSize: imgOverlay.bounds.size)
and now we have the two functions that draw the circles.
func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: screenWidth, height: screenHeight))
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
func drawCirclesOnImage(fromImage: UIImage? = nil, targetSize: CGSize) -> UIImage? {
if fromImage == nil && targetSize == CGSize.zero {
return nil
}
var tmpimg: UIImage?
if targetSize == CGSize.zero {
tmpimg = fromImage
} else {
tmpimg = getImageWithColor(color: UIColor.clear, size: targetSize)
}
guard let img = tmpimg else {
return nil
}
let imageSize = img.size
let scale: CGFloat = 0
UIGraphicsBeginImageContextWithOptions(imageSize, false, scale)
img.draw(at: CGPoint.zero)
let w = imageSize.width
//let w = screenWidth
let h = imageSize.height
//let h = screenHeight
print("Width 1: \(w) Height 1: \(h)")
let midX = imageSize.width / 2
let midY = imageSize.height / 2
//let midX = screenWidth / 2
//let midY = screenHeight / 2
// red circles - radius in %
let circleRads = [ 0.07, 0.13, 0.17, 0.22, 0.29, 0.36, 0.40, 0.48, 0.60, 0.75 ]
// center "dot" - radius is 1.5%
var circlePath = UIBezierPath(arcCenter: CGPoint(x: midX,y: midY), radius: CGFloat(w * 0.010), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
UIColor.white.setFill()
circlePath.stroke()
circlePath.fill()
lineColor = getLineColor(color: color)
lineColor?.setStroke()
for pct in circleRads {
let rad = w * CGFloat(pct)
circlePath = UIBezierPath(arcCenter: CGPoint(x: midX, y: midY), radius: CGFloat(rad), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
circlePath.lineWidth = 2.5
circlePath.stroke()
}
// draw text time stamp on image
let now = Date()
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let dateString = formatter.string(from: now)
// print(dateString)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attrs = [NSAttributedStringKey.font: UIFont(name: "HelveticaNeue-Thin", size: 26)!, NSAttributedStringKey.paragraphStyle: paragraphStyle]
let string = dateString
string.draw(with: CGRect(x: 12, y: 38, width: 448, height: 448), options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}

SpriteKit draw upside down text

In a playground, I am trying to make a SpriteKit scene with a top left origin going down (ie like a drawing program). Everything works except for text which is flipped. I need to make the text right side up. I am missing something. If I add the translate and scale, the text vanishes.
//: A SpriteKit based Playground
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene
{
static let width = 1000
static let height = 1800
override func didMove(to view: SKView)
{
self.backgroundColor = SKColor.white
let cam = SKCameraNode()
self.camera = cam
cam.yScale = -1
let node = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 500, height: 300))
node.anchorPoint = CGPoint(x: 0, y: 0)
node.position = CGPoint(x: 10, y: 10)
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200))
let img = renderer.image { ctx in
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attrs = [NSAttributedStringKey.font: UIFont(name: "Futura", size: 72)!, NSAttributedStringKey.paragraphStyle: paragraphStyle]
//ctx.cgContext.translateBy(x: 0, y:200)
//ctx.cgContext.scaleBy(x: 0, y: -1)
let string = "Test"
string.draw(with: CGRect(x: 32, y: 32, width: 200, height: 200), options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
}
let tex = SKTexture(image: img)
let zzz = SKSpriteNode(texture: tex)
zzz.anchorPoint = CGPoint(x: 0, y: 0)
zzz.position = CGPoint(x: 10, y: 10)
node.addChild(zzz)
// let z = SKSpriteNode(color: SKColor.red, size: CGSize(width: 200, height: 150))
// z.anchorPoint = CGPoint(x: 0, y: 0)
// z.position = CGPoint(x: 10, y: 10)
// node.addChild(z)
//
// let q = SKSpriteNode(color: SKColor.green, size: CGSize(width: 80, height: 70))
// q.anchorPoint = CGPoint(x: 0, y: 0)
// q.position = CGPoint(x: 10, y: 10)
// z.addChild(q)
self.addChild(node)
self.addChild(cam)
cam.position = CGPoint(x: GameScene.width/2, y: GameScene.height/2)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 480, height: 640))
let scene = GameScene(size: CGSize(width: GameScene.width, height: GameScene.height))
scene.scaleMode = .aspectFit
sceneView.presentScene(scene)
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
Update:
ctx.cgContext.textMatrix = .identity
ctx.cgContext.translateBy(x: 0, y: 200)
ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
now draws the text rightside up, but it is wrapped at 50% of the width
Final Update:
This worked, its rightsize up. The font size is proportional to the scene size (which is a dimension that fits on all iOS devices.
self.camera = cam
cam.yScale = -1
let boxSize = CGSize(width: 500, height: 500)
let node = SKSpriteNode(color: SKColor.yellow, size: boxSize)
node.anchorPoint = CGPoint(x: 0, y: 0)
node.position = CGPoint(x: 10, y: 10)
let renderer = UIGraphicsImageRenderer(size: boxSize)
let img = renderer.image { ctx in
let bounds = CGRect(x: 16, y: 0, width: boxSize.width-32, height: boxSize.height)
let string = "This is a long test with many lines."
let range = NSRange( location: 0, length: string.count)
guard let context = UIGraphicsGetCurrentContext() else { return }
let path = CGMutablePath()
path.addRect(bounds)
let attrString = NSMutableAttributedString(string: string)
attrString.addAttribute(NSAttributedStringKey.font, value: UIFont(name: "Futura", size: 84)!, range: range )
let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil)
CTFrameDraw(frame, context)
}
You should read https://www.raywenderlich.com/153591/core-text-tutorial-ios-making-magazine-app
CoreText inverts (technically, rotates) text in iOS. This is possibly because it is shared with MacOS which uses a different coordinate system. (Though why they couldn't fix this for iOS I have no idea).
Whatever the cause, you aren't doing anything wrong, iOS is doing the wrong thing. The simple solution (as per the linked article) is to simply rotate the text before drawing it.

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
}

Resources