CGWindowListCreateImage yields blurred cgImage when zoomed - macos

I'm developing a magnifying glass like application for mac. My goal is to be able to pinpoint individual pixels when zoomed in. I'm using this code in mouseMoved(with event: NSEvent):
let captureSize = self.frame.size.width / 9 //9 is the scale factor
let screenFrame = (NSScreen.main()?.frame)!
let x = floor(point.x) - floor(captureSize / 2)
let y = screenFrame.size.height - floor(point.y) - floor(captureSize / 2)
let windowID = CGWindowID(self.windowNumber)
cgImageExample = CGWindowListCreateImage(CGRect(x: x, y: y, width: captureSize,
height: captureSize), CGWindowListOption.optionOnScreenBelowWindow, windowID,
CGWindowImageOption.bestResolution)
The creation of the cgImage takes place in the CGWindowListCreateImage method. When I later draw this in an NSView, the result looks like this:
It looks blurred / like some anti-aliasing was applied during the creation of the cgImage. My goal is to get a razor sharp representation of each pixel. Can anyone point me in the right direction?

Ok, I figured it out. It was a matter of setting the interpolation quality to none on the drawing context:
context.interpolationQuality = .none
Result:
On request some more code:
//get the context
guard let context = NSGraphicsContext.current()?.cgContext else { return }
//get the CGImage
let image: CGImage = //pass the result from CGWindowListCreateImage call
//draw
context.draw(image, in: (CGRect of choice))

Related

macOS, how resize window across screens?

I'm trying to programmatically resize macOS windows. Similar to Rectangle.
I have the basic resizing code working, for example, move the window to the right half, and when there is only one screen it works fine, however when I try to resize with two screens (in a vertical layout) the math does not work:
public func moveRight() {
guard let frontmostWindowElement = AccessibilityElement.frontmostWindow()
else {
NSSound.beep()
return
}
let screens = screenDetector.detectScreens(using: frontmostWindowElement)
guard let usableScreens = screens else {
NSSound.beep()
print("Unable to obtain usable screens")
return
}
let screenFrame = usableScreens.currentScreen.adjustedVisibleFrame
print("Visible frame of current screen \(usableScreens.visibleFrameOfCurrentScreen)")
let halfPosition = CGPoint(x: screenFrame.origin.x + screenFrame.width / 2, y: -screenFrame.origin.y)
let halfSize = CGSize(width: screenFrame.width / 2, height: screenFrame.height)
frontmostWindowElement.set(size: halfSize)
frontmostWindowElement.set(position: halfPosition)
frontmostWindowElement.set(size: halfSize)
print("movedWindowRect \(frontmostWindowElement.rectOfElement())")
}
If my window is on the main screen then the resizing works correctly, however if it is a screen below (#3 in the diagram below) then the Y coordinate ends up in the top monitor (#2 or #1 depending on x coordinate) instead of the original one.
The output of the code:
Visible frame of current screen (679.0, -800.0, 1280.0, 775.0)
Raw Frame (679.0, -800.0, 1280.0, 800.0)
movedWindowRect (1319.0, 25.0, 640.0, 775.0)
As far as I can see the problem lies in how Screens and windows are positioned:
I'm trying to understand how should I position the window so that it remains in the correct screen (#3), but having no luck so far, there doesn't seem to be any method to get the absolute screen dimensions to place the screen in the correct origin.
Any idea how can this be solved?
I figured it out, I completely missed one of the functions used in the AccessibilityElement class:
static func normalizeCoordinatesOf(_ rect: CGRect) -> CGRect {
var normalizedRect = rect
let frameOfScreenWithMenuBar = NSScreen.screens[0].frame as CGRect
normalizedRect.origin.y = frameOfScreenWithMenuBar.height - rect.maxY
return normalizedRect
}
Basically, since everything is calculated based on the main screen then there is no other option than to take the coordinates of that one and then offset to get the real position of the screen element.

NSClipView bounds - Problems to find out the scroll position

I try to find out, when the user scrolls to the end of a NSTableView. So of course, the tableview is embedded as usual in a NSClipView an this is embedded in a NSScrollView. There are no more changes except the fact of adding insets to the ClipView (Top: 20, Left/Right: 20, Bottom: 0).
To get notified about the scrolling, I connected an outlet of the NSClipView.
So I use this code:
self.clipView.postsBoundsChangedNotifications = true
NotificationCenter.default.addObserver(self,
selector: #selector(scrollViewDidScroll(notification:)),
name: NSView.boundsDidChangeNotification,
object: self.clipView)
...
#objc func scrollViewDidScroll(notification: Notification) {
print("\(self.clipView.contentInsets.bottom) - \(self.clipView.contentInsets.top)")
print(self.clipView.bounds)
}
If I'm at the top of the ScrollView, the print-results look like this:
0.0 - 20.0
(-20.0, -20.0, 600.0, 556.0)
// x y width height
This seems ok for me. But if I'm at the bottom, it looks like this:
0.0 - 20.0
(-20.0, 595.0, 600.0, 556.0)
In my understanding, the y-value should be 536, not 595. Where is this difference coming from?
I found the solution by my one by observing the different values especially after resizing the window. So:
clipView.documentVisibleRect is that, what you really can see. Of course I cannot use the height value of this part. The origin value tells me about the position in the ScrollView.
So necessary is the height of the total content. Therefore I have to use clipView.documentRect.
Now it's very easy: If the visibleRects y-position + the height of the visible rect is the same as the height of the total clipview, we are at the end. This code works:
#objc func scrollViewDidScroll(notification: Notification) {
let scrollY = self.clipView.documentVisibleRect.origin.y
let visibleHeight = self.clipView.documentVisibleRect.size.height
let totalHeight = self.clipView.documentRect.size.height
if scrollY + visibleHeight >= totalHeight {
print("end")
}
}

