windows-mcp 0.5.9__py3-none-any.whl → 0.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- windows_mcp/__main__.py +57 -15
- windows_mcp/desktop/service.py +182 -100
- windows_mcp/desktop/views.py +32 -15
- windows_mcp/tree/service.py +29 -29
- windows_mcp/tree/utils.py +21 -21
- windows_mcp/tree/views.py +9 -9
- windows_mcp/uia/controls.py +0 -42
- windows_mcp/uia/core.py +19 -929
- windows_mcp/vdm/core.py +147 -135
- {windows_mcp-0.5.9.dist-info → windows_mcp-0.6.1.dist-info}/METADATA +4 -5
- {windows_mcp-0.5.9.dist-info → windows_mcp-0.6.1.dist-info}/RECORD +14 -14
- {windows_mcp-0.5.9.dist-info → windows_mcp-0.6.1.dist-info}/WHEEL +0 -0
- {windows_mcp-0.5.9.dist-info → windows_mcp-0.6.1.dist-info}/entry_points.txt +0 -0
- {windows_mcp-0.5.9.dist-info → windows_mcp-0.6.1.dist-info}/licenses/LICENSE.md +0 -0
windows_mcp/uia/core.py
CHANGED
|
@@ -101,6 +101,10 @@ class _AutomationClient:
|
|
|
101
101
|
return cls._instance
|
|
102
102
|
|
|
103
103
|
def __init__(self):
|
|
104
|
+
try:
|
|
105
|
+
ctypes.windll.ole32.CoInitialize(None)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
104
108
|
tryCount = 3
|
|
105
109
|
for retry in range(tryCount):
|
|
106
110
|
try:
|
|
@@ -111,62 +115,9 @@ class _AutomationClient:
|
|
|
111
115
|
break
|
|
112
116
|
except Exception as ex:
|
|
113
117
|
if retry + 1 == tryCount:
|
|
114
|
-
Logger.WriteLine('''
|
|
115
|
-
{}
|
|
116
|
-
Can not load UIAutomationCore.dll.
|
|
117
|
-
1, You may need to install Windows Update KB971513 if your OS is Windows XP, see https://github.com/yinkaisheng/WindowsUpdateKB971513ForIUIAutomation
|
|
118
|
-
2, You need to use an UIAutomationInitializerInThread object if use uiautomation in a thread, see demos/uiautomation_in_thread.py'''.format(ex), ConsoleColor.Yellow)
|
|
119
118
|
raise ex
|
|
120
119
|
|
|
121
120
|
|
|
122
|
-
class _DllClient:
|
|
123
|
-
_instance = None
|
|
124
|
-
|
|
125
|
-
@classmethod
|
|
126
|
-
def instance(cls) -> '_DllClient':
|
|
127
|
-
"""Singleton instance (this prevents com creation on import)."""
|
|
128
|
-
if cls._instance is None:
|
|
129
|
-
cls._instance = cls()
|
|
130
|
-
return cls._instance
|
|
131
|
-
|
|
132
|
-
def __init__(self):
|
|
133
|
-
binPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
134
|
-
os.environ["PATH"] = binPath + os.pathsep + os.environ["PATH"]
|
|
135
|
-
load = False
|
|
136
|
-
if IsPy38OrHigher and IsNT6orHigher:
|
|
137
|
-
os.add_dll_directory(binPath)
|
|
138
|
-
if CurrentProcessIs64Bit:
|
|
139
|
-
try:
|
|
140
|
-
self.dll = ctypes.cdll.UIAutomationClient_VC140_X64
|
|
141
|
-
load = True
|
|
142
|
-
except Exception as ex:
|
|
143
|
-
print(ex)
|
|
144
|
-
else:
|
|
145
|
-
try:
|
|
146
|
-
self.dll = ctypes.cdll.UIAutomationClient_VC140_X86
|
|
147
|
-
load = True
|
|
148
|
-
except Exception as ex:
|
|
149
|
-
print(ex)
|
|
150
|
-
if load:
|
|
151
|
-
self.dll.BitmapCreate.restype = ctypes.c_size_t
|
|
152
|
-
self.dll.BitmapGetWidthAndHeight.restype = ctypes.c_uint64
|
|
153
|
-
self.dll.BitmapFromWindow.restype = ctypes.c_size_t
|
|
154
|
-
self.dll.BitmapFromHBITMAP.restype = ctypes.c_size_t
|
|
155
|
-
self.dll.BitmapToHBITMAP.restype = ctypes.c_size_t
|
|
156
|
-
self.dll.BitmapFromFile.restype = ctypes.c_size_t
|
|
157
|
-
self.dll.BitmapFromBytes.restype = ctypes.c_size_t
|
|
158
|
-
self.dll.BitmapResize.restype = ctypes.c_size_t
|
|
159
|
-
self.dll.BitmapRotate.restype = ctypes.c_size_t
|
|
160
|
-
self.dll.BitmapGetPixel.restype = ctypes.c_uint32
|
|
161
|
-
self.dll.Initialize()
|
|
162
|
-
else:
|
|
163
|
-
self.dll = None
|
|
164
|
-
Logger.WriteLine('Can not load dll.\nFunctionalities related to Bitmap are not available.\nYou may need to install Microsoft Visual C++ 2015 Redistributable Package.', ConsoleColor.Yellow)
|
|
165
|
-
|
|
166
|
-
def __del__(self):
|
|
167
|
-
if self.dll:
|
|
168
|
-
self.dll.Uninitialize()
|
|
169
|
-
|
|
170
121
|
|
|
171
122
|
# set Windows dll restype
|
|
172
123
|
ctypes.windll.user32.GetAncestor.restype = ctypes.c_void_p
|
|
@@ -178,7 +129,6 @@ ctypes.windll.user32.GetWindowLongW.restype = ctypes.wintypes.LONG
|
|
|
178
129
|
ctypes.windll.user32.OpenDesktopW.restype = ctypes.c_void_p
|
|
179
130
|
ctypes.windll.user32.SendMessageW.restype = ctypes.wintypes.LONG
|
|
180
131
|
ctypes.windll.user32.WindowFromPoint.restype = ctypes.c_void_p
|
|
181
|
-
ctypes.windll.gdi32.CreateBitmap.restype = ctypes.c_void_p
|
|
182
132
|
ctypes.windll.gdi32.CreateCompatibleDC.restype = ctypes.c_void_p
|
|
183
133
|
ctypes.windll.gdi32.SelectObject.restype = ctypes.c_void_p
|
|
184
134
|
ctypes.windll.kernel32.GetConsoleWindow.restype = ctypes.c_void_p
|
|
@@ -188,7 +138,6 @@ ctypes.windll.kernel32.GlobalLock.restype = ctypes.c_void_p
|
|
|
188
138
|
ctypes.windll.kernel32.OpenProcess.restype = ctypes.c_void_p
|
|
189
139
|
ctypes.windll.ntdll.NtQueryInformationProcess.restype = ctypes.c_uint32
|
|
190
140
|
|
|
191
|
-
|
|
192
141
|
def _GetDictKeyName(theDict: Dict[str, Any], theValue: Any, keyCondition: Optional[Callable[[str], bool]] = None) -> str:
|
|
193
142
|
for key, value in theDict.items():
|
|
194
143
|
if keyCondition:
|
|
@@ -588,6 +537,21 @@ def GetVirtualScreenSize() -> Tuple[int, int]:
|
|
|
588
537
|
return w, h
|
|
589
538
|
|
|
590
539
|
|
|
540
|
+
def GetVirtualScreenRect() -> Tuple[int, int, int, int]:
|
|
541
|
+
"""Returns (left, top, width, height) of the virtual screen."""
|
|
542
|
+
SM_XVIRTUALSCREEN = 76
|
|
543
|
+
SM_YVIRTUALSCREEN = 77
|
|
544
|
+
SM_CXVIRTUALSCREEN = 78
|
|
545
|
+
SM_CYVIRTUALSCREEN = 79
|
|
546
|
+
return (
|
|
547
|
+
ctypes.windll.user32.GetSystemMetrics(SM_XVIRTUALSCREEN),
|
|
548
|
+
ctypes.windll.user32.GetSystemMetrics(SM_YVIRTUALSCREEN),
|
|
549
|
+
ctypes.windll.user32.GetSystemMetrics(SM_CXVIRTUALSCREEN),
|
|
550
|
+
ctypes.windll.user32.GetSystemMetrics(SM_CYVIRTUALSCREEN)
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
|
|
591
555
|
def GetMonitorsRect() -> List[Rect]:
|
|
592
556
|
"""
|
|
593
557
|
Get monitors' rect.
|
|
@@ -1765,837 +1729,6 @@ def _ExitHandler():
|
|
|
1765
1729
|
atexit.register(_ExitHandler)
|
|
1766
1730
|
|
|
1767
1731
|
|
|
1768
|
-
class RotateFlipType:
|
|
1769
|
-
RotateNoneFlipNone = 0
|
|
1770
|
-
Rotate90FlipNone = 1
|
|
1771
|
-
Rotate180FlipNone = 2
|
|
1772
|
-
Rotate270FlipNone = 3
|
|
1773
|
-
RotateNoneFlipX = 4
|
|
1774
|
-
Rotate90FlipX = 5
|
|
1775
|
-
Rotate180FlipX = 6
|
|
1776
|
-
Rotate270FlipX = 7
|
|
1777
|
-
RotateNoneFlipY = Rotate180FlipX
|
|
1778
|
-
Rotate90FlipY = Rotate270FlipX
|
|
1779
|
-
Rotate180FlipY = RotateNoneFlipX
|
|
1780
|
-
Rotate270FlipY = Rotate90FlipX
|
|
1781
|
-
RotateNoneFlipXY = Rotate180FlipNone
|
|
1782
|
-
Rotate90FlipXY = Rotate270FlipNone
|
|
1783
|
-
Rotate180FlipXY = RotateNoneFlipNone
|
|
1784
|
-
Rotate270FlipXY = Rotate90FlipNone
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
class RawFormat:
|
|
1788
|
-
Undefined = 0
|
|
1789
|
-
MemoryBMP = 1
|
|
1790
|
-
BMP = 2
|
|
1791
|
-
EMF = 3
|
|
1792
|
-
WMF = 4
|
|
1793
|
-
JPEG = 5
|
|
1794
|
-
PNG = 6
|
|
1795
|
-
GIF = 7 # MultiFrameBitmap
|
|
1796
|
-
TIFF = 8 # MultiFrameBitmap
|
|
1797
|
-
EXIF = 9
|
|
1798
|
-
Icon = 10
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
class Bitmap:
|
|
1802
|
-
"""
|
|
1803
|
-
A simple Bitmap class wraps Windows GDI+ Gdiplus::Bitmap, but may not have high efficiency.
|
|
1804
|
-
The color format is Gdiplus::PixelFormat32bppARGB 0xAARRGGBB, byte order is B G R A.
|
|
1805
|
-
"""
|
|
1806
|
-
|
|
1807
|
-
def __init__(self, width: int = 0, height: int = 0):
|
|
1808
|
-
"""
|
|
1809
|
-
Create a black transparent(ARGB=0x00000000) bimap of size(width, height).
|
|
1810
|
-
"""
|
|
1811
|
-
self._width = width
|
|
1812
|
-
self._height = height
|
|
1813
|
-
self._bitmap = 0
|
|
1814
|
-
self._format = RawFormat.Undefined
|
|
1815
|
-
self._formatStr = ''
|
|
1816
|
-
if width > 0 and height > 0:
|
|
1817
|
-
self._bitmap = _DllClient.instance().dll.BitmapCreate(width, height)
|
|
1818
|
-
|
|
1819
|
-
def __del__(self):
|
|
1820
|
-
self.Close()
|
|
1821
|
-
|
|
1822
|
-
def __enter__(self):
|
|
1823
|
-
return self
|
|
1824
|
-
|
|
1825
|
-
def __exit__(self, exceptionType, exceptionValue, exceptionTraceback):
|
|
1826
|
-
self.Close()
|
|
1827
|
-
|
|
1828
|
-
def __bool__(self):
|
|
1829
|
-
return self._bitmap > 0
|
|
1830
|
-
|
|
1831
|
-
def _GetSize(self) -> None:
|
|
1832
|
-
size = _DllClient.instance().dll.BitmapGetWidthAndHeight(ctypes.c_size_t(self._bitmap))
|
|
1833
|
-
self._width = size & 0xFFFFFFFF
|
|
1834
|
-
self._height = size >> 32
|
|
1835
|
-
|
|
1836
|
-
def Close(self) -> None:
|
|
1837
|
-
"""Close the underlying Gdiplus::Bitmap object."""
|
|
1838
|
-
if self._bitmap:
|
|
1839
|
-
_DllClient.instance().dll.BitmapRelease(ctypes.c_size_t(self._bitmap))
|
|
1840
|
-
self._bitmap = 0
|
|
1841
|
-
self._width = 0
|
|
1842
|
-
self._height = 0
|
|
1843
|
-
|
|
1844
|
-
Release = Close
|
|
1845
|
-
|
|
1846
|
-
@property
|
|
1847
|
-
def Width(self) -> int:
|
|
1848
|
-
"""
|
|
1849
|
-
Property Width.
|
|
1850
|
-
Return int.
|
|
1851
|
-
"""
|
|
1852
|
-
return self._width
|
|
1853
|
-
|
|
1854
|
-
@property
|
|
1855
|
-
def Height(self) -> int:
|
|
1856
|
-
"""
|
|
1857
|
-
Property Height.
|
|
1858
|
-
Return int.
|
|
1859
|
-
"""
|
|
1860
|
-
return self._height
|
|
1861
|
-
|
|
1862
|
-
@staticmethod
|
|
1863
|
-
def FromHandle(handle: int, left: int, top: int, right: int, bottom: int,
|
|
1864
|
-
captureCursor: bool = False) -> Optional['MemoryBMP']:
|
|
1865
|
-
"""
|
|
1866
|
-
Create a `Bitmap` from a native window handle.
|
|
1867
|
-
handle: int, the handle of a native window.
|
|
1868
|
-
left: int.
|
|
1869
|
-
top: int.
|
|
1870
|
-
right: int.
|
|
1871
|
-
bottom: int.
|
|
1872
|
-
left, top, right and bottom are control's internal postion(from 0,0).
|
|
1873
|
-
Return `Bitmap` or None.
|
|
1874
|
-
"""
|
|
1875
|
-
rect = None
|
|
1876
|
-
toplevelHandle = GetAncestor(handle, GAFlag.Root)
|
|
1877
|
-
if toplevelHandle and toplevelHandle == handle:
|
|
1878
|
-
if DwmIsCompositionEnabled():
|
|
1879
|
-
rect = DwmGetWindowExtendFrameBounds(handle)
|
|
1880
|
-
if rect is None:
|
|
1881
|
-
rect = GetWindowRect(handle)
|
|
1882
|
-
if rect is None:
|
|
1883
|
-
return None
|
|
1884
|
-
root = GetRootControl()
|
|
1885
|
-
left, top, right, bottom = left + rect.left, top + rect.top, right + rect.left, bottom + rect.top
|
|
1886
|
-
cbmp = _DllClient.instance().dll.BitmapFromWindow(ctypes.c_size_t(root.NativeWindowHandle),
|
|
1887
|
-
left, top, right, bottom,
|
|
1888
|
-
0, 0, int(captureCursor))
|
|
1889
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
1890
|
-
|
|
1891
|
-
@staticmethod
|
|
1892
|
-
def FromControl(control: 'Control', x: int = 0, y: int = 0, width: int = 0, height: int = 0,
|
|
1893
|
-
captureCursor: bool = False) -> Optional['MemoryBMP']:
|
|
1894
|
-
"""
|
|
1895
|
-
Create a `Bitmap` from a `Control`.
|
|
1896
|
-
control: `Control` or its subclass.
|
|
1897
|
-
x: int.
|
|
1898
|
-
y: int.
|
|
1899
|
-
width: int.
|
|
1900
|
-
height: int.
|
|
1901
|
-
x, y: the point in control's internal position(from 0,0)
|
|
1902
|
-
width, height: image's width and height from x, y, use 0 for entire area,
|
|
1903
|
-
If width(or height) < 0, image size will be control's width(or height) - width(or height).
|
|
1904
|
-
Return `Bitmap` or None.
|
|
1905
|
-
"""
|
|
1906
|
-
rect = control.BoundingRectangle
|
|
1907
|
-
while rect.width() == 0 or rect.height() == 0:
|
|
1908
|
-
# some controls maybe visible but their BoundingRectangle are all 0, capture its parent until valid
|
|
1909
|
-
control = control.GetParentControl()
|
|
1910
|
-
if not control:
|
|
1911
|
-
return None
|
|
1912
|
-
rect = control.BoundingRectangle
|
|
1913
|
-
handle = control.NativeWindowHandle
|
|
1914
|
-
if handle:
|
|
1915
|
-
toplevelHandle = GetAncestor(handle, GAFlag.Root)
|
|
1916
|
-
if toplevelHandle and toplevelHandle == handle:
|
|
1917
|
-
if DwmIsCompositionEnabled():
|
|
1918
|
-
rect = DwmGetWindowExtendFrameBounds(handle) or rect
|
|
1919
|
-
if width <= 0:
|
|
1920
|
-
width = rect.width() + width
|
|
1921
|
-
if height <= 0:
|
|
1922
|
-
height = rect.height() + height
|
|
1923
|
-
root = GetRootControl()
|
|
1924
|
-
left, top = rect.left + x, rect.top + y
|
|
1925
|
-
right, bottom = left + width, top + height
|
|
1926
|
-
cbmp = _DllClient.instance().dll.BitmapFromWindow(ctypes.c_size_t(root.NativeWindowHandle),
|
|
1927
|
-
left, top, right, bottom,
|
|
1928
|
-
0, 0, int(captureCursor))
|
|
1929
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
1930
|
-
if width <= 0:
|
|
1931
|
-
width = rect.width() + width
|
|
1932
|
-
if height <= 0:
|
|
1933
|
-
height = rect.height() + height
|
|
1934
|
-
while True:
|
|
1935
|
-
control = control.GetParentControl()
|
|
1936
|
-
handle = control.NativeWindowHandle
|
|
1937
|
-
if handle:
|
|
1938
|
-
pRect = control.BoundingRectangle
|
|
1939
|
-
toplevelHandle = GetAncestor(handle, GAFlag.Root)
|
|
1940
|
-
if toplevelHandle and toplevelHandle == handle:
|
|
1941
|
-
if DwmIsCompositionEnabled():
|
|
1942
|
-
pRect = DwmGetWindowExtendFrameBounds(handle) or pRect
|
|
1943
|
-
left = rect.left - pRect.left + x
|
|
1944
|
-
top = rect.top - pRect.top + y
|
|
1945
|
-
right = left + width
|
|
1946
|
-
bottom = top + height
|
|
1947
|
-
break
|
|
1948
|
-
return Bitmap.FromHandle(handle, left, top, right, bottom, captureCursor)
|
|
1949
|
-
|
|
1950
|
-
@staticmethod
|
|
1951
|
-
def _FromGdiplusBitmap(cbmp: int) -> 'Bitmap':
|
|
1952
|
-
"""
|
|
1953
|
-
Return `Bitmap`'s subclass instance or None.
|
|
1954
|
-
"""
|
|
1955
|
-
if not cbmp:
|
|
1956
|
-
return None
|
|
1957
|
-
formatType = ctypes.c_uint()
|
|
1958
|
-
_DllClient.instance().dll.BitmapGetRawFormat(ctypes.c_size_t(cbmp), ctypes.byref(formatType))
|
|
1959
|
-
if formatType.value == RawFormat.JPEG:
|
|
1960
|
-
bitmap = JPEG()
|
|
1961
|
-
elif formatType.value == RawFormat.PNG:
|
|
1962
|
-
bitmap = PNG()
|
|
1963
|
-
elif formatType.value == RawFormat.GIF:
|
|
1964
|
-
bitmap = GIF()
|
|
1965
|
-
bitmap._bitmap = cbmp
|
|
1966
|
-
bitmap._frameCount = _DllClient.instance().dll.MultiBitmapGetFrameCount(ctypes.c_size_t(cbmp))
|
|
1967
|
-
bitmap._GetGifDealy()
|
|
1968
|
-
elif formatType.value == RawFormat.TIFF:
|
|
1969
|
-
bitmap = TIFF()
|
|
1970
|
-
bitmap._frameCount = _DllClient.instance().dll.MultiBitmapGetFrameCount(ctypes.c_size_t(cbmp))
|
|
1971
|
-
elif formatType.value == RawFormat.BMP:
|
|
1972
|
-
bitmap = BMP()
|
|
1973
|
-
elif formatType.value == RawFormat.MemoryBMP:
|
|
1974
|
-
bitmap = MemoryBMP()
|
|
1975
|
-
elif formatType.value == RawFormat.Icon:
|
|
1976
|
-
bitmap = ICON()
|
|
1977
|
-
elif formatType.value == RawFormat.EMF:
|
|
1978
|
-
bitmap = EMF()
|
|
1979
|
-
elif formatType.value == RawFormat.WMF:
|
|
1980
|
-
bitmap = WMF()
|
|
1981
|
-
elif formatType.value == RawFormat.EXIF:
|
|
1982
|
-
bitmap = EXIF()
|
|
1983
|
-
else:
|
|
1984
|
-
bitmap = Bitmap()
|
|
1985
|
-
bitmap._bitmap = cbmp
|
|
1986
|
-
bitmap._format = formatType.value
|
|
1987
|
-
bitmap._GetSize()
|
|
1988
|
-
return bitmap
|
|
1989
|
-
|
|
1990
|
-
@staticmethod
|
|
1991
|
-
def FromBytes(data: Union[bytes, bytearray], format: str = None, width: int = None, height: int = None) -> Optional['Bitmap']:
|
|
1992
|
-
"""
|
|
1993
|
-
Create a `Bitmap` instance from raw BGRA pixel data or image file byte content.
|
|
1994
|
-
data: bytes or bytearray.
|
|
1995
|
-
format (str, optional): Specifies the format of the input data.
|
|
1996
|
-
- Use 'BGRA' for raw pixel data, byte order is B G R A.
|
|
1997
|
-
- Use None for standard image file data (e.g., PNG, JPEG).
|
|
1998
|
-
width (int, optional): The width of the image. Required only when `format` is 'BGRA'.
|
|
1999
|
-
height (int, optional): The height of the image. Required only when `format` is 'BGRA'.
|
|
2000
|
-
Return `Bitmap`'s subclass instance or None.
|
|
2001
|
-
"""
|
|
2002
|
-
if format is not None:
|
|
2003
|
-
assert format == 'bgra' or format == 'BGRA'
|
|
2004
|
-
assert len(data) == width * height * 4
|
|
2005
|
-
if isinstance(data, bytearray):
|
|
2006
|
-
pixelBytes = data
|
|
2007
|
-
else:
|
|
2008
|
-
pixelBytes = bytearray(data)
|
|
2009
|
-
pixelArrayType = (ctypes.c_uint32 * (len(pixelBytes) // 4))
|
|
2010
|
-
pixelArray = pixelArrayType.from_buffer(pixelBytes)
|
|
2011
|
-
bitmap = Bitmap(width, height)
|
|
2012
|
-
bitmap.SetAllPixelColors(pixelArray)
|
|
2013
|
-
return bitmap
|
|
2014
|
-
cbmp = _DllClient.instance().dll.BitmapFromBytes(ctypes.c_char_p(data), len(data), 0)
|
|
2015
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2016
|
-
|
|
2017
|
-
@staticmethod
|
|
2018
|
-
def FromFile(filePath: str) -> Optional['Bitmap']:
|
|
2019
|
-
"""
|
|
2020
|
-
Create a `Bitmap` from a file path.
|
|
2021
|
-
filePath: str.
|
|
2022
|
-
Return `Bitmap`'s subclass instance or None.
|
|
2023
|
-
"""
|
|
2024
|
-
cbmp = _DllClient.instance().dll.BitmapFromFile(ctypes.c_wchar_p(filePath))
|
|
2025
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2026
|
-
|
|
2027
|
-
@staticmethod
|
|
2028
|
-
def FromNDArray(image: 'numpy.ndarray') -> 'MemoryBMP':
|
|
2029
|
-
"""
|
|
2030
|
-
Create a `MemoryBMP` from a numpy.ndarray(BGR or BGRA).
|
|
2031
|
-
"""
|
|
2032
|
-
import numpy as np
|
|
2033
|
-
assert len(image.shape) == 3 and (image.shape[2] == 3 or image.shape[2] == 4)
|
|
2034
|
-
height, width = image.shape[:2]
|
|
2035
|
-
if image.shape[2] == 3:
|
|
2036
|
-
bgraArray = np.zeros((height, width, 4), dtype=np.uint8)
|
|
2037
|
-
bgraArray[:, :, :3] = image[:, :, :]
|
|
2038
|
-
bgraArray[:, :, 3] = 0xFF
|
|
2039
|
-
else: # 4
|
|
2040
|
-
bgraArray = image
|
|
2041
|
-
pixelBytes = bytearray(bgraArray.tobytes())
|
|
2042
|
-
pixelArrayType = (ctypes.c_uint32 * (len(pixelBytes) // 4))
|
|
2043
|
-
pixelArray = pixelArrayType.from_buffer(pixelBytes)
|
|
2044
|
-
bitmap = MemoryBMP(width, height)
|
|
2045
|
-
bitmap.SetAllPixelColors(pixelArray)
|
|
2046
|
-
return bitmap
|
|
2047
|
-
|
|
2048
|
-
@staticmethod
|
|
2049
|
-
def FromPILImage(image: 'PIL.Image.Image') -> 'MemoryBMP':
|
|
2050
|
-
"""
|
|
2051
|
-
Create a `MemoryBMP` from a PIL Image.
|
|
2052
|
-
"""
|
|
2053
|
-
assert image.mode == 'RGB' or image.mode == 'RGBA'
|
|
2054
|
-
try:
|
|
2055
|
-
import numpy as np
|
|
2056
|
-
imgArray = np.array(image)
|
|
2057
|
-
if image.mode == 'RGB':
|
|
2058
|
-
imgArray = imgArray[:, :, ::-1]
|
|
2059
|
-
else: # 'RGBA'
|
|
2060
|
-
imgArray = imgArray[:, :, [2, 1, 0, 3]]
|
|
2061
|
-
return Bitmap.FromNDArray(imgArray)
|
|
2062
|
-
except:
|
|
2063
|
-
if image.mode == 'RGB':
|
|
2064
|
-
rgbaImg = image.convert('RGBA')
|
|
2065
|
-
rgbaBytes = rgbaImg.tobytes()
|
|
2066
|
-
else: # 'RGBA'
|
|
2067
|
-
rgbaBytes = image.tobytes()
|
|
2068
|
-
pixelBytes = bytearray(rgbaBytes)
|
|
2069
|
-
for i in range(0, len(pixelBytes), 4):
|
|
2070
|
-
pixel = pixelBytes[i:i+4]
|
|
2071
|
-
pixelBytes[i:i+4] = pixel[2], pixel[1], pixel[0], pixel[3]
|
|
2072
|
-
pixelArrayType = (ctypes.c_uint32 * (len(pixelBytes) // 4))
|
|
2073
|
-
pixelArray = pixelArrayType.from_buffer(pixelBytes)
|
|
2074
|
-
bitmap = MemoryBMP(image.width, image.height)
|
|
2075
|
-
bitmap.SetAllPixelColors(pixelArray)
|
|
2076
|
-
return bitmap
|
|
2077
|
-
|
|
2078
|
-
def ToFile(self, savePath: str, *, quality: int = None) -> bool:
|
|
2079
|
-
"""
|
|
2080
|
-
Save to a file.
|
|
2081
|
-
savePath: str, should end with .bmp, .jpg, .jpeg, .png, .gif, .tif, .tiff.
|
|
2082
|
-
quality: int, 1-100, only used when save to jpeg.
|
|
2083
|
-
Return bool, True if succeed otherwise False.
|
|
2084
|
-
Note: If file extension is .gif or .tiff, it is only saved as a single frame.
|
|
2085
|
-
If you want to save a gif or a tiff file with multiple frames, use `GIF.ToGifFile` or `TIFF.ToTiffFile`.
|
|
2086
|
-
"""
|
|
2087
|
-
name, ext = os.path.splitext(savePath)
|
|
2088
|
-
extMap = {'.bmp': 'image/bmp',
|
|
2089
|
-
'.jpg': 'image/jpeg',
|
|
2090
|
-
'.jpeg': 'image/jpeg',
|
|
2091
|
-
'.gif': 'image/gif',
|
|
2092
|
-
'.tif': 'image/tiff',
|
|
2093
|
-
'.tiff': 'image/tiff',
|
|
2094
|
-
'.png': 'image/png',
|
|
2095
|
-
}
|
|
2096
|
-
gdiplusImageFormat = extMap.get(ext.lower(), 'image/png')
|
|
2097
|
-
gdiStatus = _DllClient.instance().dll.BitmapToFile(ctypes.c_size_t(self._bitmap), ctypes.c_wchar_p(savePath), ctypes.c_wchar_p(gdiplusImageFormat), quality or 80)
|
|
2098
|
-
return gdiStatus == 0
|
|
2099
|
-
|
|
2100
|
-
def ToBytes(self, format: str, *, quality: int = None) -> bytearray:
|
|
2101
|
-
"""
|
|
2102
|
-
Convert to a bytearray in the specified format.
|
|
2103
|
-
format: str, The desired output format. Supported formats include:
|
|
2104
|
-
- 'BGRA': Raw pixel data without any file header, byte order is B G R A.
|
|
2105
|
-
- 'bmp', 'jpg', 'jpeg', 'gif', 'tif', 'tiff', 'png':
|
|
2106
|
-
Standard image file formats with corresponding file headers.
|
|
2107
|
-
quality: int, 1-100, only used when format is jpg and jpeg.
|
|
2108
|
-
Note: If format is gif or tiff, only the active single frame is saved to a bytearray.
|
|
2109
|
-
Currently multiple frames are not supported.
|
|
2110
|
-
"""
|
|
2111
|
-
format = format.lower()
|
|
2112
|
-
if format == 'bgra':
|
|
2113
|
-
return bytearray(memoryview(self.GetAllPixelColors()))
|
|
2114
|
-
|
|
2115
|
-
if format[0] != '.':
|
|
2116
|
-
format = '.' + format
|
|
2117
|
-
extMap = {'.bmp': 'image/bmp',
|
|
2118
|
-
'.jpg': 'image/jpeg',
|
|
2119
|
-
'.jpeg': 'image/jpeg',
|
|
2120
|
-
'.gif': 'image/gif',
|
|
2121
|
-
'.tif': 'image/tiff',
|
|
2122
|
-
'.tiff': 'image/tiff',
|
|
2123
|
-
'.png': 'image/png',
|
|
2124
|
-
}
|
|
2125
|
-
gdiplusImageFormat = extMap.get(format.lower(), 'image/png')
|
|
2126
|
-
stream = ctypes.c_size_t()
|
|
2127
|
-
gdiStatus = _DllClient.instance().dll.BitmapToStream(
|
|
2128
|
-
ctypes.c_size_t(self._bitmap), ctypes.byref(stream), ctypes.c_wchar_p(gdiplusImageFormat), quality or 80)
|
|
2129
|
-
if stream.value == 0:
|
|
2130
|
-
return None
|
|
2131
|
-
streamSize = _DllClient.instance().dll.GetStreamSize(stream)
|
|
2132
|
-
if streamSize == 0:
|
|
2133
|
-
return None
|
|
2134
|
-
data = bytearray(streamSize)
|
|
2135
|
-
cdata = (ctypes.c_ubyte * streamSize).from_buffer(data)
|
|
2136
|
-
streamSize = _DllClient.instance().dll.CopyStreamData(stream, cdata, streamSize)
|
|
2137
|
-
_DllClient.instance().dll.ReleaseStream(stream)
|
|
2138
|
-
return data
|
|
2139
|
-
|
|
2140
|
-
def ToPILImage(self) -> 'PIL.Image.Image':
|
|
2141
|
-
"""
|
|
2142
|
-
Convert to a PIL image.
|
|
2143
|
-
usage:
|
|
2144
|
-
image = bitmap.ToPILImage()
|
|
2145
|
-
image.save("pil.png")
|
|
2146
|
-
image.save("pil.png")
|
|
2147
|
-
image.convert('RGB').save('pil.jpg', quality=80, optimize=True)
|
|
2148
|
-
"""
|
|
2149
|
-
from PIL import Image
|
|
2150
|
-
return Image.frombytes('RGBA', (self.Width, self.Height), self.ToBytes('BGRA'), 'raw', 'BGRA')
|
|
2151
|
-
|
|
2152
|
-
def ToNDArray(self) -> 'numpy.ndarray':
|
|
2153
|
-
"""
|
|
2154
|
-
Convert to a numpy.ndarray(shape=(height,width,4), compatible with OpenCV).
|
|
2155
|
-
usage:
|
|
2156
|
-
bgraImage = bitmap.ToNDArray()
|
|
2157
|
-
bgrImage = cv2.cvtColor(bgraImage, cv2.COLOR_BGRA2BGR)
|
|
2158
|
-
"""
|
|
2159
|
-
import numpy as np
|
|
2160
|
-
npArray = np.frombuffer(self.ToBytes('BGRA'), dtype=np.uint8)
|
|
2161
|
-
return npArray.reshape((self.Height, self.Width, 4))
|
|
2162
|
-
|
|
2163
|
-
def GetRawFormat(self) -> int:
|
|
2164
|
-
"""
|
|
2165
|
-
return a int value in class `RawFormat`
|
|
2166
|
-
"""
|
|
2167
|
-
if self._format == RawFormat.Undefined:
|
|
2168
|
-
formatType = ctypes.c_uint()
|
|
2169
|
-
_DllClient.instance().dll.BitmapGetRawFormat(ctypes.c_size_t(self._bitmap), ctypes.byref(formatType))
|
|
2170
|
-
self._format = formatType.value
|
|
2171
|
-
return self._format
|
|
2172
|
-
|
|
2173
|
-
def GetRawFormatStr(self) -> str:
|
|
2174
|
-
"""
|
|
2175
|
-
return the string description of `RawFormat` value
|
|
2176
|
-
"""
|
|
2177
|
-
if not self._formatStr:
|
|
2178
|
-
self._formatStr = _GetDictKeyName(RawFormat.__dict__, self.GetRawFormat())
|
|
2179
|
-
return self._formatStr
|
|
2180
|
-
|
|
2181
|
-
def GetPixelColor(self, x: int, y: int) -> int:
|
|
2182
|
-
"""
|
|
2183
|
-
Get color value of a pixel.
|
|
2184
|
-
x: int.
|
|
2185
|
-
y: int.
|
|
2186
|
-
Return int, ARGB(0xAARRGGBB) color format.
|
|
2187
|
-
b = argb & 0x0000_00FF
|
|
2188
|
-
g = (argb & 0x0000_FF00) >> 8
|
|
2189
|
-
r = (argb & 0x00FF_0000) >> 16
|
|
2190
|
-
a = (argb & 0xFF00_0000) >> 24
|
|
2191
|
-
"""
|
|
2192
|
-
return _DllClient.instance().dll.BitmapGetPixel(ctypes.c_size_t(self._bitmap), x, y)
|
|
2193
|
-
|
|
2194
|
-
def SetPixelColor(self, x: int, y: int, argb: int) -> bool:
|
|
2195
|
-
"""
|
|
2196
|
-
Set color value of a pixel.
|
|
2197
|
-
x: int.
|
|
2198
|
-
y: int.
|
|
2199
|
-
argb: int, ARGB(0xAARRGGBB) color format.
|
|
2200
|
-
Return bool, True if succeed otherwise False.
|
|
2201
|
-
"""
|
|
2202
|
-
gdiStatus = _DllClient.instance().dll.BitmapSetPixel(ctypes.c_size_t(self._bitmap), x, y, argb)
|
|
2203
|
-
return gdiStatus == 0
|
|
2204
|
-
|
|
2205
|
-
def GetPixelColorsHorizontally(self, x: int, y: int, count: int) -> ctypes.Array:
|
|
2206
|
-
"""
|
|
2207
|
-
x: int.
|
|
2208
|
-
y: int.
|
|
2209
|
-
count: int.
|
|
2210
|
-
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format form point x,y horizontally.
|
|
2211
|
-
"""
|
|
2212
|
-
#assert count <= self.Width * (self.Height - y) - x, 'count > max available from x,y'
|
|
2213
|
-
arrayType = ctypes.c_uint32 * count
|
|
2214
|
-
values = arrayType()
|
|
2215
|
-
gdiStatus = _DllClient.instance().dll.BitmapGetPixelsHorizontally(ctypes.c_size_t(self._bitmap), x, y, values, count)
|
|
2216
|
-
return values
|
|
2217
|
-
|
|
2218
|
-
def SetPixelColorsHorizontally(self, x: int, y: int, colors: Sequence[int]) -> bool:
|
|
2219
|
-
"""
|
|
2220
|
-
Set pixel colors form x,y horizontally.
|
|
2221
|
-
x: int.
|
|
2222
|
-
y: int.
|
|
2223
|
-
colors: Sequence[int], an iterable list of int color values in ARGB(0xAARRGGBB) color format,
|
|
2224
|
-
use ctypes.Array for better performance, such as `ctypes.c_uint32 * length`.
|
|
2225
|
-
Return bool, True if succeed otherwise False.
|
|
2226
|
-
"""
|
|
2227
|
-
count = len(colors)
|
|
2228
|
-
#assert count <= self.Width * (self.Height - y) - x, 'len(colors) > max available from x,y'
|
|
2229
|
-
if not isinstance(colors, ctypes.Array):
|
|
2230
|
-
arrayType = ctypes.c_uint32 * count
|
|
2231
|
-
colors = arrayType(*colors)
|
|
2232
|
-
gdiStatus = _DllClient.instance().dll.BitmapSetPixelsHorizontally(ctypes.c_size_t(self._bitmap), x, y, colors, count)
|
|
2233
|
-
return gdiStatus == 0
|
|
2234
|
-
|
|
2235
|
-
def GetPixelColorsVertically(self, x: int, y: int, count: int) -> ctypes.Array:
|
|
2236
|
-
"""
|
|
2237
|
-
x: int.
|
|
2238
|
-
y: int.
|
|
2239
|
-
count: int.
|
|
2240
|
-
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format form point x,y vertically.
|
|
2241
|
-
"""
|
|
2242
|
-
#assert count <= self.Height * (self.Width - x) - y, 'count > max available from x,y'
|
|
2243
|
-
arrayType = ctypes.c_uint32 * count
|
|
2244
|
-
values = arrayType()
|
|
2245
|
-
gdiStatus = _DllClient.instance().dll.BitmapGetPixelsVertically(ctypes.c_size_t(self._bitmap), x, y, values, count)
|
|
2246
|
-
return values
|
|
2247
|
-
|
|
2248
|
-
def SetPixelColorsVertically(self, x: int, y: int, colors: Sequence[int]) -> bool:
|
|
2249
|
-
"""
|
|
2250
|
-
Set pixel colors form x,y vertically.
|
|
2251
|
-
x: int.
|
|
2252
|
-
y: int.
|
|
2253
|
-
colors: Sequence[int], an iterable list of int color values in ARGB(0xAARRGGBB) color format,
|
|
2254
|
-
use ctypes.Array for better performance, such as `ctypes.c_uint32 * length`.
|
|
2255
|
-
Return bool, True if succeed otherwise False.
|
|
2256
|
-
"""
|
|
2257
|
-
count = len(colors)
|
|
2258
|
-
#assert count <= self.Height * (self.Width - x) - y, 'len(colors) > max available from x,y'
|
|
2259
|
-
if not isinstance(colors, ctypes.Array):
|
|
2260
|
-
arrayType = ctypes.c_uint32 * count
|
|
2261
|
-
colors = arrayType(*colors)
|
|
2262
|
-
gdiStatus = _DllClient.instance().dll.BitmapSetPixelsVertically(ctypes.c_size_t(self._bitmap), x, y, colors, count)
|
|
2263
|
-
return gdiStatus == 0
|
|
2264
|
-
|
|
2265
|
-
def GetPixelColorsOfRow(self, y: int) -> ctypes.Array:
|
|
2266
|
-
"""
|
|
2267
|
-
y: int, row index.
|
|
2268
|
-
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format of y row.
|
|
2269
|
-
"""
|
|
2270
|
-
return self.GetPixelColorsOfRect(0, y, self.Width, 1)
|
|
2271
|
-
|
|
2272
|
-
def GetPixelColorsOfColumn(self, x: int) -> ctypes.Array:
|
|
2273
|
-
"""
|
|
2274
|
-
x: int, column index.
|
|
2275
|
-
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format of x column.
|
|
2276
|
-
"""
|
|
2277
|
-
return self.GetPixelColorsOfRect(x, 0, 1, self.Height)
|
|
2278
|
-
|
|
2279
|
-
def GetPixelColorsOfRect(self, x: int, y: int, width: int, height: int) -> ctypes.Array:
|
|
2280
|
-
"""
|
|
2281
|
-
x: int.
|
|
2282
|
-
y: int.
|
|
2283
|
-
width: int.
|
|
2284
|
-
height: int.
|
|
2285
|
-
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format of the input rect.
|
|
2286
|
-
"""
|
|
2287
|
-
arrayType = ctypes.c_uint32 * (width * height)
|
|
2288
|
-
values = arrayType()
|
|
2289
|
-
gdiStatus = _DllClient.instance().dll.BitmapGetPixelsOfRect(ctypes.c_size_t(self._bitmap), x, y, width, height, values)
|
|
2290
|
-
return values
|
|
2291
|
-
|
|
2292
|
-
def SetPixelColorsOfRect(self, x: int, y: int, width: int, height: int, colors: Sequence[int]) -> bool:
|
|
2293
|
-
"""
|
|
2294
|
-
x: int.
|
|
2295
|
-
y: int.
|
|
2296
|
-
width: int.
|
|
2297
|
-
height: int.
|
|
2298
|
-
colors: Sequence[int], a sequence of int values in ARGB(0xAARRGGBB) color format, it's length must equal to width*height,
|
|
2299
|
-
use ctypes.Array for better performance, such as `ctypes.c_uint32 * (width*height)`.
|
|
2300
|
-
Return bool.
|
|
2301
|
-
"""
|
|
2302
|
-
#assert len(colors) == width * height, 'len(colors) != width * height'
|
|
2303
|
-
if not isinstance(colors, ctypes.Array):
|
|
2304
|
-
arrayType = ctypes.c_uint32 * (width * height)
|
|
2305
|
-
colors = arrayType(*colors)
|
|
2306
|
-
gdiStatus = _DllClient.instance().dll.BitmapSetPixelsOfRect(ctypes.c_size_t(self._bitmap), x, y, width, height, colors)
|
|
2307
|
-
return gdiStatus == 0
|
|
2308
|
-
|
|
2309
|
-
def GetPixelColorsOfRects(self, rects: List[Tuple[int, int, int, int]]) -> List[ctypes.Array]:
|
|
2310
|
-
"""
|
|
2311
|
-
rects: List[Tuple[int, int, int, int]], such as [(0,0,10,10), (10,10,20,20), (x,y,width,height)].
|
|
2312
|
-
Return List[ctypes.Array], a list whose elements are ctypes.Array which is an iterable array of
|
|
2313
|
-
int values in ARGB(0xAARRGGBB) color format.
|
|
2314
|
-
"""
|
|
2315
|
-
return [self.GetPixelColorsOfRect(x, y, width, height) for x, y, width, height in rects]
|
|
2316
|
-
|
|
2317
|
-
def GetAllPixelColors(self) -> ctypes.Array:
|
|
2318
|
-
"""
|
|
2319
|
-
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format.
|
|
2320
|
-
"""
|
|
2321
|
-
return self.GetPixelColorsOfRect(0, 0, self.Width, self.Height)
|
|
2322
|
-
|
|
2323
|
-
def SetAllPixelColors(self, colors: Sequence[int]) -> bool:
|
|
2324
|
-
"""
|
|
2325
|
-
colors: Sequence[int], a sequence of int values in ARGB(0xAARRGGBB) color format, it's length must equal to width*height,
|
|
2326
|
-
use ctypes.Array for better performance, such as `ctypes.c_uint32 * (width*height)`.
|
|
2327
|
-
Return bool.
|
|
2328
|
-
"""
|
|
2329
|
-
return self.SetPixelColorsOfRect(0, 0, self.Width, self.Height, colors)
|
|
2330
|
-
|
|
2331
|
-
def Clear(self, color: int = 0xFFFFFFFF, x: int = 0, y: int = 0, width: int = 0, height: int = 0) -> bool:
|
|
2332
|
-
"""
|
|
2333
|
-
Set the color of rect(x,y,width,height).
|
|
2334
|
-
color: int, ARGB(0xAARRGGBB) color format.
|
|
2335
|
-
x: int.
|
|
2336
|
-
y: int.
|
|
2337
|
-
width: int, if == 0, the width will be self.Width-x
|
|
2338
|
-
height: int, if == 0, the height will be self.Height-y
|
|
2339
|
-
Return bool.
|
|
2340
|
-
"""
|
|
2341
|
-
if width == 0:
|
|
2342
|
-
width = self.Width - x
|
|
2343
|
-
if height == 0:
|
|
2344
|
-
height = self.Height - y
|
|
2345
|
-
arrayType = ctypes.c_uint * (width * height)
|
|
2346
|
-
nativeArray = arrayType(*[color]*(width * height))
|
|
2347
|
-
return self.SetPixelColorsOfRect(x, y, width, height, nativeArray)
|
|
2348
|
-
|
|
2349
|
-
def Clone(self) -> 'MemoryBMP':
|
|
2350
|
-
"""
|
|
2351
|
-
Return `Bitmap`'s subclass instance.
|
|
2352
|
-
The cloned Bitmap's RawFormat is same as original Bitmap.
|
|
2353
|
-
If Bitmap is multiple frames, the cloned Bitmap is also multiple frames.
|
|
2354
|
-
"""
|
|
2355
|
-
cbmp = _DllClient.instance().dll.BitmapClone(ctypes.c_size_t(self._bitmap), 0, 0, self._width, self._height)
|
|
2356
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2357
|
-
|
|
2358
|
-
def Copy(self, x: int = 0, y: int = 0, width: int = 0, height: int = 0) -> 'MemoryBMP':
|
|
2359
|
-
"""
|
|
2360
|
-
x: int, must >= 0.
|
|
2361
|
-
y: int, must >= 0.
|
|
2362
|
-
width: int, must <= self.Width-x.
|
|
2363
|
-
height: int, must <= self.Height-y.
|
|
2364
|
-
Return `MemoryBMP`, a new Bitmap copied from (x,y,width,height).
|
|
2365
|
-
If Bitmap is multiple frames, the cloned Bitmap is only single frame of the active frame.
|
|
2366
|
-
"""
|
|
2367
|
-
if width == 0:
|
|
2368
|
-
width = self.Width - x
|
|
2369
|
-
if height == 0:
|
|
2370
|
-
height = self.Height - y
|
|
2371
|
-
nativeArray = self.GetPixelColorsOfRect(x, y, width, height)
|
|
2372
|
-
bitmap = MemoryBMP(width, height)
|
|
2373
|
-
bitmap.SetPixelColorsOfRect(0, 0, width, height, nativeArray)
|
|
2374
|
-
return bitmap
|
|
2375
|
-
# cbmp = _DllClient.instance().dll.BitmapClone(ctypes.c_size_t(self._bitmap), x, y, width, height)
|
|
2376
|
-
# return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2377
|
-
|
|
2378
|
-
def Paste(self, x: int, y: int, bitmap: 'Bitmap') -> bool:
|
|
2379
|
-
"""
|
|
2380
|
-
Paste bitmap to (x,y) of self, modify the original Bitmap,
|
|
2381
|
-
if x < 0 or x+bitmap.Width > self.Width, only the intersection part of bitmap is pasted,
|
|
2382
|
-
if y < 0 or y+bitmap.Height > self.Height, only the intersection part of bitmap is pasted.
|
|
2383
|
-
x: int, can < 0.
|
|
2384
|
-
y: int, can < 0.
|
|
2385
|
-
bitmap: `Bitmap`.
|
|
2386
|
-
Return bool, True if bitmap or a part of bitmap is pasted.
|
|
2387
|
-
"""
|
|
2388
|
-
left, top, right, bottom = max(0, x), max(0, y), min(self.Width, x + bitmap.Width), min(self.Height, y + bitmap.Height)
|
|
2389
|
-
width, height = right - left, bottom - top
|
|
2390
|
-
if width <= 0 or height <= 0:
|
|
2391
|
-
return False
|
|
2392
|
-
srcX = 0 if x >= 0 else -x
|
|
2393
|
-
srcY = 0 if y >= 0 else -y
|
|
2394
|
-
nativeArray = bitmap.GetPixelColorsOfRect(srcX, srcY, width, height)
|
|
2395
|
-
return self.SetPixelColorsOfRect(left, top, width, height, nativeArray)
|
|
2396
|
-
|
|
2397
|
-
def PastePart(self, dstX: int, dstY: int, srcBitmap: 'Bitmap', srcX: int = 0, srcY: int = 0, srcWidth: int = 0, srcHeight: int = 0) -> bool:
|
|
2398
|
-
"""
|
|
2399
|
-
Paste (srcX, srcY, srcWidth, srcHeight) of bitmap to (dstX, dstY) of self, modify the original Bitmap,
|
|
2400
|
-
only the intersection part of the bitmap is pasted.
|
|
2401
|
-
dstX: int, must >= 0.
|
|
2402
|
-
dstY: int, must >= 0.
|
|
2403
|
-
srcBitmap: `Bitmap`.
|
|
2404
|
-
srcX: int, must >= 0.
|
|
2405
|
-
srcY: int, must >= 0.
|
|
2406
|
-
srcWidth: int, must >= 0 and <= srcBitmap.Width - srcX.
|
|
2407
|
-
srcHeight: int, must >= 0 and <= srcBitmap.Height - srcY.
|
|
2408
|
-
Return bool, True if a part of srcBitmap is pasted.
|
|
2409
|
-
"""
|
|
2410
|
-
if srcWidth == 0:
|
|
2411
|
-
srcWidth = srcBitmap.Width - srcX
|
|
2412
|
-
if srcHeight == 0:
|
|
2413
|
-
srcHeight = srcBitmap.Height - srcY
|
|
2414
|
-
left, top, right, bottom = max(0, dstX), max(0, dstY), min(self.Width, dstX + srcWidth), min(self.Height, dstY + srcHeight)
|
|
2415
|
-
width, height = right - left, bottom - top
|
|
2416
|
-
if width <= 0 or height <= 0:
|
|
2417
|
-
return False
|
|
2418
|
-
nativeArray = srcBitmap.GetPixelColorsOfRect(srcX, srcY, width, height)
|
|
2419
|
-
return self.SetPixelColorsOfRect(dstX, dstY, width, height, nativeArray)
|
|
2420
|
-
|
|
2421
|
-
def Resize(self, width: int, height: int) -> 'MemoryBMP':
|
|
2422
|
-
"""
|
|
2423
|
-
Resize a copy of the original to size (width, height), the original Bitmap is not modified.
|
|
2424
|
-
width: int.
|
|
2425
|
-
height: int.
|
|
2426
|
-
Return a new `MemoryBMP`, the original is not modified.
|
|
2427
|
-
"""
|
|
2428
|
-
cbmp = _DllClient.instance().dll.BitmapResize(ctypes.c_size_t(self._bitmap), width, height)
|
|
2429
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2430
|
-
|
|
2431
|
-
def Rotate(self, angle: float, backgroundColor: int = 0xFFFFFFFF) -> 'MemoryBMP':
|
|
2432
|
-
"""
|
|
2433
|
-
Rotate angle degrees clockwise around the center point.
|
|
2434
|
-
Return a copy of the original with angle, the original Bitmap is not modified.
|
|
2435
|
-
angle: float, closewise.
|
|
2436
|
-
backgroundColor: int, ARGB(0xAARRGGBB) color format.
|
|
2437
|
-
Return a new `MemoryBMP`, the new Bitmap size may not be the same as the original.
|
|
2438
|
-
"""
|
|
2439
|
-
cbmp = _DllClient.instance().dll.BitmapRotate(ctypes.c_size_t(self._bitmap), ctypes.c_float(angle), backgroundColor)
|
|
2440
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2441
|
-
|
|
2442
|
-
def RotateFlip(self, rotateFlip: int) -> 'MemoryBMP':
|
|
2443
|
-
"""
|
|
2444
|
-
Rotate 90*n or Filp a copy of the original, the original Bitmap is not modified.
|
|
2445
|
-
rotateFlip: int, a value in class `RotateFlipType`.
|
|
2446
|
-
Return a new `MemoryBMP`, the original is not modified.
|
|
2447
|
-
"""
|
|
2448
|
-
bitmap = self.Copy()
|
|
2449
|
-
gdiStatus = _DllClient.instance().dll.BitmapRotateFlip(ctypes.c_size_t(bitmap._bitmap), rotateFlip)
|
|
2450
|
-
bitmap._GetSize()
|
|
2451
|
-
return bitmap
|
|
2452
|
-
|
|
2453
|
-
def RotateWithSameSize(self, dx: float, dy: float, angle: float, backgroundColor: int = 0xFFFFFFFF) -> 'MemoryBMP':
|
|
2454
|
-
"""
|
|
2455
|
-
Rotate angle degrees clockwise around the point (dx, dy) from a copy, the original Bitmap is not modified.
|
|
2456
|
-
angle: float, closewise.
|
|
2457
|
-
backgroundColor: int, ARGB(0xAARRGGBB) color format.
|
|
2458
|
-
Return a new `MemoryBMP`, the original is not modified.
|
|
2459
|
-
"""
|
|
2460
|
-
cbmp = _DllClient.instance().dll.BitmapRotateWithSameSize(ctypes.c_size_t(self._bitmap),
|
|
2461
|
-
ctypes.c_float(dx), ctypes.c_float(dy), ctypes.c_float(angle), backgroundColor)
|
|
2462
|
-
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2463
|
-
|
|
2464
|
-
def __str__(self) -> str:
|
|
2465
|
-
return '{}(Width={}, Height={})'.format(self.__class__.__name__, self._width, self._height)
|
|
2466
|
-
|
|
2467
|
-
def __repr__(self) -> str:
|
|
2468
|
-
return '<{}(Width={}, Height={}) at 0x{:X}>'.format(self.__class__.__name__, self._width, self._height, id(self))
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
class MultiFrameBitmap(Bitmap):
|
|
2472
|
-
def __init__(self, width: int = 0, height: int = 0):
|
|
2473
|
-
super().__init__(width, height)
|
|
2474
|
-
self._frameCount = 0
|
|
2475
|
-
self._index = 0
|
|
2476
|
-
|
|
2477
|
-
def SelectActiveFrame(self, index: int) -> bool:
|
|
2478
|
-
gdiStatus = _DllClient.instance().dll.MultiBitmapSelectActiveFrame(ctypes.c_size_t(self._bitmap), index)
|
|
2479
|
-
return gdiStatus == 0
|
|
2480
|
-
|
|
2481
|
-
def GetFrameCount(self) -> int:
|
|
2482
|
-
return self._frameCount
|
|
2483
|
-
|
|
2484
|
-
def __iter__(self):
|
|
2485
|
-
self._index = 0
|
|
2486
|
-
return self
|
|
2487
|
-
|
|
2488
|
-
def __next__(self) -> Bitmap:
|
|
2489
|
-
if self._index < self._frameCount:
|
|
2490
|
-
self.SelectActiveFrame(self._index)
|
|
2491
|
-
self._GetSize()
|
|
2492
|
-
self._index += 1
|
|
2493
|
-
return self.Copy()
|
|
2494
|
-
else:
|
|
2495
|
-
raise StopIteration
|
|
2496
|
-
|
|
2497
|
-
def __str__(self) -> str:
|
|
2498
|
-
return '{}(Width={}, Height={}, FrameCount={})'.format(self.__class__.__name__, self._width, self._height, self._frameCount)
|
|
2499
|
-
|
|
2500
|
-
def __repr__(self) -> str:
|
|
2501
|
-
return '<{}(Width={}, Height={}, FrameCount={}) at 0x{:X}>'.format(self.__class__.__name__, self._width, self._height, self._frameCount, id(self))
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
class MemoryBMP(Bitmap):
|
|
2505
|
-
pass
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
class BMP(Bitmap):
|
|
2509
|
-
pass
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
class JPEG(Bitmap):
|
|
2513
|
-
pass
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
class PNG(Bitmap):
|
|
2517
|
-
pass
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
class EMF(Bitmap):
|
|
2521
|
-
pass
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
class WMF(Bitmap):
|
|
2525
|
-
pass
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
class ICON(Bitmap):
|
|
2529
|
-
pass
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
class EXIF(Bitmap):
|
|
2533
|
-
pass
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
class TIFF(MultiFrameBitmap):
|
|
2537
|
-
@staticmethod
|
|
2538
|
-
def ToTiffFile(path: str, bitmaps: List['Bitmap']) -> bool:
|
|
2539
|
-
'''
|
|
2540
|
-
Save a list of bitmaps to a multi frame tiff file.
|
|
2541
|
-
path: str, file path.
|
|
2542
|
-
bitmaps: List[Bitmap].
|
|
2543
|
-
'''
|
|
2544
|
-
cbitmaps = (ctypes.c_size_t * len(bitmaps))()
|
|
2545
|
-
for i, bmp in enumerate(bitmaps):
|
|
2546
|
-
cbitmaps[i] = bmp._bitmap
|
|
2547
|
-
gdiStatus = _DllClient.instance().dll.MultiBitmapToFile(cbitmaps, None, len(bitmaps),
|
|
2548
|
-
ctypes.c_wchar_p(path), "image/tiff")
|
|
2549
|
-
return gdiStatus == 0
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
class GIF(MultiFrameBitmap):
|
|
2553
|
-
def __init__(self, width: int = 0, height: int = 0):
|
|
2554
|
-
super().__init__(width, height)
|
|
2555
|
-
self._frameDelay = ()
|
|
2556
|
-
|
|
2557
|
-
def _GetGifDealy(self):
|
|
2558
|
-
if self._bitmap:
|
|
2559
|
-
delayDataSize = _DllClient.instance().dll.MultiBitmapGetFrameDelaySize(ctypes.c_size_t(self._bitmap))
|
|
2560
|
-
delayData = (ctypes.c_byte * delayDataSize)()
|
|
2561
|
-
valueOffset = ctypes.c_int()
|
|
2562
|
-
gdiStatus = _DllClient.instance().dll.MultiBitmapGetFrameDelay(ctypes.c_size_t(self._bitmap), delayData, delayDataSize, ctypes.byref(valueOffset))
|
|
2563
|
-
if gdiStatus == 0:
|
|
2564
|
-
valueOffset = valueOffset.value
|
|
2565
|
-
# the uint of frame delay is 1/100 second
|
|
2566
|
-
self._frameDelay = tuple(10*int.from_bytes(delayData[i:i+4], byteorder='little') for i in range(valueOffset, valueOffset+4*self._frameCount, 4))
|
|
2567
|
-
|
|
2568
|
-
def GetFrameDelay(self, index: int) -> int:
|
|
2569
|
-
'''
|
|
2570
|
-
return frame delay in milliseconds
|
|
2571
|
-
'''
|
|
2572
|
-
return self._frameDelay[index]
|
|
2573
|
-
|
|
2574
|
-
def GetFrameDelays(self) -> List[int]:
|
|
2575
|
-
'''
|
|
2576
|
-
return a list of frame delays in milliseconds
|
|
2577
|
-
'''
|
|
2578
|
-
return list(self._frameDelay)
|
|
2579
|
-
|
|
2580
|
-
@staticmethod
|
|
2581
|
-
def ToGifFile(path: str, bitmaps: List[Bitmap], delays: List[int] = None) -> bool:
|
|
2582
|
-
'''
|
|
2583
|
-
Save a list of bitmaps to a multi frame gif file.
|
|
2584
|
-
path: str, file path.
|
|
2585
|
-
bitmaps: List[Bitmap].
|
|
2586
|
-
delays: List[int], frame delay time in milliseconds.
|
|
2587
|
-
Note: every frame delay must be > 10 ms.
|
|
2588
|
-
'''
|
|
2589
|
-
assert len(bitmaps) == len(delays)
|
|
2590
|
-
blen = len(bitmaps)
|
|
2591
|
-
cbitmaps = (ctypes.c_size_t * blen)()
|
|
2592
|
-
cdelays = (ctypes.c_uint32 * blen)()
|
|
2593
|
-
for i, bmp in enumerate(bitmaps):
|
|
2594
|
-
cbitmaps[i] = bmp._bitmap
|
|
2595
|
-
cdelays[i] = delays[i] // 10
|
|
2596
|
-
gdiStatus = _DllClient.instance().dll.SaveGif(cbitmaps, cdelays, blen, ctypes.c_wchar_p(path))
|
|
2597
|
-
return gdiStatus == 0
|
|
2598
|
-
|
|
2599
1732
|
|
|
2600
1733
|
_ClipboardLock = threading.Lock()
|
|
2601
1734
|
|
|
@@ -2728,49 +1861,6 @@ def SetClipboardHtml(htmlText: str) -> bool:
|
|
|
2728
1861
|
return ret
|
|
2729
1862
|
|
|
2730
1863
|
|
|
2731
|
-
def GetClipboardBitmap() -> Optional[Bitmap]:
|
|
2732
|
-
with _ClipboardLock:
|
|
2733
|
-
if _OpenClipboard(0):
|
|
2734
|
-
if ctypes.windll.user32.IsClipboardFormatAvailable(ClipboardFormat.CF_BITMAP):
|
|
2735
|
-
hClipboardData = ctypes.windll.user32.GetClipboardData(ClipboardFormat.CF_BITMAP)
|
|
2736
|
-
cbmp = _DllClient.instance().dll.BitmapFromHBITMAP(ctypes.c_size_t(hClipboardData), 0, 0, 0, 0)
|
|
2737
|
-
bitmap = Bitmap._FromGdiplusBitmap(cbmp)
|
|
2738
|
-
ctypes.windll.user32.CloseClipboard()
|
|
2739
|
-
return bitmap
|
|
2740
|
-
return None
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
def SetClipboardBitmap(bitmap: Bitmap) -> bool:
|
|
2744
|
-
"""
|
|
2745
|
-
Return bool, True if succeed otherwise False.
|
|
2746
|
-
"""
|
|
2747
|
-
ret = False
|
|
2748
|
-
with _ClipboardLock:
|
|
2749
|
-
if bitmap._bitmap and _OpenClipboard(0):
|
|
2750
|
-
ctypes.windll.user32.EmptyClipboard()
|
|
2751
|
-
hBitmap = _DllClient.instance().dll.BitmapToHBITMAP(ctypes.c_size_t(bitmap._bitmap), 0xFFFFFFFF)
|
|
2752
|
-
hBitmap2 = ctypes.windll.gdi32.CreateBitmap(bitmap.Width, bitmap.Height, 1, 32, 0)
|
|
2753
|
-
hdc = ctypes.windll.user32.GetDC(0)
|
|
2754
|
-
hdc1 = ctypes.windll.gdi32.CreateCompatibleDC(ctypes.c_void_p(hdc))
|
|
2755
|
-
hdc2 = ctypes.windll.gdi32.CreateCompatibleDC(ctypes.c_void_p(hdc))
|
|
2756
|
-
ctypes.windll.user32.ReleaseDC(0, ctypes.c_void_p(hdc))
|
|
2757
|
-
hOldBmp1 = ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc1), ctypes.c_void_p(hBitmap))
|
|
2758
|
-
hOldBmp2 = ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc2), ctypes.c_void_p(hBitmap2))
|
|
2759
|
-
ctypes.windll.gdi32.BitBlt(ctypes.c_void_p(hdc2), 0, 0, bitmap.Width, bitmap.Height, ctypes.c_void_p(hdc1), 0, 0, 0x00CC0020) # SRCCOPY
|
|
2760
|
-
ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc1), ctypes.c_void_p(hOldBmp1))
|
|
2761
|
-
ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc2), ctypes.c_void_p(hOldBmp2))
|
|
2762
|
-
ctypes.windll.gdi32.DeleteDC(ctypes.c_void_p(hdc1))
|
|
2763
|
-
ctypes.windll.gdi32.DeleteDC(ctypes.c_void_p(hdc2))
|
|
2764
|
-
ctypes.windll.gdi32.DeleteObject(ctypes.c_void_p(hBitmap))
|
|
2765
|
-
# system owns hClipboardData after calling SetClipboardData,
|
|
2766
|
-
# application can not write to or free the data once ownership has been transferred to the system
|
|
2767
|
-
if ctypes.windll.user32.SetClipboardData(ctypes.c_uint(ClipboardFormat.CF_BITMAP), ctypes.c_void_p(hBitmap2)):
|
|
2768
|
-
ret = True
|
|
2769
|
-
else:
|
|
2770
|
-
ctypes.windll.gdi32.DeleteObject(ctypes.c_void_p(hBitmap2))
|
|
2771
|
-
ctypes.windll.user32.CloseClipboard()
|
|
2772
|
-
return ret
|
|
2773
|
-
|
|
2774
1864
|
|
|
2775
1865
|
def Input(prompt: str, consoleColor: int = ConsoleColor.Default) -> str:
|
|
2776
1866
|
Logger.Write(prompt, consoleColor, writeToFile=False)
|