AutoHotkey Community

It is currently May 27th, 2012, 10:55 am

All times are UTC [ DST ]




Post new topic Reply to topic  [ 16 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: December 18th, 2006, 8:14 am 
Offline

Joined: July 15th, 2006, 1:30 am
Posts: 48
Ok, this is another Clipboard to bmp thread.

As the thread title says I want to make a small script that could save the clipboard image to a bmp file.
This is quite doable and not so complicate to do, although lack (or limitation) on binary access prevents me from finishing it.

To save a clipboard image to a bmp file all you have to do is:
1. Read some specific bytes from the clipboard (width, height)
2. Get rid of the erroneous bmp header (always 68 bytes long)
3. Append a valid BMP header at the start of the clipboard (before image data)

Although it sounds easy I fail to do this mainly because I don't have the means to manipulate the binary data easily.
The BMP header has many Chr(0) bytes which Autohotkey can't save via FileAppend. Also all string manipulation functions can't work for the same reason.

I tried to implement it with BinWriteRead.ahk but it seems that I can't use those functions with large files (1024*768 = 3mb file)

I would like help into one of these:
1. How to strip the clipboard from the erroneous 68 byte clipboard header?
I could use a pointer to get in byte 69 but then how can I save the contents? FileAppend can't handle binary and BinWriteRead.ahk has problems with long files.
2. How to make a variable be Chr(0) and then actually save the result into a file?

Maybe I am missing something but right now replacing 68 bytes with 54 (right bmp header) seems very difficult to do with the functions given in autohotkey.

Thanks in advance.

_________________
One hotkey to rule them all!


Last edited by XavierGr on January 1st, 2007, 11:36 pm, edited 1 time in total.

Report this post
Top
 Profile  
Reply with quote  
PostPosted: December 18th, 2006, 9:52 am 
Online
User avatar

Joined: December 26th, 2005, 4:40 pm
Posts: 8776
XavierGr wrote:
Maybe I am missing something


Sure you are !!! :o

The kingpin PhiLho has already provided a solution: Image conversions and capturing with GDI+

Happy Clipping, :)

_________________
URLGet - Internet Explorer based Downloader
StartEx - Portable Shortcut Link


Report this post
Top
 Profile  
Reply with quote  
PostPosted: December 18th, 2006, 11:25 am 
Offline

Joined: July 15th, 2006, 1:30 am
Posts: 48
Thanks for your reply

Still I think that the whole GDI+ include seems a bit overkill for just a 54 byte missing BMP header.
I would prefer to see if there is a way to manipulate the binary data.

I will try to work more on it and see if I can get a solution.
Thanks again.

_________________
One hotkey to rule them all!


Report this post
Top
 Profile  
Reply with quote  
PostPosted: December 18th, 2006, 12:35 pm 
Offline

Joined: December 27th, 2005, 1:46 pm
Posts: 6837
Location: France (near Paris)
XavierGr wrote:
Although it sounds easy I fail to do this mainly because I don't have the means to manipulate the binary data easily.
The BMP header has many Chr(0) bytes which Autohotkey can't save via FileAppend. Also all string manipulation functions can't work for the same reason.
Chris provides functions to read and write binary data. My version of these look like:
Code:
/*
// Get and return a numerical value from the given buffer (@source) at given
// offset (_offset, in bytes). By default get a UInt/DWORD/Long (4 bytes)
// but _size parameter can be set to another size (in bytes).
// If _bIsSigned is true, interpret the result as signed number.
*/
; @source is a string (buffer) whose memory area contains a raw/binary integer at _offset.
; The caller should pass true for _bIsSigned to interpret the result as signed vs. unsigned.
; _size is the size of @source's integer in bytes (e.g. 4 bytes for a DWORD or Int).
; @source must be ByRef to avoid corruption during the formal-to-actual copying process
; (since @source might contain valid data beyond its first binary zero).
GetInteger(ByRef @source, _offset = 0, _size = 4, _bIsSigned = false)
{
   local result

   Loop %_size%  ; Build the integer by adding up its bytes.
   {
      result += *(&@source + _offset + A_Index-1) << 8*(A_Index-1)
   }
   If (!_bIsSigned OR _size > 4 OR result < 0x80000000)
      Return result  ; Signed vs. unsigned doesn't matter in these cases.
   ; Otherwise, convert the value (now known to be 32-bit & negative) to its signed counterpart:
   return -(0xFFFFFFFF - result + 1)
}

