I'm trying to figure out how setColor works. I have the following code:
lazy var imageView:NSImageView = {
let imageView = NSImageView(frame: view.frame)
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
createColorProjection()
view.wantsLayer = true
view.addSubview(imageView)
view.needsDisplay = true
}
func createColorProjection() {
var bitmap = NSBitmapImageRep(cgImage: cgImage!)
var x = 0
while x < bitmap.pixelsWide {
var y = 0
while y < bitmap.pixelsHigh {
//pixels[Point(x: x, y: y)] = (getColor(x: x, y: y, bitmap: bitmap))
bitmap.setColor(NSColor(cgColor: .black)!, atX: x, y: y)
y += 1
}
x += 1
}
let image = createImage(bitmap: bitmap)
imageView.image = image
imageView.needsDisplay = true
}
func createImage(bitmap:NSBitmapImageRep) -> NSImage {
let image = bitmap.cgImage
return NSImage(cgImage: image! , size: CGSize(width: image!.width, height: image!.height))
}
The intention of the code is to change a photo (a rainbow) to be entirely black (I'm just testing with black right now to make sure I understand how it works). However, when I run the program, the unchanged picture of the rainbow is shown, not a black photo.
I am getting these errors:
Unrecognized colorspace number -1 and Unknown number of components for colorspace model -1.
Thanks.
First, you're right: setColor has been broken at least since Catalina. Apple hasn't fixed it probably because it's so slow and inefficient, and nobody ever used it.
Second, docs say NSBitmapImageRep(cgImage: CGImage) produces a read-only bitmap so your code wouldn't have worked even if setColor worked.
As Alexander says, making your own CIFilter is the best way to change a photo's pixels to different colors. Writing and implementing the OpenGL isn't easy, but it's the best.
If you were to add an extension to NSBitmapImageRep like this:
extension NSBitmapImageRep {
func setColorNew(_ color: NSColor, atX x: Int, y: Int) {
guard let data = bitmapData else { return }
let ptr = data + bytesPerRow * y + samplesPerPixel * x
ptr[0] = UInt8(color.redComponent * 255.1)
ptr[1] = UInt8(color.greenComponent * 255.1)
ptr[2] = UInt8(color.blueComponent * 255.1)
if samplesPerPixel > 3 {
ptr[3] = UInt8(color.alphaComponent * 255.1)
}
}
}
Then simply changing an image's pixels could be done like this:
func changePixels(image: NSImage, newColor: NSColor) -> NSImage {
guard let imgData = image.tiffRepresentation,
let bitmap = NSBitmapImageRep(data: imgData),
let color = newColor.usingColorSpace(.deviceRGB)
else { return image }
var y = 0
while y < bitmap.pixelsHigh {
var x = 0
while x < bitmap.pixelsWide {
bitmap.setColorNew(color, atX: x, y: y)
x += 1
}
y += 1
}
let newImage = NSImage(size: image.size)
newImage.addRepresentation(bitmap)
return newImage
}
I am trying to create pdf from scrollview. The pdf is created successfully. But there is no padding on the top and bottom of the pdf file. i want the padding in each page of the pdf file.can anyone help me to resolve it? if the pdf file is consists on two or more page so parts are not visible as there is no padding
class CreatePDF {
func PDFWithScrollView(scrollview: UIScrollView) -> NSData {
let pageDimensions = scrollview.bounds
let pageSize = pageDimensions.size
let totalSize = scrollview.contentSize
let numberOfPagesThatFitHorizontally = Int(ceil(totalSize.width / pageSize.width))
// let numberOfPagesThatFitVertically = 1
let numberOfPagesThatFitVertically = Int(ceil(totalSize.height / pageSize.height))
let outputData = NSMutableData()
UIGraphicsBeginPDFContextToData(outputData, pageDimensions, nil)
let savedContentOffset = scrollview.contentOffset
let savedContentInset = scrollview.contentInset
scrollview.contentInset = UIEdgeInsets.zero
if let context = UIGraphicsGetCurrentContext()
{
for indexHorizontal in 0 ..< numberOfPagesThatFitHorizontally
{
for indexVertical in 0 ..< numberOfPagesThatFitVertically
{
UIGraphicsBeginPDFPage()
let offsetHorizontal = CGFloat(indexHorizontal) * pageSize.width
let offsetVertical = CGFloat(indexVertical) * pageSize.height
scrollview.contentOffset = CGPoint(x: offsetHorizontal, y: offsetVertical)
context.translateBy(x: -offsetHorizontal, y: -offsetVertical)
scrollview.layer.render(in: context)
}
}
}
UIGraphicsEndPDFContext()
scrollview.contentInset = savedContentInset
scrollview.contentOffset = savedContentOffset
return outputData
}
}
let snapshotter = CreatePDF()
let data = snapshotter.PDFWithScrollView(scrollview: scrollView)
I'm trying to make something simple to test Vision Framework on MacOS.
I tried to modify code from this tutorial to use a single image from screenshot instead of camera feed.
https://www.appcoda.com/vision-framework-introduction/
However, I get this error:
Error Domain=com.apple.vis Code=3 "Failed to create image for processing due to invalid requested buffer dimensions"
Is it because screenshot image doesn't fit certain specification? Do I need to preprocess the file?
If so, how can I process it in order to fit the dimensions?
My testing code is below.
Thanks!
import Cocoa
import Vision
class ViewController: NSViewController {
var requests = [VNRequest]()
func start() {
let textRequest = VNDetectTextRectanglesRequest(completionHandler: self.detectTextHandler)
textRequest.reportCharacterBoxes = true
self.requests = [textRequest]
let url = URL(fileURLWithPath:NSString(string:"~/Screenshot.png").expandingTildeInPath)
let imageRequestHandler = VNImageRequestHandler(url:url)
do {
try imageRequestHandler.perform(self.requests)
} catch {
print(error)
}
}
func detectTextHandler(request: VNRequest, error: Error?) {
guard let observations = request.results else {
print("no result")
return
}
let result = observations.map({$0 as? VNTextObservation})
DispatchQueue.main.async() {
for region in result {
guard let rg = region else {
continue
}
self.highlightWord(box: rg)
if let boxes = region?.characterBoxes {
for characterBox in boxes {
self.highlightLetters(box: characterBox)
}
}
}
}
}
func highlightWord(box: VNTextObservation) {
guard let boxes = box.characterBoxes else {
return
}
var maxX: CGFloat = 9999.0
var minX: CGFloat = 0.0
var maxY: CGFloat = 9999.0
var minY: CGFloat = 0.0
for char in boxes {
if char.bottomLeft.x < maxX {
maxX = char.bottomLeft.x
}
if char.bottomRight.x > minX {
minX = char.bottomRight.x
}
if char.bottomRight.y < maxY {
maxY = char.bottomRight.y
}
if char.topRight.y > minY {
minY = char.topRight.y
}
}
let xCord = maxX
let yCord = (1 - minY)
let width = (minX - maxX)
let height = (minY - maxY)
let frame = CGRect(x: xCord, y: yCord, width: width, height: height)
print("Word: \(frame)")
}
func highlightLetters(box: VNRectangleObservation) {
let xCord = box.topLeft.x
let yCord = (1 - box.topLeft.y)
let width = (box.topRight.x - box.bottomLeft.x)
let height = (box.topLeft.y - box.bottomLeft.y)
let frame = CGRect(x: xCord, y: yCord, width: width, height: height)
print("Letter: \(frame)")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
start()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
On my users profile page, there is a scrollview users slide across to see more photos. Everything works with adding the photos but the problem is that each image added to the scrollviews subview, is added twice. This is the code I am using to populate the scrollview. I currently have an Imageview embed inside the scrollview on storyboard and use that as the default if user has no photos. The at the bottom should show the bug. I have tried using a combination of clipstobounds, scaleaspectfit, and setting background color to clear but nothing stops from showing an additional image in the background. Any ideas would be appreciated. On a side note, the bug is only noticed on the iphone 8 plus
var pages: Int = 1
var numPhotos: CGFloat = 1
var position: CGFloat = 1
let scrollWidth: CGFloat = profileImage.frame.width
let scrollHeight: CGFloat = profileImage.frame.height
let scrollY: CGFloat = profileImage.frame.origin.y
if let profileImg = user.profileImage {
profileResource = ImageResource(downloadURL: URL(string: profileImg)!, cacheKey: profileImg)
profileImage.kf.setImage(with: profileResource)
} else {
profileImage.image = #imageLiteral(resourceName: "requests_icon")
}
nameLbl.text = user.fullName
influenceLbl.text = "\(user.score)"
followersLbl.text = "\(user.followers)"
followingLbl.text = "\(user.following)"
if let image1 = user.photo1 {
let imageOne = UIImageView(frame: CGRect(x: scrollWidth * position, y: scrollY, width: scrollWidth, height: scrollHeight))
imageOne.contentMode = .scaleAspectFit
photo1Resource = ImageResource(downloadURL: URL(string: image1)!, cacheKey: image1)
imageOne.kf.setImage(with: photo1Resource)
pages = pages + 1
numPhotos = numPhotos + 1
position = position + 1
scrollView.addSubview(imageOne)
} else {
}
if let image2 = user.photo2 {
let imageTwo = UIImageView(frame: CGRect(x: scrollWidth * position, y: scrollY, width: scrollWidth, height: scrollHeight))
imageTwo.contentMode = .scaleAspectFit
photo2Resource = ImageResource(downloadURL: URL(string: image2)!, cacheKey: image2)
imageTwo.kf.setImage(with: photo2Resource)
pages = pages + 1
numPhotos = numPhotos + 1
position = position + 1
scrollView.addSubview(imageTwo)
} else {
}
if let image3 = user.photo3 {
let imageThree = UIImageView(frame: CGRect(x: scrollWidth * position, y: scrollY, width: scrollWidth, height: scrollHeight))
imageThree.contentMode = .scaleAspectFit
photo3Resource = ImageResource(downloadURL: URL(string: image3)!, cacheKey: image3)
imageThree.kf.setImage(with: photo3Resource)
pages = pages + 1
numPhotos = numPhotos + 1
position = position + 1
scrollView.addSubview(imageThree)
} else {
}
scrollView.contentSize = CGSize(width: profileImage.frame.width * numPhotos, height: scrollHeight)
scrollView.delegate = self
scrollView.contentMode = .scaleAspectFit
scrollView.clipsToBounds = true
pageControl.numberOfPages = pages
pageControl.currentPage = 0
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){
let pageWidth:CGFloat = scrollView.frame.width
let currentPage:CGFloat = floor((scrollView.contentOffset.x-pageWidth/2)/pageWidth) + 1
self.pageControl.currentPage = Int(currentPage)
}
The app I'm working on uses collection view cells to display data to the user. I want the user to be able to share the data that's contained in the cells, but there are usually too many cells to try to re-size and fit onto a single iPhone-screen-sized window and get a screenshot.
So the problem I'm having is trying to get an image of all the cells in a collection view, both on-screen and off-screen. I'm aware that off-screen cells don't actually exist, but I'd be interested in a way to kind of fake an image and draw in the data (if that's possible in swift).
In short, is there a way to programmatically create an image from a collection view and the cells it contains, both on and off screen with Swift?
Update
If memory is not a concern :
mutating func screenshot(scale: CGFloat) -> UIImage {
let currentSize = frame.size
let currentOffset = contentOffset // temp store current offset
frame.size = contentSize
setContentOffset(CGPointZero, animated: false)
// it might need a delay here to allow loading data.
let rect = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
self.drawViewHierarchyInRect(rect, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
frame.size = currentSize
setContentOffset(currentOffset, animated: false)
return resizeUIImage(image, scale: scale)
}
This works for me:
github link -> contains up to date code
getScreenshotRects creates the offsets to which to scroll and the frames to capture. (naming is not perfect)
takeScreenshotAtPoint scrolls to the point, sets a delay to allow a redraw, takes the screenshot and returns this via completion handler.
stitchImages creates a rect with the same size as the content and draws all images in them.
makeScreenshots uses the didSet on a nested array of UIImage and a counter to create all images while also waiting for completion. When this is done it fires it own completion handler.
Basic parts :
scroll collectionview -> works
take screenshot with delay for a redraw -> works
crop images that are overlapping -> apparently not needed
stitch all images -> works
basic math -> works
maybe freeze screen or hide when all this is happening (this is not in my answer)
Code :
protocol ScrollViewImager {
var bounds : CGRect { get }
var contentSize : CGSize { get }
var contentOffset : CGPoint { get }
func setContentOffset(contentOffset: CGPoint, animated: Bool)
func drawViewHierarchyInRect(rect: CGRect, afterScreenUpdates: Bool) -> Bool
}
extension ScrollViewImager {
func screenshot(completion: (screenshot: UIImage) -> Void) {
let pointsAndFrames = getScreenshotRects()
let points = pointsAndFrames.points
let frames = pointsAndFrames.frames
makeScreenshots(points, frames: frames) { (screenshots) -> Void in
let stitched = self.stitchImages(images: screenshots, finalSize: self.contentSize)
completion(screenshot: stitched!)
}
}
private func makeScreenshots(points:[[CGPoint]], frames : [[CGRect]],completion: (screenshots: [[UIImage]]) -> Void) {
var counter : Int = 0
var images : [[UIImage]] = [] {
didSet {
if counter < points.count {
makeScreenshotRow(points[counter], frames : frames[counter]) { (screenshot) -> Void in
counter += 1
images.append(screenshot)
}
} else {
completion(screenshots: images)
}
}
}
makeScreenshotRow(points[counter], frames : frames[counter]) { (screenshot) -> Void in
counter += 1
images.append(screenshot)
}
}
private func makeScreenshotRow(points:[CGPoint], frames : [CGRect],completion: (screenshots: [UIImage]) -> Void) {
var counter : Int = 0
var images : [UIImage] = [] {
didSet {
if counter < points.count {
takeScreenshotAtPoint(point: points[counter]) { (screenshot) -> Void in
counter += 1
images.append(screenshot)
}
} else {
completion(screenshots: images)
}
}
}
takeScreenshotAtPoint(point: points[counter]) { (screenshot) -> Void in
counter += 1
images.append(screenshot)
}
}
private func getScreenshotRects() -> (points:[[CGPoint]], frames:[[CGRect]]) {
let vanillaBounds = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
let xPartial = contentSize.width % bounds.size.width
let yPartial = contentSize.height % bounds.size.height
let xSlices = Int((contentSize.width - xPartial) / bounds.size.width)
let ySlices = Int((contentSize.height - yPartial) / bounds.size.height)
var currentOffset = CGPoint(x: 0, y: 0)
var offsets : [[CGPoint]] = []
var rects : [[CGRect]] = []
var xSlicesWithPartial : Int = xSlices
if xPartial > 0 {
xSlicesWithPartial += 1
}
var ySlicesWithPartial : Int = ySlices
if yPartial > 0 {
ySlicesWithPartial += 1
}
for y in 0..<ySlicesWithPartial {
var offsetRow : [CGPoint] = []
var rectRow : [CGRect] = []
currentOffset.x = 0
for x in 0..<xSlicesWithPartial {
if y == ySlices && x == xSlices {
let rect = CGRect(x: bounds.width - xPartial, y: bounds.height - yPartial, width: xPartial, height: yPartial)
rectRow.append(rect)
} else if y == ySlices {
let rect = CGRect(x: 0, y: bounds.height - yPartial, width: bounds.width, height: yPartial)
rectRow.append(rect)
} else if x == xSlices {
let rect = CGRect(x: bounds.width - xPartial, y: 0, width: xPartial, height: bounds.height)
rectRow.append(rect)
} else {
rectRow.append(vanillaBounds)
}
offsetRow.append(currentOffset)
if x == xSlices {
currentOffset.x = contentSize.width - bounds.size.width
} else {
currentOffset.x = currentOffset.x + bounds.size.width
}
}
if y == ySlices {
currentOffset.y = contentSize.height - bounds.size.height
} else {
currentOffset.y = currentOffset.y + bounds.size.height
}
offsets.append(offsetRow)
rects.append(rectRow)
}
return (points:offsets, frames:rects)
}
private func takeScreenshotAtPoint(point point_I: CGPoint, completion: (screenshot: UIImage) -> Void) {
let rect = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
let currentOffset = contentOffset
setContentOffset(point_I, animated: false)
delay(0.001) {
UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
self.drawViewHierarchyInRect(rect, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setContentOffset(currentOffset, animated: false)
completion(screenshot: image)
}
}
private func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
private func crop(image image_I:UIImage, toRect rect:CGRect) -> UIImage? {
guard let imageRef: CGImageRef = CGImageCreateWithImageInRect(image_I.CGImage, rect) else {
return nil
}
return UIImage(CGImage:imageRef)
}
private func stitchImages(images images_I: [[UIImage]], finalSize : CGSize) -> UIImage? {
let finalRect = CGRect(x: 0, y: 0, width: finalSize.width, height: finalSize.height)
guard images_I.count > 0 else {
return nil
}
UIGraphicsBeginImageContext(finalRect.size)
var offsetY : CGFloat = 0
for imageRow in images_I {
var offsetX : CGFloat = 0
for image in imageRow {
let width = image.size.width
let height = image.size.height
let rect = CGRect(x: offsetX, y: offsetY, width: width, height: height)
image.drawInRect(rect)
offsetX += width
}
offsetX = 0
if let firstimage = imageRow.first {
offsetY += firstimage.size.height
} // maybe add error handling here
}
let stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return stitchedImages
}
}
extension UIScrollView : ScrollViewImager {
}
Draw the bitmap data of your UICollectionView into a UIImage using UIKit graphics functions. Then you'll have a UIImage that you could save to disk or do whatever you need with it. Something like this should work:
// your collection view
#IBOutlet weak var myCollectionView: UICollectionView!
//...
let image: UIImage!
// draw your UICollectionView into a UIImage
UIGraphicsBeginImageContext(myCollectionView.frame.size)
myCollectionView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
For swift 4 to make screenshot of UICollectionView
func makeScreenShotToShare()-> UIImage{
UIGraphicsBeginImageContextWithOptions(CGSize.init(width: self.colHistory.contentSize.width, height: self.colHistory.contentSize.height + 84.0), false, 0)
colHistory.scrollToItem(at: IndexPath.init(row: 0, section: 0), at: .top, animated: false)
colHistory.layer.render(in: UIGraphicsGetCurrentContext()!)
let row = colHistory.numberOfItems(inSection: 0)
let numberofRowthatShowinscreen = self.colHistory.size.height / (self.arrHistoryData.count == 1 ? 130 : 220)
let scrollCount = row / Int(numberofRowthatShowinscreen)
for i in 0..<scrollCount {
colHistory.scrollToItem(at: IndexPath.init(row: (i+1)*Int(numberofRowthatShowinscreen), section: 0), at: .top, animated: false)
colHistory.layer.render(in: UIGraphicsGetCurrentContext()!)
}
let image:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext();
return image
}