rotationAngle data is lost when creating a CGPDFPage - ios

I have this PDF file with me. I use following code to render the first page of this PDF file.
let page = self.pdfDocument.page(at: 0 )! // get first page
let rotationAngle = page.rotation // this value is 90
let renderedPdfData = renderPDF(page: page.pageRef!)
where renderPDF method is,
func renderPDF(page: CGPDFPage) -> Data {
let pageRect = page.getBoxRect(.mediaBox)
let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
return renderer.pdfData { (context) in
context.beginPage()
context.cgContext.setFillColor(UIColor.white.cgColor)
context.fill(pageRect)
context.cgContext.translateBy(x: 0.0, y: pageRect.size.height)
context.cgContext.scaleBy(x: 1, y: -1)
context.cgContext.drawPDFPage(page)
}
}
Then later I want to create a CGPDFPage back with above renderedPdfData . For that I use following method.
var newPage: CGPDFPage = createPDFPage(pdfData: renderedPdfData)
where createPDFPage method is,
func createPDFPage(pdfData: Data) -> CGPDFPage? {
guard let provider = CGDataProvider(data: pdfData as CFData) else {
return nil
}
guard let document = CGPDFDocument(provider) else {
return nil
}
// page starting at 1. The input PDF data only has 1 page.
return document.page(at: 1)
}
But how should I set newPage's rotation angle data (ie. 90)? Because 'rotationAngle' is a get-only property. Since I haven't set a value for it, it's value is always 0.
let angle = newPage.rotationAngle // value is 0
Is there a way I can create a CGPDFPage with rotation information available with it?

Related

Inserting an Image into a PDF with Swift

I want insert company logo image into pdf of every page. So we are referring following site
Tutorial Link
I want to insert logo right bottom page of pdf, I am unable do to that process.
Please find the tried both way code:
let documentURL = url
// Create a `CGPDFDocument` object for accessing the PDF pages.
// We need these pages in order to draw the original/existing content, because `UIGraphicsBeginPDFContextToFile` creates a file with a clean slate.
// We will have the original file contents in memory as long as the `CGPDFDocument` object is around, even after we have started rewriting the file at the path.
guard let originalDocument = CGPDFDocument(documentURL as CFURL) else {
print("Unable to create read document.")
return
}
// Create a new PDF at the same path to draw the contents into.
UIGraphicsBeginPDFContextToFile(documentURL.path, CGRect.zero, nil)
let image = UIImage(named: "inactive")!
guard let pdfContext = UIGraphicsGetCurrentContext() else {
print("Unable to access PDF Context.")
return
}
let pageSize = UIGraphicsGetPDFContextBounds().size
for pageIndex in 0..<originalDocument.numberOfPages {
// Mark the beginning of the page.
pdfContext.beginPDFPage(nil)
// Pages are numbered starting from 1.
// Access the `CGPDFPage` object with the original contents.
guard let currentPage = originalDocument.page(at: pageIndex + 1) else {
return
}
// Draw the existing page contents.
pdfContext.drawPDFPage(currentPage)
// Save the context state to restore after we are done drawing the image.
pdfContext.saveGState()
// Change the PDF context to match the UIKit coordinate system.
pdfContext.translateBy(x: 0, y: pageSize.height)
pdfContext.scaleBy(x: 1, y: -1)
// Location of the image to be drawn in UIKit coordinates.
let imagePosition = CGRect(x: 100, y: 0, width: 50, height: 50)
image.draw(in: imagePosition)
// UIColor.orange.set()
// UIRectFill(CGRect(x: 100, y: 0, width: 50, height: 50))
// Restoring the context back to its original state.
pdfContext.restoreGState()
// Mark the end of the current page.
pdfContext.endPDFPage()
}
// End the PDF context, essentially closing the PDF document context.
UIGraphicsEndPDFContext()
Updated Code:
let documentURL = url
guard let originalDocument = CGPDFDocument(documentURL as CFURL) else {
print("Unable to create read document.")
return
}
UIGraphicsBeginPDFContextToFile(documentURL.path, CGRect.zero, nil)
// let image = UIImage(named: "verified_kuwy")
let image = UIImage(named: "watermark")
guard let pdfContext = UIGraphicsGetCurrentContext() else {
print("Unable to access PDF Context.")
return
}
let pageSize = UIGraphicsGetPDFContextBounds().size
for pageIndex in 0..<originalDocument.numberOfPages {
pdfContext.beginPDFPage(nil)
guard let currentPage = originalDocument.page(at: pageIndex + 1) else {
return
}
pdfContext.drawPDFPage(currentPage)
pdfContext.saveGState()
pdfContext.translateBy(x: 0, y: pageSize.height)
pdfContext.scaleBy(x: 1, y: -1)
let imagePosition = CGRect(x: pageSize.width - 150, y: pageSize.height - 150, width: 100, height: 100)
image!.draw(in: imagePosition)
pdfContext.restoreGState()
pdfContext.endPDFPage()
}
// End the PDF context, essentially closing the PDF document context.
UIGraphicsEndPDFContext()