swift cancel blur effect slowly

I'm trying to unblur my image slowly, i blurred it with this code
let blur = UIBlurEffect(style: UIBlurEffectStyle.light)
let blurView = UIVisualEffectView(effect: blur)
blurView.frame = imageview.bounds
imageview.addSubview(blurView)
then i tried to unblur my image with this code :
UIView.animate(withDuration: 6.0) {
blurView.alpha = 0.5
}
The problem is that there is no effect going on, the image is loading with 0.5 alpha.
The problem was that my unblur effect was in the viewDidLoad Method, i had to put it in the viewDidApper Method.

How to make button with image and determine CGSize in sprite kit swift

How can I create a button with an image so I can still decided the CGSize myself. Right now I can only do this.
let playNode = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 100, height: 44))
playNode.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
playNode.name = "play"
addChild(playNode)
I would like to replace the red color with an actual image. So far I haven't found a way how to actual create a button with an image AND decide its CGSize. I do know how to create a button just with an image, but I can't determine its CGSize then. Any help would be appreciated !
You can set the size property of your node after you've set the image. Like that:
let playNode = SKSpriteNode(imageNamed: "yourImage")
//Set it after you've set the image.
playNode.size = CGSizeMake(200, 200)
playNode.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
playNode.name = "play"
addChild(playNode)

On OSX, how do I gradient fill a path stroke?

Using the plethora of drawing functions in Cocoa or Quartz it's rather easy to draw paths, and fill them using a gradient. I can't seem to find an acceptable way however, to 'stroke'-draw a path with a line width of a few pixels and fill this stroke using a gradient. How is this done?
Edit: Apparently the question wasn't clear enough. Thanks for the responses so far, but I already figured that out. What I want to do is this:
(source: emle.nl)
The left square is NSGradient drawn in a path followed by a path stroke message. The right is what I want to do; I want to fill the stroke using the gradient.
If you convert the NSBezierPath to a CGPath, you can use the CGContextReplacePathWithStrokedPath() method to retrieve a path that is the outline of the stroked path. Graham Cox's excellent GCDrawKit has a -strokedPath category method on NSBezierPath that will do this for you without needing to drop down to Core Graphics.
Once you have the outlined path, you can fill that path with an NSGradient.
I can't seem to find an acceptable way however, to 'stroke'-draw a path with a line width of a few pixels and fill this stroke using a gradient. How is this done?
[Original answer replaced with the following]
Ah, I see. You want to apply the gradient to the stroke.
To do that, you use a blend mode. I explained how to do this in an answer on another question. Here's the list of steps, adapted to your goal:
Begin a transparency layer.
Stroke the path with any non-transparent color.
Set the blend mode to source in.
Draw the gradient.
End the transparency layer.
According to Peter Hosey's answer I've managed to do a simple gradient curve, which looks like this:
I've done this in drawRect(_:) method of UIView class by writing the code below:
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextBeginTransparencyLayer (context, nil)
let path = createCurvePath()
UIColor.blueColor().setStroke()
path.stroke()
CGContextSetBlendMode(context, .SourceIn)
let colors = [UIColor.blueColor().CGColor, UIColor.redColor().CGColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let colorLocations :[CGFloat] = [0.0, 1.0]
let gradient = CGGradientCreateWithColors(colorSpace, colors, colorLocations)
let startPoint = CGPoint(x: 0.0, y: rect.size.height / 2)
let endPoint = CGPoint(x: rect.size.width, y: rect.size.height / 2)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, CGGradientDrawingOptions.DrawsBeforeStartLocation)
CGContextEndTransparencyLayer(context)
}
Function createCurvePath() returns an UIBezierPath object. I've also set path.lineWidth to 5 points.

Resources