2022年9月10日土曜日

Pythonで画面切り取り

 PythonでPyAutoGUIを入れる方法


Pythonを使うのが初めてで、勘違いしていてうまくいかなかったのでメモ。

Pythonのシェルではなく、DOSプロンプトやPowerShellを使わないといけなかった。


(1)Pythonをインストールする

(2)インストールディレクトリ下に「Scripts」というフォルダがあり、その下にpip.exeがある。

(3)そのフォルダの何もないところを右クリック→「ターミナルで開く」

(4)Power Shellが開くので下記を入力してエンター

~\Programs\Python\Python310\Scripts> .\pip install pyautogui

  * ¥は実際はバックスラッシュ

エラーが出ずに処理が進めば成功

このへんもインストールしてください。

.\pip install pywin32

.\pip install pillow



任意の名前.pyw (実行ファイル)

a="100,200,600,800"
exec(open("00_set_psc.pyw").read()); psc(a)


00_Set_PSC.pyw (メインのスクリプト)

使い方:

(1) Sin+Shift+Sで画面を切り取る

(2) 00_set_psc.pywを実行
 クリップボードの画像のスクリーン上での位置を読み取ります。
 失敗した場合は、手動で座標設定を行います。
 座標を含んだ実行ファイル(~.pyw)を保存します。

(3) 上で作成した実行ファイル(省略時は 01_new.pyw)を実行する
 指定範囲のキャプチャができます


00_set_psc.pyw

import io
import pyautogui
from PIL import ImageGrab, Image
from tkinter import messagebox
from tkinter import filedialog
import win32clipboard

def send_to_clipboard(clip_type, data):
    win32clipboard.OpenClipboard()
    win32clipboard.EmptyClipboard()
    win32clipboard.SetClipboardData(clip_type, data)
    win32clipboard.CloseClipboard()

def psc(a):
    xy = map(int,a.split(","))
    screen_shot = ImageGrab.grab(all_screens=True , bbox=((tuple(xy))))
    try:
        screen_shot.save('02_new.png')
    finally:
        output = io.BytesIO()
        screen_shot.convert('RGB').save(output, 'BMP')
        data = output.getvalue()[14:]
        output.close()
        send_to_clipboard(win32clipboard.CF_DIB, data)

def set_xy():
    msg1a = "Place the cursor at the **"
    msg1b = "** corner of the snipping area and press ENTER key."

    messagebox.showinfo("",  msg1a + "Upper Left" + msg1b)
    x1, y1 = pyautogui.position()

    messagebox.showinfo("", msg1a + "Lower Right" + msg1b)
    x2, y2 = pyautogui.position()
    return  ','.join(map(str,[x1,y1,x2,y2]))

def save_file(xy):
    # Save dialog
    filename = filedialog.asksaveasfilename(
        title = " x1,y1,x2,y2 = " + xy + " (If omitted, the script is saved as 01_new.pyw)",
        filetypes = [("pyw", ".pyw")],
        initialdir = "./",
        defaultextension = "pyw"
    )

    if filename == "":
        filename = "01_new.pyw"

    t1 =f"a='{xy}'\nexec(open('00_set_psc.pyw').read()); psc(a)"
    f = open(filename, 'w')
    f.write(t1)
    f.close()

def get_latest():
    img = ImageGrab.grabclipboard()
    if isinstance(img, Image.Image):
        # print(Save clipboard image to PNG file (not mandatory))
        img.save('02_new.png')
        # print(Serch image location on screen)
        p = pyautogui.locateOnScreen(img)

        # print(Retry in the relieved condition)
        if p==None:
            p = pyautogui.locateOnScreen(img,confidence=0.9)

        if p!=None:
            #print("case1: Success to find the location")
            xy = ','.join(map(str,[p.left,p.top,p.left+p.width,p.top+p.height]))
            save_file(xy)
        else:
            #print("case2: Fail to find the location")
            save_file(set_xy())
    else:
        #print("No image in the clipboard")
        save_file(set_xy())

# If script is executed by itself, execute get_latest()
#print(__file__)
if "00_set_psc" in __file__:
    get_latest()


PyautoGUIのLocateonScreenがマルチモニタに対応していないので対策しておきます。

Qiita:pyautoguiを【超適当に】マルチディスプレイ環境に対応させる Part1 Part2

https://qiita.com/kznSk2/items/a6833c095aec3b8ce72e

https://qiita.com/kznSk2/items/1c756eb4bee80c66233d

上記を参考に

C:\Users\(ユーザー名)\AppData\Local\Programs\Python\Python310\Lib\site-packages\pyscreeze\

__init__.py

を下記のように編集する

下記は必須

ImageGrabに引数all_screens=Trueを渡す