How to detect the orientation of a PDF page in Swift with PDFKit IOS

I'm trying to get the orientation property of a PDF document. The purpose is, I would like to add a button widget in a location that depends on the orientation of the PDF document.
For example:
func openPDFDocument() {
if let documentURL = Bundle.main.url(forResource: "PDF document", withExtension: "pdf"),
let document = PDFDocument(url: documentURL),
let page = document.page(at: 0) {
// Set our document to the view, center it, and set a background color
pdfView?.document = document
pdfView?.autoScales = true
pdfView?.backgroundColor = UIColor.lightGray
//I think I should be able to add a code here like:
if page.orientation = Horizontal {
self.insertResetButtonInto(page)
} else {
//do nothing or do something else
}
}
}
This is the function I would like to add in case the document is in Landscape mode:
func insertResetButtonInto(_ page: PDFPage) {
let pageBounds = page.bounds(for: .cropBox)
let resetButtonBounds = CGRect(x: 90, y: pageBounds.size.height - 300, width: 106, height: 32)
let resetButton = PDFAnnotation(bounds: resetButtonBounds, forType: PDFAnnotationSubtype(rawValue: PDFAnnotationSubtype.widget.rawValue), withProperties: nil)
resetButton.widgetFieldType = PDFAnnotationWidgetSubtype(rawValue: PDFAnnotationWidgetSubtype.button.rawValue)
resetButton.widgetControlType = .pushButtonControl
resetButton.caption = "Reset"
page.addAnnotation(resetButton)
// Create PDFActionResetForm action to clear form fields.
let resetFormAction = PDFActionResetForm()
resetFormAction.fieldsIncludedAreCleared = false
resetButton.action = resetFormAction
}
I got the example project from Apple's documentation website. I looked at a previous similar question, however it seems this was in Objective C.
I would appreciate the help in this matter.
There is no direct API to get the orientation from a PDFPage. But you can first get the page size from .mediaBox, then calculate the orientation like below.
let pageSize = page.bounds(for: .mediaBox).size
if pageSize.width > pageSize.height {
//landscape
} else {
//portrait
}
I use another way to get the orientation of my pdf page.
func IsLandscape(page: PDFPage) -> Bool {
let pointZero = pdfView.convert(CGPoint(x: 0, y: 0), from: page)
let pointTen = pdfView.convert(CGPoint(x: 10, y: 10), from: page)
let caculate = pointTen.x - pointZero.x
print("pointZero: \(pointZero), pointTen:\(pointTen)")
if (caculate > 0) {
print("landscape")
return true
}
else {
print("portrait")
return false
}
}

Having trouble with input image with iOS Swift TensorFlowLite Image Classification Model?

