[python-win32] windows images on clipboard

Konovalov, Vadim Vadim.Konovalov at dell.com
Fri Aug 13 03:55:48 EDT 2021


Here is the better tcl/tk version of script which then could be reused with tkinter to solve the task

#
# rotate image in clipboard
#
# TBD - user-friendly - message box if no image in clipboard, also - "ready" message
#
# most code from here - http://wiki.tcl.tk/15647
#

package require Tk
package require img::bmp
package require base64
package require twapi

#twapi::open_clipboard
#foreach {i} [twapi::get_clipboard_formats] {
#  puts "$i"
#  # [twapi::get_registered_clipboard_format_name $i]
#}
#twapi::close_clipboard

# Copy the contents of the Windows clipboard into a photo image.
# Return the photo image identifier.
proc Clipboard2Img {} {
    twapi::open_clipboard

    # Assume clipboard content is in format 8 (CF_DIB)
    set retVal [catch {twapi::read_clipboard 8} clipData]
    if { $retVal != 0 } {
        error "Invalid or no content in clipboard"
    }

    # First parse the bitmap data to collect header information
    binary scan $clipData "iiissiiiiii" \
           size width height planes bitcount compression sizeimage \
           xpelspermeter ypelspermeter clrused clrimportant

    # We only handle BITMAPINFOHEADER right now (size must be 40)
    if {$size != 40} {
        error "Unsupported bitmap format. Header size=$size"
    }

    # We need to figure out the offset to the actual bitmap data
    # from the start of the file header. For this we need to know the
    # size of the color table which directly follows the BITMAPINFOHEADER
    if {$bitcount == 0} {
        error "Unsupported format: implicit JPEG or PNG"
    } elseif {$bitcount == 1} {
        set color_table_size 2
    } elseif {$bitcount == 4} {
        # TBD - Not sure if this is the size or the max size
        set color_table_size 16
    } elseif {$bitcount == 8} {
        # TBD - Not sure if this is the size or the max size
        set color_table_size 256
    } elseif {$bitcount == 16 || $bitcount == 32} {
        if {$compression == 0} {
            # BI_RGB
            set color_table_size $clrused
        } elseif {$compression == 3} {
            # BI_BITFIELDS
            set color_table_size 3
        } else {
            error "Unsupported compression type '$compression' for bitcount value $bitcount"
        }
    } elseif {$bitcount == 24} {
        set color_table_size $clrused
    } else {
        error "Unsupported value '$bitcount' in bitmap bitcount field"
    }

    set phImg [image create photo]
    set filehdr_size 14                 ; # sizeof(BITMAPFILEHEADER)
    set bitmap_file_offset [expr {$filehdr_size+$size+($color_table_size*4)}]
    set filehdr [binary format "a2 i x2 x2 i" \
                 "BM" [expr {$filehdr_size + [string length $clipData]}] \
                 $bitmap_file_offset]

    append filehdr $clipData
    $phImg put $filehdr -format bmp

    twapi::close_clipboard
    return $phImg
}

# Copy photo image "phImg" into Windows clipboard.
proc Img2Clipboard { phImg } {
    # First 14 bytes are bitmapfileheader - get rid of this
    set data [string range [base64::decode [$phImg data -format bmp]] 14 end]
    twapi::open_clipboard
    twapi::empty_clipboard
    twapi::write_clipboard 8 $data
    twapi::close_clipboard
}

proc rotate { phImg angle } {
    set w [image width  $phImg]
    set h [image height $phImg]

    switch -- $angle {
        180 {
            set tmp [image create photo -width $w -height $h]
            $tmp copy $phImg -subsample -1 -1
            return $tmp
        }
        270 - 90 - -90 {
            set tmp [image create photo -width $h -height $w]
            set matrix [string repeat "{[string repeat {0 } $h]} " $w]
            if { $angle == -90 || $angle == 270 } {
                set x0 0; set y [expr {$h-1}]; set dx 1; set dy -1
            } else {
                set x0 [expr {$w-1}]; set y 0; set dx -1; set dy 1
            }
            foreach row [$phImg data] {
                set x $x0
                foreach pixel $row {
                    lset matrix $x $y $pixel
                    incr x $dx
                }
                incr y $dy
            }
            $tmp put $matrix
            return $tmp
        }
        default {
            error "Invalid angle $angle specified"
        }
    }
}