@requiresPillow
def _screenshot_win32(imageFilename=None, region=None):
    """
    TODO
    """
    # TODO - Use the winapi to get a screenshot, and compare performance with ImageGrab.grab()
    # https://stackoverflow.com/a/3586280/1893164
    im = ImageGrab.grab(all_screens=True)


プライマリモニタが左上の人は以下不要。セカンダリモニタがプライマリの左や上にある場合は下記も必要です。

最初のimportセクションで win32apiをインポート

try:
    from PIL import Image
    from PIL import ImageOps
    from PIL import ImageDraw
    if sys.platform == 'win32': # TODO - Pillow now supports ImageGrab on macOS.
        import win32api
        from PIL import ImageGrab


LocateonScreenの出力を編集

def locateOnScreen(image, minSearchTime=0, **kwargs): """TODO - rewrite this minSearchTime - amount of time in seconds to repeat taking screenshots and trying to locate a match. The default of 0 performs a single search. """ start = time.time() while True: try: screenshotIm = screenshot(region=None) # the locateAll() function must handle cropping to return accurate coordinates, so don't pass a region here. retVal = locate(image, screenshotIm, **kwargs) # ここから if not(retVal == None) and sys.platform == 'win32': displays = win32api.EnumDisplayMonitors() left_min = min([display[2][0] for display in displays]) top_min = min([display[2][1] for display in displays]) retVal = Box( left = retVal[0] + left_min, top = retVal[1] + top_min, width = retVal[2], height = retVal[3] ) # ここまで追加分 try:











===============

設定用ファイルWSH版

セキュリティ的にも危ないし、そのうちデフォルト無効になってしまうと思いますが、一応載せておきます。

set.vbs

Set Excel = WScript.CreateObject("Excel.Application")

Dim fn, xy1, xy2, str1 , str2, fso

msgbox "カーソルをpoint1に合わせてエンターキーを押してください"
xy1 = API_GetMessagePos
fn = Inputbox( "カーソルをpoint2に合わせてエンターキーを押してください")
xy2 = API_GetMessagePos

str1= "a='" & xy1(0) & "," & xy1(1) & "," & xy2(0) & "," & xy2(1) & "'"
str2="exec(open('scr.py').read()); psc(a)"

If fn="" then fn ="03_new"
Set fso = CreateObject("Scripting.FileSystemObject")
Set tso = fso.CreateTextFile(fn & ".pyw", true)
tso.Write(str1 & vbcrlf & str2)
tso.Close

Function API_GetMessagePos()
    Dim ret, strHex, x, y
    Dim strFunction
    Const API_STRING = "CALL(""user32"",""GetMessagePos"",""J"")"
    strFunction = API_STRING
    ret = Excel.ExecuteExcel4Macro(strFunction)
    strHex = Right("00000000" & Hex(ret), 8)
    x = CLng("&H" & Right(strHex, 4))
    y = CLng("&H" &  Left(strHex, 4))
    API_GetMessagePos = Array(x, y)
End Function


=====以下作業時のメモ========

エクセルを前面に出してクリップボードから貼り付け、サイズと位置変更

import win32gui
import win32com.client

def excel_paste(a):
    xy = a.split(",")
    # Activate Excel
    memoapp = win32gui.FindWindow(None,'Excel')
    win32gui.SetForegroundWindow(memoapp)

    # set Excel application
    xl_app = win32com.client.GetObject(Class='Excel.Application')
    xl_app.Visible = True

    # Paste
    active_sheet = xl_app.ActiveSheet
    active_sheet.Paste()
    Sh = xl_app.ActiveSheet.Shapes(active_sheet.Shapes.Count)

    #change size
    Sh.Left = xy[0]
    Sh.Top = xy[1]
    Sh.Width = xy[2]


PowerPointを前面に出して貼り付け、位置変更(動作未確認)

import win32gui
import win32com.client

def ppt_paste(a):
    xy = a.split(",")
    # Activate ppt
    memoapp = win32gui.FindWindow(None,'PowerPoint')
    win32gui.SetForegroundWindow(memoapp)

    # set power point application
    pp_app = win32com.client.GetObject(Class='PowerPoint.Application')


    pp_app.Visible = True

    # If Slide index pain is selected
    if (pp_app.ActiveWindow.Selection.Type == 1):
        pp_app.ActiveWindow.WiewType = 1
        pp_app.ActiveWindow.WiewType = 9


    # get current slide number
    now_slno = pp_app.ActiveWindow.Selection.SlideRange.SlideIndex
    sld = pp_app.ActiveWindow.Slides(now_slno)

    # paste
    sld.Paste()

    #change size
    Sh = sld.Shapes(sld.Shapes.Count)
    Sh.Left = xy[0]
    Sh.Top = xy[1]
    Sh.Width = xy[2]



paste.pyw (実行ファイル)

a="100,200,400"
exec(open("scr.py").read()); ppt_paste(a)




0 件のコメント:

コメントを投稿