I've been trying to add a plant recognition classifier to my app through a Firebase cloud-hosted ML model, and I've gotten close - problem is, I'm pretty sure I'm messing up the input for the image data somewhere along the way. My classifier is churning out nonsense probabilities/results based on this classifier's output, and I've been testing the same classifier through a python script which is giving me accurate results.
The input for the model requires a 224x224 image with 3 channels scaled to 0,1. I've done all this but can't seem to figure out the CGImage through the Camera/ImagePicker. Here is the bit of the code that processes the input for the image:
if let imageData = info[.originalImage] as? UIImage {
DispatchQueue.main.async {
let resizedImage = imageData.scaledImage(with: CGSize(width:224, height:224))
let ciImage = CIImage(image: resizedImage!)
let CGcontext = CIContext(options: nil)
let image : CGImage = CGcontext.createCGImage(ciImage!, from: ciImage!.extent)!
guard let context = CGContext(
data: nil,
width: image.width, height: image.height,
bitsPerComponent: 8, bytesPerRow: image.width * 4,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
) else {
return
}
context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
guard let imageData = context.data else { return }
print("Image data showing as: \(imageData)")
var inputData = Data()
do {
for row in 0 ..< 224 {
for col in 0 ..< 224 {
let offset = 4 * (row * context.width + col)
// (Ignore offset 0, the unused alpha channel)
let red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
let green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
let blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)
// Normalize channel values to [0.0, 1.0].
var normalizedRed = Float32(red) / 255.0
var normalizedGreen = Float32(green) / 255.0
var normalizedBlue = Float32(blue) / 255.0
// Append normalized values to Data object in RGB order.
let elementSize = MemoryLayout.size(ofValue: normalizedRed)
var bytes = [UInt8](repeating: 0, count: elementSize)
memcpy(&bytes, &normalizedRed, elementSize)
inputData.append(&bytes, count: elementSize)
memcpy(&bytes, &normalizedGreen, elementSize)
inputData.append(&bytes, count: elementSize)
memcpy(&bytes, &normalizedBlue, elementSize)
inputData.append(&bytes, count: elementSize)
}
}
print("Successfully added inputData")
self.parent.invokeInterpreter(inputData: inputData)
} catch let error {
print("Failed to add input: \(error)")
}
}
}
Afterwards, I process the inputData with the following:
func invokeInterpreter(inputData: Data) {
do {
var interpreter = try Interpreter(modelPath: ProfileUserData.sharedUserData.modelPath)
var labels: [String] = []
try interpreter.allocateTensors()
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()
let output = try interpreter.output(at: 0)
switch output.dataType {
case .uInt8:
guard let quantization = output.quantizationParameters else {
print("No results returned because the quantization values for the output tensor are nil.")
return
}
let quantizedResults = [UInt8](output.data)
let results = quantizedResults.map {
quantization.scale * Float(Int($0) - quantization.zeroPoint)
}
let sum = results.reduce(0, +)
print("Sum of all dequantized results is: \(sum)")
print("Count of dequantized results is: \(results.indices.count)")
let filename = "plantLabels"
let fileExtension = "csv"
guard let labelPath = Bundle.main.url(forResource: filename, withExtension: fileExtension) else {
print("Labels file not found in bundle. Please check labels file.")
return
}
do {
let contents = try String(contentsOf: labelPath, encoding: .utf8)
labels = contents.components(separatedBy: .newlines)
print("Count of label rows is: \(labels.indices.count)")
} catch {
fatalError("Labels file named \(filename).\(fileExtension) cannot be read. Please add a " +
"valid labels file and try again.")
}
let zippedResults = zip(labels.indices, results)
// Sort the zipped results by confidence value in descending order.
let sortedResults = zippedResults.sorted { $0.1 > $1.1 }.prefix(3)
print("Printing sortedResults: \(sortedResults)")
case .float32:
print("Output tensor data type [Float32] is unsupported for this model.")
default:
print("Output tensor data type \(output.dataType) is unsupported for this model.")
return
}
} catch {
//Error with interpreter
print("Error with running interpreter: \(error.localizedDescription)")
}
}

Developing an ARKit app that leaves text for others to view

I am creating an iOS AR app that sets text in a specific location and leaves it there for others to view. Is there a better way to implement it than what I am doing?
Currently, I have it set so that the text is saved to Firebase and loads it by setting the nodes relative to the camera’s position. I’m wondering if there is a way to save ARAnchors in a fashion similar to what I am doing but is that possible?
My current function for saving the text to the location via a user tapping the screen:
/*
* Variables for saving the user touch
*/
var touchX : Float = 0.0
var touchY : Float = 0.0
var touchZ : Float = 0.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// will be used for getting the text
let textNode = SCNNode()
var writing = SCNText()
// gets the user’s touch upon tapping the screen
guard let touch = touches.first else {return}
let result = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitResult = result.last else {return}
let hitTransform = SCNMatrix4.init(hitResult.worldTransform)
let hitVector = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)
// saves X, Y, and Z coordinates of touch relative to the camera
touchX = hitTransform.m41
touchY = hitTransform.m42
touchZ = hitTransform.m43
// Was thinking of adding the ability to change colors. Probably can skip next seven lines
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: input.text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
// modifies the node’s position and size
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
// last few lines save the info to Firebase
let values = ["X" : touchX, "Y" : touchY, "Z" : touchZ, "Text" : input.text!] as [String : Any]
let childKey = reference.child("Test").childByAutoId().key
if input.text != nil && input.text != "" {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} else {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} // if
} // override func
/*
* Similar to the previous function but used in next function
*/
func placeNode(x: Float, y: Float, z: Float, text: String) -> Void {
let textNode = SCNNode()
var writing = SCNText()
let hitVector = SCNVector3Make(x, y, z)
touchX = x
touchY = y
touchZ = z
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
} // func
/*
* This next function is used in my viewDidLoad to load the data
*/
func handleData() {
reference.child("Test").observeSingleEvent(of: .value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let xCoord = Float(truncating: child.childSnapshot(forPath: "X").value as! NSNumber)
let yCoord = Float(truncating: child.childSnapshot(forPath: "Y").value as! NSNumber)
let zCoord = Float(truncating: child.childSnapshot(forPath: "Z").value as! NSNumber)
let inscription = child.childSnapshot(forPath: "Text").value
self.placeNode(x: xCoord , y: yCoord , z: zCoord , text: inscription as! String)
} // for
} // if
}) // reference
} // func
I have looked into a few things such as ARCore but that looks like it uses Objective-C. I’ve made this app in Swift and I am not sure if I can incorporate ARCore with how I have implemented my current application.
Do I just need to get over it and learn Objective-C? Can I still work with what I have?
I think that ARCore anchors are only available for 24 hours, so that could be a problem.
You probably need to use ARKit2.0's ARWorldMap and save it as data on firebase for others to see the text in the same place, otherwise you are assuming in your code that future users will start their AR session in the exact same position and direction as the person who left the text. You probably need to use core location first to see where in the world the user is.

