Using Swift 4, I'm drawing a NSMutableAttributedString using CGContext to a PDF file. The string contains an attachment with an UIImage. But the image gets lost when using the UIGraphicsGetCurrentContext.
Creation of NSMutableAttributedString with attachment:
let aString = NSMutableAttributedString()
let attachment = NSTextAttachment()
attachment.image = UIImage(named: "miniatureIcon")!
aString.append( attachmentString )
Creation of the context.
UIGraphicsBeginPDFPageWithInfo....
print("attributedString \(attributedString)") // attachement still here
// Get current graphics context
let currentContext = UIGraphicsGetCurrentContext()!
// Create a core text frame setter
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
// Save context pre manipulation
currentContext.saveGState()
// Reset text matrix, so no text scaling is affected
currentContext.textMatrix = CGAffineTransform.identity
// Create the frame and a rectangular path of the text frame
let frameRect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
let framePath = UIBezierPath(rect: frameRect).cgPath
// Create core text frame for the given attributed string
// The whole text should fit the frame, as calculations were already done
let frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedString.length), framePath, nil)
// Translate by 100% graphics height up and reverse scale, as core text does draw from bottom up and not from top down
currentContext.translateBy(x: 0, y: UIGraphicsGetPDFContextBounds().height)
currentContext.scaleBy(x: 1.0, y: -1.0)
// Translate context to actual position of text
currentContext.translateBy(x: frame.minX, y: UIGraphicsGetPDFContextBounds().height - frame.maxY)
// Draw text into context
CTFrameDraw(frameRef, currentContext)
// Restore context to pre manipulation
currentContext.restoreGState()
UIGraphicsEndPDFContext()
Since this is the last time the text gets manipulated before it gets drawn on the PDF file and since I have debugged it down to this part where the attachment is available for the last time, I assume that the error is as part of the CGContext.
Other attributes like bold or center remain on the final file.
But the attachment is gone.
What am I missing? Help is very appreciated.
Related
I'm capturing the output of a playing video using AVPlayerItemVideoOutput.copyPixelBuffer
I'm able to convert the pixel buffer into a CIImage, then render it back into a pixel buffer again, and then an AVAssetWriter writes the buffer stream out to a new movie clip successfully.
The reason I'm converting to CIImage is I want to do some manipulation of each frame. (So far I don't understand how to manipulate pixel buffers directly).
In this case I want to overlay a "scribble" style drawing that the user does with their finger. While the video plays, they can draw over it. I'm capturing this drawing successfully into a CAShapeLayer.
The code below outputs just the overlay CAShapeLayer successfully. When I try to reincorporate the original frame by uncommenting the lines shown, the entire process bogs down drastically and drops from 60fps to an unstable 10fps or so on an iPhone 12. I get stable 60fps in all cases except when I uncomment that code.
What's the best way to incorporate the shape layer into this stream of pixel buffers in 60fps "real time"?
Note: some of this code is not finalized -- setting bounds correctly, etc. However this is not related to my question and I'm aware that has to be done. The rotation/translation are there to orient the shape layer -- this all works for now.
func addShapesToBuffer(buffer: CVPixelBuffer, shapeLayer: CAShapeLayer) -> CVPixelBuffer? {
let coreImage = CoreImage.CIImage.init(cvImageBuffer: buffer)
let newBuffer = getBuffer(from: coreImage)
CVPixelBufferLockBaseAddress(newBuffer!, [])
let rect = CGRect(origin: CGPoint.zero, size: CGSize(width: 800, height: 390))
shapeLayer.shouldRasterize = true
shapeLayer.rasterizationScale = UIScreen.main.scale
shapeLayer.backgroundColor = UIColor.clear.cgColor
let renderer = UIGraphicsImageRenderer(size: rect.size)
let uiImageDrawing = renderer.image {
context in
// let videoImage = UIImage(ciImage: coreImage)
// videoImage.draw(in: rect)
let cgContext = context.cgContext
cgContext.rotate(by: deg2rad(-90))
cgContext.translateBy(x: -390, y: 0)
return shapeLayer.render(in: cgContext)
}
let ciContext = CIContext()
let newImage = CIImage(cgImage: uiImageDrawing.cgImage!)
ciContext.render(_: newImage, to: newBuffer!)
CVPixelBufferUnlockBaseAddress(newBuffer!, [])
return newBuffer
}
I have looked at every example I can find in previous questions and nothing works. I am merging two images by copying a cropped image to pasteboard and then pasting it in another image view. That works fine but when I try to save it the image is always squished(may be a scale issue but it seems only the vertical size getting distorted.
This is the result of combining the images before trying to save it
This is after saving the image to the photos album
The image will not stay in the translated location and it's distorted.
Here is the code when I save the image
let fullSize:CGSize = photoView.image!.size
let newSize:CGSize = fullSize
let scale:CGFloat = newSize.height/fullSize.height
let offset:CGFloat = (newSize.width - fullSize.width*scale)/2
let offsetRect:CGRect = CGRect.init(x: offset, y: 0, width: newSize.width - offset*2, height: newSize.height)
UIGraphicsBeginImageContext(newSize);
self.photoView.image!.draw(in: offsetRect)
self.copiedView.image!.draw(in: CGRect.init(x: 0, y: 0, width: photoView.image!.size.width, height:photoView.image!.size.height))
let combImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext();
//photoView.image = combImage
UIImageWriteToSavedPhotosAlbum(combImage, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
Any help is appreciated
I have searched everywhere but can't find an answer. The closest I have been able to find is
func saveAnnotations_forever() {
//let documentAttributes = pdfDocument?.documentAttributes
//let attachmentData = pdfDocument?.dataRepresentation()
let currentPage = pdfView.currentPage
let page_index = pdfDocument?.index(for: currentPage!)
let documentURL = self.pdfDocument?.documentURL// URL to your PDF document.
// Create a `PDFDocument` object using the URL.
let pdfDocument = PDFDocument(url: documentURL!)!
// `page` is of type `PDFPage`.
let page = self.pdfDocument!.page(at: page_index!)!
// Extract the crop box of the PDF. We need this to create an appropriate graphics context.
let bounds = page.bounds(for: .mediaBox)
// Create a `UIGraphicsImageRenderer` to use for drawing an image.
let renderer = UIGraphicsImageRenderer(bounds: bounds, format: UIGraphicsImageRendererFormat.default())
// This method returns an image and takes a block in which you can perform any kind of drawing.
let image = renderer.image { (context) in
// We transform the CTM to match the PDF's coordinate system, but only long enough to draw the page.
context.cgContext.saveGState()
context.cgContext.translateBy(x: 0, y: bounds.height)
context.cgContext.concatenate(CGAffineTransform.init(scaleX: 1, y: -1))
page.draw(with: .mediaBox, to: context.cgContext)
context.cgContext.restoreGState()
and so on to render an annotation and a PDF page together as an image. However, I cannot search through an annotated page's text (since it was rendered as an image) and I cannot erase my previous annotations once saved. iOS's Book's annotations works very well- I want to achieve that, but how?
Thanks in advance.
If you are trying to save an annotation to a PDF, maybe try this code:
// add annotation to the current page
let rect = NSRect(x: 30, y: 30, width: 30, height: 30)
let annotation = PDFAnnotation(bounds: rect, forType: .text, withProperties: nil)
pdfView.currentPage?.addAnnotation(annotation)
// save the PDF to file
if let doc = self.document,
let url = self.document?.documentURL {
doc.write(to: url)
}
You can use the PDFAnnotation documentation to further customize the annotation.
I found this extension online, it allows me to have images adhere to aspect fit/fill even when drawn inside dynamically growing/shrinking image views (currently when image is saved to camera roll after my draw function the image reverts to "scale fill" regardless of what the content mode of the image view is. I suspect the reasoning for this is because I have it drawing the image to size/bounds of the image view, but since the image view is dynamic, i don't see any way around this without using this extension):
// MARK: - Image Scaling.
extension UIImage {
/// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio.
/// Switch MIN to MAX for aspect fill instead of fit.
///
/// - parameter newSize: newSize the size of the bounds the image must fit within.
///
/// - returns: a new scaled image.
func scaleImageToSize(newSize: CGSize) -> UIImage {
var scaledImageRect = CGRect.zero
let aspectWidth = newSize.width/size.width
let aspectheight = newSize.height/size.height
let aspectRatio = max(aspectWidth, aspectheight)
scaledImageRect.size.width = size.width * aspectRatio;
scaledImageRect.size.height = size.height * aspectRatio;
scaledImageRect.origin.x = (newSize.width - scaledImageRect.size.width) / 2.0;
scaledImageRect.origin.y = (newSize.height - scaledImageRect.size.height) / 2.0;
UIGraphicsBeginImageContext(newSize)
draw(in: scaledImageRect)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage!
}
}
This is my current function I'm using for drawing the image on screen to be able to save it to camera roll (this function combines two images, a frame and an image from camera roll:
func drawImagesAndText() {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: imageView.bounds.size.width, height: imageView.bounds.size.height))
img = renderer.image { ctx in
// var newSize = currentImage.scaleImageToSize
let bgImage = currentImage
bgImage?.draw(in: CGRect(x: 0, y: 0, width: imageView.bounds.size.width, height: imageView.bounds.size.height))
frames = UIImage(named: framesAr)
frames?.draw(in: CGRect(x: 0, y: 0, width: imageView.bounds.size.width, height: imageView.bounds.size.height))
}
}
All the tutorials I've found on how to use extensions don't cover how to pass in and out variables like this one requires. Any insight would be greatly appreciated.
I understand that you don't know how to use the extension, is that correct? Since it just adds a function to every UIImage, you can simply call it on your image like this: currentImage.scaleImageToSize(newSize: someSize) and pass the size you want the image to fit into.
Dorian Roy was telling me to use that call in place of using just "currentImage", and that's what worked!
(I commented on his initial answer saying I was having issues because I was trying to use the return value from the extension itself in place of "currentImage")
I use this code to add text from textview to Image
func textToImage(drawText: NSString, inImage: UIImage, atPoint: CGPoint) -> UIImage{
// Setup the font specific variables
var textColor = UIColor.whiteColor()
var textFont = UIFont(name: "Helvetica Bold", size: 12)!
// Setup the image context using the passed image
let scale = UIScreen.mainScreen().scale
UIGraphicsBeginImageContextWithOptions(inImage.size, false, scale)
// Setup the font attributes that will be later used to dictate how the text should be drawn
let textFontAttributes = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
]
// Put the image into a rectangle as large as the original image
inImage.drawInRect(CGRectMake(0, 0, inImage.size.width, inImage.size.height))
// Create a point within the space that is as bit as the image
var rect = CGRectMake(atPoint.x, atPoint.y, inImage.size.width, inImage.size.height)
// Draw the text into an image
drawText.drawInRect(rect, withAttributes: textFontAttributes)
// Create a new image out of the images we have created
var newImage = UIGraphicsGetImageFromCurrentImageContext()
// End the context now that we have the image we need
UIGraphicsEndImageContext()
//Pass the image back up to the caller
return newImage
}
I download image from server, and add text for this. I already resize the image before I add text for that to fit the device i am using, in this case is Iphone 6s, 375*667
When I add text from textView which is red in the picture to my image, it isn't in the right position. It is always pushed to the right without reason.
Does anyone know about this problem ?
Thank you very much !!!
Imagine you have a square. If you set both the X and Y to 0, it won't place the middle of the square to the x: 0 and y: 0. It will place the top left of that square to that coordinate. Your issue is that when you call your function, it's placing the top left of the text to the point of where you tapped. You have to minus half of the texts width for the X and half of the texts height for the Y. It should look something like this:
func textToImage(drawText: "Hello, inImage: ***YOUR_IMAGE***, atPoint: CGPoint(x: point.x - (text.frame.width / 2), y: point.y - (text.frame.height / 2))