/*
// Set a numerical value (_integer) in the given buffer (@dest) at given
// offset (_offset, in bytes). By default set a UInt/DWORD/Long (4 bytes)
// but _size parameter can be set to another size (in bytes).
*/
; The caller must ensure that @dest has sufficient capacity.
; To preserve any existing contents in @dest,
; only _size number of bytes starting at _offset are altered in it.
SetInteger(ByRef @dest, _integer, _offset = 0, _size = 4)
{
   Loop %_size%  ; Copy each byte in the integer into the structure as raw binary data.
   {
      DllCall("RtlFillMemory"
            , "UInt", &@dest + _offset + A_Index-1
            , "UInt", 1
            , "UChar", (_integer >> 8*(A_Index-1)) & 0xFF)
   }
}
which can be simplified if you write byte per byte.
To move a big chunk of memory, you might want to use a DllCall on RtlMoveMemory.

Quote:
I tried to implement it with BinWriteRead.ahk but it seems that I can't use those functions with large files (1024*768 = 3mb file)
This is surprising, I though these functions will work up to 2GB, more or less.

Quote:
Still I think that the whole GDI+ include seems a bit overkill for just a 54 byte missing BMP header.
That's just an include, vs. your time to solve the problem. Of course, doing it yourself in a small and elegant manner is enhancive too... :-)

_________________
Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Last edited by PhiLho on December 18th, 2006, 1:19 pm, edited 2 times in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: December 18th, 2006, 12:44 pm 
Offline

Joined: December 27th, 2005, 1:46 pm
Posts: 6837
Location: France (near Paris)
I tested with this simple script:
Code:
#Include BinReadWrite.ahk

filename = BigFile.data

foh := OpenFileForWrite(filename)
Loop %A_WinDir%\*.bmp
{
   fih := OpenFileForRead(A_LoopFileFullPath)
   len := ReadFromFile(fih, data)
   WriteInFile(foh, data, len)   ; Give size because data var is larger than given bytes
}
CloseFile(foh)
It makes a 4MB file in a fraction of second.
And pointing to my archive directory with *.zip, I get a 24MB file in less than 10 seconds.

_________________
Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: December 18th, 2006, 1:43 pm 
Offline

Joined: July 15th, 2006, 1:30 am
Posts: 48
PhiLho wrote:
I tested with this simple script:
Code:
#Include BinReadWrite.ahk

filename = BigFile.data

foh := OpenFileForWrite(filename)
Loop %A_WinDir%\*.bmp
{
   fih := OpenFileForRead(A_LoopFileFullPath)
   len := ReadFromFile(fih, data)
   WriteInFile(foh, data, len)   ; Give size because data var is larger than given bytes
}
CloseFile(foh)
It makes a 4MB file in a fraction of second.
And pointing to my archive directory with *.zip, I get a 24MB file in less than 10 seconds.



Strange Indeed!
Whenever I pass the 30k-ish mark on the len parameter the file gets empty (0-byte) and I thought that was right because len is Int... (but then again now that I think of it Int is 4 bytes not 2 :? )

Maybe something more strange is there that I didn't notice, or something that I am doing completely wrong.

Right now I have major trouble reading from ClipboardAll.
It seems that I can't do the trick with the pointer.
E.g:

Code:
BmpData := ClipboardAll
Pointer := &BmpData
Pointer += 68

handle := OpenFileForWrite(a_filename)
WriteInFile(handle, pointer, a_length)
CloseFile(handle)


This won't give me a file with Clipboard contents from the 68th byte, instead I get various garbage and variable names from the script.