Img2Clipboard [rotate [Clipboard2Img] [lindex $argv 0]]

destroy .



Internal Use - Confidential
From: Konovalov, Vadim
Sent: Friday, August 13, 2021 10:36 AM
To: Mriswithe; Tim Roberts
Cc: python-win32 at python.org
Subject: RE: [python-win32] windows images on clipboard

There are 2 ways to solve this task, actually.

1st way is a bit more difficult but more efficient.

Read https://docs.microsoft.com/en-us/windows/win32/dataxchg/using-the-clipboard#copying-information-to-the-clipboard and implement this in python module.



This was done for perl and worked for me in Win32::Clipboard module

https://github.com/jandubois/win32-clipboard/pull/5



At least worked for me (I haven’t rechecked but I am 95% sure it worked, I wrote perl script which rotates image in windows clipboard and placed rotated image back.

2nd way does not require C coding and deep delving into clipboard documentation

Tcl/Tk can do this.
You can use tcl/tk code from python with tkinter.

Below is my tcl/tk script which you can adjust and enjoy. Its not cleaned-up, sorry, but it works.

#
# version 0.30
#  0.21 23mar2012: "lowercase" button
#  0.22 03apr2012: fix table
#  0.23 17apr2012: '?'/.
#  0.24 27apr2012: smaller button but tooltip! :), also return to prev.application (SetForegroundWindow)
#  0.25 04jun2012: Ctrl+lowercase button - UPCASE
#           Òåêñò->Текст is 4rd and not initially seen
#  0.30 - rewrite in tcl/tk, with "twapi" usage
#
# TODO: automatically invoke copy-paste,
# TODO: Ctrl+Click=1st letter upcased,
# TODO: very compact mode, bigger on mouse move
# TODO: crop-image function, and view-clipboard (because it is the same)
#
# TODO - Clipboard2Img - to outer lib file!
#

#puts [encoding names]
#encoding system utf-8

set er АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя
set el "F<DULT~:PBQRKVYJGHCNEA{WXIO}SM\">Zf,dult`;pbqrkvyjghcnea\[wxio\]sm'.z?"
set ee "ÀÁÂÃÄŨÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäå¸æçèéêëìíîïðñòóôõö÷øùúûüýþÿ?"

package require Tk
package require Ttk
package require img::bmp
package require base64
package require twapi
package require tooltip

ttk::style theme use alt
# winnative clam alt default classic xpnative

wm title . {текст clipboard}
wm attributes . -topmost 1 -toolwindow 1
wm geometry . 130x56

place [ttk::button .b1 -text {Ntrcn->Текст} -command lat2cyr] -y  0 -x 0 -height 20 -width 110
place [ttk::button .b2 -text {Еуче->Text} -command cyr2lat]   -y 20 -x 0 -height 20 -width 110
place [ttk::button .b4 -text {TEXTТеКСт->textтекст} -command lowercase] -y 40 -x 0 -height 20 -relwidth 1
place [ttk::button .b3 -text {Òåêñò->Текст} -command eur2cyr] -y 60 -x 0 -height 20 -relwidth 1

image create photo rotate_ccw -data R0lGODlhEAAQAIIAAPwCBAQCBFxaXIQChEQCBAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAM0CLrcHjA6JcK4ET68MrNDoIgjOYbdJ4IXagIa3IqVFAMEOqpfn067ErBxG0qGsCJyyQT4EwAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7
image create photo rotate_cw -data R0lGODlhEAAQAIIAAPwCBAQCBIQChFxaXEQCBAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAMzCLrcHjA6FaG4YbS6At5BR31UKI4mADGhd31rpbmCuZ6qQLA8XvqT2+QhHLKKxqSy4U8AACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=

place [ttk::button .b5 -image rotate_cw -command {Img2Clipboard [rotate [Clipboard2Img] -90]}] -y 0 -x 108 -height 20 -width 22
place [ttk::button .b6 -image rotate_ccw -command {Img2Clipboard [rotate [Clipboard2Img] 90]}] -y 20 -x 108 -height 20 -width 22


tooltip::tooltip .b1 {Ntrcn->Текст}
tooltip::tooltip .b2 {Еуче->Text}
tooltip::tooltip .b3 {Òåêñò->Текст}
tooltip::tooltip .b4 {TEXTТеКСт->textтекст; [Ctrl]+click - UPCASE (TEXTТеКСт->TEXTТЕКСТ)}
tooltip::tooltip .b5 {rotate clockwise IMAGE that is in clipboard}
tooltip::tooltip .b5 {rotate counter-clockwise IMAGE that is in clipboard}

#bind . <Motion> perl::mmotion
bind .b4 <Control-1> uppercase


proc lat2cyr {} {
    before_get
    global el er
    twapi::open_clipboard
    set s [twapi::read_clipboard_text]
    set x 0

    foreach letter [split $s {}] {
      set i [string first $letter $el]
      if {$i != -1} {
          set s [string replace "$s" $x $x [string index $er $i]]
      }
      incr x
    }
    twapi::empty_clipboard
    twapi::write_clipboard_text "$s"
    twapi::close_clipboard
    after_put
}

proc cyr2lat {} {
    before_get
    global el er
    twapi::open_clipboard
    set s [twapi::read_clipboard_text]
    set x 0

    foreach letter [split $s {}] {
      set i [string first $letter $er]
      if {$i != -1} {
          set s [string replace "$s" $x $x [string index $el $i]]
      }
      incr x
    }
    twapi::empty_clipboard
    twapi::write_clipboard_text "$s"
    twapi::close_clipboard
    after_put
}

proc eur2cyr {} {
    global ee er
    twapi::open_clipboard
    set s [twapi::read_clipboard_text]
    set x 0

    foreach letter [split $s {}] {
      set i [string first $letter $ee]
      if {$i != -1} {
          set s [string replace "$s" $x $x [string index $er $i]]
      }
      incr x
    }
    twapi::empty_clipboard
    twapi::write_clipboard_text "$s"
    twapi::close_clipboard
}

set skip 0
proc lowercase {} {
    global skip
    if {$skip != 0} {
      set skip 0
      return
    }
    before_get
    twapi::open_clipboard
    set s [twapi::read_clipboard_text]
    twapi::empty_clipboard
    twapi::write_clipboard_text [string tolower "$s"]
    twapi::close_clipboard
    after_put
}
proc uppercase {} {
    before_get
    twapi::open_clipboard
    set s [twapi::read_clipboard_text]
    twapi::empty_clipboard
    twapi::write_clipboard_text [string toupper "$s"]
    twapi::close_clipboard
    set skip 1
    after_put
}

# Copy the contents of the Windows clipboard into a photo image.
# Return the photo image identifier.
proc Clipboard2Img {} {
    twapi::open_clipboard

    # Assume clipboard content is in format 8 (CF_DIB)
    set retVal [catch {twapi::read_clipboard 8} clipData]
    if { $retVal != 0 } {
        error "Invalid or no content in clipboard"
    }

    # First parse the bitmap data to collect header information
    binary scan $clipData "iiissiiiiii" \
           size width height planes bitcount compression sizeimage \
           xpelspermeter ypelspermeter clrused clrimportant

    # We only handle BITMAPINFOHEADER right now (size must be 40)
    if {$size != 40} {
        error "Unsupported bitmap format. Header size=$size"
    }

    # We need to figure out the offset to the actual bitmap data
    # from the start of the file header. For this we need to know the
    # size of the color table which directly follows the BITMAPINFOHEADER
    if {$bitcount == 0} {
        error "Unsupported format: implicit JPEG or PNG"
    } elseif {$bitcount == 1} {
        set color_table_size 2
    } elseif {$bitcount == 4} {
        # TBD - Not sure if this is the size or the max size
        set color_table_size 16
    } elseif {$bitcount == 8} {
        # TBD - Not sure if this is the size or the max size
        set color_table_size 256
    } elseif {$bitcount == 16 || $bitcount == 32} {
        if {$compression == 0} {
            # BI_RGB
            set color_table_size $clrused
        } elseif {$compression == 3} {
            # BI_BITFIELDS
            set color_table_size 3
        } else {
            error "Unsupported compression type '$compression' for bitcount value $bitcount"
        }
    } elseif {$bitcount == 24} {
        set color_table_size $clrused
    } else {
        error "Unsupported value '$bitcount' in bitmap bitcount field"
    }

    set phImg [image create photo]
    set filehdr_size 14                 ; # sizeof(BITMAPFILEHEADER)
    set bitmap_file_offset [expr {$filehdr_size+$size+($color_table_size*4)}]
    set filehdr [binary format "a2 i x2 x2 i" \
                 "BM" [expr {$filehdr_size + [string length $clipData]}] \
                 $bitmap_file_offset]

    append filehdr $clipData
    $phImg put $filehdr -format bmp

    twapi::close_clipboard
    return $phImg
}

# Copy photo image "phImg" into Windows clipboard.
proc Img2Clipboard { phImg } {
    # First 14 bytes are bitmapfileheader - get rid of this
    set data [string range [base64::decode [$phImg data -format bmp]] 14 end]
    twapi::open_clipboard
    twapi::empty_clipboard
    twapi::write_clipboard 8 $data
    twapi::close_clipboard
}

proc rotate { phImg angle } {
    set w [image width  $phImg]
    set h [image height $phImg]

    switch -- $angle {
        180 {
            set tmp [image create photo -width $w -height $h]
            $tmp copy $phImg -subsample -1 -1
            return $tmp
        }
        270 - 90 - -90 {
            set tmp [image create photo -width $h -height $w]
            set matrix [string repeat "{[string repeat {0 } $h]} " $w]
            if { $angle == -90 || $angle == 270 } {
                set x0 0; set y [expr {$h-1}]; set dx 1; set dy -1
            } else {
                set x0 [expr {$w-1}]; set y 0; set dx -1; set dy 1
            }
            foreach row [$phImg data] {
                set x $x0
                foreach pixel $row {
                    lset matrix $x $y $pixel
                    incr x $dx
                }
                incr y $dy
            }
            $tmp put $matrix
            return $tmp
        }
        default {
            error "Invalid angle $angle specified"
        }
    }
}


set fhwnd 0
#proc tcl::mmotion {} {$fhwnd = Win32::GuiTest::GetForegroundWindow}
proc before_get {} {
    #Win32::GuiTest::SendKeys("%{TAB}{PAUSE 100}^{C}");
    #пока не работает :(
    #$fhwnd = Win32::GuiTest::GetForegroundWindow;
    global fhwnd
    set fhwnd [twapi::get_foreground_window]
}
proc after_put {} {
    global fhwnd
    twapi::set_foreground_window $fhwnd
#    #Win32::GuiTest::SendKeys("%{TAB}");
#    #Win32::GuiTest::SendKeys("^{V}");
#    #Win32::GuiTest::SendMessage($fhwnd,0x100,VK_HOME,0);
#    #Win32::GuiTest::SendMessage($fhwnd,0x100,VK_SHIFT,0);
#    #пока не работает :(
}


From: python-win32 <python-win32-bounces+vadim.konovalov=dell.com at python.org<mailto:python-win32-bounces+vadim.konovalov=dell.com at python.org>> On Behalf Of Mriswithe
Sent: Thursday, August 12, 2021 8:09 PM
To: Tim Roberts
Cc: python-win32 at python.org<mailto:python-win32 at python.org>
Subject: Re: [python-win32] windows images on clipboard

Awesome, thank you so much for that info. That makes my tail chasing make more sense! Thanks for saving me from additional posterior extension pursuit.

Last question I think, how do I put a file path reference on the clipboard similar to in explorer selecting a few files and hitting control c ?

I think I had tested that with discord and it worked, but not 100% on that.

On Thu, Aug 12, 2021, 11:57 AM Tim Roberts <timr at probo.com<mailto:timr at probo.com>> wrote:
Mriswithe wrote:
>
> I was looking at making a little helper app for Windows that will take
> an image on your clipboard and ensure it is under 8MB for posting to
> discord, and if it isn't, use Pillow to resize it until it is the
> right size.
>
> I can use Pillow's ImageGrab.grabclipboard() to get the image off the
> clipboard, but I am a little confused about writing it back. I have
> been back and forth between the pywin32 source and the windows docs
> for the windows C API, but I don't have any previous experience or
> context to know what some pieces are intended to do.
>
> I found an example to write to it from StackOverflow
> (https://stackoverflow.com/questions/34322132/copy-image-to-clipboard [stackoverflow.com]<https://urldefense.com/v3/__https:/stackoverflow.com/questions/34322132/copy-image-to-clipboard__;!!LpKI!1HpfpiZFnkKD39C6jcA6RY-Uyw-wVFHqIvpPi3wlPNKkdVaL3MhY1aEPz_HFEzOHhGtT$>
> <https://stackoverflow.com/questions/34322132/copy-image-to-clipboard [stackoverflow.com]<https://urldefense.com/v3/__https:/stackoverflow.com/questions/34322132/copy-image-to-clipboard__;!!LpKI!1HpfpiZFnkKD39C6jcA6RY-Uyw-wVFHqIvpPi3wlPNKkdVaL3MhY1aEPz_HFEzOHhGtT$>>),
> but I was wanting to dig a little deeper to see what formats other
> than BMP I could use to put on the clipboard. My ignorance of C++ and
> the Windows APIs and hell the Python C API is really biting me here.

You can't.  You have to write it as a DIB (Device Independent Bitmap),
which is the format in a .BMP file.

The Windows clipboard was designed in roughly 1986, before GIF, before
JPEG and way, way before PNG.  The clipboard is designed for universal
interchange, so it really does need to spec the lowest common
denominator.  If they allowed PNGs, then all of the graphics application
in the world would have to be modified to decode PNG data.

So, to make your app work, save the result as a BMP.


> Is there a bit of an idiots example guide for how to say, put a PNG on
> the clipboard or a JPG? Some example code? There is a ton of useful
> info in the pywin32 repo, but I haven't found anything that has made
> this click for me.

There is no idiots guide, because it cannot be done.  Well, technically
speaking you can put arbitrary binary data into the clipboard, but other
graphics applications will not be able to read it.  When they look for
image data, they look for format CF_DIB, and that means a .BMP.

--
Tim Roberts, timr at probo.com<mailto:timr at probo.com>
Providenza & Boekelheide, Inc.


_______________________________________________
python-win32 mailing list
python-win32 at python.org<mailto:python-win32 at python.org>
https://mail.python.org/mailman/listinfo/python-win32 [mail.python.org]<https://urldefense.com/v3/__https:/mail.python.org/mailman/listinfo/python-win32__;!!LpKI!1HpfpiZFnkKD39C6jcA6RY-Uyw-wVFHqIvpPi3wlPNKkdVaL3MhY1aEPz_HFEzW7GLbK$>


Internal Use - Confidential
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.python.org/pipermail/python-win32/attachments/20210813/e0c4cd74/attachment-0001.html>


More information about the python-win32 mailing list