i'm done for tonight
This commit is contained in:
parent
c7596a7b85
commit
bb23ab3ec2
@ -2,6 +2,7 @@ package controllers
|
|||||||
|
|
||||||
import controllers.windows.WindowsOSProxy
|
import controllers.windows.WindowsOSProxy
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
|
import java.awt.image.MultiResolutionImage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for a vision controller that can take screenshots.
|
* Interface for a vision controller that can take screenshots.
|
||||||
@ -22,7 +23,7 @@ interface VisionController : Automaton, OSProxy {
|
|||||||
* @return A BufferedImage containing the screenshot of the foreground
|
* @return A BufferedImage containing the screenshot of the foreground
|
||||||
* window.
|
* window.
|
||||||
*/
|
*/
|
||||||
fun takeScreenshotOfForeground(): BufferedImage
|
fun takeScreenshotOfForeground(): MultiResolutionImage
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,9 +45,9 @@ class ConcreteVisionController : VisionController, WindowsOSProxy, RobotAutomato
|
|||||||
* @return A [BufferedImage] containing a screenshot of the foreground window
|
* @return A [BufferedImage] containing a screenshot of the foreground window
|
||||||
* scaled to account for the screen DPI.
|
* scaled to account for the screen DPI.
|
||||||
*/
|
*/
|
||||||
override fun takeScreenshotOfForeground(): BufferedImage {
|
override fun takeScreenshotOfForeground(): MultiResolutionImage {
|
||||||
val rect = getScaledForegroundWindowBounds()
|
val rect = getScaledForegroundWindowBounds()
|
||||||
return robot.createScreenCapture(rect)
|
return robot.createMultiResolutionScreenCapture(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package controllers.windows
|
package controllers.windows
|
||||||
|
|
||||||
import com.sun.jna.Native
|
import com.sun.jna.Native
|
||||||
|
import com.sun.jna.NativeLong
|
||||||
import com.sun.jna.Pointer
|
import com.sun.jna.Pointer
|
||||||
import com.sun.jna.win32.StdCallLibrary
|
import com.sun.jna.win32.StdCallLibrary
|
||||||
import controllers.windows.WindowsOSProxy.WinRect
|
import controllers.windows.WindowsOSProxy.WinRect
|
||||||
@ -129,6 +130,40 @@ interface User32 : StdCallLibrary {
|
|||||||
*/
|
*/
|
||||||
fun SetForegroundWindow(hWnd: Pointer?): Boolean
|
fun SetForegroundWindow(hWnd: Pointer?): Boolean
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a logical point to a physical point for a window based on the DPI awareness per monitor.
|
||||||
|
*
|
||||||
|
* This calls the Win32 API LogicalToPhysicalPointForPerMonitorDPI function to convert a logical point
|
||||||
|
* (x, y coordinates) relative to a window into a physical point based on the DPI awareness context of
|
||||||
|
* the given window. This accounts for different DPI scaling settings on each monitor.
|
||||||
|
*
|
||||||
|
* The logical point is specified in the [lpPoint] struct as:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* struct POINT {
|
||||||
|
* LONG x;
|
||||||
|
* LONG y;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* On return, [lpPoint] will contain the converted physical coordinates.
|
||||||
|
*
|
||||||
|
* @param hWnd The window handle (HWND) to use the DPI context of for conversion.
|
||||||
|
* @param lpPoint Pointer to a POINT struct containing the logical coordinates.
|
||||||
|
* This will be updated with the converted physical coordinates.
|
||||||
|
*/
|
||||||
|
fun LogicalToPhysicalPointForPerMonitorDPI(hWnd: Pointer?, lpPoint: Pointer?): Boolean
|
||||||
|
|
||||||
|
fun PhysicalToLogicalPointForPerMonitorDPI(hWnd: Pointer?, lpPoint: Pointer?): Boolean
|
||||||
|
|
||||||
|
fun DwmGetWindowAttribute(
|
||||||
|
hwnd: Pointer?,
|
||||||
|
dwAttribute: Int,
|
||||||
|
pvAttribute: Pointer?,
|
||||||
|
cbAttribute: Int
|
||||||
|
): NativeLong
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the coordinates of a window on the screen.
|
* Retrieves the coordinates of a window on the screen.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -74,12 +74,28 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mirror of the Windows POINT struct used for window/physical coordinates
|
||||||
|
*/
|
||||||
|
@FieldOrder("x", "y")
|
||||||
|
class WinPoint() : Structure() {
|
||||||
|
constructor(x: Int, y: Int) : this() {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var x: Int = 0
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var y: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirror of the Windows RECT struct used for window coordinates.
|
* Mirror of the Windows RECT struct used for window coordinates.
|
||||||
*/
|
*/
|
||||||
@FieldOrder("left", "top", "right", "bottom")
|
@FieldOrder("left", "top", "right", "bottom")
|
||||||
public class WinRect : Structure() {
|
class WinRect : Structure() {
|
||||||
|
|
||||||
/** The x coordinate of the left edge */
|
/** The x coordinate of the left edge */
|
||||||
@JvmField
|
@JvmField
|
||||||
@ -98,6 +114,64 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
var bottom: Int = 0
|
var bottom: Int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class DwmWindowAttribute {
|
||||||
|
|
||||||
|
DWMWA_NCRENDERING_ENABLED,
|
||||||
|
DWMWA_NCRENDERING_POLICY,
|
||||||
|
DWMWA_TRANSITIONS_FORCEDISABLED,
|
||||||
|
DWMWA_ALLOW_NCPAINT,
|
||||||
|
DWMWA_CAPTION_BUTTON_BOUNDS,
|
||||||
|
DWMWA_NONCLIENT_RTL_LAYOUT,
|
||||||
|
DWMWA_FORCE_ICONIC_REPRESENTATION,
|
||||||
|
DWMWA_FLIP3D_POLICY,
|
||||||
|
DWMWA_EXTENDED_FRAME_BOUNDS,
|
||||||
|
DWMWA_HAS_ICONIC_BITMAP,
|
||||||
|
DWMWA_DISALLOW_PEEK,
|
||||||
|
DWMWA_EXCLUDED_FROM_PEEK,
|
||||||
|
DWMWA_CLOAK,
|
||||||
|
DWMWA_CLOAKED,
|
||||||
|
DWMWA_FREEZE_REPRESENTATION,
|
||||||
|
DWMWA_PASSIVE_UPDATE_MODE,
|
||||||
|
DWMWA_USE_HOSTBACKDROPBRUSH,
|
||||||
|
DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||||
|
DWMWA_WINDOW_CORNER_PREFERENCE,
|
||||||
|
DWMWA_BORDER_COLOR,
|
||||||
|
DWMWA_CAPTION_COLOR,
|
||||||
|
DWMWA_TEXT_COLOR,
|
||||||
|
DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,
|
||||||
|
DWMWA_SYSTEMBACKDROP_TYPE,
|
||||||
|
DWMWA_LAST;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DWMWA_NCRENDERING_ENABLED = 0
|
||||||
|
val DWMWA_NCRENDERING_POLICY = 1
|
||||||
|
val DWMWA_TRANSITIONS_FORCEDISABLED = 2
|
||||||
|
val DWMWA_ALLOW_NCPAINT = 3
|
||||||
|
val DWMWA_CAPTION_BUTTON_BOUNDS = 4
|
||||||
|
val DWMWA_NONCLIENT_RTL_LAYOUT = 5
|
||||||
|
val DWMWA_FORCE_ICONIC_REPRESENTATION = 6
|
||||||
|
val DWMWA_FLIP3D_POLICY = 7
|
||||||
|
val DWMWA_EXTENDED_FRAME_BOUNDS = 8
|
||||||
|
val DWMWA_HAS_ICONIC_BITMAP = 9
|
||||||
|
val DWMWA_DISALLOW_PEEK = 10
|
||||||
|
val DWMWA_EXCLUDED_FROM_PEEK = 11
|
||||||
|
val DWMWA_CLOAK = 12
|
||||||
|
val DWMWA_CLOAKED = 13
|
||||||
|
val DWMWA_FREEZE_REPRESENTATION = 14
|
||||||
|
val DWMWA_PASSIVE_UPDATE_MODE = 15
|
||||||
|
val DWMWA_USE_HOSTBACKDROPBRUSH = 16
|
||||||
|
val DWMWA_USE_IMMERSIVE_DARK_MODE = 17
|
||||||
|
val DWMWA_WINDOW_CORNER_PREFERENCE = 18
|
||||||
|
val DWMWA_BORDER_COLOR = 19
|
||||||
|
val DWMWA_CAPTION_COLOR = 20
|
||||||
|
val DWMWA_TEXT_COLOR = 21
|
||||||
|
val DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 22
|
||||||
|
val DWMWA_SYSTEMBACKDROP_TYPE = 23
|
||||||
|
val DWMWA_LAST = 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the title of the active/foreground window.
|
* Gets the title of the active/foreground window.
|
||||||
*
|
*
|
||||||
@ -244,6 +318,25 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
override fun getScaledForegroundWindowBounds(): Rectangle? {
|
override fun getScaledForegroundWindowBounds(): Rectangle? {
|
||||||
val user32 = User32.INSTANCE
|
val user32 = User32.INSTANCE
|
||||||
val hWnd = user32.GetForegroundWindow()
|
val hWnd = user32.GetForegroundWindow()
|
||||||
|
|
||||||
|
val rect = WinRect()
|
||||||
|
val success = user32.DwmGetWindowAttribute(
|
||||||
|
hWnd,
|
||||||
|
8,
|
||||||
|
rect.pointer,
|
||||||
|
rect.size()
|
||||||
|
)
|
||||||
|
return if (true){
|
||||||
|
rect.read()
|
||||||
|
Rectangle(rect.top, rect.left, (rect.right - rect.left), rect.bottom - rect.top)
|
||||||
|
}else{
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun barelyFunctionalWindowQuery(): Rectangle? {
|
||||||
|
val user32 = User32.INSTANCE
|
||||||
|
val hWnd = user32.GetForegroundWindow()
|
||||||
val rect = getRectFromWindowHandle(user32, hWnd)
|
val rect = getRectFromWindowHandle(user32, hWnd)
|
||||||
val defaultDPI = 96
|
val defaultDPI = 96
|
||||||
|
|
||||||
@ -297,10 +390,32 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
return if (success) {
|
return if (success) {
|
||||||
//the values are stuck down in memory, so we have to read these back out in order to proceed
|
//the values are stuck down in memory, so we have to read these back out in order to proceed
|
||||||
rect.read()
|
rect.read()
|
||||||
rect
|
|
||||||
|
// Convert top-left point to physical coordinates
|
||||||
|
val topLeft = WinPoint(rect.left, rect.top) //rect.top, rect.left
|
||||||
|
val tlSuccess = user32.PhysicalToLogicalPointForPerMonitorDPI(hWnd, topLeft.pointer)
|
||||||
|
|
||||||
|
// Convert bottom-right point to physical coordinates
|
||||||
|
val bottomRight = WinPoint(rect.right, rect.bottom)
|
||||||
|
val brSuccess = user32.PhysicalToLogicalPointForPerMonitorDPI(hWnd, bottomRight.pointer)
|
||||||
|
|
||||||
|
|
||||||
|
return if (tlSuccess && brSuccess) {
|
||||||
|
// Create new rect with physical coordinates
|
||||||
|
val returnValue = WinRect()
|
||||||
|
returnValue.top = topLeft.y
|
||||||
|
returnValue.left = topLeft.x
|
||||||
|
returnValue.bottom = bottomRight.y
|
||||||
|
returnValue.right = bottomRight.x
|
||||||
|
|
||||||
|
returnValue
|
||||||
} else {
|
} else {
|
||||||
|
// Failed to convert logical to physical points
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Failed to get window rect
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,12 +1,23 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import javax.imageio.ImageIO
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class VisionControllerTest {
|
class VisionControllerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testImageCapture(){
|
fun testImageCapture() {
|
||||||
val vc = ConcreteVisionController()
|
val vc = ConcreteVisionController()
|
||||||
vc.takeScreenshotOfForeground()
|
val bi = vc.takeScreenshotOfForeground()
|
||||||
|
assertNotNull(bi)
|
||||||
|
|
||||||
|
for (i in 0..<bi.resolutionVariants.size) {
|
||||||
|
val path = Paths.get("image$i.png")
|
||||||
|
val image = bi.resolutionVariants[i] as BufferedImage
|
||||||
|
ImageIO.write(image , "png", path.toFile())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user