I must have been doing something completely wrong....

_________________
One hotkey to rule them all!


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: December 18th, 2006, 1:58 pm 
Offline

Joined: December 27th, 2005, 1:46 pm
Posts: 6837
Location: France (near Paris)
Doh! I though you were using DllCalls to get the clipboard content...
ClipboardAll is an opaque variable, its content is probably dummy from a script.
See how to get the image data in the clipboard there:
http://www.autohotkey.com/forum/viewtop ... 4538#84538

_________________
Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: December 19th, 2006, 10:16 am 
Offline

Joined: July 15th, 2006, 1:30 am
Posts: 48
Ok good news.

I managed to do exactly what I want without too many lines of code.
I wish I could loose the #Include ReadWriteBinary library.... then the script would be 10-20 lines.

Anyway I will upload a crude script in the near future, but first I have to clean it up. Thanks for the help.

_________________
One hotkey to rule them all!


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: December 19th, 2006, 10:28 am 
Offline

Joined: December 27th, 2005, 1:46 pm
Posts: 6837
Location: France (near Paris)
XavierGr wrote:
I wish I could loose the #Include ReadWriteBinary library.... then the script would be 10-20 lines.
You are free to use the DllCalls in it any way that suits you. These aren't very difficult.

_________________
Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: December 19th, 2006, 12:37 pm 
Online
User avatar

Joined: December 26th, 2005, 4:40 pm
Posts: 8776
XavierGr wrote:
Ok good news.

I managed to do exactly what I want without too many lines of code.
I wish I could loose the #Include ReadWriteBinary library.... then the script would be 10-20 lines.

Anyway I will upload a crude script in the near future, but first I have to clean it up. Thanks for the help.


I eager to take a look at the code ..

:)

_________________
URLGet - Internet Explorer based Downloader
StartEx - Portable Shortcut Link


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 1st, 2007, 11:29 pm 
Offline

Joined: July 15th, 2006, 1:30 am
Posts: 48
Ok at last I have a crude version of it.
Optimization tips and advice are most welcome.

This little script copies a dummy bmp header file (54 byte file without image data, just the header) from a valid BMP file (searching it at your windows directory). It would be better to have the header file into the script but all this binary thing makes my head hurt.
Note: Make sure to have a valid BMP file in your windows directory or you can change the path to a file of your choice

Using the dummy bmp header we just replace 6 bytes from the Clipboard (Width, Height and bit depth) and then simply concatenate the whole ClipboardAll variable from the 68th byte (where the real image data is).

Note that the bit depth, in captured bmp, will be the same as your Windows Display properties. (32 bits for me, expect 3MB file for 1024*768 screens)

Summing it up: When you press PrintScreen or Alt + PrintScreen the script will capture your desktop image (or window image) into a BMP file (in the folder of the script) named after the current time.

Here is the code:

Code:
#Include BinReadWrite.ahk

Print:
~PrintScreen::
~!PrintScreen::
    ScreenShot(A_Hour "." A_Min "." A_Sec ".bmp")
return