Why is my custom MKTileOverlayRenderer drawing the same tile in multiple places?

So I'm writing a MapKit-based app which draws an overlay over the map. However, a lot of the overlay drawing is dynamic, such that tile which gets drawn is frequently changing, so I've implemented a custom MKTileOverlay and a custom MKTileOverlayRenderer. The first one to handle the url-scheme for where the tile images are stored, and the second to handle the custom drawMapRect implementation.
The issue I'm running into is that I seem to be drawing the same tile image in multiple locations. Here's a screenshot to help you visualize what I mean: (I know the tiles are upside-down and backwards and I can fix that)
iOS Simulator Screenshot
I've changed certain tile images such that they're a different color and have their tile path included. What you'll notice is that many of the tile images are repeated over different areas.
I've been trying to figure out why that might be happening, so following my code path, the overlay starting point is pretty standard--the ViewController sets the addOverlay() call, which calls the delegates' mapView(rendererForOverlay:) which returns my custom MKTileOverlayRenderer class, which then attempts to call my drawMapRect(mapRect:, zoomScale:, context). It then takes the given map_rect and calculates which tile that map_rect belongs to, calls the custom MKTileOverlay class's loadTileAtPath() and then draws the resulting tile image data. And that's exactly what it looks like my code is doing as well, so I'm not really sure where I'm going wrong. That said, it works perfectly fine if I'm not trying to implement custom drawing and use a default MKTileOverlayRenderer. Unfortunately, that's also the crux of the app so not really a viable solution.
For reference, here's the relevant code from my custom classes:
My custom MKTileOverlay class
class ExploredTileOverlay: MKTileOverlay {
var base_path: String
//var tile_path: String?
let cache: NSCache = NSCache()
var point_buffer: ExploredSegment
var last_tile_path: MKTileOverlayPath?
var tile_buffer: ExploredTiles
init(URLTemplate: String?, startingLocation location: CLLocation, city: City) {
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentsDirectory: AnyObject = paths[0]
self.base_path = documentsDirectory.stringByAppendingPathComponent("/" + city.name + "_tiles")
if (!NSFileManager.defaultManager().fileExistsAtPath(base_path)) {
try! NSFileManager.defaultManager().createDirectoryAtPath(base_path, withIntermediateDirectories: false, attributes: nil)
}
let new_point = MKMapPointForCoordinate(location.coordinate)
self.point_buffer = ExploredSegment(fromPoint: new_point, inCity: city)
self.tile_buffer = ExploredTiles(startingPoint: ExploredPoint(mapPoint: new_point, r: 50))
self.last_tile_path = Array(tile_buffer.edited_tiles.values).last!.path
super.init(URLTemplate: URLTemplate)
}
override func URLForTilePath(path: MKTileOverlayPath) -> NSURL {
let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
let tile_path = base_path + "/" + filled_template
//print("fetching tile " + filled_template)
if !NSFileManager.defaultManager().fileExistsAtPath(tile_path) {
return NSURL(fileURLWithPath: "")
}
return NSURL(fileURLWithPath: tile_path)
}
override func loadTileAtPath(path: MKTileOverlayPath, result: (NSData?, NSError?) -> Void) {
let url = URLForTilePath(path)
let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
let tile_path = base_path + "/" + filled_template
if (url != NSURL(fileURLWithPath: tile_path)) {
print("creating tile at " + String(path))
let img_data: NSData = UIImagePNGRepresentation(UIImage(named: "small")!)!
let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
let tile_path = base_path + "/" + filled_template
img_data.writeToFile(tile_path, atomically: true)
cache.setObject(img_data, forKey: url)
result(img_data, nil)
return
} else if let cachedData = cache.objectForKey(url) as? NSData {
print("using cache for " + String(path))
result(cachedData, nil)
return
} else {
print("loading " + String(path) + " from directory")
let img_data: NSData = UIImagePNGRepresentation(UIImage(contentsOfFile: tile_path)!)!
cache.setObject(img_data, forKey: url)
result(img_data, nil)
return
}
}
My custom MKTileOverlayRenderer class:
class ExploredTileRenderer: MKTileOverlayRenderer {
let tile_overlay: ExploredTileOverlay
var zoom_scale: MKZoomScale?
let cache: NSCache = NSCache()
override init(overlay: MKOverlay) {
self.tile_overlay = overlay as! ExploredTileOverlay
super.init(overlay: overlay)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(saveEditedTiles), name: "com.Coder.Wander.reachedMaxPoints", object: nil)
}
// There's some weird cache-ing thing that requires me to recall it
// whenever I re-draw over the tile, I don't really get it but it works
override func canDrawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale) -> Bool {
self.setNeedsDisplayInMapRect(mapRect, zoomScale: zoomScale)
return true
}
override func drawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale, inContext context: CGContext) {
zoom_scale = zoomScale
let tile_path = self.tilePathForMapRect(mapRect, andZoomScale: zoomScale)
let tile_path_string = stringForTilePath(tile_path)
//print("redrawing tile: " + tile_path_string)
self.tile_overlay.loadTileAtPath(tile_path, result: {
data, error in
if error == nil && data != nil {
if let image = UIImage(data: data!) {
let draw_rect = self.rectForMapRect(mapRect)
CGContextDrawImage(context, draw_rect, image.CGImage)
var path: [(CGMutablePath, CGFloat)]? = nil
self.tile_overlay.point_buffer.readPointsWithBlockAndWait({ points in
let total = self.getPathForPoints(points, zoomScale: zoomScale, offset: MKMapPointMake(0.0, 0.0))
path = total.0
//print("number of points: " + String(path!.count))
})
if ((path != nil) && (path!.count > 0)) {
//print("drawing path")
for segment in path! {
CGContextAddPath(context, segment.0)
CGContextSetBlendMode(context, .Clear)
CGContextSetLineJoin(context, CGLineJoin.Round)
CGContextSetLineCap(context, CGLineCap.Round)
CGContextSetLineWidth(context, segment.1)
CGContextStrokePath(context)
}
}
}
}
})
}
And my helper functions that handle converting between zoomScale, zoomLevel, tile path, and tile coordinates:
func tilePathForMapRect(mapRect: MKMapRect, andZoomScale zoom: MKZoomScale) -> MKTileOverlayPath {
let zoom_level = self.zoomLevelForZoomScale(zoom)
let mercatorPoint = self.mercatorTileOriginForMapRect(mapRect)
//print("mercPt: " + String(mercatorPoint))
let tilex = Int(floor(Double(mercatorPoint.x) * self.worldTileWidthForZoomLevel(zoom_level)))
let tiley = Int(floor(Double(mercatorPoint.y) * self.worldTileWidthForZoomLevel(zoom_level)))
return MKTileOverlayPath(x: tilex, y: tiley, z: zoom_level, contentScaleFactor: UIScreen.mainScreen().scale)
}
func stringForTilePath(path: MKTileOverlayPath) -> String {
return String(format: "%d_%d_%d", path.z, path.x, path.y)
}
func zoomLevelForZoomScale(zoomScale: MKZoomScale) -> Int {
let real_scale = zoomScale / UIScreen.mainScreen().scale
var z = Int((log2(Double(real_scale))+20.0))
z += (Int(UIScreen.mainScreen().scale) - 1)
return z
}
func worldTileWidthForZoomLevel(zoomLevel: Int) -> Double {
return pow(2, Double(zoomLevel))
}
func mercatorTileOriginForMapRect(mapRect: MKMapRect) -> CGPoint {
let map_region: MKCoordinateRegion = MKCoordinateRegionForMapRect(mapRect)
var x : Double = map_region.center.longitude * (M_PI/180.0)
var y : Double = map_region.center.latitude * (M_PI/180.0)
y = log10(tan(y) + 1.0/cos(y))
x = (1.0 + (x/M_PI)) / 2.0
y = (1.0 - (y/M_PI)) / 2.0
return CGPointMake(CGFloat(x), CGFloat(y))
}
This is a pretty obscure error, I think, so haven't had a whole lot of luck finding other people facing similar issues. Anything would help!

Resources