From d6e5194ef0d4ea213b72acee04fbe392cc351bcb Mon Sep 17 00:00:00 2001 From: Binit Date: Wed, 28 Sep 2022 16:57:49 +0530 Subject: [PATCH 1/4] Re-did windows clipboard using clipette This is much faster. Basically instant. Ditched support for bitmap in `push` because blender doesn't completely support bitmaps and I think we don't need it anyways. --- __init__.py | 2 +- imagepaste/clipboard/windows/clipette.py | 546 +++++++++++++++++++++++ imagepaste/clipboard/windows/windows.py | 161 ++++--- 3 files changed, 637 insertions(+), 72 deletions(-) create mode 100644 imagepaste/clipboard/windows/clipette.py diff --git a/__init__.py b/__init__.py index d3156d9..a5df0e3 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ "name": "ImagePaste", "author": "Binit", "blender": (2, 80, 0), - "version": (1, 8, 0), + "version": (1, 9, 0), "category": "Import-Export", "support": "COMMUNITY", "doc_url": "https://github.com/Yeetus3141/ImagePaste#readme", diff --git a/imagepaste/clipboard/windows/clipette.py b/imagepaste/clipboard/windows/clipette.py new file mode 100644 index 0000000..60cca2f --- /dev/null +++ b/imagepaste/clipboard/windows/clipette.py @@ -0,0 +1,546 @@ +# must open clipboard before using any function +# must close clipboard afterwards + +import ctypes +from ctypes.wintypes import * +from os.path import join as path_join +from sys import getfilesystemencoding + +GMEM_MOVABLE = 2 +INT_P = ctypes.POINTER(ctypes.c_int) + +CF_UNICODETEXT = 13 +CF_HDROP = 15 +CF_BITMAP = 2 # hbitmap +CF_DIB = 8 # DIB and BITMAP are interconvertable as from windows clipboard +CF_DIBV5 = 17 + +# bitmap compression types +BI_RGB = 0 +BI_RLE8 = 1 +BI_RLE4 = 2 +BI_BITFIELDS = 3 +BI_JPEG = 4 +BI_PNG = 5 +BI_ALPHABITFIELDS = 6 + +format_dict = { + 1: 'CF_TEXT', + 2: 'CF_BITMAP', + 3: 'CF_METAFILEPICT', + 4: 'CF_SYLK', + 5: 'CF_DIF', + 6: 'CF_TIFF', + 7: 'CF_OEMTEXT', + 8: 'CF_DIB', + 9: 'CF_PALETTE', + 10: 'CF_PENDATA', + 11: 'CF_RIFF', + 12: 'CF_WAVE', + 13: 'CF_UNICODETEXT', + 14: 'CF_ENHMETAFILE', + 15: 'CF_HDROP', + 16: 'CF_LOCALE', + 17: 'CF_DIBV5', +} + +# todo: +# implement more formats (JPEG) +# write docs docs docs + +user32 = ctypes.windll.user32 +kernel32 = ctypes.windll.kernel32 +shell32 = ctypes.windll.shell32 + +user32.OpenClipboard.argtypes = HWND, +user32.OpenClipboard.restype = BOOL +user32.GetClipboardData.argtypes = UINT, +user32.GetClipboardData.restype = HANDLE +user32.SetClipboardData.argtypes = UINT, HANDLE +user32.SetClipboardData.restype = HANDLE +user32.CloseClipboard.argtypes = None +user32.CloseClipboard.restype = BOOL +user32.IsClipboardFormatAvailable.argtypes = UINT, +user32.IsClipboardFormatAvailable.restype = BOOL +user32.CountClipboardFormats.argtypes = None +user32.CountClipboardFormats.restype = UINT +user32.EnumClipboardFormats.argtypes = UINT, +user32.EnumClipboardFormats.restype = UINT +user32.GetClipboardFormatNameA.argtypes = UINT, LPSTR, UINT +user32.GetClipboardFormatNameA.restype = UINT +user32.RegisterClipboardFormatA.argtypes = LPCSTR, +user32.RegisterClipboardFormatA.restype = UINT +user32.RegisterClipboardFormatW.argtypes = LPCWSTR, +user32.RegisterClipboardFormatW.restype = UINT +user32.RegisterClipboardFormatW.argtypes = LPCWSTR, +user32.RegisterClipboardFormatW.restype = UINT +user32.EmptyClipboard.argtypes = None +user32.EmptyClipboard.restype = BOOL + +kernel32.GlobalAlloc.argtypes = UINT, ctypes.c_size_t +kernel32.GlobalAlloc.restype = HGLOBAL +kernel32.GlobalSize.argtypes = HGLOBAL, +kernel32.GlobalSize.restype = UINT +kernel32.GlobalLock.argtypes = HGLOBAL, +kernel32.GlobalLock.restype = LPVOID +kernel32.GlobalUnlock.argtypes = HGLOBAL, +kernel32.GlobalUnlock.restype = BOOL + +shell32.DragQueryFile.argtypes = HANDLE, UINT, ctypes.c_void_p, UINT +shell32.DragQueryFile.restype = UINT + + +class BITMAPFILEHEADER(ctypes.Structure): + _pack_ = 1 # structure field byte alignment + _fields_ = [ + ('bfType', WORD), # file type ("BM") + ('bfSize', DWORD), # file size in bytes + ('bfReserved1', WORD), # must be zero + ('bfReserved2', WORD), # must be zero + ('bfOffBits', DWORD), # byte offset to the pixel array + ] +sizeof_BITMAPFILEHEADER = ctypes.sizeof(BITMAPFILEHEADER) + +class BITMAPINFOHEADER(ctypes.Structure): + _pack_ = 1 # structure field byte alignment + _fields_ = [ + ('biSize', DWORD), + ('biWidth', LONG), + ('biHeight', LONG), + ('biPLanes', WORD), + ('biBitCount', WORD), + ('biCompression', DWORD), + ('biSizeImage', DWORD), + ('biXPelsPerMeter', LONG), + ('biYPelsPerMeter', LONG), + ('biClrUsed', DWORD), + ('biClrImportant', DWORD) + ] +sizeof_BITMAPINFOHEADER = ctypes.sizeof(BITMAPINFOHEADER) + +class BITMAPV4HEADER(ctypes.Structure): + _pack_ = 1 # structure field byte alignment + _fields_ = [ + ('bV4Size', DWORD), + ('bV4Width', LONG), + ('bV4Height', LONG), + ('bV4PLanes', WORD), + ('bV4BitCount', WORD), + ('bV4Compression', DWORD), + ('bV4SizeImage', DWORD), + ('bV4XPelsPerMeter', LONG), + ('bV4YPelsPerMeter', LONG), + ('bV4ClrUsed', DWORD), + ('bV4ClrImportant', DWORD), + ('bV4RedMask', DWORD), + ('bV4GreenMask', DWORD), + ('bV4BlueMask', DWORD), + ('bV4AlphaMask', DWORD), + ('bV4CSTypes', DWORD), + ('bV4RedEndpointX', LONG), + ('bV4RedEndpointY', LONG), + ('bV4RedEndpointZ', LONG), + ('bV4GreenEndpointX', LONG), + ('bV4GreenEndpointY', LONG), + ('bV4GreenEndpointZ', LONG), + ('bV4BlueEndpointX', LONG), + ('bV4BlueEndpointY', LONG), + ('bV4BlueEndpointZ', LONG), + ('bV4GammaRed', DWORD), + ('bV4GammaGreen', DWORD), + ('bV4GammaBlue', DWORD) + ] +sizeof_BITMAPV4HEADER = ctypes.sizeof(BITMAPV4HEADER) + +class BITMAPV5HEADER(ctypes.Structure): + _pack_ = 1 # structure field byte alignment + _fields_ = [ + ('bV5Size', DWORD), + ('bV5Width', LONG), + ('bV5Height', LONG), + ('bV5PLanes', WORD), + ('bV5BitCount', WORD), + ('bV5Compression', DWORD), + ('bV5SizeImage', DWORD), + ('bV5XPelsPerMeter', LONG), + ('bV5YPelsPerMeter', LONG), + ('bV5ClrUsed', DWORD), + ('bV5ClrImportant', DWORD), + ('bV5RedMask', DWORD), + ('bV5GreenMask', DWORD), + ('bV5BlueMask', DWORD), + ('bV5AlphaMask', DWORD), + ('bV5CSTypes', DWORD), + ('bV5RedEndpointX', LONG), + ('bV5RedEndpointY', LONG), + ('bV5RedEndpointZ', LONG), + ('bV5GreenEndpointX', LONG), + ('bV5GreenEndpointY', LONG), + ('bV5GreenEndpointZ', LONG), + ('bV5BlueEndpointX', LONG), + ('bV5BlueEndpointY', LONG), + ('bV5BlueEndpointZ', LONG), + ('bV5GammaRed', DWORD), + ('bV5GammaGreen', DWORD), + ('bV5GammaBlue', DWORD), + ('bV5Intent', DWORD), + ('bV5ProfileData', DWORD), + ('bV5ProfileSize', DWORD), + ('bV5Reserved', DWORD) + ] +sizeof_BITMAPV5HEADER = ctypes.sizeof(BITMAPV5HEADER) + +def open_clipboard(): + """ + Opens clipboard. Must be called before any action in performed. + + :return: (int) 0 if function fails, otherwise 1 + """ + return user32.OpenClipboard(0) + +def close_clipboard(): + """ + Closes clipboard. Must be called after all actions are performed. + + :return: (int) 0 if function fails, otherwise 1 + """ + return user32.CloseClipboard() + +def empty_cliboard(): + """ + Empties clipboard. Should be called before any setter actions. + + :return: (int) 0 if function fails, otherwise 1 + """ + return user32.EmptyClipboard() + +def get_UNICODETEXT(): + """ + get text from clipboard as string + + :return: (str) text grabbed from clipboard + """ + + # user32.OpenClipboard(0) + data = user32.GetClipboardData(CF_UNICODETEXT) + dest = kernel32.GlobalLock(data) + text = ctypes.wstring_at(dest) + kernel32.GlobalUnlock(data) + # user32.CloseClipboard() + + return text + +def set_UNICODETEXT(text): + """ + set text to clipboard as CF_UNICODETEXT + + :param str text: text to set to clipboard + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + """ + + data = text.encode('utf-16le') + size = len(data) + 2 + + h_mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) + dest = kernel32.GlobalLock(h_mem) + ctypes.memmove(dest, data, size) + kernel32.GlobalUnlock(h_mem) + + # user32.OpenClipboard(0) + # user32.EmptyClipboard() + user32.SetClipboardData(CF_UNICODETEXT, h_mem) + # user32.CloseClipboard() + return 1 + +def get_FILEPATHS(): + """ + get list of files from clipboard. + + :return: (list) filepaths + """ + filepaths = [] + + #user32.OpenClipboard(0) + data = user32.GetClipboardData(CF_HDROP) + file_count = shell32.DragQueryFile(data, -1, None, 0) + for index in range(file_count): + buf = ctypes.c_buffer(260) + shell32.DragQueryFile(data, index, buf, ctypes.sizeof(buf)) + filepaths.append(buf.value.decode(getfilesystemencoding())) + #user32.CloseClipboard() + + return filepaths + +def get_DIB(filepath = '', filename = 'bitmap'): + """ + get image from clipboard as a bitmap and saves to filepath. + + :param str filepath: filepath to save image into + :param str filename: filename of the image + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + """ + + # user32.OpenClipboard(0) + if not user32.IsClipboardFormatAvailable(CF_DIB): + raise RuntimeError("clipboard image not available in 'CF_DIB' format") + + h_mem = user32.GetClipboardData(CF_DIB) + dest = kernel32.GlobalLock(h_mem) + size = kernel32.GlobalSize(dest) + data = bytes((ctypes.c_char*size).from_address(dest)) + + bm_ih = BITMAPINFOHEADER() + header_size = sizeof_BITMAPINFOHEADER + ctypes.memmove(ctypes.pointer(bm_ih), data, header_size) + + compression = bm_ih.biCompression + if compression not in (BI_BITFIELDS, BI_RGB): + raise RuntimeError(f'unsupported compression type {format(compression)}') + + bm_fh = BITMAPFILEHEADER() + ctypes.memset(ctypes.pointer(bm_fh), 0, sizeof_BITMAPFILEHEADER) + bm_fh.bfType = ord('B') | (ord('M') << 8) + bm_fh.bfSize = sizeof_BITMAPFILEHEADER + len(str(data)) + sizeof_COLORTABLE = 0 + bm_fh.bfOffBits = sizeof_BITMAPFILEHEADER + header_size + sizeof_COLORTABLE + + img_path = path_join(filepath, filename + '.bmp') + with open(img_path, 'wb') as bmp_file: + bmp_file.write(bm_fh) + bmp_file.write(data) + + kernel32.GlobalUnlock(h_mem) + # user32.CloseClipboard() + return 1 + +def get_DIBV5(filepath = '', filename = 'bitmapV5'): + """ + get image from clipboard as a bitmapV5 and saves to filepath + + :param str filepath: filepath to save image into + :param str filename: filename of the image + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + """ + + # user32.OpenClipboard(0) + if not user32.IsClipboardFormatAvailable(CF_DIBV5): + raise RuntimeError("clipboard image not available in 'CF_DIBV5' format") + + h_mem = user32.GetClipboardData(CF_DIBV5) + dest = kernel32.GlobalLock(h_mem) + size = kernel32.GlobalSize(dest) + data = bytes((ctypes.c_char*size).from_address(dest)) + + bm_ih = BITMAPV5HEADER() + header_size = sizeof_BITMAPV5HEADER + ctypes.memmove(ctypes.pointer(bm_ih), data, header_size) + + if bm_ih.bV5Compression == BI_RGB: + # convert BI_RGB to BI_BITFIELDS so as to properly support an alpha channel + # everything other than the usage of bitmasks is same compared to BI_BITFIELDS so we manually add that part and put bV5Compression to BI_BITFIELDS + # info on these header structures -> https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types + # and -> https://en.wikipedia.org/wiki/BMP_file_format + + bi_compression = bytes([3, 0, 0, 0]) + bi_bitmasks = bytes([0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255]) + data = data[:16] + bi_compression + data[20:40] + bi_bitmasks + data[56:] + + elif bm_ih.bV5Compression == BI_BITFIELDS: + # we still need to add bitmask (bV5AlphaMask) for softwares to recognize the alpha channel + data = data[:52] + bytes([0, 0, 0, 255]) + data[56:] + + else: + raise RuntimeError(f'unsupported compression type {format(bm_ih.bV5Compression)}') + + bm_fh = BITMAPFILEHEADER() + ctypes.memset(ctypes.pointer(bm_fh), 0, sizeof_BITMAPFILEHEADER) + bm_fh.bfType = ord('B') | (ord('M') << 8) + bm_fh.bfSize = sizeof_BITMAPFILEHEADER + len(str(data)) + sizeof_COLORTABLE = 0 + bm_fh.bfOffBits = sizeof_BITMAPFILEHEADER + header_size + sizeof_COLORTABLE + + img_path = path_join(filepath, filename + '.bmp') + with open(img_path, 'wb') as bmp_file: + bmp_file.write(bm_fh) + bmp_file.write(data) + + kernel32.GlobalUnlock(h_mem) + # user32.CloseClipboard() + return 1 + +def get_PNG(filepath = '', filename = 'PNG'): + """ + get image in 'PNG' or 'image/png' format from clipboard and saves to filepath + + :param str filepath: filepath to save image into + :param str filename: filename of the image + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + """ + + # user32.OpenClipboard(0) + png_format = 0 + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) + image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('image/png')) + if user32.IsClipboardFormatAvailable(PNG): + png_format = PNG + elif user32.IsClipboardFormatAvailable(image_png): + png_format = image_png + else: + raise RuntimeError("clipboard image not available in 'PNG'or 'image/png' format") + + h_mem = user32.GetClipboardData(png_format) + dest = kernel32.GlobalLock(h_mem) + size = kernel32.GlobalSize(dest) + data = bytes((ctypes.c_char*size).from_address(dest)) + kernel32.GlobalUnlock(h_mem) + # user32.CloseClipboard() + + img_path = path_join(filepath, filename + '.png') + with open (img_path, 'wb') as png_file: + png_file.write(data) + + return 1 + +def set_DIB(src_bmp): + """ + set source bitmap image to clipboard as a CF_DIB or CF_DIBV5 according to the image + + :param str src_bmp: filepath of source image + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + """ + + with open(src_bmp, 'rb') as img: + data = img.read() + output = data[14:] + size = len(output) + print(list(bytearray(output)[:200])) + + mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) + h_mem = kernel32.GlobalLock(mem) + ctypes.memmove(ctypes.cast(h_mem, INT_P), ctypes.cast(output, INT_P), size) + kernel32.GlobalUnlock(mem) + + + if output[0] in [56, 108, 124]: + # img contains DIBV5 or DIBV4 or DIBV3 Header + fmt = CF_DIBV5 + else: + fmt = CF_DIB + + # user32.OpenClipboard(0) + # user32.EmptyClipboard() + user32.SetClipboardData(fmt, h_mem) + # user32.CloseClipboard() + return 1 + +def set_PNG(src_png): + """ + set source png image to clipboard in 'PNG' format + + :param str src_png: filepath of source image + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + """ + with open(src_png, 'rb') as img: + data = img.read() + size = len(data) + + mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) + h_mem = kernel32.GlobalLock(mem) + ctypes.memmove(h_mem, data, size) + kernel32.GlobalUnlock(mem) + + # user32.OpenClipboard(0) + # user32.EmptyClipboard() + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) + user32.SetClipboardData(PNG, h_mem) + # user32.CloseClipboard() + return 1 + +def is_format_available(format_id): + """ + checks whether specified format is currently available on the clipboard + + :param int format_id: id of format to check for + :return: (bool) True if specified format is available + """ + + # user32.OpenClipboard(0) + is_format = user32.IsClipboardFormatAvailable(format_id) + # user32.CloseClipboard() + return bool(is_format) + +def get_available_formats(buffer_size = 32): + """ + gets a dict of all the currently available formats on the clipboard + + :param int buffer_size: (optional) buffer size to store name of each format in + :return: a dict {format_id : format_name} of all available formats + """ + available_formats = dict() + # user32.OpenClipboard(0) + fmt = 0 + for i in range(user32.CountClipboardFormats()): + # must put previous fmt (starting from 0) in EnumClipboardFormats() to get the next one + fmt = user32.EnumClipboardFormats(fmt) + name_buf = ctypes.create_string_buffer(buffer_size) + name_len = user32.GetClipboardFormatNameA(fmt, name_buf, buffer_size) + fmt_name = name_buf.value.decode() + + # standard formats do not return any name, so we set one from out dictionary + if fmt_name == '' and fmt in format_dict.keys(): + fmt_name = format_dict[fmt] + available_formats.update({fmt : fmt_name}) + + # user32.CloseClipboard() + return available_formats + +def get_image(filepath = '', filename = 'image'): + """ + gets image from clipboard in a format according to a priority list (PNG > DIBV5 > DIB) + + """ + # user32.OpenClipboard(0) + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) + image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('image/png')) + + if user32.IsClipboardFormatAvailable(PNG) or user32.IsClipboardFormatAvailable(image_png): + get_PNG(filepath, filename) + return 1 + elif user32.IsClipboardFormatAvailable(CF_DIBV5): + get_DIBV5(filepath, filename) + return 1 + elif user32.IsClipboardFormatAvailable(CF_DIB): + get_DIB(filepath, filename) + return 1 + else: + raise RuntimeError('image on clipboard not available in any supported format') + return 0 + +def set_image(src_img): + """ + (NOT FULLY IMPLEMENTED) set source image to clipboard in multiple formats (PNG, DIB). + + :param str src_img: filepath of source image + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + """ + # this is more complicated... gotta interconvert images + # looking into ways to get this done with ctypes as well - NO IM DONE + + # temporary solution + img_extn = src_img[(len(src_img)-3):].lower() + if img_extn == 'bmp': + # image format is bitmap + set_DIB(src_img) + elif img_extn == 'png': + # image format is png + set_PNG(src_img) + else: + raise RuntimeError('Unsupported image format') + + return 1 + +if __name__ == '__main__': + if open_clipboard(): + empty_cliboard() + # set_UNICODETEXT('pasta pasta pasta pasta pasta pasta') + close_clipboard() \ No newline at end of file diff --git a/imagepaste/clipboard/windows/windows.py b/imagepaste/clipboard/windows/windows.py index 11736ae..a5619a8 100644 --- a/imagepaste/clipboard/windows/windows.py +++ b/imagepaste/clipboard/windows/windows.py @@ -1,4 +1,5 @@ from __future__ import annotations +from posixpath import abspath from ..clipboard import Clipboard from ...report import Report @@ -30,38 +31,34 @@ def push(cls, save_directory: str) -> WindowsClipboard: operations under Report object and a list of Image objects holding pushed images information. """ - from os.path import join + from os.path import join, splitext + from . import clipette filename = cls.get_filename() filepath = join(save_directory, filename) - image_script = ( - "Add-Type -AssemblyName System.Windows.Forms; " - "Add-Type -AssemblyName System.Drawing; " - "$clipboard = [System.Windows.Forms.Clipboard]::GetDataObject(); " - "$imageStream = $clipboard.GetData('PNG'); " - "if ($imageStream) {" - "$bitmap = New-Object System.Drawing.Bitmap($imageStream); " - f"$bitmap.Save('{filepath}', [System.Drawing.Imaging.ImageFormat]::Png); " - "Write-Output 0" - "}" - ) - process = Process.execute(cls.get_powershell_args(image_script), split=False) - if process.stderr: + clipette.open_clipboard() + + # load multiple images first if filepaths are available (as CF_HDROP, id 15) + if clipette.is_format_available(15): + filepaths = clipette.get_FILEPATHS() + clipette.close_clipboard() + + images = [Image(filepath) for filepath in filepaths] + return cls(Report(6, f"Pasted {len(images)} image files: {images}"), images) + + # get image if available as 'PNG' or 'image/png' which covers pretty much all software. + # Ditched BITMAP support because blender doesn't completely support all Bitmap sub-formats + # and I couldn't find any software that copies only as a bitmap. + output = clipette.get_PNG(save_directory, splitext(filename)[0]) + clipette.close_clipboard() + if output != 1: image = Image(filepath) return cls(Report(3, f"Cannot save image: {image} ({process.stderr})")) - if process.stdout == "0": + else: image = Image(filepath, pasted=True) return cls(Report(6, f"Saved and pasted 1 image: {image}"), [image]) - file_script = ( - "$files = Get-Clipboard -Format FileDropList; " - "if ($files) { $files.fullname }" - ) - process = Process.execute(cls.get_powershell_args(file_script)) - if process.stdout[0] != "": - images = [Image(filepath) for filepath in process.stdout] - return cls(Report(6, f"Pasted {len(images)} image files: {images}"), images) return cls(Report(2)) @classmethod @@ -76,60 +73,82 @@ def pull(cls, image_path: str) -> WindowsClipboard: operations under Report object and a list of one Image object that holds information of the pulled image we put its path to the input. """ - # Script that supports transparency. Save images to clipboard as PNG and Bitmap. - # Populating the clipboard with Bitmap data which is equivalent to using the - # `Clipboard.SetImage` method. - script = ( - "Add-Type -Assembly System.Windows.Forms; " - "Add-Type -Assembly System.Drawing; " - f"$image = [Drawing.Image]::FromFile('{image_path}'); " - "$imageStream = New-Object System.IO.MemoryStream; " - "$image.Save($imageStream, [System.Drawing.Imaging.ImageFormat]::Png); " - "$dataObj = New-Object System.Windows.Forms.DataObject('Bitmap', $image); " - "$dataObj.SetData('PNG', $imageStream); " - "[System.Windows.Forms.Clipboard]::SetDataObject($dataObj, $true); " - ) - - process = Process.execute(cls.get_powershell_args(script)) - if process.stderr: - return cls(Report(4, f"Cannot load image: {image_path} ({process.stderr})")) + from . import clipette + from bpy.path import abspath + + clipette.open_clipboard() + clipette.empty_cliboard() + + image_path = abspath(image_path) + image_format = image_path[-3:].lower() + # bmp (as DIB, DIBV5, BITMAP) and png (as PNG) should be enough formats to work with most applications + if image_format != 'bmp': + clipette.set_DIB(cls.convert_image(image_path, 'BMP')) + else: + clipette.set_DIB(image_path) + + if image_format != 'png': + clipette.set_PNG(cls.convert_image(image_path, 'PNG')) + else: + clipette.set_PNG(image_path) + + clipette.close_clipboard() + image = Image(image_path) return cls(Report(5, f"Copied 1 image: {image}"), [image]) + @staticmethod - def get_powershell_args(script: str) -> list[str]: - """A static method to get PowerShell arguments from a script for a process. + def convert_image(image_path: str, format: str) -> str: + """A static method to convert image format and get new image filepath. + Saves converted image in ImagePaste's working directory. Args: - script (str): A script to be executed. + image_path (str): Filepath of source image. + format (str): Format to convert image to as in the image extension ('png', 'bmp', etc) Returns: - list[str]: A list of PowerShell arguments for operating a process. + str: Filepath of converted image. """ - from os import getenv - from os.path import join - - powershell_args = [ - join( - getenv("SystemRoot"), - "System32", - "WindowsPowerShell", - "v1.0", - "powershell.exe", - ), - "-NoProfile", - "-NoLogo", - "-NonInteractive", - "-WindowStyle", - "Hidden", - ] - script = ( - "$OutputEncoding = " - "[System.Console]::OutputEncoding = " - "[System.Console]::InputEncoding = " - "[System.Text.Encoding]::UTF8; " - + "$PSDefaultParameterValues['*:Encoding'] = 'utf8'; " - + script - ) - args = powershell_args + ["& { " + script + " }"] - return args + # should probably incorpoate this function into the Image class or something + from ...tree import get_save_directory + from os.path import join, basename, splitext + from bpy_extras.image_utils import load_image + import bpy + + RGBA_unsupported = ['BMP', 'JPEG'] + format_ext = { + 'BMP': '.bmp', + 'IRIS': '.rgb', + 'PNG': '.png', + 'JPEG': '.jpg', + 'JPEG2000': '.jp2', + 'TARGA': '.tga', + 'TARGA_RAW': '.tga', + 'CINEON': '.cin', + 'DPX': '.dpx', + 'OPEN_EXR_MULTILAYER': '.exr', + 'OPEN_EXR': '.exr', + 'HDR': '.hdr', + 'TIFF': '.tif', + 'WEBP': '.webp' + } + + img_settings = bpy.context.scene.render.image_settings + prev_file_format = img_settings.file_format + prev_color_mode = img_settings.color_mode + prev_quality = img_settings.quality + + img_settings.file_format = format + img_settings.quality = 100 + img_settings.color_mode = 'RGB' if format in RGBA_unsupported else 'RGBA' + + image = load_image(image_path) + image_path_c = join(get_save_directory(), splitext(basename(image_path))[0] + format_ext[format]) + image.save_render(image_path_c) + + img_settings.file_format = prev_file_format + img_settings.color_mode = prev_color_mode + img_settings.quality = prev_quality + + return image_path_c From b66b6959dd64dc146919b26429da3a0ce7438ed1 Mon Sep 17 00:00:00 2001 From: Thanh Phan Date: Sat, 1 Oct 2022 11:49:12 +0700 Subject: [PATCH 2/4] Reformat a little bit --- imagepaste/clipboard/windows/clipette.py | 438 +++++++++++++---------- imagepaste/clipboard/windows/windows.py | 78 ++-- 2 files changed, 294 insertions(+), 222 deletions(-) diff --git a/imagepaste/clipboard/windows/clipette.py b/imagepaste/clipboard/windows/clipette.py index 60cca2f..53a6c80 100644 --- a/imagepaste/clipboard/windows/clipette.py +++ b/imagepaste/clipboard/windows/clipette.py @@ -2,7 +2,20 @@ # must close clipboard afterwards import ctypes -from ctypes.wintypes import * +from ctypes.wintypes import ( + HWND, + BOOL, + HANDLE, + LPCSTR, + LPCWSTR, + LPSTR, + UINT, + HGLOBAL, + DWORD, + LONG, + WORD, + LPVOID, +) from os.path import join as path_join from sys import getfilesystemencoding @@ -11,8 +24,8 @@ CF_UNICODETEXT = 13 CF_HDROP = 15 -CF_BITMAP = 2 # hbitmap -CF_DIB = 8 # DIB and BITMAP are interconvertable as from windows clipboard +CF_BITMAP = 2 # hbitmap +CF_DIB = 8 # DIB and BITMAP are inter-convertible as from windows clipboard CF_DIBV5 = 17 # bitmap compression types @@ -25,23 +38,23 @@ BI_ALPHABITFIELDS = 6 format_dict = { - 1: 'CF_TEXT', - 2: 'CF_BITMAP', - 3: 'CF_METAFILEPICT', - 4: 'CF_SYLK', - 5: 'CF_DIF', - 6: 'CF_TIFF', - 7: 'CF_OEMTEXT', - 8: 'CF_DIB', - 9: 'CF_PALETTE', - 10: 'CF_PENDATA', - 11: 'CF_RIFF', - 12: 'CF_WAVE', - 13: 'CF_UNICODETEXT', - 14: 'CF_ENHMETAFILE', - 15: 'CF_HDROP', - 16: 'CF_LOCALE', - 17: 'CF_DIBV5', + 1: "CF_TEXT", + 2: "CF_BITMAP", + 3: "CF_METAFILEPICT", + 4: "CF_SYLK", + 5: "CF_DIF", + 6: "CF_TIFF", + 7: "CF_OEMTEXT", + 8: "CF_DIB", + 9: "CF_PALETTE", + 10: "CF_PENDATA", + 11: "CF_RIFF", + 12: "CF_WAVE", + 13: "CF_UNICODETEXT", + 14: "CF_ENHMETAFILE", + 15: "CF_HDROP", + 16: "CF_LOCALE", + 17: "CF_DIBV5", } # todo: @@ -52,38 +65,51 @@ kernel32 = ctypes.windll.kernel32 shell32 = ctypes.windll.shell32 -user32.OpenClipboard.argtypes = HWND, +user32.OpenClipboard.argtypes = (HWND,) user32.OpenClipboard.restype = BOOL -user32.GetClipboardData.argtypes = UINT, + +user32.GetClipboardData.argtypes = (UINT,) user32.GetClipboardData.restype = HANDLE + user32.SetClipboardData.argtypes = UINT, HANDLE user32.SetClipboardData.restype = HANDLE + user32.CloseClipboard.argtypes = None user32.CloseClipboard.restype = BOOL -user32.IsClipboardFormatAvailable.argtypes = UINT, + +user32.IsClipboardFormatAvailable.argtypes = (UINT,) user32.IsClipboardFormatAvailable.restype = BOOL + user32.CountClipboardFormats.argtypes = None user32.CountClipboardFormats.restype = UINT -user32.EnumClipboardFormats.argtypes = UINT, + +user32.EnumClipboardFormats.argtypes = (UINT,) user32.EnumClipboardFormats.restype = UINT + user32.GetClipboardFormatNameA.argtypes = UINT, LPSTR, UINT user32.GetClipboardFormatNameA.restype = UINT -user32.RegisterClipboardFormatA.argtypes = LPCSTR, + +user32.RegisterClipboardFormatA.argtypes = (LPCSTR,) user32.RegisterClipboardFormatA.restype = UINT -user32.RegisterClipboardFormatW.argtypes = LPCWSTR, + +user32.RegisterClipboardFormatW.argtypes = (LPCWSTR,) user32.RegisterClipboardFormatW.restype = UINT -user32.RegisterClipboardFormatW.argtypes = LPCWSTR, +user32.RegisterClipboardFormatW.argtypes = (LPCWSTR,) user32.RegisterClipboardFormatW.restype = UINT + user32.EmptyClipboard.argtypes = None user32.EmptyClipboard.restype = BOOL kernel32.GlobalAlloc.argtypes = UINT, ctypes.c_size_t kernel32.GlobalAlloc.restype = HGLOBAL -kernel32.GlobalSize.argtypes = HGLOBAL, + +kernel32.GlobalSize.argtypes = (HGLOBAL,) kernel32.GlobalSize.restype = UINT -kernel32.GlobalLock.argtypes = HGLOBAL, + +kernel32.GlobalLock.argtypes = (HGLOBAL,) kernel32.GlobalLock.restype = LPVOID -kernel32.GlobalUnlock.argtypes = HGLOBAL, + +kernel32.GlobalUnlock.argtypes = (HGLOBAL,) kernel32.GlobalUnlock.restype = BOOL shell32.DragQueryFile.argtypes = HANDLE, UINT, ctypes.c_void_p, UINT @@ -93,103 +119,115 @@ class BITMAPFILEHEADER(ctypes.Structure): _pack_ = 1 # structure field byte alignment _fields_ = [ - ('bfType', WORD), # file type ("BM") - ('bfSize', DWORD), # file size in bytes - ('bfReserved1', WORD), # must be zero - ('bfReserved2', WORD), # must be zero - ('bfOffBits', DWORD), # byte offset to the pixel array - ] + ("bfType", WORD), # file type ("BM") + ("bfSize", DWORD), # file size in bytes + ("bfReserved1", WORD), # must be zero + ("bfReserved2", WORD), # must be zero + ("bfOffBits", DWORD), # byte offset to the pixel array + ] + + sizeof_BITMAPFILEHEADER = ctypes.sizeof(BITMAPFILEHEADER) + class BITMAPINFOHEADER(ctypes.Structure): _pack_ = 1 # structure field byte alignment _fields_ = [ - ('biSize', DWORD), - ('biWidth', LONG), - ('biHeight', LONG), - ('biPLanes', WORD), - ('biBitCount', WORD), - ('biCompression', DWORD), - ('biSizeImage', DWORD), - ('biXPelsPerMeter', LONG), - ('biYPelsPerMeter', LONG), - ('biClrUsed', DWORD), - ('biClrImportant', DWORD) + ("biSize", DWORD), + ("biWidth", LONG), + ("biHeight", LONG), + ("biPLanes", WORD), + ("biBitCount", WORD), + ("biCompression", DWORD), + ("biSizeImage", DWORD), + ("biXPelsPerMeter", LONG), + ("biYPelsPerMeter", LONG), + ("biClrUsed", DWORD), + ("biClrImportant", DWORD), ] + + sizeof_BITMAPINFOHEADER = ctypes.sizeof(BITMAPINFOHEADER) + class BITMAPV4HEADER(ctypes.Structure): - _pack_ = 1 # structure field byte alignment + _pack_ = 1 # structure field byte alignment _fields_ = [ - ('bV4Size', DWORD), - ('bV4Width', LONG), - ('bV4Height', LONG), - ('bV4PLanes', WORD), - ('bV4BitCount', WORD), - ('bV4Compression', DWORD), - ('bV4SizeImage', DWORD), - ('bV4XPelsPerMeter', LONG), - ('bV4YPelsPerMeter', LONG), - ('bV4ClrUsed', DWORD), - ('bV4ClrImportant', DWORD), - ('bV4RedMask', DWORD), - ('bV4GreenMask', DWORD), - ('bV4BlueMask', DWORD), - ('bV4AlphaMask', DWORD), - ('bV4CSTypes', DWORD), - ('bV4RedEndpointX', LONG), - ('bV4RedEndpointY', LONG), - ('bV4RedEndpointZ', LONG), - ('bV4GreenEndpointX', LONG), - ('bV4GreenEndpointY', LONG), - ('bV4GreenEndpointZ', LONG), - ('bV4BlueEndpointX', LONG), - ('bV4BlueEndpointY', LONG), - ('bV4BlueEndpointZ', LONG), - ('bV4GammaRed', DWORD), - ('bV4GammaGreen', DWORD), - ('bV4GammaBlue', DWORD) + ("bV4Size", DWORD), + ("bV4Width", LONG), + ("bV4Height", LONG), + ("bV4PLanes", WORD), + ("bV4BitCount", WORD), + ("bV4Compression", DWORD), + ("bV4SizeImage", DWORD), + ("bV4XPelsPerMeter", LONG), + ("bV4YPelsPerMeter", LONG), + ("bV4ClrUsed", DWORD), + ("bV4ClrImportant", DWORD), + ("bV4RedMask", DWORD), + ("bV4GreenMask", DWORD), + ("bV4BlueMask", DWORD), + ("bV4AlphaMask", DWORD), + ("bV4CSTypes", DWORD), + ("bV4RedEndpointX", LONG), + ("bV4RedEndpointY", LONG), + ("bV4RedEndpointZ", LONG), + ("bV4GreenEndpointX", LONG), + ("bV4GreenEndpointY", LONG), + ("bV4GreenEndpointZ", LONG), + ("bV4BlueEndpointX", LONG), + ("bV4BlueEndpointY", LONG), + ("bV4BlueEndpointZ", LONG), + ("bV4GammaRed", DWORD), + ("bV4GammaGreen", DWORD), + ("bV4GammaBlue", DWORD), ] + + sizeof_BITMAPV4HEADER = ctypes.sizeof(BITMAPV4HEADER) + class BITMAPV5HEADER(ctypes.Structure): - _pack_ = 1 # structure field byte alignment + _pack_ = 1 # structure field byte alignment _fields_ = [ - ('bV5Size', DWORD), - ('bV5Width', LONG), - ('bV5Height', LONG), - ('bV5PLanes', WORD), - ('bV5BitCount', WORD), - ('bV5Compression', DWORD), - ('bV5SizeImage', DWORD), - ('bV5XPelsPerMeter', LONG), - ('bV5YPelsPerMeter', LONG), - ('bV5ClrUsed', DWORD), - ('bV5ClrImportant', DWORD), - ('bV5RedMask', DWORD), - ('bV5GreenMask', DWORD), - ('bV5BlueMask', DWORD), - ('bV5AlphaMask', DWORD), - ('bV5CSTypes', DWORD), - ('bV5RedEndpointX', LONG), - ('bV5RedEndpointY', LONG), - ('bV5RedEndpointZ', LONG), - ('bV5GreenEndpointX', LONG), - ('bV5GreenEndpointY', LONG), - ('bV5GreenEndpointZ', LONG), - ('bV5BlueEndpointX', LONG), - ('bV5BlueEndpointY', LONG), - ('bV5BlueEndpointZ', LONG), - ('bV5GammaRed', DWORD), - ('bV5GammaGreen', DWORD), - ('bV5GammaBlue', DWORD), - ('bV5Intent', DWORD), - ('bV5ProfileData', DWORD), - ('bV5ProfileSize', DWORD), - ('bV5Reserved', DWORD) + ("bV5Size", DWORD), + ("bV5Width", LONG), + ("bV5Height", LONG), + ("bV5PLanes", WORD), + ("bV5BitCount", WORD), + ("bV5Compression", DWORD), + ("bV5SizeImage", DWORD), + ("bV5XPelsPerMeter", LONG), + ("bV5YPelsPerMeter", LONG), + ("bV5ClrUsed", DWORD), + ("bV5ClrImportant", DWORD), + ("bV5RedMask", DWORD), + ("bV5GreenMask", DWORD), + ("bV5BlueMask", DWORD), + ("bV5AlphaMask", DWORD), + ("bV5CSTypes", DWORD), + ("bV5RedEndpointX", LONG), + ("bV5RedEndpointY", LONG), + ("bV5RedEndpointZ", LONG), + ("bV5GreenEndpointX", LONG), + ("bV5GreenEndpointY", LONG), + ("bV5GreenEndpointZ", LONG), + ("bV5BlueEndpointX", LONG), + ("bV5BlueEndpointY", LONG), + ("bV5BlueEndpointZ", LONG), + ("bV5GammaRed", DWORD), + ("bV5GammaGreen", DWORD), + ("bV5GammaBlue", DWORD), + ("bV5Intent", DWORD), + ("bV5ProfileData", DWORD), + ("bV5ProfileSize", DWORD), + ("bV5Reserved", DWORD), ] + + sizeof_BITMAPV5HEADER = ctypes.sizeof(BITMAPV5HEADER) + def open_clipboard(): """ Opens clipboard. Must be called before any action in performed. @@ -198,6 +236,7 @@ def open_clipboard(): """ return user32.OpenClipboard(0) + def close_clipboard(): """ Closes clipboard. Must be called after all actions are performed. @@ -206,19 +245,21 @@ def close_clipboard(): """ return user32.CloseClipboard() -def empty_cliboard(): + +def empty_clipboard(): """ Empties clipboard. Should be called before any setter actions. :return: (int) 0 if function fails, otherwise 1 """ return user32.EmptyClipboard() - + + def get_UNICODETEXT(): """ - get text from clipboard as string + get text from clipboard as string - :return: (str) text grabbed from clipboard + :return: (str) text grabbed from clipboard """ # user32.OpenClipboard(0) @@ -230,15 +271,17 @@ def get_UNICODETEXT(): return text + def set_UNICODETEXT(text): """ - set text to clipboard as CF_UNICODETEXT + set text to clipboard as CF_UNICODETEXT :param str text: text to set to clipboard - :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + :return: 1 if function succeeds, something else otherwise (or maybe just spit out an + error) """ - data = text.encode('utf-16le') + data = text.encode("utf-16le") size = len(data) + 2 h_mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) @@ -252,32 +295,35 @@ def set_UNICODETEXT(text): # user32.CloseClipboard() return 1 + def get_FILEPATHS(): """ - get list of files from clipboard. + get list of files from clipboard. :return: (list) filepaths """ filepaths = [] - #user32.OpenClipboard(0) + # user32.OpenClipboard(0) data = user32.GetClipboardData(CF_HDROP) file_count = shell32.DragQueryFile(data, -1, None, 0) for index in range(file_count): buf = ctypes.c_buffer(260) shell32.DragQueryFile(data, index, buf, ctypes.sizeof(buf)) filepaths.append(buf.value.decode(getfilesystemencoding())) - #user32.CloseClipboard() + # user32.CloseClipboard() return filepaths -def get_DIB(filepath = '', filename = 'bitmap'): + +def get_DIB(filepath="", filename="bitmap"): """ get image from clipboard as a bitmap and saves to filepath. - :param str filepath: filepath to save image into + :param str filepath: filepath to save image into :param str filename: filename of the image - :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + :return: 1 if function succeeds, something else otherwise (or maybe just spit out an + error) """ # user32.OpenClipboard(0) @@ -287,25 +333,25 @@ def get_DIB(filepath = '', filename = 'bitmap'): h_mem = user32.GetClipboardData(CF_DIB) dest = kernel32.GlobalLock(h_mem) size = kernel32.GlobalSize(dest) - data = bytes((ctypes.c_char*size).from_address(dest)) + data = bytes((ctypes.c_char * size).from_address(dest)) bm_ih = BITMAPINFOHEADER() header_size = sizeof_BITMAPINFOHEADER ctypes.memmove(ctypes.pointer(bm_ih), data, header_size) compression = bm_ih.biCompression - if compression not in (BI_BITFIELDS, BI_RGB): - raise RuntimeError(f'unsupported compression type {format(compression)}') + if compression not in (BI_BITFIELDS, BI_RGB): + raise RuntimeError(f"unsupported compression type {format(compression)}") bm_fh = BITMAPFILEHEADER() ctypes.memset(ctypes.pointer(bm_fh), 0, sizeof_BITMAPFILEHEADER) - bm_fh.bfType = ord('B') | (ord('M') << 8) + bm_fh.bfType = ord("B") | (ord("M") << 8) bm_fh.bfSize = sizeof_BITMAPFILEHEADER + len(str(data)) sizeof_COLORTABLE = 0 bm_fh.bfOffBits = sizeof_BITMAPFILEHEADER + header_size + sizeof_COLORTABLE - img_path = path_join(filepath, filename + '.bmp') - with open(img_path, 'wb') as bmp_file: + img_path = path_join(filepath, filename + ".bmp") + with open(img_path, "wb") as bmp_file: bmp_file.write(bm_fh) bmp_file.write(data) @@ -313,13 +359,15 @@ def get_DIB(filepath = '', filename = 'bitmap'): # user32.CloseClipboard() return 1 -def get_DIBV5(filepath = '', filename = 'bitmapV5'): + +def get_DIBV5(filepath="", filename="bitmapV5"): """ get image from clipboard as a bitmapV5 and saves to filepath - :param str filepath: filepath to save image into + :param str filepath: filepath to save image into :param str filename: filename of the image - :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + :return: 1 if function succeeds, something else otherwise (or maybe just spit out an + error) """ # user32.OpenClipboard(0) @@ -329,7 +377,7 @@ def get_DIBV5(filepath = '', filename = 'bitmapV5'): h_mem = user32.GetClipboardData(CF_DIBV5) dest = kernel32.GlobalLock(h_mem) size = kernel32.GlobalSize(dest) - data = bytes((ctypes.c_char*size).from_address(dest)) + data = bytes((ctypes.c_char * size).from_address(dest)) bm_ih = BITMAPV5HEADER() header_size = sizeof_BITMAPV5HEADER @@ -337,30 +385,35 @@ def get_DIBV5(filepath = '', filename = 'bitmapV5'): if bm_ih.bV5Compression == BI_RGB: # convert BI_RGB to BI_BITFIELDS so as to properly support an alpha channel - # everything other than the usage of bitmasks is same compared to BI_BITFIELDS so we manually add that part and put bV5Compression to BI_BITFIELDS - # info on these header structures -> https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types - # and -> https://en.wikipedia.org/wiki/BMP_file_format + # everything other than the usage of bitmasks is same compared to BI_BITFIELDS + # so we manually add that part and put bV5Compression to BI_BITFIELDS info on + # these header structures -> + # https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types and -> + # https://en.wikipedia.org/wiki/BMP_file_format bi_compression = bytes([3, 0, 0, 0]) - bi_bitmasks = bytes([0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255]) + bi_bitmasks = bytes([0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255]) data = data[:16] + bi_compression + data[20:40] + bi_bitmasks + data[56:] elif bm_ih.bV5Compression == BI_BITFIELDS: - # we still need to add bitmask (bV5AlphaMask) for softwares to recognize the alpha channel + # we still need to add bitmask (bV5AlphaMask) for softwares to recognize the + # alpha channel data = data[:52] + bytes([0, 0, 0, 255]) + data[56:] else: - raise RuntimeError(f'unsupported compression type {format(bm_ih.bV5Compression)}') + raise RuntimeError( + f"unsupported compression type {format(bm_ih.bV5Compression)}" + ) bm_fh = BITMAPFILEHEADER() ctypes.memset(ctypes.pointer(bm_fh), 0, sizeof_BITMAPFILEHEADER) - bm_fh.bfType = ord('B') | (ord('M') << 8) + bm_fh.bfType = ord("B") | (ord("M") << 8) bm_fh.bfSize = sizeof_BITMAPFILEHEADER + len(str(data)) sizeof_COLORTABLE = 0 bm_fh.bfOffBits = sizeof_BITMAPFILEHEADER + header_size + sizeof_COLORTABLE - img_path = path_join(filepath, filename + '.bmp') - with open(img_path, 'wb') as bmp_file: + img_path = path_join(filepath, filename + ".bmp") + with open(img_path, "wb") as bmp_file: bmp_file.write(bm_fh) bmp_file.write(data) @@ -368,59 +421,64 @@ def get_DIBV5(filepath = '', filename = 'bitmapV5'): # user32.CloseClipboard() return 1 -def get_PNG(filepath = '', filename = 'PNG'): + +def get_PNG(filepath="", filename="PNG"): """ get image in 'PNG' or 'image/png' format from clipboard and saves to filepath :param str filepath: filepath to save image into :param str filename: filename of the image - :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + :return: 1 if function succeeds, something else otherwise (or maybe just spit out an + error) """ # user32.OpenClipboard(0) png_format = 0 - PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) - image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('image/png')) + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("PNG")) + image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("image/png")) if user32.IsClipboardFormatAvailable(PNG): png_format = PNG elif user32.IsClipboardFormatAvailable(image_png): png_format = image_png else: - raise RuntimeError("clipboard image not available in 'PNG'or 'image/png' format") + raise RuntimeError( + "clipboard image not available in 'PNG'or 'image/png' format" + ) h_mem = user32.GetClipboardData(png_format) dest = kernel32.GlobalLock(h_mem) size = kernel32.GlobalSize(dest) - data = bytes((ctypes.c_char*size).from_address(dest)) + data = bytes((ctypes.c_char * size).from_address(dest)) kernel32.GlobalUnlock(h_mem) # user32.CloseClipboard() - img_path = path_join(filepath, filename + '.png') - with open (img_path, 'wb') as png_file: + img_path = path_join(filepath, filename + ".png") + with open(img_path, "wb") as png_file: png_file.write(data) return 1 + def set_DIB(src_bmp): """ set source bitmap image to clipboard as a CF_DIB or CF_DIBV5 according to the image :param str src_bmp: filepath of source image - :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + :return: 1 if function succeeds, something else otherwise (or maybe just spit out an + error) """ - with open(src_bmp, 'rb') as img: + with open(src_bmp, "rb") as img: data = img.read() output = data[14:] - size = len(output) + size = len(output) print(list(bytearray(output)[:200])) mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) h_mem = kernel32.GlobalLock(mem) - ctypes.memmove(ctypes.cast(h_mem, INT_P), ctypes.cast(output, INT_P), size) + ctypes.memmove(ctypes.cast(h_mem, INT_P), ctypes.cast(output, INT_P), size) kernel32.GlobalUnlock(mem) - if output[0] in [56, 108, 124]: # img contains DIBV5 or DIBV4 or DIBV3 Header fmt = CF_DIBV5 @@ -431,31 +489,34 @@ def set_DIB(src_bmp): # user32.EmptyClipboard() user32.SetClipboardData(fmt, h_mem) # user32.CloseClipboard() - return 1 + return 1 + def set_PNG(src_png): """ set source png image to clipboard in 'PNG' format :param str src_png: filepath of source image - :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + :return: 1 if function succeeds, something else otherwise (or maybe just spit out an + error) """ - with open(src_png, 'rb') as img: + with open(src_png, "rb") as img: data = img.read() - size = len(data) + size = len(data) mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) h_mem = kernel32.GlobalLock(mem) - ctypes.memmove(h_mem, data, size) + ctypes.memmove(h_mem, data, size) kernel32.GlobalUnlock(mem) # user32.OpenClipboard(0) # user32.EmptyClipboard() - PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("PNG")) user32.SetClipboardData(PNG, h_mem) # user32.CloseClipboard() return 1 + def is_format_available(format_id): """ checks whether specified format is currently available on the clipboard @@ -463,13 +524,14 @@ def is_format_available(format_id): :param int format_id: id of format to check for :return: (bool) True if specified format is available """ - + # user32.OpenClipboard(0) is_format = user32.IsClipboardFormatAvailable(format_id) # user32.CloseClipboard() return bool(is_format) -def get_available_formats(buffer_size = 32): + +def get_available_formats(buffer_size=32): """ gets a dict of all the currently available formats on the clipboard @@ -480,30 +542,34 @@ def get_available_formats(buffer_size = 32): # user32.OpenClipboard(0) fmt = 0 for i in range(user32.CountClipboardFormats()): - # must put previous fmt (starting from 0) in EnumClipboardFormats() to get the next one + # must put previous fmt (starting from 0) in EnumClipboardFormats() to get the + # next one fmt = user32.EnumClipboardFormats(fmt) name_buf = ctypes.create_string_buffer(buffer_size) - name_len = user32.GetClipboardFormatNameA(fmt, name_buf, buffer_size) fmt_name = name_buf.value.decode() - + # standard formats do not return any name, so we set one from out dictionary - if fmt_name == '' and fmt in format_dict.keys(): + if fmt_name == "" and fmt in format_dict.keys(): fmt_name = format_dict[fmt] - available_formats.update({fmt : fmt_name}) + available_formats.update({fmt: fmt_name}) # user32.CloseClipboard() return available_formats -def get_image(filepath = '', filename = 'image'): + +def get_image(filepath="", filename="image"): """ - gets image from clipboard in a format according to a priority list (PNG > DIBV5 > DIB) + gets image from clipboard in a format according to a priority list (PNG > DIBV5 > + DIB) """ # user32.OpenClipboard(0) - PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) - image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('image/png')) - - if user32.IsClipboardFormatAvailable(PNG) or user32.IsClipboardFormatAvailable(image_png): + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("PNG")) + image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("image/png")) + + if user32.IsClipboardFormatAvailable(PNG) or user32.IsClipboardFormatAvailable( + image_png + ): get_PNG(filepath, filename) return 1 elif user32.IsClipboardFormatAvailable(CF_DIBV5): @@ -513,34 +579,38 @@ def get_image(filepath = '', filename = 'image'): get_DIB(filepath, filename) return 1 else: - raise RuntimeError('image on clipboard not available in any supported format') + raise RuntimeError("image on clipboard not available in any supported format") return 0 + def set_image(src_img): """ - (NOT FULLY IMPLEMENTED) set source image to clipboard in multiple formats (PNG, DIB). + (NOT FULLY IMPLEMENTED) set source image to clipboard in multiple formats (PNG, + DIB). :param str src_img: filepath of source image - :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) + :return: 1 if function succeeds, something else otherwise (or maybe just spit out an + error) """ - # this is more complicated... gotta interconvert images + # this is more complicated... gotta inter-convert images # looking into ways to get this done with ctypes as well - NO IM DONE # temporary solution - img_extn = src_img[(len(src_img)-3):].lower() - if img_extn == 'bmp': + image_extension = src_img[(len(src_img) - 3) :].lower() + if image_extension == "bmp": # image format is bitmap set_DIB(src_img) - elif img_extn == 'png': + elif image_extension == "png": # image format is png set_PNG(src_img) else: - raise RuntimeError('Unsupported image format') + raise RuntimeError("Unsupported image format") return 1 -if __name__ == '__main__': + +if __name__ == "__main__": if open_clipboard(): - empty_cliboard() + empty_clipboard() # set_UNICODETEXT('pasta pasta pasta pasta pasta pasta') - close_clipboard() \ No newline at end of file + close_clipboard() diff --git a/imagepaste/clipboard/windows/windows.py b/imagepaste/clipboard/windows/windows.py index a5619a8..245543e 100644 --- a/imagepaste/clipboard/windows/windows.py +++ b/imagepaste/clipboard/windows/windows.py @@ -1,17 +1,15 @@ from __future__ import annotations -from posixpath import abspath from ..clipboard import Clipboard from ...report import Report from ...image import Image -from ...process import Process class WindowsClipboard(Clipboard): """A concrete implementation of Clipboard for Windows.""" def __init__(self, report: Report, images: list[Image] = None) -> None: - """A concreate implementation of Clipboard for Windows. + """A concrete implementation of Clipboard for Windows. Args: report (Report): A Report instance to which results should be reported. @@ -47,14 +45,15 @@ def push(cls, save_directory: str) -> WindowsClipboard: images = [Image(filepath) for filepath in filepaths] return cls(Report(6, f"Pasted {len(images)} image files: {images}"), images) - # get image if available as 'PNG' or 'image/png' which covers pretty much all software. - # Ditched BITMAP support because blender doesn't completely support all Bitmap sub-formats - # and I couldn't find any software that copies only as a bitmap. + # Get image if available as 'PNG' or 'image/png' which covers pretty much all + # software. Ditched BITMAP support because blender doesn't completely support + # all Bitmap sub-formats and I couldn't find any software that copies only as a + # bitmap. output = clipette.get_PNG(save_directory, splitext(filename)[0]) clipette.close_clipboard() if output != 1: image = Image(filepath) - return cls(Report(3, f"Cannot save image: {image} ({process.stderr})")) + return cls(Report(3, f"Cannot save image: {image}")) else: image = Image(filepath, pasted=True) return cls(Report(6, f"Saved and pasted 1 image: {image}"), [image]) @@ -73,65 +72,66 @@ def pull(cls, image_path: str) -> WindowsClipboard: operations under Report object and a list of one Image object that holds information of the pulled image we put its path to the input. """ - from . import clipette + from . import clipette from bpy.path import abspath clipette.open_clipboard() - clipette.empty_cliboard() - + clipette.empty_clipboard() + image_path = abspath(image_path) image_format = image_path[-3:].lower() - # bmp (as DIB, DIBV5, BITMAP) and png (as PNG) should be enough formats to work with most applications - if image_format != 'bmp': - clipette.set_DIB(cls.convert_image(image_path, 'BMP')) + # bmp (as DIB, DIBV5, BITMAP) and png (as PNG) should be enough formats to work + # with most applications + if image_format != "bmp": + clipette.set_DIB(cls.convert_image(image_path, "BMP")) else: clipette.set_DIB(image_path) - if image_format != 'png': - clipette.set_PNG(cls.convert_image(image_path, 'PNG')) + if image_format != "png": + clipette.set_PNG(cls.convert_image(image_path, "PNG")) else: clipette.set_PNG(image_path) - + clipette.close_clipboard() image = Image(image_path) return cls(Report(5, f"Copied 1 image: {image}"), [image]) - @staticmethod def convert_image(image_path: str, format: str) -> str: - """A static method to convert image format and get new image filepath. + """A static method to convert image format and get new image filepath. Saves converted image in ImagePaste's working directory. Args: image_path (str): Filepath of source image. - format (str): Format to convert image to as in the image extension ('png', 'bmp', etc) + format (str): Format to convert image to as in the image extension ('png', + 'bmp', etc) Returns: str: Filepath of converted image. """ - # should probably incorpoate this function into the Image class or something + # should probably incorporate this function into the Image class or something from ...tree import get_save_directory from os.path import join, basename, splitext from bpy_extras.image_utils import load_image import bpy - - RGBA_unsupported = ['BMP', 'JPEG'] + + RGBA_unsupported = ["BMP", "JPEG"] format_ext = { - 'BMP': '.bmp', - 'IRIS': '.rgb', - 'PNG': '.png', - 'JPEG': '.jpg', - 'JPEG2000': '.jp2', - 'TARGA': '.tga', - 'TARGA_RAW': '.tga', - 'CINEON': '.cin', - 'DPX': '.dpx', - 'OPEN_EXR_MULTILAYER': '.exr', - 'OPEN_EXR': '.exr', - 'HDR': '.hdr', - 'TIFF': '.tif', - 'WEBP': '.webp' + "BMP": ".bmp", + "IRIS": ".rgb", + "PNG": ".png", + "JPEG": ".jpg", + "JPEG2000": ".jp2", + "TARGA": ".tga", + "TARGA_RAW": ".tga", + "CINEON": ".cin", + "DPX": ".dpx", + "OPEN_EXR_MULTILAYER": ".exr", + "OPEN_EXR": ".exr", + "HDR": ".hdr", + "TIFF": ".tif", + "WEBP": ".webp", } img_settings = bpy.context.scene.render.image_settings @@ -141,10 +141,12 @@ def convert_image(image_path: str, format: str) -> str: img_settings.file_format = format img_settings.quality = 100 - img_settings.color_mode = 'RGB' if format in RGBA_unsupported else 'RGBA' + img_settings.color_mode = "RGB" if format in RGBA_unsupported else "RGBA" image = load_image(image_path) - image_path_c = join(get_save_directory(), splitext(basename(image_path))[0] + format_ext[format]) + image_path_c = join( + get_save_directory(), splitext(basename(image_path))[0] + format_ext[format] + ) image.save_render(image_path_c) img_settings.file_format = prev_file_format From f1dea5702c71c7d3790a7aad46ec95c8ce7d36a3 Mon Sep 17 00:00:00 2001 From: Binit Date: Sat, 4 Feb 2023 15:40:53 +0530 Subject: [PATCH 3/4] updated clipette.py to prevent improper exit to prevent exiting without closing clipboard --- imagepaste/clipboard/windows/clipette.py | 446 ++++++++++------------- imagepaste/clipboard/windows/windows.py | 110 +++--- 2 files changed, 246 insertions(+), 310 deletions(-) diff --git a/imagepaste/clipboard/windows/clipette.py b/imagepaste/clipboard/windows/clipette.py index 53a6c80..f058a90 100644 --- a/imagepaste/clipboard/windows/clipette.py +++ b/imagepaste/clipboard/windows/clipette.py @@ -2,20 +2,7 @@ # must close clipboard afterwards import ctypes -from ctypes.wintypes import ( - HWND, - BOOL, - HANDLE, - LPCSTR, - LPCWSTR, - LPSTR, - UINT, - HGLOBAL, - DWORD, - LONG, - WORD, - LPVOID, -) +from ctypes.wintypes import * from os.path import join as path_join from sys import getfilesystemencoding @@ -24,8 +11,8 @@ CF_UNICODETEXT = 13 CF_HDROP = 15 -CF_BITMAP = 2 # hbitmap -CF_DIB = 8 # DIB and BITMAP are inter-convertible as from windows clipboard +CF_BITMAP = 2 # hbitmap +CF_DIB = 8 # DIB and BITMAP are interconvertable as from windows clipboard CF_DIBV5 = 17 # bitmap compression types @@ -38,23 +25,23 @@ BI_ALPHABITFIELDS = 6 format_dict = { - 1: "CF_TEXT", - 2: "CF_BITMAP", - 3: "CF_METAFILEPICT", - 4: "CF_SYLK", - 5: "CF_DIF", - 6: "CF_TIFF", - 7: "CF_OEMTEXT", - 8: "CF_DIB", - 9: "CF_PALETTE", - 10: "CF_PENDATA", - 11: "CF_RIFF", - 12: "CF_WAVE", - 13: "CF_UNICODETEXT", - 14: "CF_ENHMETAFILE", - 15: "CF_HDROP", - 16: "CF_LOCALE", - 17: "CF_DIBV5", + 1: 'CF_TEXT', + 2: 'CF_BITMAP', + 3: 'CF_METAFILEPICT', + 4: 'CF_SYLK', + 5: 'CF_DIF', + 6: 'CF_TIFF', + 7: 'CF_OEMTEXT', + 8: 'CF_DIB', + 9: 'CF_PALETTE', + 10: 'CF_PENDATA', + 11: 'CF_RIFF', + 12: 'CF_WAVE', + 13: 'CF_UNICODETEXT', + 14: 'CF_ENHMETAFILE', + 15: 'CF_HDROP', + 16: 'CF_LOCALE', + 17: 'CF_DIBV5', } # todo: @@ -65,51 +52,38 @@ kernel32 = ctypes.windll.kernel32 shell32 = ctypes.windll.shell32 -user32.OpenClipboard.argtypes = (HWND,) +user32.OpenClipboard.argtypes = HWND, user32.OpenClipboard.restype = BOOL - -user32.GetClipboardData.argtypes = (UINT,) +user32.GetClipboardData.argtypes = UINT, user32.GetClipboardData.restype = HANDLE - user32.SetClipboardData.argtypes = UINT, HANDLE user32.SetClipboardData.restype = HANDLE - user32.CloseClipboard.argtypes = None user32.CloseClipboard.restype = BOOL - -user32.IsClipboardFormatAvailable.argtypes = (UINT,) +user32.IsClipboardFormatAvailable.argtypes = UINT, user32.IsClipboardFormatAvailable.restype = BOOL - user32.CountClipboardFormats.argtypes = None user32.CountClipboardFormats.restype = UINT - -user32.EnumClipboardFormats.argtypes = (UINT,) +user32.EnumClipboardFormats.argtypes = UINT, user32.EnumClipboardFormats.restype = UINT - user32.GetClipboardFormatNameA.argtypes = UINT, LPSTR, UINT user32.GetClipboardFormatNameA.restype = UINT - -user32.RegisterClipboardFormatA.argtypes = (LPCSTR,) +user32.RegisterClipboardFormatA.argtypes = LPCSTR, user32.RegisterClipboardFormatA.restype = UINT - -user32.RegisterClipboardFormatW.argtypes = (LPCWSTR,) +user32.RegisterClipboardFormatW.argtypes = LPCWSTR, user32.RegisterClipboardFormatW.restype = UINT -user32.RegisterClipboardFormatW.argtypes = (LPCWSTR,) +user32.RegisterClipboardFormatW.argtypes = LPCWSTR, user32.RegisterClipboardFormatW.restype = UINT - user32.EmptyClipboard.argtypes = None user32.EmptyClipboard.restype = BOOL kernel32.GlobalAlloc.argtypes = UINT, ctypes.c_size_t kernel32.GlobalAlloc.restype = HGLOBAL - -kernel32.GlobalSize.argtypes = (HGLOBAL,) +kernel32.GlobalSize.argtypes = HGLOBAL, kernel32.GlobalSize.restype = UINT - -kernel32.GlobalLock.argtypes = (HGLOBAL,) +kernel32.GlobalLock.argtypes = HGLOBAL, kernel32.GlobalLock.restype = LPVOID - -kernel32.GlobalUnlock.argtypes = (HGLOBAL,) +kernel32.GlobalUnlock.argtypes = HGLOBAL, kernel32.GlobalUnlock.restype = BOOL shell32.DragQueryFile.argtypes = HANDLE, UINT, ctypes.c_void_p, UINT @@ -119,115 +93,103 @@ class BITMAPFILEHEADER(ctypes.Structure): _pack_ = 1 # structure field byte alignment _fields_ = [ - ("bfType", WORD), # file type ("BM") - ("bfSize", DWORD), # file size in bytes - ("bfReserved1", WORD), # must be zero - ("bfReserved2", WORD), # must be zero - ("bfOffBits", DWORD), # byte offset to the pixel array - ] - - + ('bfType', WORD), # file type ("BM") + ('bfSize', DWORD), # file size in bytes + ('bfReserved1', WORD), # must be zero + ('bfReserved2', WORD), # must be zero + ('bfOffBits', DWORD), # byte offset to the pixel array + ] sizeof_BITMAPFILEHEADER = ctypes.sizeof(BITMAPFILEHEADER) - class BITMAPINFOHEADER(ctypes.Structure): _pack_ = 1 # structure field byte alignment _fields_ = [ - ("biSize", DWORD), - ("biWidth", LONG), - ("biHeight", LONG), - ("biPLanes", WORD), - ("biBitCount", WORD), - ("biCompression", DWORD), - ("biSizeImage", DWORD), - ("biXPelsPerMeter", LONG), - ("biYPelsPerMeter", LONG), - ("biClrUsed", DWORD), - ("biClrImportant", DWORD), + ('biSize', DWORD), + ('biWidth', LONG), + ('biHeight', LONG), + ('biPLanes', WORD), + ('biBitCount', WORD), + ('biCompression', DWORD), + ('biSizeImage', DWORD), + ('biXPelsPerMeter', LONG), + ('biYPelsPerMeter', LONG), + ('biClrUsed', DWORD), + ('biClrImportant', DWORD) ] - - sizeof_BITMAPINFOHEADER = ctypes.sizeof(BITMAPINFOHEADER) - class BITMAPV4HEADER(ctypes.Structure): - _pack_ = 1 # structure field byte alignment + _pack_ = 1 # structure field byte alignment _fields_ = [ - ("bV4Size", DWORD), - ("bV4Width", LONG), - ("bV4Height", LONG), - ("bV4PLanes", WORD), - ("bV4BitCount", WORD), - ("bV4Compression", DWORD), - ("bV4SizeImage", DWORD), - ("bV4XPelsPerMeter", LONG), - ("bV4YPelsPerMeter", LONG), - ("bV4ClrUsed", DWORD), - ("bV4ClrImportant", DWORD), - ("bV4RedMask", DWORD), - ("bV4GreenMask", DWORD), - ("bV4BlueMask", DWORD), - ("bV4AlphaMask", DWORD), - ("bV4CSTypes", DWORD), - ("bV4RedEndpointX", LONG), - ("bV4RedEndpointY", LONG), - ("bV4RedEndpointZ", LONG), - ("bV4GreenEndpointX", LONG), - ("bV4GreenEndpointY", LONG), - ("bV4GreenEndpointZ", LONG), - ("bV4BlueEndpointX", LONG), - ("bV4BlueEndpointY", LONG), - ("bV4BlueEndpointZ", LONG), - ("bV4GammaRed", DWORD), - ("bV4GammaGreen", DWORD), - ("bV4GammaBlue", DWORD), + ('bV4Size', DWORD), + ('bV4Width', LONG), + ('bV4Height', LONG), + ('bV4PLanes', WORD), + ('bV4BitCount', WORD), + ('bV4Compression', DWORD), + ('bV4SizeImage', DWORD), + ('bV4XPelsPerMeter', LONG), + ('bV4YPelsPerMeter', LONG), + ('bV4ClrUsed', DWORD), + ('bV4ClrImportant', DWORD), + ('bV4RedMask', DWORD), + ('bV4GreenMask', DWORD), + ('bV4BlueMask', DWORD), + ('bV4AlphaMask', DWORD), + ('bV4CSTypes', DWORD), + ('bV4RedEndpointX', LONG), + ('bV4RedEndpointY', LONG), + ('bV4RedEndpointZ', LONG), + ('bV4GreenEndpointX', LONG), + ('bV4GreenEndpointY', LONG), + ('bV4GreenEndpointZ', LONG), + ('bV4BlueEndpointX', LONG), + ('bV4BlueEndpointY', LONG), + ('bV4BlueEndpointZ', LONG), + ('bV4GammaRed', DWORD), + ('bV4GammaGreen', DWORD), + ('bV4GammaBlue', DWORD) ] - - sizeof_BITMAPV4HEADER = ctypes.sizeof(BITMAPV4HEADER) - class BITMAPV5HEADER(ctypes.Structure): - _pack_ = 1 # structure field byte alignment + _pack_ = 1 # structure field byte alignment _fields_ = [ - ("bV5Size", DWORD), - ("bV5Width", LONG), - ("bV5Height", LONG), - ("bV5PLanes", WORD), - ("bV5BitCount", WORD), - ("bV5Compression", DWORD), - ("bV5SizeImage", DWORD), - ("bV5XPelsPerMeter", LONG), - ("bV5YPelsPerMeter", LONG), - ("bV5ClrUsed", DWORD), - ("bV5ClrImportant", DWORD), - ("bV5RedMask", DWORD), - ("bV5GreenMask", DWORD), - ("bV5BlueMask", DWORD), - ("bV5AlphaMask", DWORD), - ("bV5CSTypes", DWORD), - ("bV5RedEndpointX", LONG), - ("bV5RedEndpointY", LONG), - ("bV5RedEndpointZ", LONG), - ("bV5GreenEndpointX", LONG), - ("bV5GreenEndpointY", LONG), - ("bV5GreenEndpointZ", LONG), - ("bV5BlueEndpointX", LONG), - ("bV5BlueEndpointY", LONG), - ("bV5BlueEndpointZ", LONG), - ("bV5GammaRed", DWORD), - ("bV5GammaGreen", DWORD), - ("bV5GammaBlue", DWORD), - ("bV5Intent", DWORD), - ("bV5ProfileData", DWORD), - ("bV5ProfileSize", DWORD), - ("bV5Reserved", DWORD), + ('bV5Size', DWORD), + ('bV5Width', LONG), + ('bV5Height', LONG), + ('bV5PLanes', WORD), + ('bV5BitCount', WORD), + ('bV5Compression', DWORD), + ('bV5SizeImage', DWORD), + ('bV5XPelsPerMeter', LONG), + ('bV5YPelsPerMeter', LONG), + ('bV5ClrUsed', DWORD), + ('bV5ClrImportant', DWORD), + ('bV5RedMask', DWORD), + ('bV5GreenMask', DWORD), + ('bV5BlueMask', DWORD), + ('bV5AlphaMask', DWORD), + ('bV5CSTypes', DWORD), + ('bV5RedEndpointX', LONG), + ('bV5RedEndpointY', LONG), + ('bV5RedEndpointZ', LONG), + ('bV5GreenEndpointX', LONG), + ('bV5GreenEndpointY', LONG), + ('bV5GreenEndpointZ', LONG), + ('bV5BlueEndpointX', LONG), + ('bV5BlueEndpointY', LONG), + ('bV5BlueEndpointZ', LONG), + ('bV5GammaRed', DWORD), + ('bV5GammaGreen', DWORD), + ('bV5GammaBlue', DWORD), + ('bV5Intent', DWORD), + ('bV5ProfileData', DWORD), + ('bV5ProfileSize', DWORD), + ('bV5Reserved', DWORD) ] - - sizeof_BITMAPV5HEADER = ctypes.sizeof(BITMAPV5HEADER) - def open_clipboard(): """ Opens clipboard. Must be called before any action in performed. @@ -236,7 +198,6 @@ def open_clipboard(): """ return user32.OpenClipboard(0) - def close_clipboard(): """ Closes clipboard. Must be called after all actions are performed. @@ -245,21 +206,19 @@ def close_clipboard(): """ return user32.CloseClipboard() - -def empty_clipboard(): +def empty_cliboard(): """ Empties clipboard. Should be called before any setter actions. :return: (int) 0 if function fails, otherwise 1 """ return user32.EmptyClipboard() - - + def get_UNICODETEXT(): """ - get text from clipboard as string + get text from clipboard as string - :return: (str) text grabbed from clipboard + :return: (str) text grabbed from clipboard """ # user32.OpenClipboard(0) @@ -271,17 +230,15 @@ def get_UNICODETEXT(): return text - def set_UNICODETEXT(text): """ - set text to clipboard as CF_UNICODETEXT + set text to clipboard as CF_UNICODETEXT :param str text: text to set to clipboard - :return: 1 if function succeeds, something else otherwise (or maybe just spit out an - error) + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) """ - data = text.encode("utf-16le") + data = text.encode('utf-16le') size = len(data) + 2 h_mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) @@ -295,63 +252,60 @@ def set_UNICODETEXT(text): # user32.CloseClipboard() return 1 - def get_FILEPATHS(): """ - get list of files from clipboard. + get list of files from clipboard. :return: (list) filepaths """ filepaths = [] - # user32.OpenClipboard(0) + #user32.OpenClipboard(0) data = user32.GetClipboardData(CF_HDROP) file_count = shell32.DragQueryFile(data, -1, None, 0) for index in range(file_count): buf = ctypes.c_buffer(260) shell32.DragQueryFile(data, index, buf, ctypes.sizeof(buf)) filepaths.append(buf.value.decode(getfilesystemencoding())) - # user32.CloseClipboard() + #user32.CloseClipboard() return filepaths - -def get_DIB(filepath="", filename="bitmap"): +def get_DIB(filepath = '', filename = 'bitmap'): """ get image from clipboard as a bitmap and saves to filepath. - :param str filepath: filepath to save image into + :param str filepath: filepath to save image into :param str filename: filename of the image - :return: 1 if function succeeds, something else otherwise (or maybe just spit out an - error) + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) """ # user32.OpenClipboard(0) if not user32.IsClipboardFormatAvailable(CF_DIB): - raise RuntimeError("clipboard image not available in 'CF_DIB' format") + raise_runtimerror("clipboard image not available in 'CF_DIB format") h_mem = user32.GetClipboardData(CF_DIB) dest = kernel32.GlobalLock(h_mem) size = kernel32.GlobalSize(dest) - data = bytes((ctypes.c_char * size).from_address(dest)) + data = bytes((ctypes.c_char*size).from_address(dest)) bm_ih = BITMAPINFOHEADER() header_size = sizeof_BITMAPINFOHEADER ctypes.memmove(ctypes.pointer(bm_ih), data, header_size) compression = bm_ih.biCompression - if compression not in (BI_BITFIELDS, BI_RGB): - raise RuntimeError(f"unsupported compression type {format(compression)}") + if compression not in (BI_BITFIELDS, BI_RGB): + raise_runtimerror(f'unsupported compression type {format(compression)}') bm_fh = BITMAPFILEHEADER() ctypes.memset(ctypes.pointer(bm_fh), 0, sizeof_BITMAPFILEHEADER) - bm_fh.bfType = ord("B") | (ord("M") << 8) + bm_fh.bfType = ord('B') | (ord('M') << 8) bm_fh.bfSize = sizeof_BITMAPFILEHEADER + len(str(data)) sizeof_COLORTABLE = 0 bm_fh.bfOffBits = sizeof_BITMAPFILEHEADER + header_size + sizeof_COLORTABLE - img_path = path_join(filepath, filename + ".bmp") - with open(img_path, "wb") as bmp_file: + img_path = path_join(filepath, filename + '.bmp') + with open(img_path, 'wb') as bmp_file: bmp_file.write(bm_fh) bmp_file.write(data) @@ -359,25 +313,23 @@ def get_DIB(filepath="", filename="bitmap"): # user32.CloseClipboard() return 1 - -def get_DIBV5(filepath="", filename="bitmapV5"): +def get_DIBV5(filepath = '', filename = 'bitmapV5'): """ get image from clipboard as a bitmapV5 and saves to filepath - :param str filepath: filepath to save image into + :param str filepath: filepath to save image into :param str filename: filename of the image - :return: 1 if function succeeds, something else otherwise (or maybe just spit out an - error) + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) """ # user32.OpenClipboard(0) if not user32.IsClipboardFormatAvailable(CF_DIBV5): - raise RuntimeError("clipboard image not available in 'CF_DIBV5' format") + raise_runtimerror("clipboard image not available in 'CF_DIBV5' format") h_mem = user32.GetClipboardData(CF_DIBV5) dest = kernel32.GlobalLock(h_mem) size = kernel32.GlobalSize(dest) - data = bytes((ctypes.c_char * size).from_address(dest)) + data = bytes((ctypes.c_char*size).from_address(dest)) bm_ih = BITMAPV5HEADER() header_size = sizeof_BITMAPV5HEADER @@ -385,35 +337,30 @@ def get_DIBV5(filepath="", filename="bitmapV5"): if bm_ih.bV5Compression == BI_RGB: # convert BI_RGB to BI_BITFIELDS so as to properly support an alpha channel - # everything other than the usage of bitmasks is same compared to BI_BITFIELDS - # so we manually add that part and put bV5Compression to BI_BITFIELDS info on - # these header structures -> - # https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types and -> - # https://en.wikipedia.org/wiki/BMP_file_format + # everything other than the usage of bitmasks is same compared to BI_BITFIELDS so we manually add that part and put bV5Compression to BI_BITFIELDS + # info on these header structures -> https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types + # and -> https://en.wikipedia.org/wiki/BMP_file_format bi_compression = bytes([3, 0, 0, 0]) - bi_bitmasks = bytes([0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255]) + bi_bitmasks = bytes([0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255]) data = data[:16] + bi_compression + data[20:40] + bi_bitmasks + data[56:] elif bm_ih.bV5Compression == BI_BITFIELDS: - # we still need to add bitmask (bV5AlphaMask) for softwares to recognize the - # alpha channel + # we still need to add bitmask (bV5AlphaMask) for softwares to recognize the alpha channel data = data[:52] + bytes([0, 0, 0, 255]) + data[56:] else: - raise RuntimeError( - f"unsupported compression type {format(bm_ih.bV5Compression)}" - ) + raise_runtimerror(f'unsupported compression type {format(bm_ih.bV5Compression)}') bm_fh = BITMAPFILEHEADER() ctypes.memset(ctypes.pointer(bm_fh), 0, sizeof_BITMAPFILEHEADER) - bm_fh.bfType = ord("B") | (ord("M") << 8) + bm_fh.bfType = ord('B') | (ord('M') << 8) bm_fh.bfSize = sizeof_BITMAPFILEHEADER + len(str(data)) sizeof_COLORTABLE = 0 bm_fh.bfOffBits = sizeof_BITMAPFILEHEADER + header_size + sizeof_COLORTABLE - img_path = path_join(filepath, filename + ".bmp") - with open(img_path, "wb") as bmp_file: + img_path = path_join(filepath, filename + '.bmp') + with open(img_path, 'wb') as bmp_file: bmp_file.write(bm_fh) bmp_file.write(data) @@ -421,64 +368,59 @@ def get_DIBV5(filepath="", filename="bitmapV5"): # user32.CloseClipboard() return 1 - -def get_PNG(filepath="", filename="PNG"): +def get_PNG(filepath = '', filename = 'PNG'): """ get image in 'PNG' or 'image/png' format from clipboard and saves to filepath :param str filepath: filepath to save image into :param str filename: filename of the image - :return: 1 if function succeeds, something else otherwise (or maybe just spit out an - error) + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) """ # user32.OpenClipboard(0) png_format = 0 - PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("PNG")) - image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("image/png")) + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) + image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('image/png')) if user32.IsClipboardFormatAvailable(PNG): png_format = PNG elif user32.IsClipboardFormatAvailable(image_png): png_format = image_png else: - raise RuntimeError( - "clipboard image not available in 'PNG'or 'image/png' format" - ) + raise_runtimerror("clipboard image not available in 'PNG' or 'image/png' format") h_mem = user32.GetClipboardData(png_format) dest = kernel32.GlobalLock(h_mem) size = kernel32.GlobalSize(dest) - data = bytes((ctypes.c_char * size).from_address(dest)) + data = bytes((ctypes.c_char*size).from_address(dest)) kernel32.GlobalUnlock(h_mem) # user32.CloseClipboard() - img_path = path_join(filepath, filename + ".png") - with open(img_path, "wb") as png_file: + img_path = path_join(filepath, filename + '.png') + with open (img_path, 'wb') as png_file: png_file.write(data) return 1 - def set_DIB(src_bmp): """ set source bitmap image to clipboard as a CF_DIB or CF_DIBV5 according to the image :param str src_bmp: filepath of source image - :return: 1 if function succeeds, something else otherwise (or maybe just spit out an - error) + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) """ - with open(src_bmp, "rb") as img: + with open(src_bmp, 'rb') as img: data = img.read() output = data[14:] - size = len(output) + size = len(output) print(list(bytearray(output)[:200])) mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) h_mem = kernel32.GlobalLock(mem) - ctypes.memmove(ctypes.cast(h_mem, INT_P), ctypes.cast(output, INT_P), size) + ctypes.memmove(ctypes.cast(h_mem, INT_P), ctypes.cast(output, INT_P), size) kernel32.GlobalUnlock(mem) + if output[0] in [56, 108, 124]: # img contains DIBV5 or DIBV4 or DIBV3 Header fmt = CF_DIBV5 @@ -489,34 +431,31 @@ def set_DIB(src_bmp): # user32.EmptyClipboard() user32.SetClipboardData(fmt, h_mem) # user32.CloseClipboard() - return 1 - + return 1 def set_PNG(src_png): """ set source png image to clipboard in 'PNG' format :param str src_png: filepath of source image - :return: 1 if function succeeds, something else otherwise (or maybe just spit out an - error) + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) """ - with open(src_png, "rb") as img: + with open(src_png, 'rb') as img: data = img.read() - size = len(data) + size = len(data) mem = kernel32.GlobalAlloc(GMEM_MOVABLE, size) h_mem = kernel32.GlobalLock(mem) - ctypes.memmove(h_mem, data, size) + ctypes.memmove(h_mem, data, size) kernel32.GlobalUnlock(mem) # user32.OpenClipboard(0) # user32.EmptyClipboard() - PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("PNG")) + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) user32.SetClipboardData(PNG, h_mem) # user32.CloseClipboard() return 1 - def is_format_available(format_id): """ checks whether specified format is currently available on the clipboard @@ -524,14 +463,13 @@ def is_format_available(format_id): :param int format_id: id of format to check for :return: (bool) True if specified format is available """ - + # user32.OpenClipboard(0) is_format = user32.IsClipboardFormatAvailable(format_id) # user32.CloseClipboard() return bool(is_format) - -def get_available_formats(buffer_size=32): +def get_available_formats(buffer_size = 32): """ gets a dict of all the currently available formats on the clipboard @@ -542,34 +480,30 @@ def get_available_formats(buffer_size=32): # user32.OpenClipboard(0) fmt = 0 for i in range(user32.CountClipboardFormats()): - # must put previous fmt (starting from 0) in EnumClipboardFormats() to get the - # next one + # must put previous fmt (starting from 0) in EnumClipboardFormats() to get the next one fmt = user32.EnumClipboardFormats(fmt) name_buf = ctypes.create_string_buffer(buffer_size) + name_len = user32.GetClipboardFormatNameA(fmt, name_buf, buffer_size) fmt_name = name_buf.value.decode() - + # standard formats do not return any name, so we set one from out dictionary - if fmt_name == "" and fmt in format_dict.keys(): + if fmt_name == '' and fmt in format_dict.keys(): fmt_name = format_dict[fmt] - available_formats.update({fmt: fmt_name}) + available_formats.update({fmt : fmt_name}) # user32.CloseClipboard() return available_formats - -def get_image(filepath="", filename="image"): +def get_image(filepath = '', filename = 'image'): """ - gets image from clipboard in a format according to a priority list (PNG > DIBV5 > - DIB) + gets image from clipboard in a format according to a priority list (PNG > DIBV5 > DIB) """ # user32.OpenClipboard(0) - PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("PNG")) - image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p("image/png")) - - if user32.IsClipboardFormatAvailable(PNG) or user32.IsClipboardFormatAvailable( - image_png - ): + PNG = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('PNG')) + image_png = user32.RegisterClipboardFormatW(ctypes.c_wchar_p('image/png')) + + if user32.IsClipboardFormatAvailable(PNG) or user32.IsClipboardFormatAvailable(image_png): get_PNG(filepath, filename) return 1 elif user32.IsClipboardFormatAvailable(CF_DIBV5): @@ -579,38 +513,38 @@ def get_image(filepath="", filename="image"): get_DIB(filepath, filename) return 1 else: - raise RuntimeError("image on clipboard not available in any supported format") - return 0 - + raise_runtimerror('image on clipboard not available in any supported format') def set_image(src_img): """ - (NOT FULLY IMPLEMENTED) set source image to clipboard in multiple formats (PNG, - DIB). + (NOT FULLY IMPLEMENTED) set source image to clipboard in multiple formats (PNG, DIB). :param str src_img: filepath of source image - :return: 1 if function succeeds, something else otherwise (or maybe just spit out an - error) + :return: 1 if function succeeds, something else othewise (or maybe just spit out an error) """ - # this is more complicated... gotta inter-convert images + # this is more complicated... gotta interconvert images # looking into ways to get this done with ctypes as well - NO IM DONE # temporary solution - image_extension = src_img[(len(src_img) - 3) :].lower() - if image_extension == "bmp": + img_extn = src_img[(len(src_img)-3):].lower() + if img_extn == 'bmp': # image format is bitmap set_DIB(src_img) - elif image_extension == "png": + elif img_extn == 'png': # image format is png set_PNG(src_img) else: - raise RuntimeError("Unsupported image format") + raise_runtimerror('Unsupported image format') return 1 +def raise_runtimerror(error_msg): + close_clipboard() + raise RuntimeError(error_msg) -if __name__ == "__main__": +if __name__ == '__main__': if open_clipboard(): - empty_clipboard() + # empty_cliboard() + print(get_available_formats()) # set_UNICODETEXT('pasta pasta pasta pasta pasta pasta') - close_clipboard() + close_clipboard() \ No newline at end of file diff --git a/imagepaste/clipboard/windows/windows.py b/imagepaste/clipboard/windows/windows.py index 245543e..f0a6155 100644 --- a/imagepaste/clipboard/windows/windows.py +++ b/imagepaste/clipboard/windows/windows.py @@ -1,15 +1,17 @@ from __future__ import annotations +from posixpath import abspath from ..clipboard import Clipboard from ...report import Report from ...image import Image +from ...process import Process class WindowsClipboard(Clipboard): """A concrete implementation of Clipboard for Windows.""" def __init__(self, report: Report, images: list[Image] = None) -> None: - """A concrete implementation of Clipboard for Windows. + """A concreate implementation of Clipboard for Windows. Args: report (Report): A Report instance to which results should be reported. @@ -35,30 +37,33 @@ def push(cls, save_directory: str) -> WindowsClipboard: filename = cls.get_filename() filepath = join(save_directory, filename) - clipette.open_clipboard() + if clipette.open_clipboard(): + + # load multiple images first if filepaths are available (as CF_HDROP, id 15) + if clipette.is_format_available(15): + filepaths = clipette.get_FILEPATHS() + clipette.close_clipboard() + + images = [Image(filepath) for filepath in filepaths] + return cls(Report(6, f"Pasted {len(images)} image files: {images}"), images) - # load multiple images first if filepaths are available (as CF_HDROP, id 15) - if clipette.is_format_available(15): - filepaths = clipette.get_FILEPATHS() + # get image if available as 'PNG' or 'image/png' which covers pretty much all software. + # Ditched BITMAP support because blender doesn't completely support all Bitmap sub-formats + # and I couldn't find any software that copies only as a bitmap. + output = clipette.get_PNG(save_directory, splitext(filename)[0]) clipette.close_clipboard() - images = [Image(filepath) for filepath in filepaths] - return cls(Report(6, f"Pasted {len(images)} image files: {images}"), images) + + if output != 1: + image = Image(filepath) + return cls(Report(3, f"Cannot save image: {image}")) + else: + image = Image(filepath, pasted=True) + return cls(Report(6, f"Saved and pasted 1 image: {image}"), [image]) - # Get image if available as 'PNG' or 'image/png' which covers pretty much all - # software. Ditched BITMAP support because blender doesn't completely support - # all Bitmap sub-formats and I couldn't find any software that copies only as a - # bitmap. - output = clipette.get_PNG(save_directory, splitext(filename)[0]) - clipette.close_clipboard() - if output != 1: - image = Image(filepath) - return cls(Report(3, f"Cannot save image: {image}")) else: - image = Image(filepath, pasted=True) - return cls(Report(6, f"Saved and pasted 1 image: {image}"), [image]) - - return cls(Report(2)) + image = Image(filepath) + return cls(Report(1, "Unable to access clipboard")) @classmethod def pull(cls, image_path: str) -> WindowsClipboard: @@ -72,66 +77,65 @@ def pull(cls, image_path: str) -> WindowsClipboard: operations under Report object and a list of one Image object that holds information of the pulled image we put its path to the input. """ - from . import clipette + from . import clipette from bpy.path import abspath clipette.open_clipboard() - clipette.empty_clipboard() - + clipette.empty_cliboard() + image_path = abspath(image_path) image_format = image_path[-3:].lower() - # bmp (as DIB, DIBV5, BITMAP) and png (as PNG) should be enough formats to work - # with most applications - if image_format != "bmp": - clipette.set_DIB(cls.convert_image(image_path, "BMP")) + # bmp (as DIB, DIBV5, BITMAP) and png (as PNG) should be enough formats to work with most applications + if image_format != 'bmp': + clipette.set_DIB(cls.convert_image(image_path, 'BMP')) else: clipette.set_DIB(image_path) - if image_format != "png": - clipette.set_PNG(cls.convert_image(image_path, "PNG")) + if image_format != 'png': + clipette.set_PNG(cls.convert_image(image_path, 'PNG')) else: clipette.set_PNG(image_path) - + clipette.close_clipboard() image = Image(image_path) return cls(Report(5, f"Copied 1 image: {image}"), [image]) + @staticmethod def convert_image(image_path: str, format: str) -> str: - """A static method to convert image format and get new image filepath. + """A static method to convert image format and get new image filepath. Saves converted image in ImagePaste's working directory. Args: image_path (str): Filepath of source image. - format (str): Format to convert image to as in the image extension ('png', - 'bmp', etc) + format (str): Format to convert image to as in the image extension ('png', 'bmp', etc) Returns: str: Filepath of converted image. """ - # should probably incorporate this function into the Image class or something + # should probably incorpoate this function into the Image class or something from ...tree import get_save_directory from os.path import join, basename, splitext from bpy_extras.image_utils import load_image import bpy - - RGBA_unsupported = ["BMP", "JPEG"] + + RGBA_unsupported = ['BMP', 'JPEG'] format_ext = { - "BMP": ".bmp", - "IRIS": ".rgb", - "PNG": ".png", - "JPEG": ".jpg", - "JPEG2000": ".jp2", - "TARGA": ".tga", - "TARGA_RAW": ".tga", - "CINEON": ".cin", - "DPX": ".dpx", - "OPEN_EXR_MULTILAYER": ".exr", - "OPEN_EXR": ".exr", - "HDR": ".hdr", - "TIFF": ".tif", - "WEBP": ".webp", + 'BMP': '.bmp', + 'IRIS': '.rgb', + 'PNG': '.png', + 'JPEG': '.jpg', + 'JPEG2000': '.jp2', + 'TARGA': '.tga', + 'TARGA_RAW': '.tga', + 'CINEON': '.cin', + 'DPX': '.dpx', + 'OPEN_EXR_MULTILAYER': '.exr', + 'OPEN_EXR': '.exr', + 'HDR': '.hdr', + 'TIFF': '.tif', + 'WEBP': '.webp' } img_settings = bpy.context.scene.render.image_settings @@ -141,12 +145,10 @@ def convert_image(image_path: str, format: str) -> str: img_settings.file_format = format img_settings.quality = 100 - img_settings.color_mode = "RGB" if format in RGBA_unsupported else "RGBA" + img_settings.color_mode = 'RGB' if format in RGBA_unsupported else 'RGBA' image = load_image(image_path) - image_path_c = join( - get_save_directory(), splitext(basename(image_path))[0] + format_ext[format] - ) + image_path_c = join(get_save_directory(), splitext(basename(image_path))[0] + format_ext[format]) image.save_render(image_path_c) img_settings.file_format = prev_file_format From 9a43dda8294e15c81b275cc4bd9effb418280eba Mon Sep 17 00:00:00 2001 From: Binit Date: Mon, 13 Mar 2023 16:02:02 +0530 Subject: [PATCH 4/4] don't use any color transformation while converting images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • `image.save()` doesn't consider the updated format (bug?) so we're stuck with `save_render()` which uses render settings and would transform image accordingly (often set to 'Filmic' colorspace) which is unwanted during conversion. Fixed that by settings render settings to standard values temporarily. Fixes #47 • render is saved in `get_save_directory()` instead of `tempdir` --- __init__.py | 2 +- imagepaste/clipboard/windows/clipette.py | 1 + imagepaste/clipboard/windows/windows.py | 36 +++++++++++++++++++++--- imagepaste/operators.py | 3 +- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/__init__.py b/__init__.py index fc26719..4e9600f 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ "name": "ImagePaste", "author": "Binit", "blender": (2, 80, 0), - "version": (1, 8, 1), + "version": (1, 9, 1), "category": "Import-Export", "support": "COMMUNITY", "doc_url": "https://github.com/Yeetus3141/ImagePaste#readme", diff --git a/imagepaste/clipboard/windows/clipette.py b/imagepaste/clipboard/windows/clipette.py index f058a90..7872c47 100644 --- a/imagepaste/clipboard/windows/clipette.py +++ b/imagepaste/clipboard/windows/clipette.py @@ -546,5 +546,6 @@ def raise_runtimerror(error_msg): if open_clipboard(): # empty_cliboard() print(get_available_formats()) + # print(get_UNICODETEXT()) # set_UNICODETEXT('pasta pasta pasta pasta pasta pasta') close_clipboard() \ No newline at end of file diff --git a/imagepaste/clipboard/windows/windows.py b/imagepaste/clipboard/windows/windows.py index f0a6155..d09013e 100644 --- a/imagepaste/clipboard/windows/windows.py +++ b/imagepaste/clipboard/windows/windows.py @@ -138,21 +138,49 @@ def convert_image(image_path: str, format: str) -> str: 'WEBP': '.webp' } - img_settings = bpy.context.scene.render.image_settings + image = load_image(image_path) + image_path_c = join(get_save_directory(), splitext(basename(image_path))[0] + format_ext[format]) + + # setting view_settings according to scene settings since we're working with render results + # and rest according to the image format + scene = bpy.context.scene + img_settings = scene.render.image_settings + i_view_settings = img_settings.view_settings + i_disp_settings = img_settings.display_settings + prev_file_format = img_settings.file_format prev_color_mode = img_settings.color_mode prev_quality = img_settings.quality + prev_color_management = img_settings.color_management + prev_view_transform = i_view_settings.view_transform + prev_look = i_view_settings.look + prev_gamma = i_view_settings.gamma + prev_exposure = i_view_settings.exposure + prev_use_curve_mapping = i_view_settings.use_curve_mapping + prev_disp_device = i_disp_settings.display_device img_settings.file_format = format img_settings.quality = 100 img_settings.color_mode = 'RGB' if format in RGBA_unsupported else 'RGBA' - - image = load_image(image_path) - image_path_c = join(get_save_directory(), splitext(basename(image_path))[0] + format_ext[format]) + img_settings.color_management = 'OVERRIDE' + i_view_settings.view_transform = 'Standard' + i_view_settings.look = 'None' + i_view_settings.gamma = 1.0 + i_view_settings.exposure = 0.0 + i_view_settings.use_curve_mapping = False + i_disp_settings.display_device = 'sRGB' + image.save_render(image_path_c) img_settings.file_format = prev_file_format img_settings.color_mode = prev_color_mode img_settings.quality = prev_quality + img_settings.color_management = prev_color_management + i_view_settings.view_transform = prev_view_transform + i_view_settings.look = prev_look + i_view_settings.gamma = prev_gamma + i_view_settings.exposure = prev_exposure + i_view_settings.use_curve_mapping = prev_use_curve_mapping + i_disp_settings.display_device = prev_disp_device return image_path_c diff --git a/imagepaste/operators.py b/imagepaste/operators.py index 6b2e340..7bb0391 100644 --- a/imagepaste/operators.py +++ b/imagepaste/operators.py @@ -24,6 +24,7 @@ class IMAGEPASTE_OT_imageeditor_copy(bpy.types.Operator): def execute(self, context): from os.path import join from .metadata import get_addon_preferences + from .tree import get_save_directory active_image = context.area.spaces.active.image # If active image is render result, save it first @@ -31,7 +32,7 @@ def execute(self, context): image_path = active_image.filepath else: image_extension = get_addon_preferences().image_extension - image_path = join(bpy.app.tempdir, active_image.name + image_extension) + image_path = join(get_save_directory(), active_image.name + image_extension) bpy.ops.image.save_as(save_as_render=True, copy=True, filepath=image_path) # Report and log the result clipboard = Clipboard.pull(image_path)