; Capturing Procedure
ScreenShot(filename)
{
    ; Copy clipboard data to a variable
    ClipData := ClipboardAll
           
    ; Get Image Dimensions
    ; Width Bytes
    VarSetCapacity(BinWidth, 4, 0)
    DllCall("RtlMoveMemory", UInt, &BinWidth, UInt, &(ClipData) + 20, Int, 4)
   
    ; Calculate Decimal Width
    x1 := *(&(BinWidth)+1)
    x2 := *(&(BinWidth))
    Width := (x1 * 256) + x2
   
    ; Height Bytes   
    VarSetCapacity(BinHeight, 4, 0)
    DllCall("RtlMoveMemory", UInt, &BinHeight, UInt, &(ClipData) + 24, Int, 4)

    ; Calculate Decimal Width
    y1 := *(&(BinHeight)+1)
    y2 := *(&(BinHeight))
    Height := (y1 * 256) + y2   
   
    ; Variable initialization
    VarSetCapacity(ImageFile, (Width * Height * 4) + 54, 0)
    VarSetCapacity(BmpHeader, 54, 0)
   
    ; BMP Header Construction
    ; Find a bmp file to copy the header
    Loop, % A_WinDir "*.bmp", 0, 1
    {
        FileRead, BmpHeader, % A_LoopFileLongPath
        break
    }
   
    ; Copy the correct image size to valid BMP Header
    DllCall("RtlMoveMemory", UInt, &(BmpHeader) + 18, UInt, &BinWidth, Int, 4)
    DllCall("RtlMoveMemory", UInt, &(BmpHeader) + 22, UInt, &BinHeight, Int, 4)
   
    ; Copy the correct bit depth to valid BMP Header
    DllCall("RtlMoveMemory", UInt, &(BmpHeader) + 28, UInt, &(ClipData) + 30, Int, 2)
   
    ; Copy the BMP Header to new ImageFile variable
    DllCall( "RtlMoveMemory", UInt, &ImageFile, UInt, &BmpHeader, Int, 54)
    ; Copy the Image data to new ImageFile variable
    DllCall( "RtlMoveMemory", UInt, &(ImageFile) + 54, UInt, &(ClipData) + 68, Int, Width * Height * 4)
       
    ; Write data to file
    fh := OpenFileForWrite(filename)
    WriteInFile(fh, ImageFile, (Width * Height * 4) + 54)
    CloseFile(fh)
}



Make sure to include the BinReadWrite.ahk script written by PhiLho.

I would like to make this script even smaller and better. So if you have any suggestions please share it.
Specifically I want to avoid the binary to hex (dec) conversion (the problematic toHex() function) and it would be good to manage to make the script even smaller by not including BinReadWrite.ahk

_________________
One hotkey to rule them all!


Last edited by XavierGr on January 2nd, 2007, 5:11 am, edited 3 times in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 2nd, 2007, 12:50 am 
Offline

Joined: December 27th, 2005, 1:46 pm
Posts: 6837
Location: France (near Paris)
XavierGr wrote:
Make sure to include the BinReadWrite.ahk script written by Laszlo.
Hurm, looks more like my code... ;-) Even if it is initially based on Laszlo's code.

Quote:
I want to avoid the binary to hex (dec) conversion (the problematic toHex() function)
Well, I don't see its use...

Quote:
and it would be good to manage to make the script even smaller by not including BinReadWrite.ahk
Just pick up the relevant code from there...

_________________
Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 2nd, 2007, 5:09 am 
Offline

Joined: July 15th, 2006, 1:30 am
Posts: 48
PhiLho wrote:
Hurm, looks more like my code... ;-) Even if it is initially based on Laszlo's code.


Oopps I am deeply sorry about this. My post is edited.
And thanks for the routines BTW :D

PhiLho wrote:
Well, I don't see its use...

I think you are right on this, I could easily make the calculation multiplying the decimal number with 256 (instead of padding 2 zeros in hex mode). (Script updated and removed redundant function)

PhiLho wrote:
Just pick up the relevant code from there...

I will try to fiddle it a little bit more and see where I can get.

Thanks all for your help.

_________________
One hotkey to rule them all!


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 2nd, 2007, 12:48 pm 
Offline

Joined: July 6th, 2004, 10:07 am
Posts: 171
Location: Manchester, England.
@XavierGr

Well, at least we should award you points for determination, and at least you have a program the does eactly what you set out to do. Proving once again that limitations are only in the minds of the users :roll:
Well done.

Regards
Dave.

_________________
Simple ideas lie within reach, only of complex minds


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 28th, 2010, 2:52 am 
Offline

Joined: September 23rd, 2010, 5:36 am
Posts: 25
Hey Guys,

Can I change the directory where the BMP is saved?? If so please explain =P


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 16 posts ]  Go to page 1, 2  Next

All times are UTC [ DST ]


Who is online

Users browsing this forum: BrandonHotkey, HotkeyStick and 15 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group